diff --git a/src/docs/src/Workers.md b/src/docs/src/Workers.md index fdf647e4e..4300e6388 100644 --- a/src/docs/src/Workers.md +++ b/src/docs/src/Workers.md @@ -144,6 +144,8 @@ You can see various Puter.js workers management features in action from the foll Once your worker is ready, you can put it online on a free `*.puter.work` subdomain. +
A worker is created once and keeps its name and URL. To ship changes, overwrite its source file rather than creating a new worker — see Updating a worker.
+ ### Publish from puter.com The quickest way to publish a worker is to create it on [puter.com](https://puter.com) and publish it. diff --git a/src/docs/src/Workers/create.md b/src/docs/src/Workers/create.md index ebe2b0e01..0426f37e6 100644 --- a/src/docs/src/Workers/create.md +++ b/src/docs/src/Workers/create.md @@ -10,6 +10,8 @@ Creates and deploys a new worker from a JavaScript file containing [router](../r
After a worker is created or updated, full propagation may take between 5 to 30 seconds to fully take effect across all edge servers.
+
A worker is tied to its name — you create it once and keep that name. To deploy changes, don't call create() again with a new name; instead overwrite the worker's source file (see Updating a worker below). Recreating under a different name leaves the old worker live at its old URL while your callers end up pointing at an orphaned one.
+ ## Syntax @@ -85,4 +87,21 @@ puter.workers.create('my-api', 'api-server.js') -``` \ No newline at end of file +``` + +## Updating a worker + +A worker keeps the same name and URL for its whole lifetime. You create it once with `create()`; after that, you **update it by overwriting its source file**, not by creating a new worker. + +[`puter.workers.get()`](/Workers/get/) returns the worker's [`file_path`](/Objects/workerinfo), so you can write your new code back to it: + +```js +// Look up the deployed worker's source file +const info = await puter.workers.get('my-api'); + +// Overwrite it with your new code — this redeploys the worker +// at the same name and URL +await puter.fs.write(info.file_path, updatedWorkerCode); +``` + +The worker redeploys from that file, so `https://my-api.puter.work` keeps serving — now running your updated code. Anything already calling the worker keeps working without changes. \ No newline at end of file diff --git a/src/docs/src/Workers/router.md b/src/docs/src/Workers/router.md index 117668145..455d3616e 100644 --- a/src/docs/src/Workers/router.md +++ b/src/docs/src/Workers/router.md @@ -28,35 +28,86 @@ The router object supports standard HTTP methods and provides a clean way to org ### Handler Parameters -Route handlers receive structured parameters: +Route handlers receive a single object as their parameter, which can be destructured into the following properties: - `request` - The incoming [HTTP request](https://developer.mozilla.org/en-US/docs/Web/API/Request). -- `user` - The user object, contains `user.puter` (available when called via [`puter.workers.exec()`](/Workers/exec/)) - - `user.puter` - The user's Puter resources (KV, FS, AI, etc.) -- `params` - URL parameters (for dynamic routes) -- `me` - The deployer's Puter object (your own Puter resources for KV, FS, AI, etc.) +- `user` - An object representing the user who made the request to this worker. It has a `puter` property (`user.puter`) that gives you access to that user's own Puter resources — KV, FS, AI, etc. Only available when the worker is called via [`puter.workers.exec()`](/Workers/exec/). +- `params` - Route parameters captured from the path (see [Route Parameters](#route-parameters)) ## Global Objects -When writing worker code, you have access to several global objects: +When writing worker code, you have access to these global objects: - `router` - The router object for defining API endpoints -- `me.puter` - The deployer's Puter object (your own Puter resources for KV, FS, AI, etc.) - -**Note**: `me.puter` refers to the deployer's (your) Puter resources, while `user.puter` refers to the user's resources when they execute your worker with their own token. +- `me` - An object representing you, the worker's owner. It has a `puter` property (`me.puter`) that gives you access to your own Puter resources — KV, FS, AI, etc. ## Integration with Puter.js Just like in apps or websites, you can use Puter.js in workers to access AI, cloud storage, key-value stores, and databases. -The difference is where the resources are utilized. Normally with Puter.js, all resources belong to your users; each user has their own storage and databases. Workers give you the flexibility in using resources: +The difference is *whose* resources you use. A worker gives you two `.puter` objects to work with, and operations are billed to whichever one you call: -- **Worker context** (`me.puter`) - Store data in your own storage and databases. Use this for shared application data, server-side logic, and centralized resources that you control. -- **User context** (`user.puter`) - Keep data in each user's own storage and databases. This maintains the default [User-Pays model](/user-pays-model/) while still executing logic server-side. +- **`me.puter`** is the **worker context** — your own resources, as the owner. Use this for shared application data, server-side logic, and centralized resources you control. Operations run against your account and are billed to you. +- **`user.puter`** is the **user context** — the resources of the user who called the worker (available when it's executed via [`puter.workers.exec()`](/Workers/exec/), which runs it with their token). This keeps the default [User-Pays model](/user-pays-model/): each user's data stays in their own storage, billed to them, while your logic still runs server-side. -> The `user` object is available when the worker is executed via `puter.workers.exec()` in the frontend and contains the user's own Puter resources. +So you can mix and match within the same codebase — some endpoints reading and writing your own data (`me.puter`), others acting on the calling user's data (`user.puter`). -This means you can choose which parts of your app use centralized resources (your storage/database) versus user-specific resources, all from the same codebase. +## Route Parameters + +Sometimes part of a path isn't fixed — like a post ID or a username. You can capture these segments by prefixing them with a colon (`:`) in the route path. Each captured segment becomes a property on the `params` object, keyed by the name you gave it. + +```js +router.get("/api/posts/:category/:id", async ({ params }) => { + const { category, id } = params; + return { category, id }; +}); +``` + +A request to `/api/posts/tech/42` matches this route and gives you: + +- `params.category` → `"tech"` +- `params.id` → `"42"` + +You can use as many route parameters as you need. Captured values are always strings, so convert them yourself if you expect a number. + +## Wildcard Routes + +While a route parameter (`:name`) matches a single segment, a **wildcard** (`*name`) matches the rest of the path — any number of segments. Like a route parameter, the matched value is available on `params`, keyed by the name after the `*`. + +```js +router.get("/files/*path", async ({ params }) => { + // A request to /files/images/avatars/me.png gives: + // params.path === "images/avatars/me.png" + return { path: params.path }; +}); +``` + +A common use is a catch-all route for unmatched paths — define it last so it only runs when nothing else matched (see the [404 Handler](#examples) example below). + +## CORS + +Every response from your worker automatically includes `Access-Control-Allow-Origin: *`, so **simple cross-origin requests work out of the box** — a basic `GET` or `POST` from another origin just works, no extra code. + +Some requests need a **CORS preflight** first: the browser sends an `OPTIONS` request and waits for the allowed methods and headers before sending the real one. This happens when the request uses a method like `PUT` or `DELETE`, or carries custom headers (e.g. `Authorization`). + +To handle this, you can add an `OPTIONS` handler that returns the methods and headers you want to allow: + +```js +router.options("/*path", async () => { + return new Response(null, { + status: 204, + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization", + }, + }); +}); +``` + +This answers the preflight for any path with the CORS headers the browser expects, so your other routes work cross-origin. + +If you need different CORS rules per endpoint — for example, restricting the allowed methods or headers on a specific route — define an `OPTIONS` handler on that individual path instead of using the wildcard. ## Examples @@ -91,14 +142,14 @@ router.post("/api/user", async ({ request }) => { }); ``` -URL Parameters +Query Parameters ```js -router.post("/api/user*tag", async ({ request }) => { - // Get URL parameters +router.get("/api/search", async ({ request }) => { + // Read query string parameters from the URL const url = new URL(request.url); - const queryParam = url.searchParams.get("param"); - return { processed: true }; + const query = url.searchParams.get("q"); + return { query }; }); ``` @@ -112,13 +163,12 @@ router.post("/api/user", async ({ request }) => { }); ``` -URL Parameters +Route Parameters -Use `:paramName` in your route path to capture dynamic segments: +Use `:name` in your route path to capture route parameters: ```js router.get("/api/posts/:category/:id", async ({ request, params }) => { - // Dynamic route with parameters const { category, id } = params; return { category, id }; }); @@ -210,6 +260,28 @@ router.post("/api/risky-operation", async ({ request }) => { }); ``` +Worker Context vs User Context + +The same operation can run against either Puter account. Here, one endpoint reads from the calling user's KV store (`user.puter`), the other from your own (`me.puter`). + +```js +// Read from the calling user's KV store (user context) +router.get("/api/kv/user/get", async ({ request, user }) => { + const url = new URL(request.url); + const key = url.searchParams.get("key"); + const value = await user.puter.kv.get(key); + return { value }; +}); + +// Read from the worker owner's KV store (worker context) +router.get("/api/kv/worker/get", async ({ request }) => { + const url = new URL(request.url); + const key = url.searchParams.get("key"); + const value = await me.puter.kv.get(key); + return { value }; +}); +``` + File System Integration ```js