diff --git a/src/docs/src/examples.js b/src/docs/src/examples.js index b083ec088..a42f13f17 100644 --- a/src/docs/src/examples.js +++ b/src/docs/src/examples.js @@ -745,12 +745,6 @@ const examples = [ slug: 'hosting-publish-a-website', source: '/playground/examples/hosting-publish-a-website.html', }, - { - title: 'Release and roll back a site', - description: 'Switch a website between folders to release or roll back a version with Puter.js hosting API. Run and modify this example directly in your browser.', - slug: 'hosting-manage-websites-release', - source: '/playground/examples/hosting-manage-websites-release.html', - }, ], }, { @@ -998,18 +992,6 @@ const examples = [ slug: 'workers-exec', source: '/playground/examples/workers-exec.html', }, - { - title: 'Build an API', - description: 'Deploy a worker whose routes store notes in the calling user\'s own key-value store with user.puter, and call it with puter.workers.exec(). Run and modify this example directly in your browser.', - slug: 'workers-build-an-api', - source: '/playground/examples/workers-build-an-api.html', - }, - { - title: 'Update a worker in place', - description: 'Ship new code to a deployed worker at the same URL by overwriting its source file with Puter.js workers API. Run and modify this example directly in your browser.', - slug: 'workers-deploy-from-code-update', - source: '/playground/examples/workers-deploy-from-code-update.html', - }, ], }, ]; diff --git a/src/docs/src/playground/examples/hosting-manage-websites-release.html b/src/docs/src/playground/examples/hosting-manage-websites-release.html deleted file mode 100644 index 529306a52..000000000 --- a/src/docs/src/playground/examples/hosting-manage-websites-release.html +++ /dev/null @@ -1,41 +0,0 @@ - -
- -${project.bio}
+
`;
+}
+
+const dir = `sites/${project.id}`;
+
+await puter.fs.write(`${dir}/index.html`, renderPage(project), { createMissingParents: true });
+await puter.fs.write(`${dir}/photo.jpg`, project.photo);
+
+const site = await puter.hosting.create('grace-portfolio', dir);
+
+console.log(`https://${site.subdomain}.puter.site`);
+```
+
+The site now serves the folder. A request for `/` gets `index.html`, and every
+other file is served at its path, such as `photo.jpg` at `/photo.jpg`. Everything
+in the folder is public, so write only what the user means to publish.
+
+## Let the User Pick a Name
+
+Site names are shared by every Puter user, so the name the user wants may
+already be taken. In that case [`puter.hosting.create()`](/Hosting/create/)
+rejects with a `conflict` error. Catch it to ask the user for another name, or
+let [`puter.randName()`](/Utils/randName/) pick one nobody has:
+
+```js
+async function createSite (dir, name) {
+ try {
+ return await puter.hosting.create(name, dir);
+ } catch (e) {
+ if (e?.code !== 'conflict') throw e;
+ return await puter.hosting.create(puter.randName(), dir);
+ }
+}
+```
+
+A name can use lowercase letters, digits and hyphens, and cannot start or end
+with a hyphen. Check these
+rules in your name field before calling
+[`puter.hosting.create()`](/Hosting/create/).
+
+## Remember Which Site Belongs to Which Project
+
+When the user comes back to a project, your app needs to know whether it was
+published and under which name. Save the site name with
+[`puter.kv.set()`](/KV/set/), keyed by the project:
+
+```js
+await puter.kv.set(`site:${project.id}`, site.subdomain);
+```
+
+To find the site later, read it back with [`puter.kv.get()`](/KV/get/). It
+returns `null` for a project that was never published:
+
+```js
+const subdomain = await puter.kv.get(`site:${project.id}`);
+```
+
+## Publish Again After Edits
+
+A site serves whatever is in its folder, so publishing an edit means writing the
+files again. There is nothing to redeploy. This `publish()` function handles
+the first publish and every one after it:
+
+```js
+async function publish (project, name) {
+ const dir = `sites/${project.id}`;
+
+ await puter.fs.write(`${dir}/index.html`, renderPage(project), { createMissingParents: true });
+ await puter.fs.write(`${dir}/photo.jpg`, project.photo);
+
+ let subdomain = await puter.kv.get(`site:${project.id}`);
+
+ if (!subdomain) {
+ const site = await createSite(dir, name);
+ subdomain = site.subdomain;
+ await puter.kv.set(`site:${project.id}`, subdomain);
+ }
+
+ return `https://${subdomain}.puter.site`;
+}
+```
+
+To list the user's sites or take one offline, see
+[Manage websites](/recipes/manage-websites/).
+
+## Notes
+
+- To give single files a public URL, such as images or downloads, see
+ [Host files online](/recipes/host-file/). One hosted folder serves every file,
+ with no page or site name to manage.
diff --git a/src/docs/src/recipes/workers-build-an-api.md b/src/docs/src/recipes/build-an-api.md
similarity index 87%
rename from src/docs/src/recipes/workers-build-an-api.md
rename to src/docs/src/recipes/build-an-api.md
index ad4fe1fe5..d3f057a98 100644
--- a/src/docs/src/recipes/workers-build-an-api.md
+++ b/src/docs/src/recipes/build-an-api.md
@@ -1,13 +1,19 @@
---
title: Build an API with a Worker
-description: "Learn how to write the routes of a serverless worker with Puter.js, reading requests, storing data in the calling user's own account and sending back JSON or errors."
+description: "Learn how to build a backend API for your app with Puter.js serverless workers."
tags: [workers, kv, auth]
order: 38
---
-A [serverless worker](/Workers/) is JavaScript that runs in the cloud instead of
-in the user's browser. It answers HTTP requests at its own URL, such as
-`https://notes-api.puter.work`, and you write it as a list of routes, each one a
+Some of your app's code belongs outside the user's browser, such as checking
+input before it is saved, receiving a webhook from another service, or calling
+another API. That code needs a backend, which usually means running and paying
+for a server.
+
+A [serverless worker](/Workers/) gives you that backend with no server to run,
+like Cloudflare Workers or AWS Lambda. It is JavaScript that runs in the cloud
+and answers HTTP requests at its own URL, such as
+`https://notes-api.puter.work`. You write it as a list of routes, each one a
path and the function that handles it.
When your app calls a worker with
@@ -136,8 +142,8 @@ character and does not match anything else.
## Routes Without a User
A worker is also an ordinary HTTP endpoint, so it can serve callers that have
-never heard of Puter: a webhook from another service, a `curl` in a script, or a
-page that doesn't load Puter.js. These routes never touch `user`.
+never heard of Puter. A webhook from another service, a `curl` in a script, or a
+page without Puter.js can all call it. These routes never touch `user`.
A route can do its work and answer, with nothing to store:
@@ -184,8 +190,8 @@ router.get('/weather/:city', async ({ params }) => {
});
```
-Anyone who knows the URL can call a route like these, so don't put anything
-behind one that should be private.
+Anyone who knows the URL can call a route like these, so keep private data
+behind routes that check for `user`.
## Call It From Your App
diff --git a/src/docs/src/recipes/workers-deploy-from-code.md b/src/docs/src/recipes/deploy-workers-programmatically.md
similarity index 76%
rename from src/docs/src/recipes/workers-deploy-from-code.md
rename to src/docs/src/recipes/deploy-workers-programmatically.md
index f99ff11af..56796e751 100644
--- a/src/docs/src/recipes/workers-deploy-from-code.md
+++ b/src/docs/src/recipes/deploy-workers-programmatically.md
@@ -1,17 +1,18 @@
---
-title: Deploy a Worker from Your App
-description: "Learn how to create, update, list and delete serverless workers from your own code with Puter.js, instead of publishing them by hand."
+title: Deploy Workers Programmatically
+description: "Learn how to deploy serverless workers from your app with Puter.js, so each user or project gets its own backend."
tags: [workers]
order: 42
---
-You can publish a [serverless worker](/Workers/) by hand from puter.com. You
-can also do it from code, with the [Workers API](/Workers/#workers-api). Your
-app then creates a worker, updates its code and removes it, the same way it
-writes files.
+Some apps give their users a backend, like an app builder where each project
+gets its own API. For that, your app has to deploy the backend from code, at the
+moment the user needs it.
-This lets you build tools that ship backends for their users, such as an app
-builder that gives each project its own API.
+With the [Workers API](/Workers/#workers-api), your app creates a
+[serverless worker](/Workers/), updates its code and removes it, the same way it
+writes files. Each worker answers at its own URL, and there is no server for you
+to run.
## Create a Worker
@@ -42,7 +43,7 @@ const res = await puter.workers.exec(`${deployment.url}/me`);
await res.json(); // { username: 'grace' }
```
-[Build an API with a worker](/recipes/workers-build-an-api/) covers what to put
+[Build an API with a worker](/recipes/build-an-api/) covers what to put
in the routes.
Worker names are global, like subdomains, and are stored in lowercase. A name
@@ -66,9 +67,6 @@ await puter.fs.write(info.file_path, updatedCode);
Writing the file redeploys the worker at the same URL, so anything already
calling it keeps working.
-Don't create a new worker with a new name to ship a change. The old one stays
-online at its old URL, and every caller has to be pointed at the new one.
-
## Find Your Workers
To show the workers in your account, call
@@ -124,6 +122,6 @@ await puter.workers.delete('notes-api');
Its URL stops answering, and the name is free to use again. The source file
stays in your account.
-The link also works the other way. Deleting a worker's source file with
-[`puter.fs.delete()`](/FS/delete/) deletes the worker too, so keep the file for
-as long as the worker should stay online.
+Deleting a worker's source file with [`puter.fs.delete()`](/FS/delete/) also
+deletes the worker, so keep the file for as long as the worker should stay
+online.
diff --git a/src/docs/src/recipes/host-file.md b/src/docs/src/recipes/host-file.md
index 442c13c89..ea4b8556e 100644
--- a/src/docs/src/recipes/host-file.md
+++ b/src/docs/src/recipes/host-file.md
@@ -101,3 +101,9 @@ To take a link offline, delete the file with the
```js
await puter.fs.delete('public/photo.png');
```
+
+## Notes
+
+- To publish a whole website under a name the user picks, such as a page each
+ user builds in your app, see
+ [Add website publishing](/recipes/add-website-publishing/).
diff --git a/src/docs/src/recipes/manage-websites.md b/src/docs/src/recipes/manage-websites.md
index 016d906c0..250f52bab 100644
--- a/src/docs/src/recipes/manage-websites.md
+++ b/src/docs/src/recipes/manage-websites.md
@@ -5,7 +5,7 @@ tags: [hosting]
order: 56
---
-Once your app has [published a website](/recipes/publish-a-website/), the
+Once your app has [published a website](/recipes/add-website-publishing/), the
[hosting API](/Hosting/) lets it find that site again, change which folder it
serves, and take it offline. Each operation is one call that takes the site's
name.
@@ -28,6 +28,10 @@ for (const site of sites) {
Each entry is a [`Subdomain`](/Objects/subdomain/) object. `root_dir` is the
folder it serves, and is `null` when that folder no longer exists.
+When your code runs as a Puter app, the list only has the sites that app
+created. Sites the user published from somewhere else are left out, and the
+other hosting calls cannot change them either.
+
## Get One Site
To look up a single site by name, call [`puter.hosting.get()`](/Hosting/get/):
@@ -74,13 +78,3 @@ await puter.hosting.delete('grace-portfolio');
The address stops answering. The folder and its files stay in the user's
account, so nothing is lost and you can publish them again later.
-
-## Notes
-
-- When your code runs as a Puter app, these calls only see the sites that app
- created. Sites the user published elsewhere are not listed and cannot be
- changed.
-- [`puter.hosting.list()`](/Hosting/list/) returns sites only. Workers have
- their own [`puter.workers.list()`](/Workers/list/).
-- For a user with many sites, page through them with the `limit` and `cursor`
- options of [`puter.hosting.list()`](/Hosting/list/).
diff --git a/src/docs/src/recipes/publish-a-website.md b/src/docs/src/recipes/publish-a-website.md
deleted file mode 100644
index afc367dda..000000000
--- a/src/docs/src/recipes/publish-a-website.md
+++ /dev/null
@@ -1,108 +0,0 @@
----
-title: Publish a Website
-description: "Learn how to publish a website from your app with Puter.js, giving a folder of HTML files its own public puter.site address."
-tags: [hosting, fs]
-order: 55
----
-
-The [hosting API](/Hosting/) turns a folder in the user's Puter account into a
-public website at `https://