toiljs 0.0.113 → 0.0.114
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +145 -27
- package/build/client/.tsbuildinfo +1 -1
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/index.js +1 -1
- package/build/compiler/pages.js +1 -13
- package/build/compiler/prerender.d.ts +1 -0
- package/build/compiler/prerender.js +39 -2
- package/build/compiler/toil-docs.generated.js +3 -3
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/daemon/host.d.ts +2 -1
- package/build/devserver/daemon/host.js +12 -12
- package/build/devserver/daemon/index.js +3 -3
- package/build/io/.tsbuildinfo +1 -1
- package/build/logger/.tsbuildinfo +1 -1
- package/build/shared/.tsbuildinfo +1 -1
- package/docs/background/daemons.md +49 -3
- package/docs/cli/README.md +4 -2
- package/docs/getting-started/installation.md +18 -0
- package/package.json +15 -15
- package/src/cli/create.ts +1 -0
- package/src/cli/diagnostics.ts +74 -0
- package/src/cli/doctor.ts +105 -27
- package/src/cli/update.ts +30 -3
- package/src/cli/updates.ts +23 -0
- package/src/compiler/index.ts +11 -2
- package/src/compiler/pages.ts +1 -22
- package/src/compiler/prerender.ts +57 -3
- package/src/compiler/toil-docs.generated.ts +3 -3
- package/src/devserver/daemon/host.ts +29 -19
- package/src/devserver/daemon/index.ts +12 -5
- package/test/daemon-emulation.test.ts +44 -2
- package/test/doctor.test.ts +28 -0
- package/test/fixtures/daemon-app.ts +9 -4
- package/test/prerender.test.ts +64 -2
- package/test/update.test.ts +15 -1
- package/tsconfig.base.json +0 -1
|
@@ -16,10 +16,10 @@ export const TOIL_DOCS: Record<string, string> = {
|
|
|
16
16
|
"backend/README.md": "# Backend overview\n\nYour backend is TypeScript that toilscript compiles into a small, sandboxed WebAssembly program, which runs on the Dacely edge and answers every request.\n\n## What the backend is\n\nIn a toiljs project, everything under `server/` is your backend. You write it in TypeScript, the same language as your frontend. But it does not run in a browser and it does not run in Node. Instead, a compiler called **toilscript** turns it into **WebAssembly** (often shortened to **WASM**): a compact, fast, portable binary format that many kinds of servers can run safely.\n\nThat compiled program is then deployed to the **Dacely edge**. Two pieces of jargon to unpack there:\n\n- **The edge** means a fleet of servers spread across many cities around the world. When a user makes a request, it is served by the edge node physically closest to them. Close means fast: less distance for the data to travel, so lower latency. You do not pick a region or manage servers; your one compiled backend runs everywhere at once.\n- **Sandboxed** means your WASM program runs inside a locked box. It cannot open files, reach the operating system, or make raw network connections on its own. The only way it can touch the outside world is through a small, fixed set of **host functions** that toiljs provides (read the request, build a response, query the database, send an email, and so on). Every one of those calls is metered and bounded, so a buggy or hostile backend cannot crash the node or read another app's data. This is what makes it safe to run thousands of different apps on the same shared edge.\n\nYou never call the WASM boundary by hand. You write normal TypeScript classes and functions, tag them with decorators (like `@rest` or `@service`), and the compiler wires everything up.\n\n> New to decorators? A **decorator** is the `@name` you write just above a class or method. It attaches meaning to that code without changing what the code does line by line. toiljs uses decorators to say \"this class is an HTTP controller\" or \"this method is callable from the browser.\" See [Decorators](../concepts/decorators.md).\n\n## The request lifecycle\n\nHere is what happens, end to end, when a browser talks to your backend.\n\n```mermaid\nflowchart TD\n U[\"User's browser\"] -->|\"HTTP request\"| E[\"Nearest Dacely edge node\"]\n E --> S{\"Static file<br/>for this path?\"}\n S -->|\"Yes (GET/HEAD)\"| F[\"Serve the file<br/>(HTML, JS, images)\"]\n S -->|\"No\"| W[\"Your compiled backend<br/>(server.wasm handle)\"]\n W --> H[\"Your handler runs<br/>and returns a Response\"]\n H --> E\n F --> E\n E -->|\"HTTP response\"| U\n```\n\nStep by step:\n\n1. The request lands on the closest edge node.\n2. The edge first checks whether the path is a **static file** it can serve directly (your built frontend: HTML pages, JavaScript bundles, images). If so, it serves the file and never wakes your code. This is fast and free.\n3. Otherwise the edge hands the request to your compiled backend by calling its single WASM export, `handle`.\n4. Inside, toiljs decodes the raw bytes into a friendly [`Request`](./rest.md#the-request-object) object and calls your handler's `handle(req)` method.\n5. Your handler returns a [`Response`](./rest.md#building-a-response). toiljs encodes it back into bytes.\n6. The edge sends that response to the browser.\n\nThe key mental model: your backend is a pure function of the request. Bytes in, bytes out, one request at a time.\n\n## Stateless by default\n\nA **fresh copy** of your handler serves each request. Any fields you set on a controller do not survive to the next request, and the request might even be served by a different edge node on the other side of the world. This is called being **stateless**.\n\nThat is a feature, not a limitation: it is what lets your backend scale to the whole planet with no coordination. When you need data to persist between requests (a user account, a counter, a list of posts), you store it in the built-in global database, **ToilDB**. See [the database section](../database/README.md).\n\n```mermaid\nflowchart LR\n R1[\"Request 1\"] --> B1[\"Handler copy A<br/>(fields reset)\"]\n R2[\"Request 2\"] --> B2[\"Handler copy B<br/>(fields reset)\"]\n B1 --> DB[(\"ToilDB<br/>(shared, persistent)\")]\n B2 --> DB\n```\n\n## The three surfaces\n\nYour backend can expose three different kinds of endpoint. Each is opted into with a decorator, and each has its own page:\n\n| Surface | Decorator | What it is | When to use it |\n| --- | --- | --- | --- |\n| **HTTP REST** | `@rest` + `@get`/`@post`/... | Plain HTTP routes with paths, methods, and status codes. | A public API, webhooks, anything a browser, `curl`, or a third party calls directly. See [REST](./rest.md). |\n| **Typed RPC** | `@service` / `@remote` | Server functions your own frontend calls like local async functions, fully type-checked end to end. | Talking from your own React app to your own backend. See [RPC](./rpc.md). |\n| **Realtime** | `@stream` | A long-lived connection where the server keeps state per connected client. | Chat, live cursors, notifications, anything push-based. See [Realtime](../realtime/README.md). |\n\nREST and RPC are the everyday tools. Most apps use both: REST for anything the outside world calls, RPC for your own frontend. They are not exclusive; you can use all three in one project.\n\nAll of these share the same building block for their data: **`@data` classes**, which are typed structs that travel safely between the browser and your WASM backend. See [Data types](./data.md).\n\n## Where your handler lives\n\nREST, RPC, and streams self-register: you tag a class and the compiler adds it to the right dispatcher. A tiny amount of glue lives in `server/main.ts`, which imports your route files and names your top-level handler class. In a typical project you rarely touch `main.ts`; you add route and service files and they are discovered automatically.\n\nYour handler class extends `ToilHandler` and overrides `handle`. The common pattern is to try the REST dispatcher first, then fall back to your own logic:\n\n```ts\nimport { Method, Request, Response, Rest, ToilHandler } from 'toiljs/server/runtime';\n\nexport class AppHandler extends ToilHandler {\n public handle(req: Request): Response {\n // Try every @rest controller. Returns the first match, or null.\n const hit = Rest.dispatch(req);\n if (hit != null) return hit;\n\n // Your own hand-written endpoints can go here.\n if (req.path == '/health') return Response.text('ok\\n');\n\n // \"I have no answer for this path\": let the edge serve it (a static\n // file, the client app) instead of returning a hard 404.\n return Response.unhandled();\n }\n}\n```\n\nRPC calls (to the reserved path `/__toil_rpc`) are handled by the framework before your `handle` runs, so you do not dispatch them yourself.\n\n### Per-request hooks: `onRequestStarted` and `onRequestCompleted`\n\n`ToilHandler` gives you two optional hooks that run around every request, so you can add cross-cutting logic (logging, timing, metrics) once instead of repeating it in every route, and without re-implementing `handle`:\n\n- **`onRequestStarted(req: Request): void`** runs just **before** `handle(req)`. Override it for per-request setup: start a timer, log the incoming method and path, read a header you need everywhere.\n- **`onRequestCompleted(req: Request, resp: Response): void`** runs just **after** `handle` returns, and it is handed the `Response` that is about to go out. It **also runs when your handler throws**, after the runtime has turned that throw into a `500`, so it is the right place for teardown you always want to happen (record the outcome, stop the timer, emit a metric).\n\nBoth are empty by default, so override only the one you need. The framework calls them around every request; you never call them yourself.\n\n```ts\nimport { Request, Response, Rest, ToilHandler } from 'toiljs/server/runtime';\n\nexport class AppHandler extends ToilHandler {\n public handle(req: Request): Response {\n const hit = Rest.dispatch(req);\n return hit != null ? hit : Response.unhandled();\n }\n\n // Runs before every handle().\n public onRequestStarted(req: Request): void {\n // per-request setup, e.g. note the path you are about to serve\n }\n\n // Runs after every handle(), including after a throw became a 500.\n public onRequestCompleted(req: Request, resp: Response): void {\n // always-run teardown: record the outcome, stop a timer, emit a metric\n }\n}\n```\n\nIf your project is REST-only, you do not even need a custom handler; toiljs ships a ready-made one. See [REST](./rest.md#dispatch-and-the-404-fallback).\n\n## Compute tiers\n\nThe request/response backend described here is the default and most common tier, called **L1**. toiljs also has higher tiers for long-lived connections (streams) and scheduled background work (daemons), each compiled into its own WASM artifact from the same project. You opt into a tier just by adding its entry file and surface decorator. For the full picture, see [Compute tiers](../concepts/tiers.md).\n\n## Related\n\n- [HTTP routes (`@rest`)](./rest.md): paths, methods, params, and responses.\n- [Typed RPC (`@service`/`@remote`)](./rpc.md): calling the server from your frontend with end-to-end types.\n- [Data types (`@data`)](./data.md): the serializable structs everything uses.\n- [The database (ToilDB)](../database/README.md): where persistent state lives.\n- [Compute tiers](../concepts/tiers.md): L1 request, L2/L3 stream, L4 daemon.\n- [Realtime streams](../realtime/README.md): the `@stream` surface.\n",
|
|
17
17
|
"backend/rest.md": "# HTTP routes (`@rest`)\n\nExpose a real HTTP API by tagging a class `@rest` and its methods with an HTTP verb; toiljs generates the router for you.\n\n## Why and when\n\nUse `@rest` when something outside your own frontend needs to call your backend over plain HTTP: a webhook from another service, a public API, a mobile app, a `curl` script, or a browser hitting a URL directly. REST endpoints are ordinary URLs with ordinary HTTP methods and status codes, so any HTTP client understands them.\n\nIf instead you are calling your own backend from your own React frontend, [typed RPC](./rpc.md) is usually nicer (you get end-to-end types and skip URL wrangling). The two live happily side by side.\n\n## The shape of a route\n\nA REST route is a method on a class. The class decorator says where the class is mounted; the method decorator says which HTTP method and path it answers.\n\n```ts\nimport { Response, RouteContext } from 'toiljs/server/runtime';\n\n@rest('players') // this controller is mounted at /players\nclass Players {\n @get('/:id') // answers GET /players/:id\n public get(ctx: RouteContext): Response {\n const id = ctx.param('id');\n return Response.json(`{\"id\":\"${id}\"}`);\n }\n}\n```\n\nSave that file, import it once (see [How routes are discovered](#how-routes-are-discovered)), and `GET /players/42` returns `{\"id\":\"42\"}`. That is a working endpoint.\n\n## `@rest`: mounting a controller\n\n`@rest` marks a class as a route **controller** (a group of related routes) and mounts it at a URL prefix.\n\n```ts\n@rest('api') // mounted at /api\n@rest('/') // or @rest('') : mounted at the root\n@rest({ stream: DataStream.Binary }) // root mount, binary body codec by default\n```\n\n- The string is the mount prefix. `\"api\"`, `\"/api\"`, and `\"api/\"` all normalize to `/api`. `\"\"` and `\"/\"` mean the root.\n- The object form sets class-wide defaults. `stream: DataStream.Binary` makes every route in the class use the binary body codec instead of JSON (more on that in [Request and response bodies](#request-and-response-bodies)).\n\nThe full URL of a route is the controller prefix joined with the method path. With `@rest('api')` and `@get('/todos/:id')`, the route is `GET /api/todos/:id`.\n\n## Verb decorators\n\nEach HTTP method has a decorator that takes the route path:\n\n```ts\n@get('/path') @post('/path') @put('/path') @patch('/path')\n@del('/path') @head('/path') @options('/path')\n```\n\nNote the delete decorator is `@del`, not `@delete` (the word `delete` is reserved in TypeScript, so it cannot be a decorator name).\n\n### `@route`: the explicit form\n\n`@route` is the general form. Reach for it when you want to set the body codec per route, or you just prefer an object:\n\n```ts\n@route({ method: Methods.POST, path: '/upload', stream: DataStream.Binary })\npublic upload(body: FileData): FileResult { /* ... */ }\n```\n\n`method` (from the `Methods` enum) and `path` are required; `stream` is optional and overrides the controller default. `Methods` and `DataStream` are global enums (like the decorators themselves, they need no import).\n\n## Path parameters\n\nA path segment written as `:name` is a **path parameter**: it captures whatever the request has in that position. Read it with `ctx.param('name')`.\n\n```ts\n@get('/todos/:id/items/:itemId')\npublic getItem(ctx: RouteContext): Response {\n const id = ctx.param('id'); // \"42\" for /todos/42/items/9\n const itemId = ctx.param('itemId'); // \"9\"\n return Response.json(`{\"todo\":\"${id}\",\"item\":\"${itemId}\"}`);\n}\n```\n\nMatching is **segment-exact**: the request path must have the same number of `/`-separated segments, the static segments must match literally, and each `:param` captures one segment. The query string is removed before matching. A captured param is always a string; convert it yourself if you need a number (for example `u64.parse(ctx.param('id'))`).\n\n## Method parameters: reading the request\n\nA route method may declare zero, one, or two parameters. toiljs looks at their types to decide what to pass:\n\n- a parameter typed `RouteContext` receives the [request context](#the-routecontext-object) (path params, query, headers, raw body);\n- any other type is treated as the **request body**, decoded into that [`@data`](./data.md) type.\n\n```ts\n@get('/status')\npublic status(): StatusResponse { /* no body, no context */ }\n\n@get('/user/:id')\npublic getUser(ctx: RouteContext): User { /* context only */ }\n\n@post('/create')\npublic create(input: NewTodo): Todo { /* body only */ }\n\n@post('/user/:id/score')\npublic addScore(input: ScoreDelta, ctx: RouteContext): Player {\n const id = ctx.param('id'); // body AND context\n /* ... */\n}\n```\n\nThe order of the two parameters does not matter; toiljs classifies them by type.\n\n## Return types\n\nYou return one of two things from a route, and toiljs encodes it for you:\n\n| You return | toiljs sends |\n| --- | --- |\n| a [`@data`](./data.md) value | The value serialized (JSON by default, binary if the route is in binary mode). Status `200`. |\n| a `Response` | Exactly that response: your status, headers, cookies, and body, untouched. |\n| `void` (nothing) | `204 No Content`. |\n\nReturning a `@data` value is the short path when you just want to send the data. Returning a `Response` gives you full control: a custom status, extra headers, cookies, caching. Use whichever fits.\n\n```ts\n// Short path: return the data, let toiljs serialize it as JSON.\n@get('/me')\npublic me(): Player { return currentPlayer(); }\n\n// Full control: custom 404, a header, and the body serialized by hand.\n@get('/:id')\npublic get(ctx: RouteContext): Response {\n const id = u64.parse(ctx.param('id'));\n if (!store.has(id)) return Response.notFound();\n return Response.json(store.get(id).toJSON().toString())\n .setHeader('cache-control', 'no-store');\n}\n```\n\n## Request and response bodies\n\nEvery route is either a **JSON route** (the default) or a **Binary route**. This decides how request bodies are decoded and response values are encoded:\n\n- **JSON**: the request body is parsed as JSON and revived into your `@data` type; the response value is serialized to JSON. Best for endpoints a browser or a third party calls directly, because JSON is universally readable.\n- **Binary**: the request body and response use toiljs's compact binary codec (`DataWriter`/`DataReader`). Smaller and faster, and exact for very large integers. Best for app-to-app traffic and anything performance sensitive.\n\nSet the mode on the whole controller with `@rest({ stream: DataStream.Binary })`, or per route with `@route({ ..., stream: DataStream.Binary })`. Full detail on both codecs is in [Data types](./data.md).\n\n> Large integers and JSON: numbers of 64 bits or more (`u64`, `i64`, `u128`, `u256`, and friends) are sent over JSON as decimal strings, so they stay exact at any size (plain JSON numbers would lose precision). The generated client turns them back into `bigint`. See [Types](../concepts/types.md).\n\n## The `RouteContext` object\n\n`RouteContext` is your window into the incoming request. toiljs builds one and passes it to any route method that declares a `RouteContext` parameter.\n\n| Member | Signature | What it gives you |\n| --- | --- | --- |\n| `request` | `Request` | The raw request (method, path, headers, body). |\n| `param` | `param(name: string): string` | A captured path param, or `\"\"` if absent. |\n| `query` | `query(name: string): string` | A query-string value (`?q=hi`), or `\"\"` if absent. Not URL-decoded in v1. |\n| `header` | `header(name: string): string \\| null` | A request header, case-insensitive, or `null`. |\n| `text` | `text(): string` | The raw request body decoded as UTF-8 text. |\n| `clientIp` | `clientIp(): string` | The connecting client's IP, or `\"\"` if unavailable. |\n\n`clientIp()` is the real socket address the edge observed, not a forgeable header like `X-Forwarded-For`, so it is safe to key rate limits, geo lookups, or audit logs on it.\n\n## The `Request` object\n\nThe raw request. You get it as `ctx.request`, or directly as the argument to a hand-written `handle(req)`.\n\n**Fields:**\n\n| Field | Type | Notes |\n| --- | --- | --- |\n| `method` | `Method` | `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, or `UNKNOWN`. |\n| `path` | `string` | The path, including the query string. |\n| `headers` | `Array<Header>` | Ordered list; a `Header` is `{ name, value }`. |\n| `body` | `Uint8Array` | The raw request body bytes. |\n\n**Methods:**\n\n| Method | Signature | Notes |\n| --- | --- | --- |\n| `header` | `header(name: string): string \\| null` | Case-insensitive lookup, `null` if absent. |\n| `cookies` | `cookies(): CookieMap` | Parses the `Cookie` header (values percent-decoded); cached per request. |\n| `cookie` | `cookie(name: string): string \\| null` | A single cookie value, or `null`. |\n\nThe `Method` enum and the `Header` class are exported from `toiljs/server/runtime`.\n\n## Building a response\n\n`Response` is what you return. Create one with a static factory, then chain instance methods to add headers, cookies, and caching. Every instance method returns the same `Response`, so calls chain.\n\n### Static factories\n\n| Factory | Signature | Status | Content-Type |\n| --- | --- | --- | --- |\n| `Response.text` | `text(body: string, status = 200)` | 200 | `text/plain; charset=utf-8` |\n| `Response.html` | `html(body: string, status = 200)` | 200 | `text/html; charset=utf-8` |\n| `Response.json` | `json(body: string, status = 200)` | 200 | `application/json; charset=utf-8` |\n| `Response.bytes` | `bytes(body: Uint8Array, status = 200)` | 200 | `application/octet-stream` |\n| `Response.empty` | `empty(status)` | custom | (none) |\n| `Response.notFound` | `notFound()` | 404 | text |\n| `Response.badRequest` | `badRequest(msg = 'bad request')` | 400 | text |\n| `Response.internalError` | `internalError(msg = 'internal error')` | 500 | text |\n| `Response.unhandled` | `unhandled()` | 404 | text, plus a marker header (see below) |\n\n`Response.json` takes an **already-serialized** string, not an object. Build it from a `@data` value with `value.toJSON().toString()`, or return the `@data` value directly and let toiljs serialize it (usually simpler).\n\n### Instance methods\n\n| Method | Signature | What it does |\n| --- | --- | --- |\n| `setHeader` | `setHeader(name, value): Response` | Appends a header (call again to add more). |\n| `setCookie` | `setCookie(cookie: Cookie): Response` | Appends a `Set-Cookie`. |\n| `setCookieKV` | `setCookieKV(name, value): Response` | Shorthand for a cookie with no attributes. |\n| `clearCookie` | `clearCookie(name, path = '/', domain = ''): Response` | Emits a deletion cookie (empty value, `Max-Age=0`). |\n| `cache` | `cache(edgeTtlMinutes, browserTtlSeconds = 0, privateScope = false, allowAuth = false): Response` | Marks the response cacheable. See [Caching](../services/caching.md). |\n| `cacheFor` | `cacheFor(minutes): Response` | Shorthand for edge-caching for N minutes. |\n\n```ts\nreturn Response.json('{\"id\":42}')\n .setHeader('x-trace', traceId)\n .setCookieKV('seen', '1')\n .cacheFor(5);\n```\n\nSee [Cookies](../services/cookies.md) for the full cookie builder and [Caching](../services/caching.md) for the caching rules.\n\n## How dispatch works\n\nEvery `@rest` controller registers itself into a global `Rest` registry when its module loads. At request time, `Rest.dispatch(req)` walks the controllers and tries their routes.\n\n```mermaid\nflowchart TD\n A[\"Request arrives\"] --> B[\"Rest.dispatch(req)\"]\n B --> C{\"Any controller<br/>route match<br/>the method + path?\"}\n C -->|\"Yes\"| D[\"Run that method\"]\n D --> E[\"Return its Response\"]\n C -->|\"No\"| F[\"dispatch returns null\"]\n F --> G[\"Your fallback logic<br/>(or Response.unhandled)\"]\n```\n\nMatching order is deterministic: controllers are tried in the order their modules load, and routes within a controller in declaration order. The **first match wins**, so put more specific routes before catch-all ones.\n\n## How routes are discovered\n\n`toiljs build` scans every file under `server/` and finds your decorated classes on its own, so a route file is picked up as soon as it exists. To keep a plain `toilscript` run (and your editor) seeing the same set, projects also `import` each route file once in `server/main.ts`:\n\n```ts\n// server/main.ts (excerpt)\nimport './routes/Players';\nimport './routes/Todos';\n```\n\nAdding a new controller is two steps: write the file, add the one-line import.\n\n## Dispatch and the 404 fallback\n\nInside your handler, the usual pattern is: try REST first, then fall through to anything else.\n\n```ts\nconst hit = Rest.dispatch(req); // Response, or null if nothing matched\nif (hit != null) return hit;\nreturn Response.unhandled(); // no route matched here\n```\n\nThere are two different 404s, and the difference matters:\n\n- **`Response.notFound()`** means \"I looked, and that resource does not exist.\" It is sent to the client as a plain `404`.\n- **`Response.unhandled()`** means \"this server has no route for that path.\" It is a `404` carrying a marker header (`x-toil-unhandled`). The edge (and the dev server) reads that marker and tries to serve the path another way: a static file, or the client-side app. The marker is stripped before anything reaches the browser.\n\nRule of thumb: return `unhandled()` when a path is simply not yours to handle, and `notFound()` when the path is yours but the specific thing is missing.\n\nIf your project is REST-only, you do not need a custom handler at all. toiljs ships `RestHandler`, which does exactly the dispatch-then-`unhandled` dance:\n\n```ts\nimport { Server, RestHandler } from 'toiljs/server/runtime';\nServer.handler = () => new RestHandler();\n```\n\n## Guards: auth, rate limits, caching\n\nYou can stack extra decorators on a route (or a whole controller) to protect or cache it. They compose with the verb decorators:\n\n```ts\n@rest('admin')\nclass Admin {\n @get('/stats')\n @auth // reject with 401 if there is no valid session\n @ratelimit(RateLimit.SlidingWindow, 30, 60) // at most 30 requests per 60 seconds\n public stats(): Stats { /* ... */ }\n}\n```\n\n- `@auth` requires a valid signed session, else the request is rejected with `401` before your method runs. See the [Auth guide](../auth/README.md).\n- `@ratelimit` caps how often a caller may hit the route. See [Rate limiting](../services/ratelimit.md).\n- Response caching is opt-in per response with `.cache(...)` / `.cacheFor(...)`. See [Caching](../services/caching.md).\n\n## A complete CRUD example\n\nA small in-memory players API showing create, read, update, and delete. In a real app you would store players in [ToilDB](../database/README.md) instead of a local map, because each request gets a fresh handler (see [statelessness](./README.md#stateless-by-default)); this example keeps it in memory to stay focused on routing.\n\n```ts\n// server/models/NewPlayer.ts\n@data\nexport class NewPlayer {\n name: string = '';\n}\n```\n\n```ts\n// server/models/Player.ts\n@data\nexport class Player {\n id: u64 = 0;\n name: string = '';\n score: i64 = 0;\n}\n```\n\n```ts\n// server/routes/Players.ts\nimport { Response, RouteContext } from 'toiljs/server/runtime';\nimport { NewPlayer } from '../models/NewPlayer';\nimport { Player } from '../models/Player';\n\n// A stand-in store. Real apps use ToilDB (see the database section).\nconst store = new Map<u64, Player>();\nlet nextId: u64 = 1;\n\n@rest('players')\nclass Players {\n // CREATE: POST /players (JSON body -> Player)\n @post('/')\n public create(input: NewPlayer): Player {\n const p = new Player();\n p.id = nextId++;\n p.name = input.name;\n p.score = 0;\n store.set(p.id, p);\n return p; // 200 with the player as JSON\n }\n\n // READ: GET /players/:id\n @get('/:id')\n public get(ctx: RouteContext): Response {\n const id = u64.parse(ctx.param('id'));\n if (!store.has(id)) return Response.notFound();\n return Response.json(store.get(id).toJSON().toString());\n }\n\n // UPDATE: PUT /players/:id (JSON body -> Player)\n @put('/:id')\n public update(input: NewPlayer, ctx: RouteContext): Response {\n const id = u64.parse(ctx.param('id'));\n if (!store.has(id)) return Response.notFound();\n const p = store.get(id);\n p.name = input.name;\n return Response.json(p.toJSON().toString());\n }\n\n // DELETE: DELETE /players/:id\n @del('/:id')\n public remove(ctx: RouteContext): Response {\n const id = u64.parse(ctx.param('id'));\n if (!store.has(id)) return Response.notFound();\n store.delete(id);\n return Response.empty(204); // 204 No Content\n }\n}\n```\n\nCalling it from anywhere:\n\n```sh\ncurl -X POST localhost:5173/players -d '{\"name\":\"Ada\"}'\ncurl localhost:5173/players/1\ncurl -X PUT localhost:5173/players/1 -d '{\"name\":\"Ada Lovelace\"}'\ncurl -X DELETE localhost:5173/players/1\n```\n\nBecause `@rest` also generates a typed fetch client, your React frontend can call the same routes without writing URLs:\n\n```ts\nawait Server.REST.players.create({ body: new NewPlayer('Ada') });\nawait Server.REST.players.get({ params: { id: 1 } });\n```\n\nSee [RPC and the generated client](./rpc.md#the-rest-fetch-client) for that client.\n\n## Gotchas\n\n- **Fields do not persist.** A fresh controller instance serves each request, so instance fields reset every time and are never shared between requests or edge nodes. Persist to [ToilDB](../database/README.md).\n- **`Response.json` wants a string.** Pass an already-serialized JSON string (or return the `@data` value directly). Passing an object will not do what you expect.\n- **`@del`, not `@delete`.** `delete` is a reserved word, so the decorator is `@del`.\n- **Query values are not URL-decoded in v1.** `ctx.query('q')` returns the raw value; decode it yourself if it may contain percent-encoding.\n- **Path matching is exact on segment count.** `/todos/:id` does not match `/todos/1/extra`. Add a route for the longer path.\n- **`notFound()` vs `unhandled()`.** Returning `notFound()` from your top handler stops the edge from falling through to static files or the client app. Use `unhandled()` for \"not my path.\"\n\n## Related\n\n- [Data types (`@data`)](./data.md): the request and response body structs, and the JSON vs binary codecs.\n- [Typed RPC](./rpc.md): call your backend from your frontend with end-to-end types (and the generated REST fetch client).\n- [Backend overview](./README.md): the request lifecycle and handler model.\n- [Cookies](../services/cookies.md), [Caching](../services/caching.md), [Rate limiting](../services/ratelimit.md): response helpers and guards.\n- [Auth](../auth/README.md): protecting routes with `@auth`.\n- [The database](../database/README.md): persisting data between requests.\n",
|
|
18
18
|
"backend/rpc.md": "# Typed RPC (`@service` / `@remote`)\n\nWrite a server function, tag it `@remote`, and call it from your React frontend like a local async function, with the argument and return types checked end to end.\n\n## What RPC is\n\n**RPC** stands for Remote Procedure Call. The idea is old and simple: call a function that actually runs somewhere else (here, on the edge) as if it were a normal function in your own code. You write a method on the server, and on the client you `await` it. No URLs, no `fetch`, no manual JSON. Just a function call.\n\ntoiljs makes that call **fully typed**. When you build the server, it generates a TypeScript file (`shared/server.ts`) describing every callable function, its arguments, and its return type. Your frontend imports nothing extra: a global object called `Server` is available with all of it typed. If you change a server function's signature and rebuild, your frontend code stops type-checking until you fix the call. The client can never drift from the server.\n\n## Why and when\n\nUse RPC when **your own frontend** talks to **your own backend**. It is the most ergonomic and safest way to do that:\n\n- End-to-end types: rename a field on the server, and the client sees it immediately.\n- No plumbing: no route paths to invent, no request or response shapes to hand-write.\n- Exact numbers: 64-bit-and-larger integers arrive as `bigint`, never rounded.\n\nUse [`@rest`](./rest.md) instead when the caller is **not** your frontend: a webhook, a third-party integration, a public API, a mobile client, or anything that speaks in plain URLs and HTTP methods. RPC uses one internal endpoint and a binary wire format, so it is not meant to be called by hand.\n\nYou can use both in the same project. A common split: RPC for your app's own screens, REST for everything public.\n\n## Declaring callable functions\n\nTwo decorators expose server code to the client:\n\n- **`@remote`** on a top-level function makes it directly callable.\n- **`@service`** on a class groups related `@remote` methods under a namespace.\n\n```ts\n// server/services/Stats.ts\nimport { store } from '../core/store';\n\n@service\nclass Stats {\n @remote\n public playerCount(): i32 {\n return store.size;\n }\n}\n```\n\n```ts\n// server/services/remotes.ts\n@remote\nfunction ping(n: i32): i32 {\n return n + 1;\n}\n```\n\nThat is the whole server side. Build the server and the client can call them.\n\n## Calling from the frontend\n\nThe generated client surfaces everything on a global `Server`. A `@service` becomes a namespace keyed by the class name with a lowercase first letter (`Stats` becomes `stats`). A free `@remote` sits directly on `Server`. Every call returns a `Promise`.\n\n```ts\n// anywhere in your React app, no import needed\nconst count = await Server.stats.playerCount(); // number\nconst next = await Server.ping(41); // number -> 42\n```\n\nAutocomplete, argument checking, and the return type all come from `shared/server.ts`, which the build regenerates every time (and `toiljs dev` regenerates on save), so it is always in sync.\n\n## The round trip\n\nHere is what actually happens under that innocent-looking `await`.\n\n```mermaid\nsequenceDiagram\n participant C as Browser (Server.stats.playerCount)\n participant E as Dacely edge\n participant W as Your backend (WASM)\n C->>C: encode args with DataWriter\n C->>E: POST /__toil_rpc<br/>header dacely-rpc = method id<br/>body = encoded args\n E->>W: dispatch to the matching @remote\n W->>W: run playerCount(), encode the result\n W->>E: encoded result bytes\n E->>C: 200 application/octet-stream\n C->>C: decode into the typed value\n```\n\nA few facts worth knowing:\n\n- Every callable has a stable numeric **method id** (a hash of `\"Service.method\"` or the function name). The client sends it in the `dacely-rpc` header; the server dispatches on it.\n- Arguments and results travel in the compact **binary `@data` codec** (see [Data types](./data.md)), so large integers are exact and payloads are small.\n- The endpoint is a single reserved path, `/__toil_rpc`. You never route it yourself; the framework handles it before your `handle` runs.\n\n## RPC is stateless too\n\nJust like a REST controller, a fresh service instance serves each call. Fields you set on a `@service` class do not survive between calls. If two calls need to share data, that data lives in [ToilDB](../database/README.md), not in an instance field. See [statelessness](./README.md#stateless-by-default).\n\n## Argument and return types\n\nArguments and return values may be scalars, arrays, or [`@data`](./data.md) classes, in both directions. Here is how toilscript's types map to what you see on the TypeScript client:\n\n| ToilScript type | TypeScript type |\n| --- | --- |\n| `u8`, `u16`, `u32`, `i8`, `i16`, `i32`, `f32`, `f64` | `number` |\n| `u64`, `i64`, `u128`, `i128`, `u256`, `i256` | `bigint` |\n| `bool` | `boolean` |\n| `string` | `string` |\n| a `@data` class `T` | `T` (the generated class) |\n| `T[]` | `T[]` |\n\nIntegers of 64 bits or more become `bigint` on the client, so they are exact at any magnitude. See [Types](../concepts/types.md) for the full number story.\n\nPassing a `@data` value is just as easy as a scalar. Construct it on the client and pass it in:\n\n```ts\nimport { NewPlayer } from './shared/server';\n\nconst created = await Server.roster.add(new NewPlayer('Ada')); // returns a typed Player\n```\n\nThe `@data` classes in `shared/server.ts` share a byte-for-byte identical codec with the server, so values round-trip exactly.\n\n## Reading and writing the database from a `@remote`\n\nA `@remote` can use the database, but with a safety default: **a plain `@remote` is read-only.** If it tries to write to ToilDB, the compiler rejects it. To let a `@remote` write, add `@action`:\n\n```ts\n@service\nclass Roster {\n @remote\n public count(): i32 { // read-only: fine to just read\n return db.players.count();\n }\n\n @remote\n @action // opts into writes\n public add(input: NewPlayer): Player {\n return db.players.create(/* ... */);\n }\n}\n```\n\n`@query` is the explicit opposite of `@action`: it marks a function read-only on purpose (the default for a `@remote`, so you rarely need to write it). These are ToilDB **function kinds**; the full rules, including what each kind may and may not do, are in the [database docs](../database/README.md). The takeaway for RPC: reads work out of the box, and a write needs `@action`, so a read-only endpoint can never silently mutate your data.\n\n## Guarding a `@remote`\n\nGuards stack on a `@remote` exactly as they do on a REST route:\n\n```ts\n@service\nclass Stats {\n @remote\n @auth // reject with 401 when there is no valid session\n public secretCount(): i32 {\n return store.size;\n }\n}\n```\n\nThe RPC dispatcher enforces `@auth` (and `@ratelimit`) the same way the REST router does: the guard runs first, and an unauthenticated call gets a `401` before your method body executes. See the [Auth guide](../auth/README.md) and [Rate limiting](../services/ratelimit.md).\n\n## The generated `Server` surface\n\n`shared/server.ts` declares `Server` as a global with a shape like this (schematic):\n\n```ts\ndeclare global {\n const Server: {\n // free @remote functions\n ping(n: number): Promise<number>;\n\n // @service classes, keyed by lowercased name\n readonly stats: {\n playerCount(): Promise<number>;\n secretCount(): Promise<number>;\n };\n\n // @rest controllers get a fetch client under REST (see below)\n readonly REST: { /* ... */ };\n\n // @stream classes get a client under Stream (see the realtime docs)\n readonly Stream: { /* ... */ };\n };\n}\n```\n\nYou never edit this file; the build regenerates it. If a `Server` method throws that it is \"unavailable,\" the generated client has not loaded yet: run the server build (or `toiljs dev`, which does it on save).\n\n## The REST fetch client\n\n`@rest` controllers also get a typed client, under `Server.REST.<controller>.<route>`. It is real `fetch` code (because REST is just HTTP), and it is handy when you want your frontend to call a route you also expose publicly:\n\n```ts\n// controller @rest('players') with a create route taking a NewPlayer body\nconst player = await Server.REST.players.create({\n body: new NewPlayer('Ada'), // present only if the route takes a body\n // params: { id: 7 }, // present only if the path has :params\n query: { ref: 'home' }, // optional\n headers: { 'x-trace': id }, // optional\n});\n```\n\nThe wrapper builds the URL, substitutes `:params`, appends `query`, sends the request, throws on a non-2xx status, and decodes the response into the route's return type. A route declared to return `Response` resolves to the raw `fetch` `Response` so you can inspect headers or stream it yourself. See [REST](./rest.md) for the routes themselves.\n\n## RPC vs REST at a glance\n\n| | RPC (`@service` / `@remote`) | REST (`@rest`) |\n| --- | --- | --- |\n| Caller | Your own frontend | Anyone (browser, webhook, third party, `curl`) |\n| Client | `Server.svc.method(args)` | `Server.REST.ctrl.route(args)`, or plain `fetch` |\n| URL shape | One internal endpoint | Real paths and HTTP methods you design |\n| Wire format | Compact binary `@data` | JSON (or binary), your choice |\n| Types | End to end, automatic | End to end for the generated client |\n| Best for | App-internal calls | Public APIs, integrations, webhooks |\n\n## Gotchas\n\n- **RPC is not a public API.** It uses one reserved endpoint and a binary format meant for the generated client. If an outside system needs to call in, expose a [`@rest`](./rest.md) route.\n- **Instance fields do not persist.** A fresh service instance serves every call. Shared state belongs in [ToilDB](../database/README.md).\n- **Writes need `@action`.** A plain `@remote` is read-only; the compiler rejects a database write unless the method is `@action`.\n- **Rebuild after signature changes.** `shared/server.ts` is generated. If autocomplete looks stale, rebuild the server (`toiljs dev` does this on save).\n- **`bigint`, not `number`, for 64-bit values.** A `u64`/`i64`/`u256` argument or return is a `bigint` on the client. Pass `10n`, not `10`.\n\n## Related\n\n- [Data types (`@data`)](./data.md): the structs your RPC arguments and results are made of, and the binary codec they travel in.\n- [HTTP routes (`@rest`)](./rest.md): the public-facing alternative, and the `Server.REST` fetch client.\n- [Types](../concepts/types.md): `u64`, `u256`, and how they map to `number` / `bigint`.\n- [The database](../database/README.md): `@action` vs `@query`, and persisting state.\n- [Fetching data on the frontend](../frontend/data-fetching.md): using `Server.*` from your React components.\n- [Auth](../auth/README.md): guarding a `@remote` with `@auth`.\n",
|
|
19
|
-
"background/daemons.md": "# Daemons (`@daemon` / `@scheduled`)\n\nA `@daemon` is a single, long-lived background worker for your whole app. You mark a class `@daemon`, add `@scheduled` methods that fire on a timer, and the Dacely edge keeps exactly **one** copy of it running worldwide, restarting it elsewhere if the machine it is on fails.\n\n## What a daemon is\n\nThe word \"daemon\" (say \"DEE-mon\") is an old computing term for a program that runs quietly in the background, not tied to any single user. That is exactly what this is.\n\nCompare the three ways your server code can run:\n\n| Kind | How many run | Lives for |\n| ------------------------------- | ------------------------------------ | ----------------------------- |\n| [Request handler](../backend/rest.md) (`@rest`) | a fresh one per request | one request |\n| [Stream box](../realtime/streams.md) (`@stream`) | one per open connection | one connection |\n| **Daemon** (`@daemon`) | **exactly one for the whole app** | as long as it holds the lease |\n\nBecause it is a single, resident instance, its fields persist across scheduled runs (a request handler forgets everything after each request; a daemon does not). It is the right home for work that must happen **once globally on a cadence**, not once per user and not once per server.\n\n```ts\n@daemon\nclass Jobs {\n @scheduled('1h')\n hourly(): void {\n // Runs once an hour, on the one elected worker. Put recurring background\n // work here: rollups, cleanup, polling an upstream, and so on.\n }\n}\n```\n\n## `@scheduled`: run on a cadence\n\nA `@scheduled` method fires on a schedule. The single string argument is the cadence, and it comes in two flavours.\n\n### Interval schedules\n\nAn **interval** fires every fixed span of time. Write a number followed by a unit: `s` (seconds), `m` (minutes), `h` (hours), or `d` (days). The number must be at least 1, and the span may not exceed 7 days.\n\n```ts\n@scheduled('30s') everyHalfMinute(): void { /* ... */ }\n@scheduled('5m') everyFiveMinutes(): void { /* ... */ }\n@scheduled('1h') hourly(): void { /* ... */ }\n@scheduled('1d') daily(): void { /* ... */ }\n```\n\n### Cron schedules\n\nA **cron** expression fires at wall-clock times (\"every weekday at 9:15\", \"midnight on the first of the month\"). Use it when you care about the actual time of day, not just a repeating gap. A cron spec is five fields separated by spaces, in this order:\n\n```\nminute hour day-of-month month day-of-week\n```\n\ntoiljs recognises a cron spec by the spaces in it (an interval has none).\n\n```ts\n@scheduled('15 9 * * 1-5') // 09:15, Monday to Friday\nweekdayMorning(): void { /* ... */ }\n\n@scheduled('0 0 1 * *') // 00:00 on the 1st of every month\nmonthlyReset(): void { /* ... */ }\n```\n\nCron times are evaluated in **UTC** and are **minute-granular** (the smallest cron step is one minute). A `*` means \"every value\" for that field.\n\n### Rules\n\n- A `@scheduled` method takes **no arguments and returns `void`**.\n- A daemon class may have **several** `@scheduled` methods, each on its own cadence.\n- Because only the one elected worker fires them, a task runs **once per tick for the whole app**, never once per server.\n\n### `onStart`: run once at boot\n\nA daemon may also declare a plain `onStart(): void` method (not decorated). It runs **once**, when the daemon box first starts on the elected worker. Use it to set up state or kick off a long-running loop.\n\n```ts\n@daemon\nclass Jobs {\n onStart(): void {\n // one-time setup when this daemon becomes active\n }\n\n @scheduled('1h')\n hourly(): void { /* ... */ }\n}\n```\n\n## No backfill: missed runs are skipped, not replayed\n\nThis is the single most important thing to understand about scheduling.\n\nIf the daemon is down when a tick was due (say the leader failed and a standby is still taking over), or if the clock jumps forward, toiljs does **not** go back and run all the ticks you missed. It simply fires the **next** due run and moves on. This is called a **no-backfill** policy.\n\nTwo practical consequences:\n\n1. **Design tasks to be safe to skip.** \"Recompute the summary\" is fine to miss (the next run fixes it). \"Charge every user once\" is not, unless you make it idempotent.\n2. **Make tasks idempotent where a missed run matters.** Idempotent means running it twice (or catching up later) has the same effect as running it once. For example, \"set yesterday's total to X\" is idempotent; \"add 1 to a counter\" is not.\n\n## One global worker, with safe failover\n\nThere is exactly **one** daemon running for your app at any moment. A second machine keeps a **warm standby** ready but idle. If the active worker's hold on the job expires (it crashed, lost the network, or was shut down), the standby takes over and fires the following runs.\n\nThe mechanism is a **lease**. Think of the lease as a \"who is in charge\" token that only one worker can hold at a time, and that has to be renewed to keep. Only the worker holding the lease (the **leader**) runs the schedule.\n\n```mermaid\nsequenceDiagram\n participant A as Worker A (leader)\n participant L as Lease\n participant B as Worker B (standby)\n A->>L: hold the lease, renew it\n Note over A: A fires @scheduled ticks\n Note over B: B stays idle, watching\n A--xL: A crashes, stops renewing\n Note over L: lease expires\n B->>L: acquire the lease\n Note over B: B is now leader, fires the next ticks\n```\n\nThe important guarantee: **two workers never run the same tick at the same time.** This is called **at-most-once** scheduling. The trade-off is the no-backfill behaviour above: to be sure a tick is never run twice, the edge would rather skip the in-flight tick when a leader is lost than risk running it on two machines. You never start, stop, or place the daemon yourself; the edge elects the leader and drives it.\n\n## Leadership fencing: side effects only run on the leader\n\nA subtle risk with a warm standby is a \"split brain\": for a brief moment, two workers might both think they are the leader. To make that harmless, toiljs **fences** every side effect behind a leadership check. A **side effect** is anything that changes the outside world:\n\n- **Database writes** (creating, patching, deleting rows, adding to counters, appending events, publishing views).\n- **Outbound HTTP calls** (`http_call`, described below).\n\nThese run **only** on the confirmed leader. If code that is not the leader tries one, the edge refuses it (a \"not leader\" error) rather than let it happen twice. Plain **reads** and computation are not fenced (they are safe to do anywhere). So even if two workers briefly overlap, only one of them can actually write or call out. You do not write the fencing yourself; it is automatic. The upshot: put your writes and outbound calls in `@scheduled` methods freely, and trust that they happen once.\n\n## `daemon.*` host calls\n\nA daemon has a small set of host abilities beyond ordinary computation:\n\n- **Database access.** A daemon reads and writes [ToilDB](../database/README.md) with the same collection handles you use in a route or a derive (`.get`, `.add`, `.append`, `.publish`, and so on). Writes are leader-fenced as described above.\n- **Outbound HTTP (`http_call`).** A daemon can call an external service (to poll an API, post to a webhook). This is the one place your server code reaches out to the internet, so it is deliberately restricted:\n - It is **leader-only** (fenced, like any side effect).\n - It is **SSRF-bounded**. SSRF (server-side request forgery) is an attack where code is tricked into calling internal addresses it should not reach. The edge resolves the target host and blocks private or internal addresses, so a daemon cannot use `http_call` to poke around inside the network.\n - It is **metered**: making many calls or pulling huge responses costs budget, which caps abuse.\n- **Leadership info** (`is_leader`, `current_epoch`). A daemon can check whether it is currently the leader, which is useful for guarding a long `onStart` loop.\n\n> **Note:** In `toiljs dev` (the single-process local emulator), the daemon is always the leader (there is nothing to fail over to), and `http_call` is stubbed to return a \"call failed\" result rather than make real network requests. Everything else, including the schedule and your database writes, runs exactly as it does on the edge.\n\n### `http_call` is not callable from your code yet\n\nAn honest caveat, because it is easy to assume otherwise: **there is no guest-callable `http_call` API in toiljs today.** The capability itself is fully built on the *host* side (the edge implements the leader-only, SSRF-bounded, metered outbound call as a low-level host import, `daemon.http_call(reqPtr, reqLen, outPtr, outCap) -> i64`, and the dev emulator reserves the same name), but toiljs does **not** ship a friendly TypeScript wrapper you can call from a `@scheduled` method. There is no `daemon.httpCall(...)` (or similarly named) function in the standard library, so your daemon code cannot make an outbound HTTP request at the moment.\n\nTreat outbound HTTP from a daemon as **planned, not yet available**. When the guest binding lands it will behave exactly as described above (leader-fenced, SSRF-bounded, and metered, and its usage will show up in the `Analytics` counters `daemonHttpCallAttempts` / `daemonHttpCallFailures`). Until then, if a daemon needs data from an outside service, have that service write into [ToilDB](../database/README.md) some other way, and let the daemon read it from there.\n\n## The `main.daemon.ts` file (a separate tier)\n\nLike streams, daemons live in their **own entry file**, `server/main.daemon.ts`, and compile into their **own artifact**, `build/server/release-cold.wasm`. Importing your `@daemon` module there pulls it into that artifact.\n\n```ts\n// server/main.daemon.ts\nimport { revertOnError } from 'toiljs/server/runtime/abort/abort';\n\nimport './daemon/Jobs'; // add each @daemon module here\n\n// NOTE: unlike main.ts / main.stream.ts, the daemon entry does NOT re-export the\n// request runtime. A daemon artifact exposes its schedule hooks, not the request handler.\nexport function abort(message: string, fileName: string, line: u32, column: u32): void {\n revertOnError(message, fileName, line, column);\n}\n```\n\n`toiljs build` produces `release-cold.wasm` automatically when your project declares a `@daemon` surface:\n\n```sh\n$ ls build/server/*.wasm\nbuild/server/release.wasm # L1 request (@rest / @service)\nbuild/server/release-stream.wasm # L2/L3 stream (@stream)\nbuild/server/release-cold.wasm # L4 daemon (@daemon)\n```\n\nThere is **at most one** `@daemon` class per project, and it is a compile error to put a `@daemon` in the request build. See [Compute tiers](../concepts/tiers.md) for how one source tree becomes three artifacts.\n\n## Worked example: a periodic rollup\n\nA common daemon job: every hour, read a running total and store a small summary so pages can show it with one cheap read. This reads a [counter](../database/counters.md) and writes a summary [document](../database/documents.md); the write only takes effect on the leader.\n\n```ts\n// server/models/StatKey.ts and Summary.ts (@data types)\n@data\nclass StatKey {\n name: string = 'signups';\n constructor(name: string = 'signups') { this.name = name; }\n}\n\n@data\nclass Summary {\n total: u64 = 0;\n updatedAt: u64 = 0;\n}\n\n// server/data/StatsDb.ts\n@database\nclass StatsDb {\n @collection static signups: Counter<StatKey>;\n @collection static summary: Documents<StatKey, Summary>;\n}\n\n// server/daemon/Jobs.ts\n@daemon\nclass Jobs {\n @scheduled('1h')\n rollup(): void {\n const key = new StatKey('signups');\n const total = StatsDb.signups.get(key); // a read (allowed anywhere)\n\n const s = new Summary();\n s.total = total;\n s.updatedAt = <u64>(Date.now() / 1000);\n StatsDb.summary.patch(key, s); // a write: runs only on the leader\n }\n}\n```\n\nNow any request handler can serve the summary with a single keyed read, and it is never more than an hour stale. Because the write is fenced to the leader and the rollup is idempotent (it *sets* the total rather than adding to it), a missed or failed-over tick is harmless: the next hourly run simply refreshes it.\n\n## When not to use a daemon\n\n- **When a user is waiting for the result.** Do that work in a [route](../backend/rest.md) or [RPC](../backend/rpc.md), on the request path.\n- **When you need a fast read-view kept in sync with data changes.** That is a [`@derive`](./derive.md), which runs on every write rather than on a timer.\n- **When you need exactly-once, never-skipped execution.** Scheduling is at-most-once with no backfill, so make tasks idempotent or safe to skip. A daemon is not a guaranteed job queue.\n\n## Related\n\n- [Background overview](./README.md): daemons versus `@derive`, and which to reach for.\n- [Derived views (`@derive`)](./derive.md): keep a read-view in sync on every write.\n- [Compute tiers (L1 to L4)](../concepts/tiers.md): the daemon runs on the L4 global tier.\n- [Counters](../database/counters.md), [Documents](../database/documents.md), [Events](../database/events.md): the data a daemon reads and writes.\n",
|
|
19
|
+
"background/daemons.md": "# Daemons (`@daemon` / `@scheduled`)\n\nA `@daemon` is a single, long-lived background worker for your whole app. You mark a class `@daemon`, add `@scheduled` methods that fire on a timer, and the Dacely edge keeps exactly **one** copy of it running worldwide, restarting it elsewhere if the machine it is on fails.\n\n## What a daemon is\n\nThe word \"daemon\" (say \"DEE-mon\") is an old computing term for a program that runs quietly in the background, not tied to any single user. That is exactly what this is.\n\nCompare the three ways your server code can run:\n\n| Kind | How many run | Lives for |\n| ------------------------------- | ------------------------------------ | ----------------------------- |\n| [Request handler](../backend/rest.md) (`@rest`) | a fresh one per request | one request |\n| [Stream box](../realtime/streams.md) (`@stream`) | one per open connection | one connection |\n| **Daemon** (`@daemon`) | **exactly one for the whole app** | as long as it holds the lease |\n\nBecause it is a single, resident instance, its fields persist across scheduled runs (a request handler forgets everything after each request; a daemon does not). It is the right home for work that must happen **once globally on a cadence**, not once per user and not once per server.\n\n```ts\n@daemon\nclass Jobs {\n @scheduled('1h')\n hourly(): void {\n // Runs once an hour, on the one elected worker. Put recurring background\n // work here: rollups, cleanup, polling an upstream, and so on.\n }\n}\n```\n\n## `@scheduled`: run on a cadence\n\nA `@scheduled` method fires on a schedule. The single string argument is the cadence, and it comes in two flavours.\n\n### Interval schedules\n\nAn **interval** fires every fixed span of time. Write a number followed by a unit: `s` (seconds), `m` (minutes), `h` (hours), or `d` (days). The number must be at least 1, and the span may not exceed 7 days.\n\n```ts\n@scheduled('30s') everyHalfMinute(): void { /* ... */ }\n@scheduled('5m') everyFiveMinutes(): void { /* ... */ }\n@scheduled('1h') hourly(): void { /* ... */ }\n@scheduled('1d') daily(): void { /* ... */ }\n```\n\n### Cron schedules\n\nA **cron** expression fires at wall-clock times (\"every weekday at 9:15\", \"midnight on the first of the month\"). Use it when you care about the actual time of day, not just a repeating gap. A cron spec is five fields separated by spaces, in this order:\n\n```\nminute hour day-of-month month day-of-week\n```\n\ntoiljs recognises a cron spec by the spaces in it (an interval has none).\n\n```ts\n@scheduled('15 9 * * 1-5') // 09:15, Monday to Friday\nweekdayMorning(): void { /* ... */ }\n\n@scheduled('0 0 1 * *') // 00:00 on the 1st of every month\nmonthlyReset(): void { /* ... */ }\n```\n\nCron times are evaluated in **UTC** and are **minute-granular** (the smallest cron step is one minute). A `*` means \"every value\" for that field.\n\n### Rules\n\n- A `@scheduled` method takes **no arguments and returns `void`**.\n- A daemon class may have **several** `@scheduled` methods, each on its own cadence.\n- Because only the one elected worker fires them, a task runs **once per tick for the whole app**, never once per server.\n\n### `onStart`: run once at boot\n\nA daemon may also declare a plain `onStart(): void` method (not decorated). It runs **once**, when the daemon box first starts on the elected worker. Use it to set up state or kick off a long-running loop.\n\n```ts\n@daemon\nclass Jobs {\n onStart(): void {\n // one-time setup when this daemon becomes active\n }\n\n @scheduled('1h')\n hourly(): void { /* ... */ }\n}\n```\n\n## No backfill: missed runs are skipped, not replayed\n\nThis is the single most important thing to understand about scheduling.\n\nIf the daemon is down when a tick was due (say the leader failed and a standby is still taking over), or if the clock jumps forward, toiljs does **not** go back and run all the ticks you missed. It simply fires the **next** due run and moves on. This is called a **no-backfill** policy.\n\nTwo practical consequences:\n\n1. **Design tasks to be safe to skip.** \"Recompute the summary\" is fine to miss (the next run fixes it). \"Charge every user once\" is not, unless you make it idempotent.\n2. **Make tasks idempotent where a missed run matters.** Idempotent means running it twice (or catching up later) has the same effect as running it once. For example, \"set yesterday's total to X\" is idempotent; \"add 1 to a counter\" is not.\n\n## One global worker, with safe failover\n\nThere is exactly **one** daemon running for your app at any moment. A second machine keeps a **warm standby** ready but idle. If the active worker's hold on the job expires (it crashed, lost the network, or was shut down), the standby takes over and fires the following runs.\n\nThe mechanism is a **lease**. Think of the lease as a \"who is in charge\" token that only one worker can hold at a time, and that has to be renewed to keep. Only the worker holding the lease (the **leader**) runs the schedule.\n\n```mermaid\nsequenceDiagram\n participant A as Worker A (leader)\n participant L as Lease\n participant B as Worker B (standby)\n A->>L: hold the lease, renew it\n Note over A: A fires @scheduled ticks\n Note over B: B stays idle, watching\n A--xL: A crashes, stops renewing\n Note over L: lease expires\n B->>L: acquire the lease\n Note over B: B is now leader, fires the next ticks\n```\n\nThe important guarantee: **two workers never run the same tick at the same time.** This is called **at-most-once** scheduling. The trade-off is the no-backfill behaviour above: to be sure a tick is never run twice, the edge would rather skip the in-flight tick when a leader is lost than risk running it on two machines. You never start, stop, or place the daemon yourself; the edge elects the leader and drives it.\n\n## Leadership fencing: side effects only run on the leader\n\nA subtle risk with a warm standby is a \"split brain\": for a brief moment, two workers might both think they are the leader. To make that harmless, toiljs **fences** every side effect behind a leadership check. A **side effect** is anything that changes the outside world:\n\n- **Database writes** (creating, patching, deleting rows, adding to counters, appending events, publishing views).\n- **Outbound HTTP calls** (`http_call`, described below).\n\nThese run **only** on the confirmed leader. If code that is not the leader tries one, the edge refuses it (a \"not leader\" error) rather than let it happen twice. Plain **reads** and computation are not fenced (they are safe to do anywhere). So even if two workers briefly overlap, only one of them can actually write or call out. You do not write the fencing yourself; it is automatic. The upshot: put your writes and outbound calls in `@scheduled` methods freely, and trust that they happen once.\n\n## `daemon.*` host calls\n\nA daemon has a small set of host abilities beyond ordinary computation:\n\n- **Database access.** A daemon reads and writes [ToilDB](../database/README.md) with the same collection handles you use in a route or a derive (`.get`, `.add`, `.append`, `.publish`, and so on). Writes are leader-fenced as described above.\n- **Outbound HTTP (`http_call`).** A daemon can call an external service (to poll an API, post to a webhook). This is the one place your server code reaches out to the internet, so it is deliberately restricted:\n - It is **leader-only** (fenced, like any side effect).\n - It is **SSRF-bounded**. SSRF (server-side request forgery) is an attack where code is tricked into calling internal addresses it should not reach. The edge resolves the target host and blocks private or internal addresses, so a daemon cannot use `http_call` to poke around inside the network.\n - It is **metered**: making many calls or pulling huge responses costs budget, which caps abuse.\n- **Leadership info** (`is_leader`, `current_epoch`). A daemon can check whether it is currently the leader, which is useful for guarding a long `onStart` loop.\n\n> **Note:** In `toiljs dev` (the single-process local emulator), the daemon is always the leader (there is nothing to fail over to), and `http_call` is stubbed to return a \"call failed\" result rather than make real network requests. Everything else, including the schedule and your database writes, runs exactly as it does on the edge.\n\n### The `Daemon` global\n\nInside a `@daemon` class, `Daemon` is an ambient global (no import), like `Analytics`. It needs `toilscript` 0.1.60 or newer.\n\n| Member | Description |\n| --- | --- |\n| `Daemon.isLeader(): bool` | Whether this worker currently holds the lease. A snapshot: re-check it inside a long task rather than trusting the value it started with. |\n| `Daemon.epoch(): i64` | The monotonic fencing token, bumped on every (re)acquire, or `-1` when this worker is not the leader. Stamp it into work that must not outlive the lease that authorized it. |\n| `Daemon.taskCount(): i32` | How many `@scheduled` tasks are registered. |\n| `Daemon.nextFireMs(taskId: i32): i64` | The next fire time for a task, in epoch milliseconds, or `-1` when the id is unknown or the cron never fires again. |\n| `Daemon.yieldNow(): DaemonError` | Give the edge a chance to observe a lost lease. `DaemonError.None` means you are still the leader. |\n| `Daemon.sleep(ms: i64): DaemonError` | Park the task. The edge clamps this to 3 seconds. `LeaseLost` means the lease went away while you slept, and the task should stop. |\n| `Daemon.httpCall(request, responseCap?)` | Make an outbound HTTP request. Returns a `DaemonHttpResponse`, or `null` on failure. |\n| `Daemon.lastError(): DaemonError` | The failure recorded by the most recent `Daemon` call. |\n\n### Outbound HTTP\n\n`Daemon.httpCall` performs the leader-fenced, SSRF-bounded, metered call described above. Its usage shows up in the `Analytics` counters `daemonHttpCallAttempts` / `daemonHttpCallFailures`.\n\n```ts\n@daemon\nclass Jobs {\n @scheduled('5m')\n poll(): void {\n const req = new DaemonHttpRequest('POST', 'https://api.example.com/events');\n req.header('content-type', 'application/json');\n req.body = Uint8Array.wrap(String.UTF8.encode('{\"ping\":true}'));\n\n const res = Daemon.httpCall(req);\n if (res == null) {\n // Daemon.lastError(): NotLeader, CallFailed, ResponseTooLarge, BadEnvelope\n return;\n }\n if (res.status == 200) App.events.add(new Event(res.text()));\n }\n}\n```\n\nA `null` result is never ambiguous: read `Daemon.lastError()`.\n\n| `DaemonError` | Meaning |\n| --- | --- |\n| `NotLeader` | This worker does not hold the lease. The edge refused without touching the network. |\n| `LeaseLost` | The lease went away mid-call. |\n| `CallFailed` | The request did not complete: blocked by the SSRF guard, timed out, or the transport failed. |\n| `ResponseTooLarge` | The response did not fit your buffer. **The call already happened**, so do not blindly retry it: pass a larger `responseCap` instead. |\n| `BadEnvelope` | Your request broke a host cap (nothing was sent), or the response could not be parsed. |\n\nThe caps the edge enforces, which `httpCall` checks in the guest so an over-cap request never costs you a round trip: the method is 1 to 16 bytes, the URL at most 8 KiB, at most 64 headers of at most 8 KiB each, and a request body of at most 256 KiB. Responses are truncated at 1 MiB; `responseCap` defaults to 64 KiB.\n\nIn `toiljs dev` the emulator returns `CallFailed` for every `httpCall` rather than reaching the network, so a daemon that polls an external service does nothing locally. Everything else behaves as it does on the edge.\n\n## The `main.daemon.ts` file (a separate tier)\n\nLike streams, daemons live in their **own entry file**, `server/main.daemon.ts`, and compile into their **own artifact**, `build/server/release-cold.wasm`. Importing your `@daemon` module there pulls it into that artifact.\n\n```ts\n// server/main.daemon.ts\nimport { revertOnError } from 'toiljs/server/runtime/abort/abort';\n\nimport './daemon/Jobs'; // add each @daemon module here\n\n// NOTE: unlike main.ts / main.stream.ts, the daemon entry does NOT re-export the\n// request runtime. A daemon artifact exposes its schedule hooks, not the request handler.\nexport function abort(message: string, fileName: string, line: u32, column: u32): void {\n revertOnError(message, fileName, line, column);\n}\n```\n\n`toiljs build` produces `release-cold.wasm` automatically when your project declares a `@daemon` surface:\n\n```sh\n$ ls build/server/*.wasm\nbuild/server/release.wasm # L1 request (@rest / @service)\nbuild/server/release-stream.wasm # L2/L3 stream (@stream)\nbuild/server/release-cold.wasm # L4 daemon (@daemon)\n```\n\nThere is **at most one** `@daemon` class per project, and it is a compile error to put a `@daemon` in the request build. See [Compute tiers](../concepts/tiers.md) for how one source tree becomes three artifacts.\n\n## Worked example: a periodic rollup\n\nA common daemon job: every hour, read a running total and store a small summary so pages can show it with one cheap read. This reads a [counter](../database/counters.md) and writes a summary [document](../database/documents.md); the write only takes effect on the leader.\n\n```ts\n// server/models/StatKey.ts and Summary.ts (@data types)\n@data\nclass StatKey {\n name: string = 'signups';\n constructor(name: string = 'signups') { this.name = name; }\n}\n\n@data\nclass Summary {\n total: u64 = 0;\n updatedAt: u64 = 0;\n}\n\n// server/data/StatsDb.ts\n@database\nclass StatsDb {\n @collection static signups: Counter<StatKey>;\n @collection static summary: Documents<StatKey, Summary>;\n}\n\n// server/daemon/Jobs.ts\n@daemon\nclass Jobs {\n @scheduled('1h')\n rollup(): void {\n const key = new StatKey('signups');\n const total = StatsDb.signups.get(key); // a read (allowed anywhere)\n\n const s = new Summary();\n s.total = total;\n s.updatedAt = <u64>(Date.now() / 1000);\n StatsDb.summary.patch(key, s); // a write: runs only on the leader\n }\n}\n```\n\nNow any request handler can serve the summary with a single keyed read, and it is never more than an hour stale. Because the write is fenced to the leader and the rollup is idempotent (it *sets* the total rather than adding to it), a missed or failed-over tick is harmless: the next hourly run simply refreshes it.\n\n## When not to use a daemon\n\n- **When a user is waiting for the result.** Do that work in a [route](../backend/rest.md) or [RPC](../backend/rpc.md), on the request path.\n- **When you need a fast read-view kept in sync with data changes.** That is a [`@derive`](./derive.md), which runs on every write rather than on a timer.\n- **When you need exactly-once, never-skipped execution.** Scheduling is at-most-once with no backfill, so make tasks idempotent or safe to skip. A daemon is not a guaranteed job queue.\n\n## Related\n\n- [Background overview](./README.md): daemons versus `@derive`, and which to reach for.\n- [Derived views (`@derive`)](./derive.md): keep a read-view in sync on every write.\n- [Compute tiers (L1 to L4)](../concepts/tiers.md): the daemon runs on the L4 global tier.\n- [Counters](../database/counters.md), [Documents](../database/documents.md), [Events](../database/events.md): the data a daemon reads and writes.\n",
|
|
20
20
|
"background/derive.md": "# Derived views (`@derive`)\n\nA `@derive` keeps a fast, precomputed **View** of your data in sync automatically. You write a method that reads your source data and publishes a summary; toiljs re-runs it whenever the source data changes, so your pages can read the summary with one cheap lookup instead of doing the work on every request.\n\n## The problem it solves\n\nSome reads are expensive. \"Show the 10 newest comments\" means scanning an [events](../database/events.md) log. \"Show the leaderboard\" means totalling up scores. A **scan** (walking many rows to build a result) can fan out across an unbounded amount of data, and that is too slow and too unpredictable to do while a user waits.\n\nSo toiljs **bars scans on the request path**. A [route](../backend/rest.md) handler runs under a restricted mode:\n\n- A `@get` runs as a **query** (reads only, no scans).\n- A `@post` / `@put` / `@patch` / `@del` runs as an **action** (keyed reads and writes, still no scans).\n\nIf you cannot scan in a route, how do you show \"the latest 10\"? You precompute it. A `@derive` does the scan **off** the request path, folds the result into a [View](../database/views.md) (a read-optimized snapshot stored by key), and your route reads that View with a single keyed lookup, which is not a scan and so is allowed.\n\n```mermaid\nflowchart LR\n W[\"POST /guestbook<br/>(action: append + count)\"] --> S[(Events + Counter<br/>source data)]\n S -->|write triggers| D[\"@derive recompute()<br/>scans, builds, publishes\"]\n D --> V[(View<br/>precomputed snapshot)]\n G[\"GET /guestbook<br/>(query: one keyed read)\"] --> V\n```\n\n## A worked example: a guestbook\n\nHere is the whole pattern in one database class: an [events](../database/events.md) log of signatures, a [counter](../database/counters.md) of how many there are, and a [View](../database/views.md) that holds the ready-to-serve page.\n\n```ts\n@data\nclass GuestKey {\n room: string = 'main';\n constructor(room: string = 'main') { this.room = room; }\n}\n\n@database\nclass GuestbookDb {\n @collection static entries: Events<GuestKey, GuestEntry>; // a source: the log\n @collection static totals: Counter<GuestKey>; // a source: the count\n @collection static book: View<GuestKey, GuestbookView>; // the view we publish\n\n // Recompute the view from the sources. It MAY scan and publish; a route may not.\n @derive\n recompute(): void {\n const key = new GuestKey('main');\n const view = new GuestbookView();\n view.total = GuestbookDb.totals.get(key); // a keyed read\n view.entries = GuestbookDb.entries.latest(key, 10); // a scan: allowed here\n GuestbookDb.book.publish(key, view); // publish the snapshot\n }\n}\n```\n\nThe route then writes to the sources and reads the view:\n\n```ts\n@rest('guestbook')\nclass Guestbook {\n @get('/')\n list(): GuestbookView {\n const key = new GuestKey('main');\n const view = GuestbookDb.book.get(key); // one keyed read, not a scan\n return view == null ? new GuestbookView() : view; // empty until first publish\n }\n\n @post('/')\n sign(input: NewMessage): GuestbookView {\n const key = new GuestKey('main');\n GuestbookDb.entries.append(key, new GuestEntry(input.author, input.message, 0));\n GuestbookDb.totals.add(key, 1);\n // The @derive republishes `book` right after this action returns, so GET\n // serves the new entry. The action just acks with the new total (a counter\n // read is allowed here; scanning the entries list is not).\n const view = new GuestbookView();\n view.total = GuestbookDb.totals.get(key);\n return view;\n }\n}\n```\n\nSign the guestbook twice and the total climbs across requests, because the data lives in the database (and its view), not in module memory.\n\n## What a `@derive` may do\n\nA `@derive` runs under a special **derive** mode that is more powerful than a route:\n\n| Ability | Query (`@get`) | Action (`@post` ...) | Derive |\n| -------------------------------------------- | :------------: | :------------------: | :----------: |\n| Keyed reads (`.get`, counter total) | yes | yes | yes |\n| Writes (`create`, `patch`, `add`, `append`) | no | yes | yes |\n| **Scans** (`events.latest`, membership list) | **no** | **no** | **yes** |\n| `view.publish` / `view.append` | no | no | yes |\n\nSo a derive is exactly the place to do the reads and scans a route cannot, and to publish the result.\n\n## Declaring a derive\n\nA `@derive` is a method on your [`@database`](../database/setup.md) class, next to the collections it reads and the View it writes.\n\n```ts\n@database\nclass MyDb {\n @collection static events: Events<Key, Fact>; // a source\n @collection static home: View<Key, HomePage>; // the materialized view\n\n @derive\n rebuild(): void {\n // read sources, build the value, publish it\n }\n}\n```\n\nRules:\n\n- A `@derive` method takes **no arguments and returns `void`**.\n- A database may declare **multiple** `@derive` methods; each runs independently.\n- The View value and its key are ordinary [`@data`](../backend/data.md) types, so they round-trip through the codec like any other stored value.\n\n## When a derive runs\n\nYou never call a derive yourself. The runtime runs it for you at two moments:\n\n1. **After a write to a source.** When a request writes one of the database's source collections (an `events.append`, a `counter.add`, a document `create` or `patch`), that database's derives run **right after the response is produced**, so the view reflects the new data on the next read. Many writes to one database in a single request are **coalesced** into one recompute (it does not run once per write).\n\n2. **On box load.** When a server box starts, hot-reloads, or notices the underlying data changed out of band, the views are rebuilt from their sources **before the first read is served**. This is also where a value type's [`@migrate`](../backend/data.md) runs against old stored events, as the derive re-reads and republishes them.\n\nA derive's own `view.publish` never re-triggers it, so there is no infinite loop.\n\nThe same code runs under `toiljs dev` (the in-process emulator) and on the production edge, with no flags or wiring to change.\n\n## Folding a growing log incrementally\n\nThe guestbook above uses `events.latest` and **recomputes** the view from scratch on every change. That is simple, correct, and the right default for a **bounded** read: the latest N, a counter total, a small set.\n\nSome views instead fold an **unbounded** log: a running total over every event ever, an activity rollup, an audit summary. Rescanning the whole log on every change gets slower as it grows. For those, read the source with [`events.since`](../database/events.md) instead of `latest`. `since` hands you only the events you have **not folded yet**, so the derive folds forward incrementally.\n\n```ts\n@derive\nrollup(): void {\n const key = new StatsKey('all');\n const view = StatsDb.summary.get(key) ?? new Summary(); // the running view so far\n let batch = StatsDb.events.since(key, 500);\n while (batch.length > 0) { // drain the new events in bounded batches\n for (let i = 0; i < batch.length; i++) view.apply(batch[i]);\n batch = StatsDb.events.since(key, 500);\n }\n StatsDb.summary.publish(key, view);\n}\n```\n\nThe host owns the cursor, you never manage it: it seeds `since` from a durable checkpoint, advances it as it hands you events, and saves it **only after** your `publish` lands (so a crash re-folds the batch instead of skipping it). On the next trigger the derive resumes exactly where it left off.\n\n**One rule: the fold must be idempotent per event.** A rare crash-recovery case (an event that \"heals\" at an older position after the derive already passed it) makes the host re-read the whole log that one run, so applying an event twice must not change the result. Use set-style updates keyed by the event's id, not blind `count += 1` accumulation. If your fold cannot be idempotent, stay on the simple `latest` recompute path.\n\n## Guarantees and limitations\n\n**Guarantees**\n\n- **It converges to a correct snapshot.** Publishes are last-writer-wins (the host versions each publish so a later one always supersedes an earlier one), and a derive recomputes from the source of truth, so the view always ends up matching its sources.\n- **Reads stay cheap.** A route serves the view with a single keyed lookup, never a scan.\n\n**Limitations (read these)**\n\n- **By default it recomputes from scratch each time.** A derive re-reads its sources and republishes on every trigger. That is the simple path and a great fit for a **bounded** read: the latest N, a counter total, a small set. To fold an **unbounded, ever-growing** log efficiently, read it with [`events.since`](../database/events.md) instead of `latest` (see [Folding a growing log incrementally](#folding-a-growing-log-incrementally) above); it folds only the new events since a checkpoint.\n- **It is eventually consistent, by a moment.** The view is republished right after the writing request finishes, so there is a tiny window where a reader could see the pre-write view. For most pages (a feed, a leaderboard) that is invisible and fine.\n- **`view.publish` is derive-only.** Routes cannot publish; they can only read the view. That is the whole point: the expensive build happens off the request path.\n\n## When not to use a derive\n\n- **When the read is already cheap.** If a route can answer with a plain keyed `.get`, you do not need a view at all.\n- **When you need it on a timer, not on a data change.** Recomputing \"yesterday's report\" at 2am is a [daemon](./daemons.md) job, not a derive (a derive is triggered by writes, not by the clock).\n- **When the source is an unbounded full-history fold.** See the limitation above; keep derives to bounded reads.\n\n## Related\n\n- [Views](../database/views.md): the `View<K, V>` family a derive publishes into, and how to read it.\n- [Events](../database/events.md): the append-only log a derive commonly folds into a view.\n- [Counters](../database/counters.md): running totals a derive can read.\n- [Background overview](./README.md): `@derive` versus `@daemon`, and which to reach for.\n- [Data types (`@data`)](../backend/data.md): the value and key types a view stores.\n",
|
|
21
21
|
"background/README.md": "# Background work\n\nBackground work is code that runs **without a user waiting for it**: on a timer, once globally, or automatically after your data changes. toiljs gives you two tools for it, `@daemon` and `@derive`, and this page helps you pick the right one.\n\n## Why background work\n\nMost of your server code runs **because a user asked**: a browser hits a [route](../backend/rest.md) or calls an [RPC](../backend/rpc.md), your handler runs, and a response goes back. But some work does not belong on that path:\n\n- It should happen **on a schedule** (every hour, every night at 2am), not when a request happens to arrive.\n- It would be **too slow** to do inside a request, so you want it precomputed and ready.\n- It must happen **exactly once across the whole world**, not once per user and not once per server.\n\nThat is background work. In toiljs there are two kinds, and they solve two different problems.\n\n## The two tools\n\n```mermaid\nflowchart TD\n Q{What do you need?}\n Q -->|Run on a timer, or once globally| D[\"@daemon + @scheduled<br/>a single background worker\"]\n Q -->|Keep a fast read-view in sync with your data| V[\"@derive<br/>maintains a materialized View\"]\n D --> DD[\"e.g. nightly cleanup, hourly poll,<br/>periodic rollup\"]\n V --> VV[\"e.g. a leaderboard, a 'latest 10' feed,<br/>a home-page summary\"]\n```\n\n### `@daemon`: a scheduled, global worker\n\nA [daemon](./daemons.md) is one long-lived background worker for your whole app. It runs on a **schedule** you set (an interval like every 5 minutes, or a cron time like \"9:15 on weekdays\"), and there is exactly **one** of it worldwide at any moment (a \"singleton\"). Use it for work that is driven by **time** or that must run **once globally**:\n\n- clean up stale rows every night;\n- poll an external API every few minutes;\n- send a daily digest email;\n- roll up yesterday's numbers into a summary.\n\n### `@derive`: keep a read-view up to date\n\nA [derive](./derive.md) is not on a timer. It runs **automatically whenever the data it depends on changes**, and its job is to keep a precomputed **View** (a read-optimized copy of your data) fresh. Use it when a page needs data that is **expensive to compute on every read**, like a leaderboard or a \"latest 10 comments\" list, so the read itself stays a single cheap lookup:\n\n- fold an [events](../database/events.md) log into a \"latest N\" list;\n- total up [counters](../database/counters.md) into a scoreboard;\n- assemble a home-page summary from several sources.\n\n## Which one do I reach for?\n\n| Question | Use |\n| ----------------------------------------------------- | ------------ |\n| \"Run this every hour / at midnight.\" | `@daemon` |\n| \"Do this once for the whole app, not per server.\" | `@daemon` |\n| \"Poll or call an outside service on a schedule.\" | `@daemon` |\n| \"This page's data is too slow to compute per request.\"| `@derive` |\n| \"Keep a leaderboard / feed in sync as data changes.\" | `@derive` |\n\nA simple rule of thumb: if the trigger is **the clock**, use a `@daemon`. If the trigger is **a change to your data** and the goal is a fast read, use a `@derive`.\n\nThey also combine well. A `@daemon` might do a heavy nightly aggregation and write a summary row, while a `@derive` keeps a small live view fresh on every write. They are different tiers of the edge and are covered on their own pages.\n\n## `@job`: the widest database surface for background work\n\n`@daemon` and `@derive` decide **where and when** background code runs. `@job` answers a different question: **what a function is allowed to do to the database.** It is one of the ToilDB **function kinds** (the same family as `@query`, `@action`, and `@derive`), and it grants the **widest** data surface of them all.\n\nA **function kind** is a label the compiler puts on a backend function to gate which database operations it may issue. This is a safety rail: a read-only endpoint physically cannot write, and an expensive scan cannot run on the hot request path. The kinds line up from narrowest to widest:\n\n| Kind | Typical trigger | Point reads | **Scan** (`latest`, membership `list`) | Writes | `publish` a View |\n| ---------- | ------------------------- | ----------- | -------------------------------------- | ----------------- | ---------------- |\n| `@query` | a `@get` / plain `@remote`| yes | no | no | no |\n| `@action` | a `@post` / `@action` | yes | no | yes (bounded) | no |\n| `@derive` | your data changed | yes | yes | append / counter add only | yes |\n| `@job` | you drive it (background) | yes | yes | yes (all) | yes |\n\nA **scan** is a read that can fan out across many rows (like \"the newest 50 events\" via `events.latest`, or \"every member of this set\" via `membership.list`). Scans are **barred from request handlers** because a request must stay fast and bounded. `@derive` and `@job` run **off the request path**, so they are the only kinds allowed to scan.\n\n### When to reach for `@job`\n\nUse `@job` when a piece of background work needs **more** database power than the default:\n\n- it must **scan** (fold `events.latest`, walk `membership.list`), and/or\n- it must **publish a View** *while also* doing arbitrary writes (`create`, `patch`, `delete`). A `@derive` can publish a View but cannot `patch` or `delete` a record; a `@job` can do everything.\n\nThe compiler accepts `@job` on a method, including a `@daemon`'s `@scheduled` method. Tag a scheduled task `@job` when it needs that full surface (say, a nightly repair that scans a log, fixes rows, and republishes a summary View). A plain, **untagged** `@scheduled` method runs with the **`@action`** surface instead: point reads plus bounded writes, which is all most rollups and cleanups need.\n\n```ts\n@daemon\nclass Jobs {\n // A plain scheduled method: point reads + bounded writes (the @action surface).\n @scheduled('1h')\n rollup(): void { /* get a counter, patch a summary row */ }\n\n // A scheduled method that also needs SCANS and to PUBLISH a View: tag it @job.\n @scheduled('1d')\n @job\n nightlyRepair(): void {\n // @job unlocks scan-class reads (events.latest / membership.list)\n // and publishing a View, on top of ordinary reads and writes.\n }\n}\n```\n\n### `@job` versus `@derive`\n\nThey overlap (both run off the request path, both may scan, both may publish a View), but they are triggered differently and sized differently:\n\n- **`@derive`** is **change-triggered**: it re-runs automatically whenever its source data changes, and its narrow job is to keep one **View** in sync. It cannot `create` / `patch` / `delete` records (only append, counter-add, and publish). Reach for it when a read is too slow and you want it kept fresh on every write.\n- **`@job`** is **you-drive-it** background work (typically a `@scheduled` daemon method) with the **full** write surface. Reach for it when the work is clock-driven or one-off and needs to both scan and mutate freely.\n\n> **Rule of thumb.** Clock-driven and needs the full database surface: a `@job` (usually inside a `@daemon`). Change-driven and only maintains a View: a `@derive`. Clock-driven with modest reads and writes: a plain `@scheduled` method (no `@job` needed).\n\nFor the complete permission grid see the [function-kind matrix](../database/setup.md#how-access-is-gated-query-action-and-friends), and for the decorator itself see [every decorator](../concepts/decorators.md#database-function-kinds-data-access-policy).\n\n## Related\n\n- [Daemons and scheduled jobs](./daemons.md): `@daemon`, `@scheduled`, interval vs cron, and how a single global worker fails over safely.\n- [Derived views (`@derive`)](./derive.md): keeping a materialized View in sync with its source data.\n- [Compute tiers (L1 to L4)](../concepts/tiers.md): where daemons and request handlers each run on the edge.\n- [Views](../database/views.md) and [Events](../database/events.md): the database families a `@derive` reads from and writes to.\n",
|
|
22
|
-
"cli/README.md": "# The toiljs CLI\n\nThe `toiljs` command is how you scaffold, run, build, self-host, and diagnose a toiljs app. This page lists every command and every flag, with copy-pasteable examples.\n\n## What it is\n\nWhen you install the `toiljs` package, it adds one executable to your project: `toiljs`. Everything you do day to day (start the dev server, produce a production build, check your setup) goes through it.\n\nYou almost never type the full path. A freshly scaffolded project already has npm scripts that call it, so you run `npm run dev` and `npm run build`. When you want a command that has no script (like `doctor` or `db`), run it with `npx`:\n\n```bash\nnpx toiljs doctor\n```\n\n`toiljs` needs **Node.js 24 or newer** (older versions will not run it).\n\n## Command overview\n\n| Command | What it does |\n| --- | --- |\n| `toiljs create [name]` | Scaffold a brand new toiljs app in a new folder. |\n| `toiljs dev` | Start the local development server with hot reload. |\n| `toiljs build` | Produce the optimized production build (client bundle + server WebAssembly). |\n| `toiljs start` | Self-host the built app on a fast production HTTP server. |\n| `toiljs configure` | Turn styling features (Sass/Less/Stylus, Tailwind) on or off in an existing project. |\n| `toiljs doctor` | Diagnose your project setup and (with `--fix`) repair common wiring. |\n| `toiljs update` | Check npm for newer dependency versions and apply the ones you pick. |\n| `toiljs db <action>` | Inspect, reset, snapshot, or restore the local dev database. |\n| `toiljs help` | Print the built-in help. Also `--help` or `-h`. |\n| `toiljs --version` | Print the installed toiljs version. Also `-v`. |\n\nRun `toiljs help` any time to see this list in your terminal.\n\n## Global behavior\n\nA few things apply to every command.\n\n- **`--root <dir>`**: run the command against a project in another directory instead of the current one. Every command accepts it.\n- **Automatic update check**: on every run (including `npm run dev`, which calls the CLI under the hood), toiljs quietly asks the npm registry whether a newer `toiljs` exists and prints a one-line notice on stderr if you are behind. It never blocks or slows the command in a meaningful way (the answer is cached for an hour and the network call is capped at two seconds). To turn it off, set the environment variable `TOILJS_NO_UPDATE_CHECK=1`. It also respects the common `NO_UPDATE_NOTIFIER` and `CI` variables.\n\n```bash\n# Run any command against a project in another folder.\ntoiljs build --root ./apps/marketing\n\n# Silence the \"newer version available\" notice.\nTOILJS_NO_UPDATE_CHECK=1 toiljs dev\n```\n\n## `toiljs create`\n\nScaffolds a new project into a new folder. By default it is interactive: it asks a short series of questions (project name, template, styling, and so on), then writes the files and installs dependencies. Pass flags to skip questions, or `-y` to accept every default and run with no questions at all.\n\n```bash\n# Interactive: answer the prompts.\nnpx toiljs create\n\n# Give it a name up front.\nnpx toiljs create my-app\n\n# Fully non-interactive (great for scripts and CI).\nnpx toiljs create my-app --yes --template app --style css\n```\n\n### What it sets up\n\nEvery new project comes wired for you: the enforced TypeScript, ESLint, and Prettier presets, file-based routing, a `toil.config.ts`, a `toilconfig.json` (the server compiler settings), a `.gitignore`, and the editor settings that make the toilscript language plugin work. It also scaffolds a `server/migrations/` folder (where ToilDB schema migrations live) and, unless you opt out, a set of AI assistant helper files.\n\nThe scaffolded `package.json` includes these scripts:\n\n| Script | Runs |\n| --- | --- |\n| `npm run dev` | `toiljs dev` |\n| `npm run build` | `toiljs build` |\n| `npm run build:server` | `toiljs build --server` |\n| `npm run lint` | `eslint client` |\n| `npm run typecheck` | `tsc --noEmit` |\n| `npm run format` | `prettier --write ...` |\n\n### Generated docs and AI-assistant pointers\n\nEvery project carries a full copy of this documentation set at `.toil/docs/`. You do not maintain it: toiljs regenerates it from the installed toiljs version on every `toiljs dev` and `toiljs build`, so it always matches the version you are on. Do not edit those files by hand (your changes are overwritten on the next dev or build).\n\nUnless you opt out, `toiljs create` also writes small **pointer files** at the project root that tell AI coding assistants to read `.toil/docs/` before touching the project: `CLAUDE.md` (Claude Code), `AGENTS.md` (Codex and others), `.cursor/rules/toiljs.mdc` (Cursor), and `.github/copilot-instructions.md` (GitHub Copilot). These are written once, committed, and yours to edit. Control them with `--ai` / `--no-ai` (when you pass neither, `create` asks).\n\n### create options\n\n| Flag | Meaning |\n| --- | --- |\n| `[name]` | The project folder name (a positional argument). If omitted, you are asked. |\n| `-t, --template <app\\|minimal>` | `app` is the full starter (landing page, layout, styles, demo routes). `minimal` is just a layout and a home route. Default `app`. |\n| `--style <css\\|sass\\|less\\|stylus>` | Which CSS flavor to set up. Default `css` (plain CSS). |\n| `--tailwind` / `--no-tailwind` | Add or skip Tailwind CSS (v4). Off by default. |\n| `--ai` / `--no-ai` | Include or skip AI assistant files (like `CLAUDE.md`). When omitted, you are asked. |\n| `--images` / `--no-images` | Enable or skip build-time image optimization. On by default. |\n| `--git` / `--no-git` | Initialize a git repository. When omitted, you are asked (default yes). |\n| `--install` / `--no-install` | Install dependencies after scaffolding. When omitted, you are asked (default yes). |\n| `--pm <npm\\|pnpm\\|yarn\\|bun>` | Which package manager to install with. Default `npm`. |\n| `-y, --yes` | Accept all defaults and skip every prompt. |\n\nFor a walkthrough, see [Create a project](../getting-started/create-project.md).\n\n## `toiljs dev`\n\nStarts the local development server with hot reload, so you edit a file and the browser updates in place. This is the command you leave running while you build.\n\n```bash\nnpm run dev\n# or, to pick a port:\nnpx toiljs dev --port 4000\n```\n\n### What the dev server emulates\n\nThe whole point of the dev server is to run your app locally the same way the real Dacely edge runs it in production, so what you see locally is what you ship. It emulates three things:\n\n1. **The edge.** For a project with a server (any project with a `toilconfig.json`), a small local HTTP server takes the public port and dispatches incoming requests into your compiled `server.wasm` using the exact same request envelope the production edge uses. Anything your server does not claim (page routes, static assets, the hot-reload websocket) is proxied to Vite behind the scenes. So your React frontend and your WebAssembly backend run together, on one URL, exactly like production.\n2. **The database.** ToilDB runs in-process as a local emulator. Every family (documents, views, unique, events, counters, membership, capacity) works, and the data is written to `.toil/devdata.json` so it survives restarts. You manage that file with [`toiljs db`](#toiljs-db).\n3. **The host functions.** The platform services your server code calls (email, environment variables and secrets, time, crypto, rate limiting, auth) are wired up locally so they behave like the edge. Email actually sends if you configure a provider (see [Email](../services/email.md)).\n\nA **client-only** project (no `toilconfig.json`) just gets the plain Vite dev server on your port, unchanged.\n\n### How hot reload works\n\n```mermaid\nflowchart TD\n E[\"You edit a file\"] --> Q{\"Which file?\"}\n Q -->|\"client/ (React, CSS)\"| V[\"Vite hot-swaps it in the browser\"]\n Q -->|\"server/ (a @rest / @data / @service file)\"| S[\"toilscript recompiles the server\"]\n S --> G[\"shared/server.ts is regenerated\"]\n G --> V2[\"Vite hot-swaps the new typed client\"]\n S --> W[\"the dev server hot-swaps the new server.wasm\"]\n V --> B[\"Browser updates, no full reload\"]\n V2 --> B\n W --> B\n```\n\nClient edits go straight through Vite's hot module replacement. Server edits trigger a toilscript rebuild: toiljs recompiles your backend, regenerates `shared/server.ts` (the typed client the browser imports to call your server), and hot-swaps the recompiled WebAssembly, all without you touching the browser. Rebuilds are debounced (grouped over about 150 milliseconds) so a \"save all\" or a formatter pass does not trigger a storm of builds.\n\nIf your project has an `emails/` folder, the dev server also prints an email-preview URL at `/__toil/emails`.\n\n### dev options\n\n| Flag | Meaning |\n| --- | --- |\n| `--port <n>` | Port to listen on. Default `3000` (or `client.port` from your config). |\n| `--root <dir>` | Run against a project in another directory. |\n\nPress `Ctrl+C` to stop. toiljs restores your terminal and force-exits even if a native listener is slow to close, so you never end up with an orphaned dev server rebuilding in the background.\n\n## `toiljs build`\n\nProduces the optimized production build. Run this before you deploy or before `toiljs start`.\n\n```bash\nnpm run build\n# or build only the server:\nnpx toiljs build --server\n```\n\n### What it produces\n\nA full build runs in a careful order so the pieces line up:\n\n```mermaid\nflowchart LR\n A[\"Your project\"] --> S[\"1. Compile the server<br/>(toilscript)\"]\n S --> W[\"build/server/release.wasm\"]\n S --> R[\"shared/server.ts<br/>(typed client, regenerated)\"]\n R --> C[\"2. Bundle the client<br/>(Vite)\"]\n C --> O[\"build/client/<br/>(HTML, JS, CSS, assets)\"]\n C --> X[\"3. Prerender + SEO<br/>(SSG pages, robots.txt,<br/>sitemap.xml, llms.txt)\"]\n```\n\n1. **The server is built first.** toilscript compiles every decorated server file (not just the entry) into `build/server/release.wasm`, and regenerates `shared/server.ts`. Doing this first means the client always bundles against a current, correct typed server client. A project that also declares `@stream` or `@daemon` surfaces compiles those into their own artifacts (`release-stream.wasm`, `release-cold.wasm`).\n2. **The client is bundled** by Vite into `build/client/` (your HTML, JavaScript, CSS, and optimized assets). The dev toolbar and error overlay are stripped out of the production bundle.\n3. **Static pages and SEO files are generated**: any route that opts into static generation is prerendered to HTML, and if you configured `client.seo`, toiljs writes `robots.txt`, `sitemap.xml`, and `llms.txt`. Routes that opt into server-side rendering get their HTML-with-holes templates baked for the edge.\n\n### build options\n\n| Flag | Meaning |\n| --- | --- |\n| `--server` | Build **only** the server (recompile the wasm and regenerate `shared/server.ts`), and skip the client bundle. Fast when you only touched backend code. This is what `npm run build:server` runs. |\n| `--root <dir>` | Run against a project in another directory. |\n\nA client-only project (no `toilconfig.json`) skips step 1 and just bundles the client.\n\n## `toiljs start`\n\nSelf-hosts the app you just built, on a fast production HTTP server (hyper-express, backed by uWebSockets.js). It serves your static client, runs your `server.wasm` for dynamic requests, does server-side rendering, supports daemons, and exposes a `/_toil` websocket channel. Use it to run your app on your own machine or server instead of deploying to the Dacely edge.\n\n```bash\nnpm run build # start needs a build to serve\nnpx toiljs start\nnpx toiljs start --port 8080 --host 0.0.0.0 --threads 4\n```\n\n`start` fails fast if there is no build yet (it looks for `build/client/index.html`), so run `toiljs build` first.\n\n### start options\n\n| Flag | Meaning |\n| --- | --- |\n| `--port <n>` | Port to listen on. Default `3000` (or `client.port`). |\n| `--host <host>` | Address to bind. Default `127.0.0.1` (loopback only). Pass `0.0.0.0` to accept connections from other machines. |\n| `--threads <n>` | Number of HTTP worker processes. Default is automatic (one per available CPU). Pass `1` to disable the worker pool. `--workers` is an accepted alias. This can also be set as `server.threads` in your config. |\n| `--root <dir>` | Run against a project in another directory. |\n\n## `toiljs configure`\n\nToggles a project's client styling features (the CSS preprocessor and Tailwind) after the fact, on an existing app. It detects your current setup, asks what you want, then rewrites the stylesheets and your app entry's imports, edits `package.json`, and syncs `node_modules` so removed packages are actually uninstalled.\n\n```bash\n# Interactive.\nnpx toiljs configure\n\n# Non-interactive: switch to Sass and turn Tailwind on.\nnpx toiljs configure --style sass --tailwind\n```\n\n### configure options\n\n| Flag | Meaning |\n| --- | --- |\n| `--style <css\\|sass\\|less\\|stylus>` | Switch the CSS preprocessor. |\n| `--tailwind` / `--no-tailwind` | Turn Tailwind on or off. |\n| `--images` / `--no-images` | Turn build-time image optimization on or off (sets `client.images` in your config). |\n| `--no-install` | Edit the files but do not run the package manager. You then run install yourself. |\n| `--root <dir>` | Run against a project in another directory. |\n\nPassing any of `--style`, `--tailwind`, or `--images` makes the command non-interactive (it skips the prompts you did not answer with a flag). See [Styling](../frontend/styling.md) for the full picture.\n\n## `toiljs doctor`\n\nRead-only project diagnostics. It gathers facts from disk (your `package.json`, lockfiles, the resolved config, your app entry, `index.html`, your routes, and the server target), runs a set of checks, and prints a grouped report. It never changes anything unless you pass `--fix`, and it never crashes on a partial or non-toiljs project (missing pieces just become warnings or failures). It exits with a non-zero status when any check **fails** (warnings do not fail), so it is safe to run in CI.\n\n```bash\n# Human-readable report.\nnpx toiljs doctor\n\n# Machine-readable, for CI.\nnpx toiljs doctor --json\n\n# Auto-repair the common wiring.\nnpx toiljs doctor --fix\n```\n\n### What it checks\n\nThe report is grouped:\n\n| Group | Example checks |\n| --- | --- |\n| **Environment** | Node.js version, that `toiljs` and its peer dependencies (React, TypeScript, and so on) are installed and new enough, that a lockfile exists, and that your scripts do not wrap `toiljs` in a stray `npx`. |\n| **Project + routing** | The `client/` and `routes/` folders exist, `index.html` has a `<div id=\"root\">`, your app entry calls `mount(...)` with the `slots` argument, at least one route exists, no two routes collide on the same URL, and no asset paths are written in a way that 404s on nested routes. |\n| **Config + assets** | Your `toil.config` loads, the base path is well formed, `client.seo` has a `url` if SEO is configured, and your styling packages are actually installed. |\n| **Server / WASM** | The `toilconfig.json` and its entry files exist, `toilscript` is installed, a compiled `.wasm` exists, the typed-RPC wiring is in place, your `@rest` controllers are actually dispatched, the Prettier and editor plugins are wired, and a `migrations/` folder exists. |\n| **Security** | If your server uses auth, whether `AUTH_SESSION_SECRET` is set (an unset secret means sessions fall back to a published dev key, which is forgeable). |\n\n### What `--fix` repairs\n\n`--fix` only touches a server project (one with a `toilconfig.json`), and it repairs the wiring that is easy to get wrong or that older projects predate:\n\n- adds `--rpcModule shared/server.ts` to your server build scripts,\n- adds `shared` and the `shared/*` path alias to `tsconfig.json`,\n- adds `shared/server.ts` to `.gitignore`,\n- lifts the `toilscript` version floor if it is too old,\n- adds the `toiljs/prettier-plugin` to your Prettier config (so Prettier does not choke on server decorators),\n- adds the toilscript language-service plugin to your server `tsconfig.json` and points VS Code at the workspace TypeScript (so the editor stops false-flagging `@database` collections and `@data` members),\n- refreshes the editor-only server globals declaration file.\n\nIt is idempotent: it only writes files it actually needs to change, and it tells you which ones changed and which need a manual edit (for example a `tsconfig.json` that contains comments). If it changed `package.json`, run your installer afterward.\n\n### doctor options\n\n| Flag | Meaning |\n| --- | --- |\n| `--json` | Emit machine-readable JSON instead of the human report (the banner is suppressed so stdout stays valid JSON). |\n| `--fix` | Repair the server wiring in place, as above. |\n| `--root <dir>` | Run against a project in another directory. |\n\n## `toiljs update`\n\nA friendly wrapper over `npm-check-updates`. It checks the registry for newer versions of your dependencies, groups them by how big the jump is (major, minor, patch), lets you pick which to apply (or `-y` to apply all), bumps `package.json`, and runs your package manager's install. It also makes sure your `server/migrations/` folder exists (older projects predate it). `npm-check-updates` runs via `npx`, so it never becomes a permanent dependency of your project.\n\n```bash\n# Interactive picker.\nnpx toiljs update\n\n# Apply everything, non-interactively.\nnpx toiljs update --yes\n\n# Only patch-level updates.\nnpx toiljs update --target patch\n```\n\n### update options\n\n| Flag | Meaning |\n| --- | --- |\n| `-y, --yes` | Apply all available updates without the picker. |\n| `--target <latest\\|minor\\|patch\\|newest\\|greatest>` | How far to bump. Default `latest`. |\n| `--root <dir>` | Run against a project in another directory. |\n\n## `toiljs db`\n\nManages the local dev database: the on-disk ToilDB store your dev server writes to `.toil/devdata.json`. Use it to inspect data, wipe a corrupt state, save a snapshot to share as a fixture, or restore one. The snapshot is exactly the JSON the dev database uses, so an exported file imports cleanly.\n\n```bash\n# See what is stored.\nnpx toiljs db status\n\n# Wipe all dev data.\nnpx toiljs db reset\n\n# Save a snapshot (to a file, or to stdout if you omit the file).\nnpx toiljs db export fixture.json\n\n# Restore a snapshot.\nnpx toiljs db import fixture.json\n\n# Print the on-disk path (scriptable).\nnpx toiljs db path\n```\n\n### db actions\n\n| Action | What it does |\n| --- | --- |\n| `status` (alias `info`) | Show the database path, its size, and per-family row counts. |\n| `reset` (alias `purge`) | Delete all dev data (removes `devdata.json`). |\n| `export [file]` | Write a formatted snapshot to `file`, or to stdout if you omit it (pipe-friendly). |\n| `import <file>` | Replace the dev database with the snapshot in `file`. It refuses a file that is not a valid snapshot. |\n| `path` | Print the `devdata.json` path and nothing else. |\n\nThe dev database is per-project and lives under `.toil/`, which is gitignored. See [Database setup](../database/setup.md) for what actually populates it.\n\n## The dev, build, and deploy flow\n\nHere is how the commands fit together across your workflow.\n\n```mermaid\nflowchart LR\n subgraph Develop\n D[\"toiljs dev<br/>(hot reload, local edge + DB)\"]\n end\n subgraph Ship\n B[\"toiljs build<br/>(client bundle + server.wasm)\"]\n end\n subgraph Run\n ST[\"toiljs start<br/>(self-host locally)\"]\n DP[\"deploy to the Dacely edge\"]\n end\n D --> B\n B --> ST\n B --> DP\n```\n\n- **Develop** with `toiljs dev`. Everything runs locally and reloads as you type.\n- **Ship** with `toiljs build`. This produces the artifacts: the client bundle in `build/client/` and the server WebAssembly in `build/server/`.\n- **Run** the build two ways: `toiljs start` self-hosts it on your own machine, or you deploy the same build to the Dacely edge to serve it worldwide.\n\n## Gotchas\n\n- **`start` needs a build.** `toiljs start` serves what is in `build/`. Run `toiljs build` first, or it exits with an error.\n- **Run `toiljs`, not `npx toiljs`, inside npm scripts.** Under `npm run`, `node_modules/.bin` is already on your PATH, so an extra `npx` layer is redundant and can leave your terminal in a broken input mode after `Ctrl+C`. `doctor` warns about this; the scaffold gets it right.\n- **`--host` and `--threads` are `start`-only.** `toiljs dev` always binds locally and does not take `--host` or `--threads`.\n- **`--server` builds the backend only.** Use it while iterating on server code, but run a full `toiljs build` before you deploy so the client bundle is current.\n- **The update notice is not an error.** The \"newer toiljs available\" line is informational and prints to stderr. Set `TOILJS_NO_UPDATE_CHECK=1` to hide it.\n\n## Related\n\n- [Installation](../getting-started/installation.md) and [Create a project](../getting-started/create-project.md)\n- [Project structure](../getting-started/project-structure.md)\n- [Configuration reference (`toil.config.ts`)](../concepts/config.md)\n- [Styling](../frontend/styling.md)\n- [Database setup](../database/setup.md) and the [database overview](../database/README.md)\n- [Compute tiers (L1 to L4)](../concepts/tiers.md)\n",
|
|
22
|
+
"cli/README.md": "# The toiljs CLI\n\nThe `toiljs` command is how you scaffold, run, build, self-host, and diagnose a toiljs app. This page lists every command and every flag, with copy-pasteable examples.\n\n## What it is\n\nWhen you install the `toiljs` package, it adds one executable to your project: `toiljs`. Everything you do day to day (start the dev server, produce a production build, check your setup) goes through it.\n\nYou almost never type the full path. A freshly scaffolded project already has npm scripts that call it, so you run `npm run dev` and `npm run build`. When you want a command that has no script (like `doctor` or `db`), run it with `npx`:\n\n```bash\nnpx toiljs doctor\n```\n\n`toiljs` needs **Node.js 24 or newer** (older versions will not run it).\n\n## Command overview\n\n| Command | What it does |\n| --- | --- |\n| `toiljs create [name]` | Scaffold a brand new toiljs app in a new folder. |\n| `toiljs dev` | Start the local development server with hot reload. |\n| `toiljs build` | Produce the optimized production build (client bundle + server WebAssembly). |\n| `toiljs start` | Self-host the built app on a fast production HTTP server. |\n| `toiljs configure` | Turn styling features (Sass/Less/Stylus, Tailwind) on or off in an existing project. |\n| `toiljs doctor` | Diagnose your project setup and (with `--fix`) repair common wiring. |\n| `toiljs update` | Check npm for newer dependency versions and apply the ones you pick. |\n| `toiljs db <action>` | Inspect, reset, snapshot, or restore the local dev database. |\n| `toiljs help` | Print the built-in help. Also `--help` or `-h`. |\n| `toiljs --version` | Print the installed toiljs version. Also `-v`. |\n\nRun `toiljs help` any time to see this list in your terminal.\n\n## Global behavior\n\nA few things apply to every command.\n\n- **`--root <dir>`**: run the command against a project in another directory instead of the current one. Every command accepts it.\n- **Automatic update check**: on every run (including `npm run dev`, which calls the CLI under the hood), toiljs quietly asks the npm registry whether a newer `toiljs` exists and prints a one-line notice on stderr if you are behind. It never blocks or slows the command in a meaningful way (the answer is cached for an hour and the network call is capped at two seconds). To turn it off, set the environment variable `TOILJS_NO_UPDATE_CHECK=1`. It also respects the common `NO_UPDATE_NOTIFIER` and `CI` variables.\n\n```bash\n# Run any command against a project in another folder.\ntoiljs build --root ./apps/marketing\n\n# Silence the \"newer version available\" notice.\nTOILJS_NO_UPDATE_CHECK=1 toiljs dev\n```\n\n## `toiljs create`\n\nScaffolds a new project into a new folder. By default it is interactive: it asks a short series of questions (project name, template, styling, and so on), then writes the files and installs dependencies. Pass flags to skip questions, or `-y` to accept every default and run with no questions at all.\n\n```bash\n# Interactive: answer the prompts.\nnpx toiljs create\n\n# Give it a name up front.\nnpx toiljs create my-app\n\n# Fully non-interactive (great for scripts and CI).\nnpx toiljs create my-app --yes --template app --style css\n```\n\n### What it sets up\n\nEvery new project comes wired for you: the enforced TypeScript, ESLint, and Prettier presets, file-based routing, a `toil.config.ts`, a `toilconfig.json` (the server compiler settings), a `.gitignore`, and the editor settings that make the toilscript language plugin work. It also scaffolds a `server/migrations/` folder (where ToilDB schema migrations live) and, unless you opt out, a set of AI assistant helper files.\n\nThe scaffolded `package.json` includes these scripts:\n\n| Script | Runs |\n| --- | --- |\n| `npm run dev` | `toiljs dev` |\n| `npm run build` | `toiljs build` |\n| `npm run build:server` | `toiljs build --server` |\n| `npm run lint` | `eslint client` |\n| `npm run typecheck` | `tsc --noEmit` |\n| `npm run format` | `prettier --write ...` |\n\n### Generated docs and AI-assistant pointers\n\nEvery project carries a full copy of this documentation set at `.toil/docs/`. You do not maintain it: toiljs regenerates it from the installed toiljs version on every `toiljs dev` and `toiljs build`, so it always matches the version you are on. Do not edit those files by hand (your changes are overwritten on the next dev or build).\n\nUnless you opt out, `toiljs create` also writes small **pointer files** at the project root that tell AI coding assistants to read `.toil/docs/` before touching the project: `CLAUDE.md` (Claude Code), `AGENTS.md` (Codex and others), `.cursor/rules/toiljs.mdc` (Cursor), and `.github/copilot-instructions.md` (GitHub Copilot). These are written once, committed, and yours to edit. Control them with `--ai` / `--no-ai` (when you pass neither, `create` asks).\n\n### create options\n\n| Flag | Meaning |\n| --- | --- |\n| `[name]` | The project folder name (a positional argument). If omitted, you are asked. |\n| `-t, --template <app\\|minimal>` | `app` is the full starter (landing page, layout, styles, demo routes). `minimal` is just a layout and a home route. Default `app`. |\n| `--style <css\\|sass\\|less\\|stylus>` | Which CSS flavor to set up. Default `css` (plain CSS). |\n| `--tailwind` / `--no-tailwind` | Add or skip Tailwind CSS (v4). Off by default. |\n| `--ai` / `--no-ai` | Include or skip AI assistant files (like `CLAUDE.md`). When omitted, you are asked. |\n| `--images` / `--no-images` | Enable or skip build-time image optimization. On by default. |\n| `--git` / `--no-git` | Initialize a git repository. When omitted, you are asked (default yes). |\n| `--install` / `--no-install` | Install dependencies after scaffolding. When omitted, you are asked (default yes). |\n| `--pm <npm\\|pnpm\\|yarn\\|bun>` | Which package manager to install with. Default `npm`. |\n| `-y, --yes` | Accept all defaults and skip every prompt. |\n\nFor a walkthrough, see [Create a project](../getting-started/create-project.md).\n\n## `toiljs dev`\n\nStarts the local development server with hot reload, so you edit a file and the browser updates in place. This is the command you leave running while you build.\n\n```bash\nnpm run dev\n# or, to pick a port:\nnpx toiljs dev --port 4000\n```\n\n### What the dev server emulates\n\nThe whole point of the dev server is to run your app locally the same way the real Dacely edge runs it in production, so what you see locally is what you ship. It emulates three things:\n\n1. **The edge.** For a project with a server (any project with a `toilconfig.json`), a small local HTTP server takes the public port and dispatches incoming requests into your compiled `server.wasm` using the exact same request envelope the production edge uses. Anything your server does not claim (page routes, static assets, the hot-reload websocket) is proxied to Vite behind the scenes. So your React frontend and your WebAssembly backend run together, on one URL, exactly like production.\n2. **The database.** ToilDB runs in-process as a local emulator. Every family (documents, views, unique, events, counters, membership, capacity) works, and the data is written to `.toil/devdata.json` so it survives restarts. You manage that file with [`toiljs db`](#toiljs-db).\n3. **The host functions.** The platform services your server code calls (email, environment variables and secrets, time, crypto, rate limiting, auth) are wired up locally so they behave like the edge. Email actually sends if you configure a provider (see [Email](../services/email.md)).\n\nA **client-only** project (no `toilconfig.json`) just gets the plain Vite dev server on your port, unchanged.\n\n### How hot reload works\n\n```mermaid\nflowchart TD\n E[\"You edit a file\"] --> Q{\"Which file?\"}\n Q -->|\"client/ (React, CSS)\"| V[\"Vite hot-swaps it in the browser\"]\n Q -->|\"server/ (a @rest / @data / @service file)\"| S[\"toilscript recompiles the server\"]\n S --> G[\"shared/server.ts is regenerated\"]\n G --> V2[\"Vite hot-swaps the new typed client\"]\n S --> W[\"the dev server hot-swaps the new server.wasm\"]\n V --> B[\"Browser updates, no full reload\"]\n V2 --> B\n W --> B\n```\n\nClient edits go straight through Vite's hot module replacement. Server edits trigger a toilscript rebuild: toiljs recompiles your backend, regenerates `shared/server.ts` (the typed client the browser imports to call your server), and hot-swaps the recompiled WebAssembly, all without you touching the browser. Rebuilds are debounced (grouped over about 150 milliseconds) so a \"save all\" or a formatter pass does not trigger a storm of builds.\n\nIf your project has an `emails/` folder, the dev server also prints an email-preview URL at `/__toil/emails`.\n\n### dev options\n\n| Flag | Meaning |\n| --- | --- |\n| `--port <n>` | Port to listen on. Default `3000` (or `client.port` from your config). |\n| `--root <dir>` | Run against a project in another directory. |\n\nPress `Ctrl+C` to stop. toiljs restores your terminal and force-exits even if a native listener is slow to close, so you never end up with an orphaned dev server rebuilding in the background.\n\n## `toiljs build`\n\nProduces the optimized production build. Run this before you deploy or before `toiljs start`.\n\n```bash\nnpm run build\n# or build only the server:\nnpx toiljs build --server\n```\n\n### What it produces\n\nA full build runs in a careful order so the pieces line up:\n\n```mermaid\nflowchart LR\n A[\"Your project\"] --> S[\"1. Compile the server<br/>(toilscript)\"]\n S --> W[\"build/server/release.wasm\"]\n S --> R[\"shared/server.ts<br/>(typed client, regenerated)\"]\n R --> C[\"2. Bundle the client<br/>(Vite)\"]\n C --> O[\"build/client/<br/>(HTML, JS, CSS, assets)\"]\n C --> X[\"3. Prerender + SEO<br/>(SSG pages, robots.txt,<br/>sitemap.xml, llms.txt)\"]\n```\n\n1. **The server is built first.** toilscript compiles every decorated server file (not just the entry) into `build/server/release.wasm`, and regenerates `shared/server.ts`. Doing this first means the client always bundles against a current, correct typed server client. A project that also declares `@stream` or `@daemon` surfaces compiles those into their own artifacts (`release-stream.wasm`, `release-cold.wasm`).\n2. **The client is bundled** by Vite into `build/client/` (your HTML, JavaScript, CSS, and optimized assets). The dev toolbar and error overlay are stripped out of the production bundle.\n3. **Static pages and SEO files are generated**: any route that opts into static generation is prerendered to HTML, and if you configured `client.seo`, toiljs writes `robots.txt`, `sitemap.xml`, and `llms.txt`. Routes that opt into server-side rendering get their HTML-with-holes templates baked for the edge.\n\n### build options\n\n| Flag | Meaning |\n| --- | --- |\n| `--server` | Build **only** the server (recompile the wasm and regenerate `shared/server.ts`), and skip the client bundle. Fast when you only touched backend code. This is what `npm run build:server` runs. |\n| `--root <dir>` | Run against a project in another directory. |\n\nA client-only project (no `toilconfig.json`) skips step 1 and just bundles the client.\n\n## `toiljs start`\n\nSelf-hosts the app you just built, on a fast production HTTP server (hyper-express, backed by uWebSockets.js). It serves your static client, runs your `server.wasm` for dynamic requests, does server-side rendering, supports daemons, and exposes a `/_toil` websocket channel. Use it to run your app on your own machine or server instead of deploying to the Dacely edge.\n\n```bash\nnpm run build # start needs a build to serve\nnpx toiljs start\nnpx toiljs start --port 8080 --host 0.0.0.0 --threads 4\n```\n\n`start` fails fast if there is no build yet (it looks for `build/client/index.html`), so run `toiljs build` first.\n\n### start options\n\n| Flag | Meaning |\n| --- | --- |\n| `--port <n>` | Port to listen on. Default `3000` (or `client.port`). |\n| `--host <host>` | Address to bind. Default `127.0.0.1` (loopback only). Pass `0.0.0.0` to accept connections from other machines. |\n| `--threads <n>` | Number of HTTP worker processes. Default is automatic (one per available CPU). Pass `1` to disable the worker pool. `--workers` is an accepted alias. This can also be set as `server.threads` in your config. |\n| `--root <dir>` | Run against a project in another directory. |\n\n## `toiljs configure`\n\nToggles a project's client styling features (the CSS preprocessor and Tailwind) after the fact, on an existing app. It detects your current setup, asks what you want, then rewrites the stylesheets and your app entry's imports, edits `package.json`, and syncs `node_modules` so removed packages are actually uninstalled.\n\n```bash\n# Interactive.\nnpx toiljs configure\n\n# Non-interactive: switch to Sass and turn Tailwind on.\nnpx toiljs configure --style sass --tailwind\n```\n\n### configure options\n\n| Flag | Meaning |\n| --- | --- |\n| `--style <css\\|sass\\|less\\|stylus>` | Switch the CSS preprocessor. |\n| `--tailwind` / `--no-tailwind` | Turn Tailwind on or off. |\n| `--images` / `--no-images` | Turn build-time image optimization on or off (sets `client.images` in your config). |\n| `--no-install` | Edit the files but do not run the package manager. You then run install yourself. |\n| `--root <dir>` | Run against a project in another directory. |\n\nPassing any of `--style`, `--tailwind`, or `--images` makes the command non-interactive (it skips the prompts you did not answer with a flag). See [Styling](../frontend/styling.md) for the full picture.\n\n## `toiljs doctor`\n\nRead-only project diagnostics. It gathers facts from disk (your `package.json`, lockfiles, the resolved config, your app entry, `index.html`, your routes, and the server target), runs a set of checks, and prints a grouped report. It never changes anything unless you pass `--fix`, and it never crashes on a partial or non-toiljs project (missing pieces just become warnings or failures). It exits with a non-zero status when any check **fails** (warnings do not fail), so it is safe to run in CI.\n\n```bash\n# Human-readable report.\nnpx toiljs doctor\n\n# Machine-readable, for CI.\nnpx toiljs doctor --json\n\n# Auto-repair the common wiring.\nnpx toiljs doctor --fix\n```\n\n### What it checks\n\nThe report is grouped:\n\n| Group | Example checks |\n| --- | --- |\n| **Environment** | Node.js version, that `toiljs` and its peer dependencies (React and so on) are installed and new enough, that your TypeScript is a supported 6.x rather than the unsupported native 7.x, that a lockfile exists, and that your scripts do not wrap `toiljs` in a stray `npx`. |\n| **Project + routing** | The `client/` and `routes/` folders exist, `index.html` has a `<div id=\"root\">`, your app entry calls `mount(...)` with the `slots` argument, at least one route exists, no two routes collide on the same URL, and no asset paths are written in a way that 404s on nested routes. |\n| **Config + assets** | Your `toil.config` loads, the base path is well formed, `client.seo` has a `url` if SEO is configured, and your styling packages are actually installed. |\n| **Server / WASM** | The `toilconfig.json` and its entry files exist, `toilscript` is installed, a compiled `.wasm` exists, the typed-RPC wiring is in place, your `@rest` controllers are actually dispatched, the Prettier and editor plugins are wired, and a `migrations/` folder exists. |\n| **Security** | If your server uses auth, whether `AUTH_SESSION_SECRET` is set (an unset secret means sessions fall back to a published dev key, which is forgeable). |\n\n### What `--fix` repairs\n\n`--fix` pins an unsupported TypeScript (the native 7.x, which ships no compiler API) back to `^6.0.3` in any project. The rest only touch a server project (one with a `toilconfig.json`), repairing the wiring that is easy to get wrong or that older projects predate:\n\n- adds `--rpcModule shared/server.ts` to your server build scripts,\n- adds `shared` and the `shared/*` path alias to `tsconfig.json`,\n- adds `shared/server.ts` to `.gitignore`,\n- lifts the `toilscript` version floor if it is too old,\n- adds the `toiljs/prettier-plugin` to your Prettier config (so Prettier does not choke on server decorators),\n- adds the toilscript language-service plugin to your server `tsconfig.json` and points VS Code at the workspace TypeScript (so the editor stops false-flagging `@database` collections and `@data` members),\n- refreshes the editor-only server globals declaration file.\n\nIt is idempotent: it only writes files it actually needs to change, and it tells you which ones changed and which need a manual edit (for example a `tsconfig.json` that contains comments). If it changed `package.json`, run your installer afterward.\n\n### doctor options\n\n| Flag | Meaning |\n| --- | --- |\n| `--json` | Emit machine-readable JSON instead of the human report (the banner is suppressed so stdout stays valid JSON). |\n| `--fix` | Repair the server wiring in place, as above. |\n| `--root <dir>` | Run against a project in another directory. |\n\n## `toiljs update`\n\nA friendly wrapper over `npm-check-updates`. It checks the registry for newer versions of your dependencies, groups them by how big the jump is (major, minor, patch), lets you pick which to apply (or `-y` to apply all), bumps `package.json`, and runs your package manager's install. It also makes sure your `server/migrations/` folder exists (older projects predate it). `npm-check-updates` runs via `npx`, so it never becomes a permanent dependency of your project.\n\nUpgrades into a major toiljs does not support are held back and listed separately, so neither the picker nor `-y` can install one. Today that means **TypeScript 7**, the native port, which ships no JavaScript compiler API (see [Installation](../getting-started/installation.md)). Bumps inside TypeScript 6 are still offered.\n\n```bash\n# Interactive picker.\nnpx toiljs update\n\n# Apply everything, non-interactively.\nnpx toiljs update --yes\n\n# Only patch-level updates.\nnpx toiljs update --target patch\n```\n\n### update options\n\n| Flag | Meaning |\n| --- | --- |\n| `-y, --yes` | Apply all available updates without the picker. |\n| `--target <latest\\|minor\\|patch\\|newest\\|greatest>` | How far to bump. Default `latest`. |\n| `--root <dir>` | Run against a project in another directory. |\n\n## `toiljs db`\n\nManages the local dev database: the on-disk ToilDB store your dev server writes to `.toil/devdata.json`. Use it to inspect data, wipe a corrupt state, save a snapshot to share as a fixture, or restore one. The snapshot is exactly the JSON the dev database uses, so an exported file imports cleanly.\n\n```bash\n# See what is stored.\nnpx toiljs db status\n\n# Wipe all dev data.\nnpx toiljs db reset\n\n# Save a snapshot (to a file, or to stdout if you omit the file).\nnpx toiljs db export fixture.json\n\n# Restore a snapshot.\nnpx toiljs db import fixture.json\n\n# Print the on-disk path (scriptable).\nnpx toiljs db path\n```\n\n### db actions\n\n| Action | What it does |\n| --- | --- |\n| `status` (alias `info`) | Show the database path, its size, and per-family row counts. |\n| `reset` (alias `purge`) | Delete all dev data (removes `devdata.json`). |\n| `export [file]` | Write a formatted snapshot to `file`, or to stdout if you omit it (pipe-friendly). |\n| `import <file>` | Replace the dev database with the snapshot in `file`. It refuses a file that is not a valid snapshot. |\n| `path` | Print the `devdata.json` path and nothing else. |\n\nThe dev database is per-project and lives under `.toil/`, which is gitignored. See [Database setup](../database/setup.md) for what actually populates it.\n\n## The dev, build, and deploy flow\n\nHere is how the commands fit together across your workflow.\n\n```mermaid\nflowchart LR\n subgraph Develop\n D[\"toiljs dev<br/>(hot reload, local edge + DB)\"]\n end\n subgraph Ship\n B[\"toiljs build<br/>(client bundle + server.wasm)\"]\n end\n subgraph Run\n ST[\"toiljs start<br/>(self-host locally)\"]\n DP[\"deploy to the Dacely edge\"]\n end\n D --> B\n B --> ST\n B --> DP\n```\n\n- **Develop** with `toiljs dev`. Everything runs locally and reloads as you type.\n- **Ship** with `toiljs build`. This produces the artifacts: the client bundle in `build/client/` and the server WebAssembly in `build/server/`.\n- **Run** the build two ways: `toiljs start` self-hosts it on your own machine, or you deploy the same build to the Dacely edge to serve it worldwide.\n\n## Gotchas\n\n- **`start` needs a build.** `toiljs start` serves what is in `build/`. Run `toiljs build` first, or it exits with an error.\n- **Run `toiljs`, not `npx toiljs`, inside npm scripts.** Under `npm run`, `node_modules/.bin` is already on your PATH, so an extra `npx` layer is redundant and can leave your terminal in a broken input mode after `Ctrl+C`. `doctor` warns about this; the scaffold gets it right.\n- **`--host` and `--threads` are `start`-only.** `toiljs dev` always binds locally and does not take `--host` or `--threads`.\n- **`--server` builds the backend only.** Use it while iterating on server code, but run a full `toiljs build` before you deploy so the client bundle is current.\n- **The update notice is not an error.** The \"newer toiljs available\" line is informational and prints to stderr. Set `TOILJS_NO_UPDATE_CHECK=1` to hide it.\n\n## Related\n\n- [Installation](../getting-started/installation.md) and [Create a project](../getting-started/create-project.md)\n- [Project structure](../getting-started/project-structure.md)\n- [Configuration reference (`toil.config.ts`)](../concepts/config.md)\n- [Styling](../frontend/styling.md)\n- [Database setup](../database/setup.md) and the [database overview](../database/README.md)\n- [Compute tiers (L1 to L4)](../concepts/tiers.md)\n",
|
|
23
23
|
"concepts/ai-guide.md": "# Writing toiljs correctly\n\nThis is a fast, high-signal cheat-sheet for writing toiljs and toilscript code that compiles and behaves. It is aimed at AI assistants generating code (and at humans in a hurry): the patterns to follow, the mistakes to avoid, and a link to the deep page whenever you need more. It does not re-teach the framework; each section points you at the authoritative doc.\n\n## Project shape\n\nA toiljs project has three top-level folders, and knowing which one a file belongs to tells you which rules apply:\n\n- **`client/`**: your React app that runs in the browser. Pages live under `client/routes/`, one file per URL, and each route file `export default`s its component. See [Frontend](../frontend/README.md) and [Routing](../frontend/routing.md).\n- **`shared/`**: a generated typed bridge (`shared/server.ts`) that lets the browser call the backend with full types. It is regenerated on every build, so **never hand-edit it**.\n- **`server/`**: your backend, written in TypeScript but compiled by **toilscript** to WebAssembly. It runs on the Dacely edge, not in Node or the browser. See [Backend](../backend/README.md).\n\nMost code runs on the L1 request tier. Long-lived connections (`@stream`) and scheduled work (`@daemon`) are opt-in tiers in their own entry files. See [Compute tiers](./tiers.md).\n\n## Client rules (the browser, React side)\n\n**Call the backend only through the generated `Server` client.** This is the number one rule. Use `Server.REST.<controller>.<route>(args)` for `@rest` HTTP controllers, or `Server.<service>.<method>(args)` / `Server.<remote>(args)` for RPC. Raw `fetch` to your own backend throws away the type safety, the argument and result decoding, and the binary wire codec, and it silently breaks when a route is renamed. `fetch` is only for third-party URLs (an external API, a CDN). See [Fetching data](../frontend/data-fetching.md).\n\n```ts\n// WRONG: raw fetch to your own backend\nawait fetch('/account/session', { method: 'POST' });\n\n// RIGHT: the typed client (renames become compile errors; args + result typed)\nawait Server.REST.account.session();\n```\n\n**Use the ambient `Toil.*` globals with no import.** `Toil` is the `toiljs/client` package exposed as a global and typed via a generated `toil-env.d.ts`, so it autocompletes with no import: `Toil.Link` / `Toil.NavLink` for navigation, `Toil.useParams`, `Toil.useLoaderData`, `Toil.Image`, `Toil.useHead` / `Toil.Head`, `Toil.Form` / `Toil.useAction`, and more. `Server` and `parseError` are also bare globals; `FastMap`, `FastSet`, `DataWriter`, and `DataReader` are bare globals too (write `new DataWriter()`, not `new Toil.DataWriter()`). Full index: [The Toil global](../frontend/toil-global.md).\n\n**Load page data with a `loader`, not a `useEffect` fetch.** A route file exports a `loader`, and the component reads its result with `Toil.useLoaderData`. The loader runs on navigation in parallel with the route chunk, integrates with `loading.tsx`, caching, and SSR hydration. A `useEffect` fetch runs only after mount (slower, invisible to the server).\n\n```tsx\n// client/routes/blog/[id].tsx\nexport const loader = async ({ params }: Toil.LoaderArgs) => {\n return Server.REST.blog.get({ params: { id: params.id } });\n};\n\nexport default function BlogPost() {\n const post = Toil.useLoaderData<typeof loader>();\n return <article><h1>{post.title}</h1></article>;\n}\n```\n\n**Mutate with `Toil.useAction` or `<Toil.Form>`, then revalidate.** Both track pending and error state and refetch the affected loader data on success, so the page updates without a manual refetch. Use `<Toil.Form>` for form submits, `useAction` for anything else (a delete button, a toggle). Reach for `Toil.revalidate()` / `router.revalidate()` after a write that these do not cover.\n\n**Per-route SEO with `export const metadata`** (or `Toil.useHead` / `<Toil.Head>` from inside a component). Opt a route into edge SSR with `export const ssr = true`. See [Metadata and SEO](../frontend/metadata.md) and [Rendering and SSR](../frontend/rendering.md).\n\n**Typed hrefs.** `Toil.Link` `href`, `Toil.navigate`, and `router.push` are all type-checked against your real routes, so a typo is a compile error. Use `Toil.href(str)` only when a URL is assembled from data and TypeScript cannot prove it is a real route. See [Navigation](../frontend/navigation.md).\n\n**`getUser()` on the client is display-only.** It reads a readable session cookie with no network call, so it is instant but **forgeable**. Use it to render \"logged in as ...\", never to gate real access. The authoritative check is a server route guarded with `@auth`. See [Auth usage](../auth/usage.md).\n\n## Server rules (toilscript, compiles to WASM)\n\nThe decorators, each doing one job (full list in [Decorators](./decorators.md)):\n\n- **`@data`** on a class: generates the binary and JSON codec so the value can cross the wire and the database. Every field needs a default; field order **is** the binary layout, so add new fields at the end. See [Data types](../backend/data.md).\n- **`@rest('name')`** on a class mounts an HTTP controller at `/name`; **`@get`/`@post`/`@put`/`@del`/`@patch`** on its methods declare routes (`@del`, not `@delete`, which is reserved). See [REST](../backend/rest.md).\n- **`@service`** + **`@remote`** expose typed RPC callable from your own frontend as `Server.<service>.<method>()`. See [RPC](../backend/rpc.md).\n- **`@user`** declares the authenticated-user shape and enables `AuthService.getUser()`. **Exactly one `@user` per project.** **`@auth`** guards a route or a whole `@rest` class. See [Auth](../auth/README.md).\n- **`@ratelimit(strategy, limit, window)`** caps how often a caller may hit a route, rejected at the edge before your code runs. See [Security](./security.md).\n- **`@database`** + **`@collection`** declare a ToilDB schema (below).\n\n```ts\n@rest('players')\nclass Players {\n @get('/:id')\n public get(ctx: RouteContext): Response {\n const id = ctx.param('id');\n return Response.json(`{\"id\":\"${id}\"}`);\n }\n}\n```\n\n**`@auth` rejects with 401 before your handler runs.** So inside an `@auth`-guarded handler, `AuthService.getUser()!` is guaranteed non-null and safe to assert. Without `@auth`, `getUser()` can be null, so null-check it instead. Either way the server re-verifies the signed session, so it (not the client cookie) is the real authorization boundary.\n\n```ts\n@auth\n@get('/settings')\npublic settings(): Response {\n const user = AuthService.getUser()!; // safe: @auth guarantees a session\n return Response.text('hi ' + user.username);\n}\n```\n\n**Server code is a strict, WASM-targeted dialect (AssemblyScript), not full TypeScript.** The real constraints, verified against the toilscript standard library:\n\n- **Use explicit value types**, never `number`: `i32` / `u32` / `i64` / `u64` / `f64` (and `i8`/`u8`/`i16`/`u16`/`f32`, plus `u128`..`u256`), `bool`, `string`, `Uint8Array`. Plain `number` resolves to `f64`, which is wrong for ids and counts. Integer math **wraps** on overflow (it never throws), and integer `/` truncates. See [The type system](./types.md).\n- **No `any`.** Every value has a concrete type; that is what lets it compile to WASM.\n- **No arbitrary npm.** Only the toilscript standard library plus the toiljs host APIs.\n- **Use `null`, not `undefined`** for \"no value\" (`T | null`, narrowed with `if (x != null)` or a `!` assertion).\n- **No usable built-in `RegExp`** (it is a host stub that throws). Parse strings by hand.\n- **Structured values are `@data` classes, not object literals.** Encode and decode raw binary with `DataWriter` / `DataReader` (imported on the server from the `data` module: `import { DataWriter, DataReader } from 'data';`). For dynamic JSON, use the ambient `JSON` value tree.\n- **Read config with `Environment.get(key)` and secrets with `Environment.getSecure(key)`** (each returns `string | null`; the two buckets are disjoint so a secret can never leak through `get`). See [Environment](../services/environment.md).\n- **Return a runtime `Response`** (`Response.json` / `.text` / `.html` / `.bytes` / `.notFound` / ...), or return a `@data` value and let toiljs serialize it.\n\n**ToilDB has seven collection families; pick the one that matches the job** (do not force everything into one). Declare each as a `static` `@collection` field on a `@database` class, typed by its family, and reach it statically (`AppDb.users.get(...)`). See [The database](../database/README.md) and [Setup](../database/setup.md).\n\n- **`Documents<K,V>`**: the default record store, looked up by id (users, posts, orders).\n- **`Unique<K,V>`**: a globally one-of-a-kind claim (usernames, emails, slugs).\n- **`Counter<K>`**: a running total many callers bump at once (likes, views); `add` a delta only.\n- **`Events<K,V>`**: an append-only log kept in order (feeds, audit trails).\n- **`Capacity<K>`**: a limited quantity handed out without overselling (tickets, seats).\n- **`Membership<K,M>`**: sets of who belongs to what (followers, tags, room members).\n- **`View<K,V>`**: a precomputed read-optimized snapshot (leaderboards, home pages).\n\n**Data access is gated by function kind.** A `@get` route is a read-only **Query**; a `@post` route is an **Action** (may write); a plain `@remote` defaults to read-only Query, so add **`@action`** to let it write. Scans (`events.latest`, `membership.list`) are barred from request handlers: do them in a `@derive` and have the request read the resulting `View`. The compiler enforces this and the edge re-checks it.\n\n## Common mistakes\n\nA short do-not list. Every one of these is a real, avoidable error:\n\n- **Raw `fetch` to your own backend.** Go through `Server.*` instead.\n- **Importing `Toil.*`.** They are ambient globals; importing them is wrong.\n- **Fetching page data in a `useEffect`.** Use a route `loader` + `Toil.useLoaderData`.\n- **A plain `<a>` for in-app links.** Use `Toil.Link` (a bare `<a>` triggers a full reload).\n- **More than one `@user`.** Exactly one per project.\n- **Trusting client `getUser()` for authorization.** It is display-only and forgeable; enforce on the server with `@auth`.\n- **`any`, `number`, or `RegExp` in server code.** Use explicit value types; there is no usable `RegExp`.\n- **Reordering `@data` fields** (or changing a field type) on a stored type. Field order is the layout; add at the end and use `@migrate` to evolve. See [Data types](../backend/data.md).\n- **Writing from a plain `@remote` or `@get`.** Reads are the default; a write needs `@action`.\n- **Calling a scan (`events.latest`, `membership.list`) from a request handler.** Do it in a `@derive`.\n- **Hand-editing `shared/server.ts` or `.toil/`.** They are generated; rebuild instead.\n\n## Where to go deeper\n\n- Concepts: [Decorators](./decorators.md), [Type system](./types.md), [Security](./security.md), [Compute tiers](./tiers.md).\n- Backend: [Overview](../backend/README.md), [REST](../backend/rest.md), [RPC](../backend/rpc.md), [Data types](../backend/data.md).\n- Frontend: [Overview](../frontend/README.md), [Fetching data](../frontend/data-fetching.md), [Routing](../frontend/routing.md), [Navigation](../frontend/navigation.md), [The Toil global](../frontend/toil-global.md).\n- Database: [ToilDB overview](../database/README.md), [Setup](../database/setup.md).\n- Auth: [Overview](../auth/README.md), [Usage](../auth/usage.md).\n- Services: [Environment](../services/environment.md), [Rate limiting](../services/ratelimit.md).\n</content>\n</invoke>\n",
|
|
24
24
|
"concepts/config.md": "# Configuration (`toil.config.ts`)\n\n`toil.config.ts` is the one file that configures your whole toiljs app: the client (React/Vite) side and the server (WebAssembly) side. Every field is optional and has a sensible default, so most projects keep it tiny.\n\n## The shortest possible config\n\nA scaffolded project ships something like this:\n\n```ts\n// toil.config.ts\nimport { defineConfig } from 'toiljs/compiler';\n\nexport default defineConfig({\n client: {\n // Optimize images at build time (resize and compress imported images).\n images: true,\n },\n});\n```\n\n`defineConfig` does not do anything at runtime: it is an identity helper that gives you full editor autocomplete and type-checking for the config shape. Always wrap your config in it.\n\nAn empty config is valid too. `export default defineConfig({})` gives you a working app with all defaults.\n\n## Where the file lives\n\ntoiljs looks in your project root for the first file that matches, in this order:\n\n```\ntoil.config.ts toil.config.mts toil.config.js toil.config.mjs\ntoiljs.config.ts toiljs.config.mts toiljs.config.js toiljs.config.mjs\n```\n\nUse whichever you like. `toil.config.ts` is the convention.\n\n## Config vs. environment variables\n\nThere are two very different places settings live, and it matters which one you use.\n\n| | `toil.config.ts` (this page) | Environment variables |\n| --- | --- | --- |\n| **When it applies** | Build time and dev time | Runtime, per request |\n| **What it holds** | Framework and build options (routing, styling, SEO, which features to compile) | Values and secrets your running server reads (API keys, feature flags, connection info) |\n| **Committed to git?** | Yes, it is source | No. Local values go in `.env` / `.env.secrets` (both gitignored); production values live on your deploy target |\n| **Read in code with** | Not read from your app code | `Environment.get(...)` / `Environment.getSecure(...)` on the server |\n\nRule of thumb: if it is a **secret** (a password, an API key, a session key), it does **not** go in `toil.config.ts`. It goes in the environment. See [Environment and secrets](../services/environment.md) for the full story.\n\nThere is also a **third** file, `toilconfig.json`, which is a different thing entirely. See [`toil.config.ts` is not `toilconfig.json`](#toilconfigts-is-not-toilconfigjson) at the bottom.\n\n## The top-level shape\n\n```ts\ninterface ToilConfig {\n root?: string; // project root (defaults to the current working directory)\n client?: ClientConfig; // the React / Vite frontend\n server?: ServerConfig; // the toilscript / WebAssembly backend\n}\n```\n\nEverything else lives under `client` or `server`.\n\n## `client` reference\n\nConfigures the frontend: source folders, the dev server, and build-time optimizations.\n\n| Field | Type | Default | What it does |\n| --- | --- | --- | --- |\n| `srcDir` | `string` | `\"client\"` | Your frontend source directory, relative to the project root. |\n| `routesDir` | `string` | `\"routes\"` | Your file-based routes directory, relative to `srcDir`. |\n| `publicDir` | `string` | `\"<srcDir>/public\"` | Static assets directory. Holds `index.html` (which you own) plus files served as-is (favicons, images). |\n| `outDir` | `string` | `\"build/client\"` | Where the production client bundle is written. |\n| `base` | `string` | `\"/\"` | The public base path. Use this if you serve the app under a sub-path (a non-root base should start and end with `/`, like `\"/app/\"`). |\n| `port` | `number` | `3000` | The dev server port. `--port` on the CLI overrides it. |\n| `images` | `boolean` | `true` | Optimize imported images at build time (resize and convert). See [Images](../frontend/images.md). |\n| `fonts` | `boolean` | `true` | Preload bundled fonts (inject `<link rel=\"preload\">` for each `@font-face`) so text paints faster. |\n| `viewTransitions` | `boolean` | `false` | Animate page navigations with the browser View Transitions API (a crossfade by default). Respects `prefers-reduced-motion`. |\n| `transitions` | `boolean` | `false` | Wrap navigations in a React transition, keeping the current page visible while the next route's loader runs (instead of showing its loading state immediately). |\n| `devtools` | `boolean` or object | `true` | The floating dev toolbar (route/build info, errors, live controls). It is dev-only and never ships in production. Set `false` to disable, or pass an object to configure its AI integration. |\n| `seo` | object | (off) | Build-time SEO: bakes site-level metadata into the HTML and generates `robots.txt`, `sitemap.xml`, `llms.txt`. See [`client.seo`](#clientseo) below. |\n| `vite` | Vite `InlineConfig` | `{}` | An escape hatch: raw Vite options, deep-merged over the framework's own Vite setup. toiljs owns the Vite config; use this only to override specific options. |\n\n### Example\n\n```ts\nimport { defineConfig } from 'toiljs/compiler';\n\nexport default defineConfig({\n client: {\n images: true,\n fonts: true,\n viewTransitions: true,\n // Serve the app under https://example.com/app/\n base: '/app/',\n },\n});\n```\n\n### `client.devtools`\n\nThe dev toolbar is on by default. To turn it off, or to give it an AI provider (so its \"explain this error\" helpers can call a model), pass an object:\n\n```ts\nimport { defineConfig, AiProvider } from 'toiljs/compiler';\n\nexport default defineConfig({\n client: {\n devtools: {\n ai: {\n provider: AiProvider.Anthropic, // 'anthropic' or 'openai'\n model: 'claude-sonnet-4-6',\n // The name of the env var holding the API key. It is read\n // server-side by the dev process and never sent to the browser.\n apiKeyEnv: 'ANTHROPIC_API_KEY',\n // Optional: a custom POST endpoint ({ prompt } in, { text } out)\n // that overrides `provider` entirely.\n // endpoint: 'http://localhost:5000/ai',\n },\n },\n },\n});\n```\n\n| `devtools.ai` field | Type | What it does |\n| --- | --- | --- |\n| `provider` | `AiProvider` | Built-in provider: `AiProvider.Anthropic` (`'anthropic'`) or `AiProvider.OpenAI` (`'openai'`). |\n| `model` | `string` | The model id, for example `'claude-sonnet-4-6'` or `'gpt-4o'`. |\n| `apiKeyEnv` | `string` | The **name** of the environment variable that holds the API key. The key stays server-side and never reaches the browser. |\n| `endpoint` | `string` | A custom POST endpoint. When set, it takes precedence over `provider`. |\n\nThe toolbar always offers hand-off links to Claude and ChatGPT even without any AI config; this only enables the in-toolbar helpers.\n\n### `client.seo`\n\n`client.seo` turns on build-time search-engine and social metadata. It bakes tags into your HTML `<head>` (so crawlers and link-preview bots that do not run JavaScript still see real metadata) and generates `robots.txt`, `sitemap.xml`, and `llms.txt`. This is a large section with its own guide; see [Metadata and SEO](../frontend/metadata.md) for the complete field list and examples. The one thing worth knowing here: set `seo.url` to your absolute site URL (like `\"https://example.com\"`), because the sitemap and canonical links need it.\n\n```ts\nexport default defineConfig({\n client: {\n seo: {\n url: 'https://example.com',\n title: 'My App',\n description: 'A toiljs app.',\n },\n },\n});\n```\n\n## `server` reference\n\nConfigures the backend: which platform features to compile in, and how the dev server and self-host behave.\n\n| Field | Type | Default | What it does |\n| --- | --- | --- | --- |\n| `auth` | `boolean` | `false` | Opt into the framework's built-in post-quantum login. See [`server.auth`](#serverauth) below. |\n| `email` | object | (off) | The non-secret email backend config for the dev server and self-host. See [`server.email`](#serveremail) below. |\n| `daemon` | object | (defaults) | Background-job (L4 daemon) settings for dev and self-host. See [`server.daemon`](#serverdaemon) below. |\n| `nodeMode` | string | `\"all\"` | Which compute layer the single dev/self-host process emulates. See [`server.nodeMode`](#servernodemode) below. |\n| `threads` | `number` or `\"auto\"` | `\"auto\"` | HTTP worker count for `toiljs start`. `\"auto\"` uses one per CPU; `1` disables the worker pool. `--threads` on the CLI overrides it. |\n| `srcDir` | `string` | `\"server\"` | Declarative: your server source directory. See the note below. |\n| `outDir` | `string` | `\"build/server\"` | Declarative: the server build output directory. See the note below. |\n\n> **Note on `server.srcDir` / `server.outDir`.** These fields exist in the config type, but the actual location of your server source and its compiled output is driven by `toilconfig.json` (the toilscript compiler config), not by these two fields. Change your server paths in `toilconfig.json`. These fields are currently declarative and left for forward compatibility. See [not `toilconfig.json`](#toilconfigts-is-not-toilconfigjson) below.\n\n### `server.auth`\n\nSet `auth: true` to get a complete post-quantum login system with no boilerplate. The build appends a shipped `@rest('auth')` controller and its `@user` shape to your server, giving you `/auth/register`, `/auth/login`, `/auth/me`, and `/auth/logout` plus sessions.\n\n```ts\nexport default defineConfig({\n server: {\n auth: true,\n },\n});\n```\n\nTwo things to know:\n\n- If you opt in, your app must **not** declare its own `@user` type. The built-in auth owns the single per-program one.\n- There is an escape hatch: adding `import 'toiljs/server/auth'` in `server/main.ts` turns on the same built-in auth surface without this flag.\n\nAuth has its own configuration (session secrets, the OPRF and KEM keys) that lives in the **environment**, not here. See [Auth configuration](../auth/configuration.md).\n\n### `server.email`\n\nThe **non-secret** part of your email setup: which provider, the \"from\" address, and send caps. The dev server and self-host read it. The API key or SMTP password is a **secret** and never goes here; it comes from `.env.secrets` (`TOIL_EMAIL_API_KEY`). Any `TOIL_EMAIL_*` environment variable overrides the matching field here.\n\n```ts\nexport default defineConfig({\n server: {\n email: {\n provider: 'resend',\n from: 'hello@example.com',\n maxPerMin: 60,\n },\n },\n});\n```\n\n| Field | Type | Default | What it does |\n| --- | --- | --- | --- |\n| `provider` | `'resend'` \\| `'gmail'` \\| `'smtp'` | `'resend'` | Which email backend to use. |\n| `from` | `string` | (none) | The \"from\" address. Validated (single address, no line breaks). |\n| `maxPerMin` | `number` | `60` | Per-process send ceiling per minute (rolling). `0` means unlimited. |\n| `maxPerDay` | `number` | `0` | Per-process send ceiling per day (rolling). `0` means unlimited. |\n| `maxPerRecipientPerHour` | `number` | `5` | Per-recipient hourly cap (anti-abuse). |\n| `smtp` | object | (none) | Connection details for the `gmail` / `smtp` providers: `host`, `port` (defaults to 587 STARTTLS; 465 is implicit TLS), and `user` (defaults to `from`). |\n\nSee the full guide at [Email and 2FA](../services/email.md).\n\n### `server.daemon`\n\nSettings for the daemon (L4) background layer, used by the dev process and self-host. In dev, the local process is always the leader, so region fields are informational.\n\n```ts\nexport default defineConfig({\n server: {\n daemon: {\n defaultIntervalMs: 60000,\n maxTasks: 64,\n },\n },\n});\n```\n\n| Field | Type | Default | What it does |\n| --- | --- | --- | --- |\n| `region` | `string` | (none) | Region the daemon is pinned to (informational in dev). |\n| `standbyRegion` | `string` | (none) | Warm standby region (informational in dev). |\n| `defaultIntervalMs` | `number` | `60000` | Default interval for a `@scheduled` task that declares none. Values below `1000` are clamped up to `1000` (a sub-second loop would flood the console). |\n| `tickBudgetMs` | `number` | `30000` | Per-tick wall-clock budget before the dev scheduler logs an overrun. |\n| `gasTick` | `number` | `0` | Per-tick gas cap (a dev stub: charged then ignored). |\n| `maxTasks` | `number` | `64` | Maximum number of `@scheduled` tasks. Clamped to the range 1 to 1024. |\n\nSee [Daemons and scheduled jobs](../background/daemons.md).\n\n### `server.nodeMode`\n\nWhich compute layer the single local process emulates. This is a dev and self-host knob only; in production the Dacely edge decides each server's role. Valid values are `hot`, `regional`, `continental`, `daemon`, and `all`. The default, `all`, runs every surface (requests, streams, and daemons) in one process, which is what you want for a full local run. An invalid value falls back to `all` with a warning rather than failing. See [Compute tiers (L1 to L4)](../concepts/tiers.md) for what each layer means.\n\n```ts\nexport default defineConfig({\n server: {\n nodeMode: 'all', // run everything locally (the default)\n },\n});\n```\n\n## Defaults at a glance\n\nIf you write nothing, this is what you get.\n\n| Setting | Default |\n| --- | --- |\n| `client.srcDir` | `\"client\"` |\n| `client.routesDir` | `\"routes\"` |\n| `client.publicDir` | `\"client/public\"` |\n| `client.outDir` | `\"build/client\"` |\n| `client.base` | `\"/\"` |\n| `client.port` | `3000` |\n| `client.images` | `true` |\n| `client.fonts` | `true` |\n| `client.viewTransitions` | `false` |\n| `client.transitions` | `false` |\n| `client.devtools` | on |\n| `client.seo` | off |\n| `server.auth` | `false` |\n| `server.email` | off |\n| `server.nodeMode` | `\"all\"` |\n| `server.threads` | `\"auto\"` |\n| `server.daemon.defaultIntervalMs` | `60000` |\n| `server.daemon.tickBudgetMs` | `30000` |\n| `server.daemon.maxTasks` | `64` |\n\n## A fuller example\n\n```ts\nimport { defineConfig, AiProvider } from 'toiljs/compiler';\n\nexport default defineConfig({\n client: {\n images: true,\n fonts: true,\n viewTransitions: true,\n seo: {\n url: 'https://example.com',\n title: 'Example',\n description: 'Built with toiljs.',\n },\n devtools: {\n ai: { provider: AiProvider.Anthropic, model: 'claude-sonnet-4-6', apiKeyEnv: 'ANTHROPIC_API_KEY' },\n },\n },\n server: {\n auth: true,\n email: { provider: 'resend', from: 'hello@example.com' },\n threads: 'auto',\n },\n});\n```\n\n## `toil.config.ts` is not `toilconfig.json`\n\nThese two look almost the same and are easy to confuse. They are not the same file.\n\n| File | What it is |\n| --- | --- |\n| `toil.config.ts` | **This page.** The framework config: client and server options, styling, SEO, which features to build. You edit it often. |\n| `toilconfig.json` | The **toilscript compiler** config: which server files are entry points, where the `.wasm` is written, and low-level WebAssembly options (optimization level, memory layout, enabled wasm features). It is scaffolded for you and you rarely touch it. Its presence is also what tells toiljs \"this project has a server.\" |\n\nIf you ever need to change where your server source lives or what the compiled artifact is named, that is `toilconfig.json`, not `toil.config.ts`.\n\n## The `toilconfig.json` reference\n\n`toilconfig.json` is the **toilscript compiler** config. toilscript is the compiler that turns your `server/` TypeScript into a `.wasm` file (WebAssembly, the sandboxed binary your backend ships as). This file tells toilscript which server files to compile, where to write the output, and which low-level WebAssembly options to use.\n\n`toiljs create` scaffolds it for you and most projects never touch it. You only edit it if you want to rename the compiled artifact, move your server entry, or hand-tune the WebAssembly codegen. Its presence at the project root is also the signal toiljs uses to decide \"this project has a server\" (a project with no `toilconfig.json` is a client-only app).\n\nA scaffolded file looks like this:\n\n```json\n{\n \"entries\": [\"server/main.ts\"],\n \"targets\": {\n \"release\": {\n \"outFile\": \"build/server/release.wasm\",\n \"textFile\": \"build/server/release.wat\"\n }\n },\n \"options\": {\n \"sourceMap\": false,\n \"optimizeLevel\": 3,\n \"shrinkLevel\": 1,\n \"converge\": true,\n \"noAssert\": false,\n \"enable\": [\n \"sign-extension\",\n \"mutable-globals\",\n \"nontrapping-f2i\",\n \"bulk-memory\",\n \"simd\",\n \"reference-types\",\n \"multi-value\"\n ],\n \"runtime\": \"stub\",\n \"lib\": [\"node_modules/toiljs/server/globals\"],\n \"memoryBase\": 65536,\n \"initialMemory\": 4,\n \"debug\": false,\n \"trapMode\": \"allow\"\n }\n}\n```\n\n### `entries`\n\nAn array of your server entry files: the toilscript starting points for the compile. The scaffold lists just `server/main.ts`, and `main.ts` imports your other surface modules so they all get pulled in. (Under `toiljs build`, toiljs compiles every decorated server file it finds, not only the entries, so a `@rest` or `@data` file you drop in is picked up even if `main.ts` does not import it.)\n\nA project that also has a streams tier or a daemon tier lists their entry files here too, so each tier can compile into its own artifact:\n\n```json\n\"entries\": [\"server/main.ts\", \"server/main.stream.ts\", \"server/main.daemon.ts\"]\n```\n\n### `targets`\n\nA map of named build targets. Each target names its output files. The scaffold has one target, `release`.\n\n| Field | Type | What it does |\n| --- | --- | --- |\n| `outFile` | `string` | Where the compiled `.wasm` is written. The scaffold uses `build/server/release.wasm`. |\n| `textFile` | `string` | Where the `.wat` (WebAssembly **text** format, the human-readable text form of the same module) is written. Handy for inspecting the output; not needed at runtime. |\n\n### `options`\n\nLow-level WebAssembly codegen options passed straight to toilscript. The defaults are already tuned for production, so change these only if you know you need to.\n\n| Field | Type | Scaffold value | What it controls |\n| --- | --- | --- | --- |\n| `sourceMap` | `boolean` | `false` | Emit a source map alongside the `.wasm` so a debugger can map machine code back to your TypeScript. Off by default (it makes the build bigger). |\n| `optimizeLevel` | `number` | `3` | How hard the optimizer works on **speed**, from `0` (none) to `3` (most). `3` is a production release setting. |\n| `shrinkLevel` | `number` | `1` | How hard the optimizer works on **size**, from `0` to `2`. `1` trades a little speed for a smaller binary. |\n| `converge` | `boolean` | `true` | Re-run the optimizer until the output stops getting better. Squeezes out a bit more at the cost of a slower build. |\n| `noAssert` | `boolean` | `false` | Strip `assert(...)` checks from the output (replace them with just their value, no trap). `false` keeps the safety checks in. |\n| `enable` | `string[]` | (see below) | Which modern WebAssembly features the compiled module is allowed to use. |\n| `runtime` | `string` | `\"stub\"` | The memory-management runtime baked into the module. `\"stub\"` is a minimal runtime that never frees memory, which fits toiljs's model exactly: the edge runs one fresh instance per request and throws its whole memory away when the request ends, so there is nothing to garbage-collect. |\n| `lib` | `string[]` | `[\"node_modules/toiljs/server/globals\"]` | Extra library paths whose top-level exports become **ambient globals** (usable with no `import`). This is what makes toiljs's server globals (like `crypto` and the auth primitives) available everywhere in `server/`. |\n| `memoryBase` | `number` | `65536` | The byte offset where your server's static data starts. toiljs reserves the first 64 KiB (`[0, 65536)`) for the **request envelope** the edge writes at offset 0, so a large request body can never overwrite your program's state. Raise it to accept larger request bodies (it costs a little more initial memory). |\n| `initialMemory` | `number` | `4` | How much linear memory the module starts with, in **pages**. One WebAssembly page is 64 KiB, so `4` is 256 KiB. It grows on demand past this. |\n| `debug` | `boolean` | `false` | Include debug information (names and the like) in the binary. Off for production. |\n| `trapMode` | `string` | `\"allow\"` | What happens on a trapping operation (like a bad float-to-int conversion). `\"allow\"` lets it trap (the default and correct choice); `\"clamp\"` replaces traps with clamping instead. |\n\nThe `enable` array turns on WebAssembly features that are off by default in the compiler. The scaffold enables the modern set the compiled server relies on: `sign-extension`, `mutable-globals`, `nontrapping-f2i` (non-trapping float-to-int conversions), `bulk-memory` (fast `memory.copy` / `memory.fill`), `simd` (vector operations), `reference-types`, and `multi-value` (functions that return more than one value). Leave this list as scaffolded unless you have a specific reason to change it; removing an entry can make the module fail to compile or run.\n\n### The `--rpcModule` build flag\n\nOne toilscript flag is worth knowing even though it lives in your npm scripts, not in `toilconfig.json`: `--rpcModule shared/server.ts`. It tells the compiler to also emit `shared/server.ts`, the fully typed client the browser imports to call your server (the `@data` codec plus the typed `Server` surface). toiljs adds this flag for you on the request build. If your `build:server` script is missing it (older projects predate it), `toiljs doctor --fix` injects it. See [the CLI reference](../cli/README.md#what---fix-repairs).\n\n## Gotchas\n\n- **Wrap the config in `defineConfig`.** Without it you lose autocomplete and type errors on typos.\n- **Secrets never go here.** API keys, session secrets, and passwords belong in the environment, not in `toil.config.ts` (which is committed to git). See [Environment and secrets](../services/environment.md).\n- **`server.srcDir` / `server.outDir` do not move your server.** The server source location is governed by `toilconfig.json` entries. Editing these two config fields has no effect today.\n- **`nodeMode` and `daemon` are dev/self-host only.** In production the edge assigns each server its role; these settings never override that.\n- **An invalid `nodeMode` does not crash the build.** It warns and falls back to `all`.\n\n## Related\n\n- [The CLI](../cli/README.md): the commands that read this config.\n- [Environment and secrets](../services/environment.md): runtime values and how they differ from build-time config.\n- [Auth configuration](../auth/configuration.md): the auth-related environment settings behind `server.auth`.\n- [Email and 2FA](../services/email.md): the full email setup behind `server.email`.\n- [Metadata and SEO](../frontend/metadata.md): the full `client.seo` field list.\n- [Daemons and scheduled jobs](../background/daemons.md): the background layer behind `server.daemon`.\n- [Compute tiers (L1 to L4)](../concepts/tiers.md): what `server.nodeMode` selects.\n- [Images](../frontend/images.md) and [Styling](../frontend/styling.md): the features `client.images` and `toiljs configure` control.\n",
|
|
25
25
|
"concepts/decorators.md": "# Decorators reference\n\nEvery feature of a toiljs backend, an HTTP route, an RPC method, a database collection, a scheduled job, is switched on by a **decorator**. This page lists them all, grouped by what they do, so you can find the right one at a glance and jump to the page that covers it in depth.\n\n## What a decorator is\n\nA **decorator** is the `@name` you write on the line just above a class, a method, or a field. It attaches meaning to that code without changing what the code itself does. You are labelling it so the compiler knows how to wire it up.\n\n```ts\n@rest('users') // <- a class decorator: \"this class is an HTTP controller\"\nclass Users {\n @get('/:id') // <- a method decorator: \"this method answers GET /users/:id\"\n public byId(): Response { /* ... */ }\n}\n```\n\nThree things to notice, because they decide where each decorator can go:\n\n- Some decorators apply to a **class** (`@rest`, `@service`, `@stream`, `@daemon`, `@database`, `@data`, `@user`).\n- Some apply to a **method** or a free **function** (`@get`, `@remote`, `@scheduled`, `@query`).\n- One applies to a **field** (`@collection`).\n\nSome take arguments, like `@get('/:id')` or `@cache(60)`; the bare ones, like `@rest` or `@daemon`, do not. You never register anything by hand: tagging the code is enough, and the build discovers it.\n\nEach decorator also belongs to a **tier** (where its code runs) or is **shared** (compiled into every tier). If tiers are new to you, read [Compute tiers](./tiers.md) first; the short version is L1 = per-request at the edge, L2 / L3 = long-lived stream connections, L4 = one global daemon.\n\n## Routing and HTTP (L1)\n\nTurn a class into an HTTP controller and its methods into routes. All run on the L1 request tier. Covered in [REST](../backend/rest.md).\n\n| Decorator | Applies to | What it does |\n| --- | --- | --- |\n| `@rest` | class | Marks the class an HTTP controller, mounted at a prefix (`@rest('users')` -> `/users`). |\n| `@route` | method | Declares a route with an explicit method + path: `@route({ method: Methods.GET, path: '/' })`. |\n| `@get` | method | Shorthand for a `GET` route: `@get('/:id')`. |\n| `@post` | method | Shorthand for a `POST` route. |\n| `@put` | method | Shorthand for a `PUT` route. |\n| `@del` | method | Shorthand for a `DELETE` route (named `del` because `delete` is a reserved word). |\n| `@patch` | method | Shorthand for a `PATCH` route. |\n| `@head` | method | Shorthand for a `HEAD` route. |\n| `@options` | method | Shorthand for an `OPTIONS` route. |\n\n## RPC (L1)\n\nExpose server functions that your own frontend calls like typed async functions. Run on L1. Covered in [RPC](../backend/rpc.md).\n\n| Decorator | Applies to | What it does |\n| --- | --- | --- |\n| `@service` | class | Marks an RPC service; its `@remote` methods are namespaced under the generated client as `Server.<service>.<method>()`. |\n| `@remote` | method / function | Marks a method (of a `@service`) or a top-level function as a client-callable RPC endpoint. |\n\n## Guards and policy (L1)\n\nAttach a rule to a route (or a whole controller). These stack above a route method. Run on L1.\n\n| Decorator | Applies to | What it does | Covered in |\n| --- | --- | --- | --- |\n| `@auth` | class / method | Requires a valid session; returns `401` otherwise. On a class it guards every route. | [Auth usage](../auth/usage.md) |\n| `@cache` | method | Caches the response at the edge and browser: `@cache(edgeMinutes, browserSeconds?, privateScope?, allowAuth?)`. | [Caching](../services/caching.md) |\n| `@ratelimit` | method | Rate-limits a route: `@ratelimit(strategy, limit, window)`. | [Rate limiting](../services/ratelimit.md) |\n\n## Realtime streams (L2 / L3)\n\nHandle a long-lived connection, keeping state per connected client. The `@stream` class runs on the L2 / L3 stream tier; its lifecycle-hook methods fire as events arrive. Covered in [Realtime streams](../realtime/streams.md).\n\n| Decorator | Applies to | What it does |\n| --- | --- | --- |\n| `@stream` | class | Marks a stream protocol handler. `@stream('name')` sets the mount name; `@stream({ scope })` picks Regional (L2) or Continental (L3). |\n| `@connect` | method | Lifecycle hook: fires when a client connects (returns a `StreamOutbound` accept/reject). |\n| `@message` | method | Lifecycle hook: fires on each inbound packet. |\n| `@close` | method | Lifecycle hook: fires on a graceful close. |\n| `@disconnect` | method | Lifecycle hook: fires on an abrupt transport loss. |\n\nA server-side broadcast hook, `@channel`, is planned but not live in the current runtime. See [Channels](../realtime/channels.md) for the status and the working client-side `useChannel` hook you can use today.\n\n## Background and daemon (L4)\n\nRecurring, run-once-globally background work. `@daemon` runs on the single L4 leader. Covered in [Daemons](../background/daemons.md).\n\n| Decorator | Applies to | What it does |\n| --- | --- | --- |\n| `@daemon` | class | Marks the single daemon class (at most one per project). May declare a zero-arg `onStart()` run once at boot. |\n| `@scheduled` | method | Runs the method on a cadence: an interval (`'30s'`, `'5m'`, `'1h'`, `'1d'`) or a 5-field cron string (`'15 9 * * 1-5'`). |\n\n## Database structure (shared)\n\nDeclare your database schema. These carry no tier of their own; they are compiled into every artifact so any tier can use them. Covered in the [database section](../database/README.md).\n\n| Decorator | Applies to | What it does |\n| --- | --- | --- |\n| `@database` | class | Marks a class as a ToilDB database; each `@collection` field becomes a typed handle (`App.users.get(...)`). |\n| `@collection` | field | Declares a field as a collection handle (`Documents` / `View` / `Unique` / `Counter` / `Events` / `Membership` / `Capacity`). |\n| `@data` | class | Marks a class serializable: the compiler generates a binary codec so it can cross the wire and the database. See [Data types](../backend/data.md). |\n| `@migrate` | function | Schema migration for a `@data` type: a free function that upgrades a record written under an old layout to the current shape (lives in `migrations/<Type>.migration.ts`). See [Data types](../backend/data.md). |\n\n## Database function kinds (data-access policy)\n\nA **function kind** labels a function or method with *which* database operations it is allowed to issue, and the compiler enforces it (a read-only `@query` that tries to write is a compile error). Covered across the [database section](../database/README.md).\n\n| Decorator | Applies to | What it does | Covered in |\n| --- | --- | --- | --- |\n| `@query` | function / method | Read-only data access. | [Database](../database/README.md) |\n| `@action` | function / method | Read plus bounded writes and claims. | [Database](../database/README.md) |\n| `@derive` | function / method | Publishes materialized views and rollups (a background materializer). | [@derive](../background/derive.md) |\n| `@job` | function / method | Background work. | [Background work](../background/README.md) |\n| `@admin` | function / method | Control-plane only operations. | [Database](../database/README.md) |\n\n## Auth and structure\n\n| Decorator | Applies to | What it does | Covered in |\n| --- | --- | --- | --- |\n| `@user` | class | Declares the authenticated-user shape; enables the typed `AuthService.getUser()`. At most one per project. | [Auth usage](../auth/usage.md) |\n| `@main` | function | Marks a single top-level function as the module entry point (exported as the WASM `main`). Rarely written by hand: toiljs supplies the entry glue. | [Project structure](../getting-started/project-structure.md) |\n\n## A note on low-level decorators\n\nAssemblyScript (the language toilscript is built on) has its own low-level decorators such as `@inline`, `@unsafe`, and `@operator`. They tune how a symbol compiles and are for advanced library authors, not everyday app code. You will not need them to build a normal toiljs app, and they are out of scope here.\n\n## Related\n\n- [Compute tiers](./tiers.md): where each decorator's code runs (L1 to L4).\n- [The type system](./types.md): the types (`u64`, `string`, `@data`) these decorators work with.\n- [REST](../backend/rest.md) and [RPC](../backend/rpc.md): the L1 surfaces.\n- [Realtime streams](../realtime/streams.md): the `@stream` surface.\n- [Daemons](../background/daemons.md) and [@derive](../background/derive.md): L4 and background work.\n- [The database (ToilDB)](../database/README.md): `@database`, `@collection`, and the function kinds.\n- [Auth](../auth/README.md): `@auth` and `@user`.\n",
|
|
@@ -50,7 +50,7 @@ export const TOIL_DOCS: Record<string, string> = {
|
|
|
50
50
|
"getting-started/create-project.md": "# Create a project\n\nScaffold a brand-new toiljs app with one command. The CLI asks a few questions, writes the files, and (optionally) installs dependencies for you.\n\n## Why and when\n\n`toiljs create` is how every project starts. It wires up the enforced toiljs presets (TypeScript, ESLint, and Prettier config), the file-based routing, and a working client and server, so you get a project that builds and runs on the first try. Use it for a new app. To bring an **existing** React app into toiljs instead, see [Migrating](./migrating.md).\n\n## The command\n\n```sh\ntoiljs create my-app\n```\n\nReplace `my-app` with your project name (it becomes the folder name). If you leave the name off, the wizard asks for one.\n\nBy default this runs an interactive wizard. To skip every question and accept the defaults (handy for scripts and CI), add `--yes`:\n\n```sh\ntoiljs create my-app --yes\n```\n\n## What the wizard asks\n\nRunning `toiljs create` walks you through these prompts. Each one has a flag you can pass instead, so you can answer some or all of them up front.\n\n| Prompt | What it decides | Flag | Default |\n| --- | --- | --- | --- |\n| Project name | The folder and package name | (the first argument) | `my-toil-app` |\n| Which template? | How much starter code you get | `-t, --template <app\\|minimal>` | `app` |\n| Styling | The CSS flavor for `client/` | `--style <css\\|sass\\|less\\|stylus>` | `css` |\n| Add Tailwind CSS? | Adds Tailwind on top of the styling | `--tailwind` / `--no-tailwind` | off |\n| AI assistant files | Editor hint files for Claude, Cursor, Codex, Copilot | `--no-ai` (to skip) | all |\n| Optimize images at build time? | Resize and compress imported images | `--images` / `--no-images` | on |\n| Initialize a git repository? | Runs `git init` and stages the files | `--git` / `--no-git` | on |\n| Install dependencies now? | Runs your package manager's install | `--install` / `--no-install` | on |\n\nTwo more flags do not have a prompt:\n\n- `--pm <npm\\|pnpm\\|yarn\\|bun>` picks the package manager to install with (default `npm`).\n- `-y, --yes` accepts every default and runs without any prompts.\n\n### The two templates\n\n- **`app`** (default) is the full starter: a landing page, a shared layout, styles, and a set of demo routes that show off HTTP routes, typed RPC, cookies, auth, and a ToilDB-backed guestbook. Great for learning by reading real, working code.\n- **`minimal`** is the bare minimum: a layout, a single home page, and a tiny server handler with one example endpoint. Great when you want a clean slate.\n\nA fully non-interactive example:\n\n```sh\ntoiljs create my-app --yes --template minimal --style css --no-tailwind --pm pnpm\n```\n\n## What gets scaffolded\n\nHere is the shape of a new **`app`** project. The exact set of demo routes may grow over time, so this is trimmed for readability.\n\n```text\nmy-app/\n package.json scripts + dependencies (toiljs, react, toilscript, ...)\n toil.config.ts client/build config (SEO, images, page transitions)\n toilconfig.json server (wasm) build config for toilscript\n tsconfig.json TypeScript config for the client\n eslint.config.js linting preset\n .prettierrc formatting preset\n .gitignore ignores build output, generated files, and .env files\n toil-env.d.ts generated editor types for client globals (Toil.*)\n toil-routes.d.ts generated typed-route names (filled in on first build)\n README.md\n CLAUDE.md / AGENTS.md AI assistant hint files (if you kept them)\n\n client/ your React app (runs in the browser)\n toil.tsx the client entry: mounts routes + layout\n layout.tsx the root layout that wraps every page\n 404.tsx the not-found page\n global-error.tsx the top-level error page\n routes/ file-based pages (index.tsx = \"/\", about.tsx = \"/about\", ...)\n components/ shared React components\n styles/main.css global styles\n public/ static files served as-is (favicon, robots.txt, images)\n\n server/ your backend (compiled to wasm, runs on the edge)\n main.ts the entry: wires the handler + imports your surface modules\n tsconfig.json server-only TS config (loads the toilscript editor plugin)\n toil-server-env.d.ts generated editor types for server globals (Cookie, crypto, ...)\n core/ your top-level request handler and shared logic\n models/ @data classes (the typed wire types)\n routes/ @rest controllers (HTTP endpoints)\n services/ @service classes and @remote functions (typed RPC)\n migrations/ ToilDB schema migrations (README explains the convention)\n scheduled/ reserved for scheduled tasks\n\n shared/ (created by the build)\n server.ts GENERATED typed client: the Server proxy + @data codecs\n```\n\nThe **`minimal`** template is the same layout with far fewer files: `client/` has just `layout.tsx`, `routes/index.tsx`, and `styles/main.css`; `server/` has `main.ts` and `core/AppHandler.ts` with a single example endpoint.\n\nA few files are worth calling out now, and the next page ([Project structure](./project-structure.md)) walks through all of them:\n\n- **`shared/server.ts` does not exist yet** in a fresh project. It is generated the first time you run `toiljs dev` or `toiljs build`. That is normal and expected.\n- **`toil-routes.d.ts`** starts as a stub and gets filled in with your real route names on the first build, which is what makes `Toil.Link` route names type-check.\n- **`.env` and `.env.secrets` are not created** for you. You add them yourself when you need local environment variables or secrets. They are already listed in `.gitignore` so you never commit them. See [Environment and secrets](../services/environment.md).\n\n## Run it\n\nOnce scaffolding (and install) finishes, the CLI prints your next steps:\n\n```sh\ncd my-app\nnpm run dev\n```\n\n`npm run dev` runs `toiljs dev`, which builds your server to wasm, generates `shared/server.ts`, and starts the dev server with hot reload. Open the printed URL (by default `http://localhost:3000`) and you have a live app.\n\nIf you told the wizard **not** to install dependencies, run `npm install` first.\n\n## Gotchas and notes\n\n- **Scaffolding into a non-empty folder** asks for confirmation in interactive mode, and fails in `--yes` mode. Create into a fresh, empty directory.\n- **The project name must be a valid package name** and must stay inside the current directory (no `..`, no absolute paths).\n- **Git init is best-effort.** If `git` is not installed, the CLI skips that step and keeps going.\n- **You do not run `toilscript` yourself.** It is added as a dependency and driven by `toiljs dev` / `toiljs build`.\n\n## Related\n\n- [Project structure](./project-structure.md)\n- [Your first app](./first-app.md)\n- [The CLI reference](../cli/README.md)\n- [Configuration](../concepts/config.md)\n- [Styling](../frontend/styling.md)\n",
|
|
51
51
|
"getting-started/deploy.md": "# Deploying a toiljs app\n\nThis page is an honest look at how you get a toiljs app in front of real users. There are two paths, and it helps to be clear about which one exists today.\n\n- **Self-host it yourself.** You build the app and run it on a machine you control, with `toiljs build` then `toiljs start`. This works right now, on your laptop, a VPS, or any server that can run Node.js.\n- **Run it on the managed Dacely edge.** This is the platform toiljs is built for: a worldwide fleet of servers that runs your compiled backend close to every user. It is the target the whole framework is designed around.\n\nOne thing up front, so you do not go looking for it: **there is no `toiljs deploy` command.** The CLI can build and self-host; pushing a build onto the managed edge is a platform step, not a CLI subcommand. The rest of this page covers what you can do today (self-hosting) and what the managed edge gives you.\n\n## The one build powers both\n\nBoth paths serve the exact same artifacts. `toiljs build` produces them once:\n\n```mermaid\nflowchart LR\n A[\"Your project\"] -->|toiljs build| B[\"build/client/<br/>(HTML, JS, CSS, assets)\"]\n A -->|toiljs build| C[\"build/server/release.wasm<br/>(your backend)\"]\n B --> H[\"toiljs start<br/>(self-host)\"]\n C --> H\n B --> E[\"managed Dacely edge<br/>(worldwide)\"]\n C --> E\n```\n\nSo you never build differently for the two targets. You build once, then either run it yourself or hand the same output to the edge.\n\n## Self-hosting with `toiljs start`\n\n`toiljs start` runs your built app on a fast production HTTP server. It serves your static client, dispatches dynamic requests into your `release.wasm`, does server-side rendering, runs daemons, and exposes a `/_toil` websocket channel. Use it to put your app on your own machine or server instead of the managed edge.\n\n### Step 1: build\n\n`start` serves whatever is in `build/`, so you must build first. If there is no build, `start` exits with an error (it looks for `build/client/index.html`).\n\n```bash\nnpm run build\n```\n\n### Step 2: start\n\n```bash\n# Serve on http://127.0.0.1:3000 (loopback only, one worker per CPU).\nnpx toiljs start\n\n# Accept outside connections, on port 8080, with 4 worker processes.\nnpx toiljs start --host 0.0.0.0 --port 8080 --threads 4\n```\n\n### start flags\n\n| Flag | Meaning |\n| --- | --- |\n| `--port <n>` | Port to listen on. Default `3000` (or `client.port` from your config). |\n| `--host <host>` | Address to bind. Default `127.0.0.1`, which is **loopback only** (reachable just from the same machine). Pass `0.0.0.0` to accept connections from other machines. |\n| `--threads <n>` | Number of HTTP worker processes. Default is automatic (one per available CPU). Pass `1` to disable the worker pool. `--workers` is an accepted alias, and you can also set `server.threads` in your config or the `TOILJS_THREADS` environment variable. |\n| `--root <dir>` | Run against a project in another directory instead of the current one. |\n\nThese flags are verified against the CLI. Note that `--host` and `--threads` are `start`-only: `toiljs dev` always binds locally and does not take them. For the full command reference, see [the CLI](../cli/README.md#toiljs-start).\n\n### A realistic self-host\n\nOn a small server (say a VPS), a minimal run looks like this:\n\n```bash\n# Install and build.\nnpm ci\nnpm run build\n\n# Bind on all interfaces so a reverse proxy or the public can reach it.\nnpx toiljs start --host 0.0.0.0 --port 8080 --threads 4\n```\n\nThen put a reverse proxy (nginx, Caddy) in front for TLS and a real hostname, pointing it at `http://127.0.0.1:8080`. Keep the process alive with your usual tool (systemd, pm2, a container restart policy).\n\n### Self-host gotchas\n\n- **`start` needs a fresh build.** It serves `build/`, not your source. After any code change, run `toiljs build` again before restarting.\n- **The default bind is loopback.** With no `--host`, only the same machine can reach it. Set `--host 0.0.0.0` (usually behind a reverse proxy) to serve real traffic.\n- **Self-host is one place, not the whole world.** You get one server (or however many you run yourself). The latency and worldwide reach of the managed edge is exactly the thing self-hosting does not give you.\n- **The database is different.** Self-hosting runs the built server, but ToilDB's worldwide data layer is an edge feature. Treat self-host as a way to run and test your build, not as a substitute for the managed edge's global database.\n\n## The managed Dacely edge\n\nThe managed **Dacely edge** is the platform toiljs targets: a fleet of servers in many cities that runs your compiled backend as close to each user as possible, backed by the worldwide **ToilDB** database. You write one project, and the build already decides which part of your code belongs to which layer of the edge.\n\nYour backend runs across four **compute tiers**, from a per-request handler at the very edge up to a single worldwide coordinator:\n\n| Tier | Name | Where it runs | Typical code |\n| --- | --- | --- | --- |\n| **L1** | Hot / edge | The node nearest the user, fresh per request | `@rest`, `@service` / `@remote` |\n| **L2** | Regional | One box per connection, per region | `@stream` (Regional) |\n| **L3** | Continental | One box per connection, per continent | `@stream` (Continental) |\n| **L4** | Global / daemon | Exactly one leader worldwide | `@daemon`, `@scheduled` |\n\nBecause the same `toiljs build` output feeds the edge, everything you built and tested with `toiljs dev` and `toiljs start` is what runs there. The edge decides each server's role for you; you do not configure tiers by hand. For the full picture of what each tier means and how to write for it, see [Compute tiers (L1 to L4)](../concepts/tiers.md).\n\n## Related\n\n- [The CLI](../cli/README.md): the full reference for `build` and `start` and every flag.\n- [Configuration (`toil.config.ts`)](../concepts/config.md): `server.threads` and the other self-host knobs.\n- [Compute tiers (L1 to L4)](../concepts/tiers.md): how your code maps onto the managed edge.\n- [Getting started](./README.md): the path from install to a running feature.\n",
|
|
52
52
|
"getting-started/first-app.md": "# Your first app\n\nBuild a tiny feature end to end: a page with a \"Like\" button that reads and writes a real number in ToilDB, so the count survives page reloads and restarts. Along the way you meet the typed client, an HTTP route, and the database, the three pieces you use in almost every toiljs app.\n\n## What you will build\n\nA `/likes` page that shows a running like count and a button to add one. The count lives in ToilDB, so it is shared by everyone and it persists.\n\n```mermaid\nsequenceDiagram\n participant B as Browser (/likes page)\n participant SH as shared/server.ts (typed client)\n participant W as server.wasm (@rest route)\n participant DB as ToilDB (counter)\n B->>SH: Server.REST.likes.add()\n SH->>W: POST /likes\n W->>DB: counter.add(key, 1)\n W-->>SH: { count: 43 }\n SH-->>B: typed LikeCount\n B->>B: show \"43 likes\"\n```\n\n## Before you start\n\nYou need a project. If you do not have one yet, create one and start the dev server:\n\n```sh\ntoiljs create my-app\ncd my-app\nnpm run dev\n```\n\nLeave `npm run dev` running in a terminal. It rebuilds your server, regenerates the typed client, and hot-reloads the browser every time you save. The app is at `http://localhost:3000`.\n\n## Why the database (and not a variable)\n\nYour first instinct might be to keep the count in a normal variable on the server. That will not work, and it is worth understanding why now.\n\n**The server runs a fresh copy of your `.wasm` for every request, and wipes its memory when the request ends.** So a variable you set while handling one request is gone by the next request. Anything that must outlive a single request, a counter, a user, a post, has to go into a store. toiljs ships one: **ToilDB**, a database built into the edge.\n\nToilDB offers a few specialized shapes called **families**. For a running total, the right one is a **counter**: a value you can atomically add to and read back. (Others include documents, events, and views. See [Database overview](../database/README.md).)\n\n## Step 1: add a data type for the response\n\nYour server and client talk in typed messages called `@data` classes. A `@data` class can cross the wire and be parsed into a real typed object on the other side. Create one to hold the count.\n\nCreate `server/models/LikeCount.ts`:\n\n```ts\n// The response our route returns: just the current like count.\n@data\nexport class LikeCount {\n count: i64 = 0;\n constructor(count: i64 = 0) {\n this.count = count;\n }\n}\n```\n\nTwo things to notice:\n\n- `@data` is a decorator that marks this class as a wire type. You use it with no import (it is a compiler built-in). See [Data types](../backend/data.md).\n- The field type is `i64`, a 64-bit integer. The server uses precise integer types like `i64` and `u64`. On the client side these arrive as JavaScript `bigint`. More on this in [Types](../concepts/types.md).\n\n## Step 2: add the HTTP route\n\nNow the backend endpoint. Create `server/routes/Likes.ts`:\n\n```ts\nimport { RouteContext } from 'toiljs/server/runtime';\n\nimport { LikeCount } from '../models/LikeCount';\n\n// The KEY that names one counter. A counter is a map from a key to a number,\n// so a single fixed key (\"home\") gives us one global tally.\n@data\nclass LikeKey {\n page: string = 'home';\n constructor(page: string = 'home') {\n this.page = page;\n }\n}\n\n// A @database declares your ToilDB collections. Each @collection is one named\n// store. Here we declare a single counter keyed by LikeKey.\n@database\nclass LikesDb {\n @collection static likes: Counter<LikeKey>;\n}\n\n// A @rest controller exposes HTTP endpoints. 'likes' is the URL prefix, so the\n// routes below live under /likes.\n@rest('likes')\nclass Likes {\n // GET /likes -> read the current count.\n // A @get handler is a \"query\": it may read but not write. Reading one\n // counter by its key is a point read, which queries are allowed to do.\n @get('/')\n public show(): LikeCount {\n const key = new LikeKey('home');\n return new LikeCount(LikesDb.likes.get(key));\n }\n\n // POST /likes -> add one, then return the new count.\n // A @post handler is an \"action\": it may write. `add` bumps the counter.\n // (A body-less POST takes the RouteContext; we do not need it here.)\n @post('/')\n public add(_ctx: RouteContext): LikeCount {\n const key = new LikeKey('home');\n LikesDb.likes.add(key, 1);\n return new LikeCount(LikesDb.likes.get(key));\n }\n}\n```\n\nWhat each decorator does:\n\n- **`@rest('likes')`** turns the class into an HTTP controller mounted at `/likes`.\n- **`@get('/')`** and **`@post('/')`** map a method to a verb and path. A `@get` is a read-only **query**; a `@post` is a write-capable **action**. This split is how toiljs keeps expensive or unsafe operations out of read paths.\n- **`@database`** and **`@collection`** declare your ToilDB stores. `Counter<LikeKey>` is a counter you look up by a `LikeKey`.\n\nThe counter gives you two operations: `add(key, delta)` to change it and `get(key)` to read it. See [Counters](../database/counters.md) and [HTTP routes](../backend/rest.md) for the full API.\n\n## Step 3: register the route\n\nOpen `server/main.ts` and add an import for your new route, next to the others:\n\n```ts\nimport './routes/Likes';\n```\n\nThe build actually discovers decorated files under `server/` on its own, but importing them from `main.ts` is the convention: it keeps a direct `toilscript` build finding the same code. (Every `app` project already imports its demo routes this way.)\n\nSave. Watch the terminal: `toiljs dev` recompiles the server to wasm and regenerates `shared/server.ts`. A moment later your new endpoint exists, fully typed.\n\n## Step 4: add the client page\n\nNow the frontend. Create `client/routes/likes.tsx`. The file name is the URL, so this page is at `/likes`.\n\n```tsx\nimport { useEffect, useState } from 'react';\n\nexport default function LikesPage() {\n // The count is a bigint because the server field is i64.\n const [count, setCount] = useState(0n);\n\n // Read the current count once, when the page first loads.\n useEffect(() => {\n Server.REST.likes.show().then((res) => setCount(res.count));\n }, []);\n\n // Add a like, then update the UI with the fresh count the server returns.\n const like = async () => {\n const res = await Server.REST.likes.add();\n setCount(res.count);\n };\n\n return (\n <main>\n <h1>{String(count)} likes</h1>\n <button onClick={like}>Like</button>\n </main>\n );\n}\n```\n\nThe magic here is `Server.REST.likes`. You never wrote it. toiljs generated it into `shared/server.ts` from your `@rest` controller, so:\n\n- `Server.REST.likes.show()` returns a `Promise<LikeCount>`, and `res.count` is typed for you.\n- `Server.REST.likes.add()` sends the POST and returns the updated `LikeCount`.\n- `Server` is a global in client code, so you do not import it. Its types come from the generated file.\n\nIf you rename the route or change its return type on the server, this client code stops type-checking until you fix it. That is the whole point: the browser and the backend cannot silently disagree.\n\n## Step 5: try it\n\nOpen `http://localhost:3000/likes`. You should see \"0 likes\" and a button. Click **Like** a few times and the number climbs.\n\nNow the important test: **reload the page.** The count is still there. Stop the dev server, start it again, reload: still there. That is ToilDB persisting your counter, exactly as it would on the real edge. The same code that ran against the local dev database runs against the worldwide database in production, with no connection string to configure.\n\n## What just happened\n\n```mermaid\nflowchart LR\n M[\"server/models/LikeCount.ts<br/>@data\"] --> GEN\n R[\"server/routes/Likes.ts<br/>@rest + @database\"] --> GEN\n GEN[\"toiljs dev<br/>compiles + generates\"] --> WASM[\"release.wasm\"]\n GEN --> SS[\"shared/server.ts<br/>(typed Server.REST.likes)\"]\n SS --> P[\"client/routes/likes.tsx<br/>calls Server.REST.likes\"]\n```\n\nYou wrote three small files. toiljs compiled the server to WebAssembly, generated a typed client from your route, and your React page called it with full type safety. The like count lives in ToilDB, so it persists.\n\n## Gotchas and notes\n\n- **`res.count` is a `bigint`, not a `number`.** That is why the page uses `useState(0n)` and `String(count)`. Server integer types map to `bigint` on the client. See [Types](../concepts/types.md).\n- **A `@get` cannot write, and a `@post` can.** If you try to call `.add(...)` from a `@get`, the compiler rejects it. Reads that scan many rows are also blocked in handlers; a single-key `get` is fine.\n- **You do not edit `shared/server.ts`.** It is regenerated on every server build. Change the route, and the client updates itself.\n- **If the editor does not know `Server.REST.likes` yet**, it is because the server has not rebuilt since you added the route. Save a server file (or restart `toiljs dev`) to regenerate `shared/server.ts`.\n- **The counter is global.** Everyone hitting `/likes` shares the same \"home\" tally, because we used one fixed key. Use a different key per page or per user to split the count.\n\n## Where to go next\n\n- Return richer objects and lists: [Data types](../backend/data.md) and [HTTP routes](../backend/rest.md).\n- Call the server without URLs, as plain function calls: [Typed RPC](../backend/rpc.md).\n- Store more than a number: [Documents](../database/documents.md), [Events](../database/events.md), and [Views](../database/views.md).\n- Add login and sessions: [Auth](../auth/README.md).\n- Style your pages: [Styling](../frontend/styling.md) and [Routing](../frontend/routing.md).\n\n## Related\n\n- [Project structure](./project-structure.md)\n- [Database overview](../database/README.md)\n- [Counters](../database/counters.md)\n- [Backend overview](../backend/README.md)\n",
|
|
53
|
-
"getting-started/installation.md": "# Installation\n\nInstall the toiljs command-line tool (CLI) so you can create and run projects. This takes a couple of minutes.\n\n## Why and when\n\nYou need the toiljs CLI once, before you create your first project. The CLI is the single tool you use to scaffold, run, build, and check toiljs apps. After a project exists, the same CLI is also installed inside that project, so day-to-day you can run it through your package scripts (`npm run dev`) without a global install.\n\n## Prerequisites\n\nYou need **Node.js version 24.0.0 or newer**. Node.js is the JavaScript runtime that powers the toiljs CLI, the dev server, and the build. (Your backend does not run on Node.js, but the tools that build it do.)\n\nCheck your version:\n\n```sh\nnode --version\n```\n\nIf it prints `v24.0.0` or higher, you are set. If it is older or the command is not found, install a current Node.js from [nodejs.org](https://nodejs.org), or use a version manager like [nvm](https://github.com/nvm-sh/nvm) or [fnm](https://github.com/Schniz/fnm):\n\n```sh\n# with nvm\nnvm install 24\nnvm use 24\n```\n\nYou also need a package manager. **npm** comes with Node.js, so you already have it. toiljs also supports **pnpm**, **yarn**, and **bun** if you prefer one of those.\n\n## Install the CLI\n\nThe package is called `toiljs`, and it provides a command named `toiljs`. You have two ways to use it.\n\n### Option A: run it on demand with npx (no install)\n\n`npx` comes with npm and runs a package without installing it globally. This is the quickest way to create your first project:\n\n```sh\nnpx toiljs create my-app\n```\n\n### Option B: install it globally\n\nIf you plan to create projects often, install it once so `toiljs` is always on your path:\n\n```sh\nnpm install -g toiljs\n```\n\nThen you can run `toiljs` directly:\n\n```sh\ntoiljs create my-app\n```\n\nBoth options end up at the same place. The rest of these docs write `toiljs <command>`; if you did not install globally, just put `npx` in front (`npx toiljs <command>`).\n\n## Verify it works\n\nCheck the version:\n\n```sh\ntoiljs --version\n```\n\nYou should see a version number printed (for example, `0.0.86`).\n\nTo see every command and flag, run help:\n\n```sh\ntoiljs --help\n```\n\nInside a project, there is a deeper health check called **doctor**. It inspects your setup and dependencies and tells you exactly what to fix. You will use it after you create a project, but it is good to know it exists:\n\n```sh\ntoiljs doctor\n```\n\n`toiljs doctor` reads your project (its `package.json`, config, routes, and build output), runs a series of checks, and prints a grouped report. One of the first checks is your Node.js version against the required `>=24.0.0`, so if your Node is too old, doctor will say so. Add `--fix` to let it repair the things it can (such as the typed-RPC wiring), or `--json` for machine-readable output in a CI pipeline.\n\n## How the tooling fits together\n\nThe `toiljs` CLI is a friendly front end over two underlying tools. You rarely call them directly, but it helps to know they exist.\n\n```mermaid\nflowchart LR\n CLI[\"toiljs CLI\"] --> TS[\"toilscript<br/>(compiles server/ to wasm)\"]\n CLI --> VITE[\"Vite<br/>(bundles client/ React)\"]\n TS --> W[\"build/server/release.wasm\"]\n VITE --> B[\"build/client/\"]\n```\n\nWhen you create a project, both `toiljs` and `toilscript` are added to it as dependencies, so everything is pinned to versions that work together. You do not install `toilscript` separately.\n\n## Gotchas and notes\n\n- **\"command not found: toiljs\"** after a global install usually means npm's global bin folder is not on your `PATH`. Either fix your `PATH` or just use `npx toiljs ...` instead.\n- **Node too old** is the most common first-time failure. WebAssembly tooling and the build depend on features in Node 24 and up. Upgrade before creating a project.\n- **Every command checks for updates.** On each run, the CLI quietly checks npm for a newer toiljs and prints a note if you are behind. It never blocks the command. To turn it off, set the environment variable `TOILJS_NO_UPDATE_CHECK=1`.\n- You do **not** need to install a database, a Docker container, or any cloud account to develop locally. The dev server includes a local ToilDB so your data-backed features run out of the box.\n\n## Related\n\n- [Create a project](./create-project.md)\n- [The CLI reference](../cli/README.md)\n- [Getting started overview](./README.md)\n",
|
|
53
|
+
"getting-started/installation.md": "# Installation\n\nInstall the toiljs command-line tool (CLI) so you can create and run projects. This takes a couple of minutes.\n\n## Why and when\n\nYou need the toiljs CLI once, before you create your first project. The CLI is the single tool you use to scaffold, run, build, and check toiljs apps. After a project exists, the same CLI is also installed inside that project, so day-to-day you can run it through your package scripts (`npm run dev`) without a global install.\n\n## Prerequisites\n\nYou need **Node.js version 24.0.0 or newer**. Node.js is the JavaScript runtime that powers the toiljs CLI, the dev server, and the build. (Your backend does not run on Node.js, but the tools that build it do.)\n\nCheck your version:\n\n```sh\nnode --version\n```\n\nIf it prints `v24.0.0` or higher, you are set. If it is older or the command is not found, install a current Node.js from [nodejs.org](https://nodejs.org), or use a version manager like [nvm](https://github.com/nvm-sh/nvm) or [fnm](https://github.com/Schniz/fnm):\n\n```sh\n# with nvm\nnvm install 24\nnvm use 24\n```\n\nYou also need a package manager. **npm** comes with Node.js, so you already have it. toiljs also supports **pnpm**, **yarn**, and **bun** if you prefer one of those.\n\n### TypeScript 6, not 7\n\ntoiljs requires **TypeScript 6** (`>=6.0.0 <7.0.0`). TypeScript 7 is not supported yet.\n\nTypeScript 7 is the native (Go) port of the compiler. It ships a much faster `tsc`, but its package no longer exports the JavaScript compiler API, whose main entry is now just `{ version, versionMajorMinor }`. toiljs reads each route's static `metadata` export through that API to bake your SEO tags into the built HTML, and the `toiljs/eslint` preset loads it too. On TypeScript 7 the metadata baking silently stops (your built pages lose their tags) and typescript-eslint fails to load at all.\n\nPin TypeScript in your `package.json`:\n\n```json\n{\n \"devDependencies\": {\n \"typescript\": \"^6.0.3\"\n }\n}\n```\n\n`toiljs doctor` flags an unsupported TypeScript, and `toiljs doctor --fix` pins it back for you. `toiljs update` will not upgrade you into TypeScript 7. Support will land once the tools toiljs builds on can read the new `typescript/unstable/*` API.\n\n## Install the CLI\n\nThe package is called `toiljs`, and it provides a command named `toiljs`. You have two ways to use it.\n\n### Option A: run it on demand with npx (no install)\n\n`npx` comes with npm and runs a package without installing it globally. This is the quickest way to create your first project:\n\n```sh\nnpx toiljs create my-app\n```\n\n### Option B: install it globally\n\nIf you plan to create projects often, install it once so `toiljs` is always on your path:\n\n```sh\nnpm install -g toiljs\n```\n\nThen you can run `toiljs` directly:\n\n```sh\ntoiljs create my-app\n```\n\nBoth options end up at the same place. The rest of these docs write `toiljs <command>`; if you did not install globally, just put `npx` in front (`npx toiljs <command>`).\n\n## Verify it works\n\nCheck the version:\n\n```sh\ntoiljs --version\n```\n\nYou should see a version number printed (for example, `0.0.86`).\n\nTo see every command and flag, run help:\n\n```sh\ntoiljs --help\n```\n\nInside a project, there is a deeper health check called **doctor**. It inspects your setup and dependencies and tells you exactly what to fix. You will use it after you create a project, but it is good to know it exists:\n\n```sh\ntoiljs doctor\n```\n\n`toiljs doctor` reads your project (its `package.json`, config, routes, and build output), runs a series of checks, and prints a grouped report. One of the first checks is your Node.js version against the required `>=24.0.0`, so if your Node is too old, doctor will say so. Add `--fix` to let it repair the things it can (such as the typed-RPC wiring), or `--json` for machine-readable output in a CI pipeline.\n\n## How the tooling fits together\n\nThe `toiljs` CLI is a friendly front end over two underlying tools. You rarely call them directly, but it helps to know they exist.\n\n```mermaid\nflowchart LR\n CLI[\"toiljs CLI\"] --> TS[\"toilscript<br/>(compiles server/ to wasm)\"]\n CLI --> VITE[\"Vite<br/>(bundles client/ React)\"]\n TS --> W[\"build/server/release.wasm\"]\n VITE --> B[\"build/client/\"]\n```\n\nWhen you create a project, both `toiljs` and `toilscript` are added to it as dependencies, so everything is pinned to versions that work together. You do not install `toilscript` separately.\n\n## Gotchas and notes\n\n- **\"command not found: toiljs\"** after a global install usually means npm's global bin folder is not on your `PATH`. Either fix your `PATH` or just use `npx toiljs ...` instead.\n- **Node too old** is the most common first-time failure. WebAssembly tooling and the build depend on features in Node 24 and up. Upgrade before creating a project.\n- **Every command checks for updates.** On each run, the CLI quietly checks npm for a newer toiljs and prints a note if you are behind. It never blocks the command. To turn it off, set the environment variable `TOILJS_NO_UPDATE_CHECK=1`.\n- You do **not** need to install a database, a Docker container, or any cloud account to develop locally. The dev server includes a local ToilDB so your data-backed features run out of the box.\n\n## Related\n\n- [Create a project](./create-project.md)\n- [The CLI reference](../cli/README.md)\n- [Getting started overview](./README.md)\n",
|
|
54
54
|
"getting-started/migrating.md": "# Migrating an existing React app\n\nHow to bring a React app you already have into toiljs. The frontend usually moves over with small changes. The backend is the real work, because a toiljs server is not Node.js, and this page is honest about what that means.\n\n## Why and when\n\nMove to toiljs when you want your frontend and backend in one typed repo, deployed to the edge, with a built-in global database, and you are willing to rewrite your server logic against toiljs's rules. If you only want a React bundler, toiljs is more than you need. If you want the full-stack, typed, edge model, this is the payoff.\n\nThe safest approach is **not** to convert your old project in place. Instead, scaffold a fresh toiljs project and move code into it piece by piece. A fresh project comes with the required presets, config, and routing already wired, so you spend your time on your code, not on plumbing.\n\n```sh\ntoiljs create my-app\ncd my-app\n```\n\n## The big picture: what moves where\n\n```mermaid\nflowchart LR\n subgraph OLD[\"Your current app\"]\n FE[\"React components,<br/>pages, styles, assets\"]\n BE[\"Node/Express backend,<br/>DB access, npm libs\"]\n end\n FE -->|\"mostly copy\"| C[\"toiljs client/\"]\n BE -->|\"rewrite\"| S[\"toiljs server/\"]\n C -.->|\"generated typed client\"| S\n S --> DB[(\"ToilDB\")]\n```\n\n- Your **React frontend** copies into `client/` with modest changes (routing and asset paths).\n- Your **backend** is **rewritten** into `server/` as toilscript, because it compiles to WebAssembly, not Node.\n\n## The frontend: what changes\n\n`client/` is a normal Vite + React app, so most of your frontend works as is. The common adjustments:\n\n- **Routing becomes file-based.** toiljs has no `<Routes>`/`<Route>` config. A file at `client/routes/about.tsx` is the `/about` page; `client/routes/blog/[slug].tsx` is `/blog/:slug`. Move each page component to the matching file and delete your router setup. Use `Toil.Link` for navigation instead of your router's `Link`. See [Routing](../frontend/routing.md).\n- **Static assets move to `client/public/`.** Files there are served as is (images at `/images/...`, plus `favicon` and `robots.txt`).\n- **Global styles move to `client/styles/`**, and are imported from `client/toil.tsx`. See [Styling](../frontend/styling.md).\n- **Your React npm packages are fine.** The client is regular JavaScript, so component libraries, state managers, and browser APIs all work.\n- **Data fetching changes** if you want the typed client: replace hand-written `fetch('/api/...')` calls with the generated `Server.REST.*` methods. You can keep raw `fetch` too, but you lose the type safety. See [Fetching data](../frontend/data-fetching.md).\n\nMetadata and SEO that you used a helmet library for is built in: set a `metadata` export per route, or use `Toil.Head`. See [Metadata and SEO](../frontend/metadata.md).\n\n## The backend: the honest part\n\nThis is where migration takes real effort. Read this section carefully before you plan the work.\n\nYour toiljs server is compiled by **toilscript** into WebAssembly. toilscript accepts a **strict subset of TypeScript**. It looks like TypeScript, but treat it as a small, separate language that happens to share the syntax. The practical consequences:\n\n### What is NOT available on the server\n\n- **No arbitrary npm packages.** You cannot `import` a library from `node_modules` into `server/`. There is no `express`, no `pg`, no `stripe` SDK, no `lodash`. If your route logic leans on npm libraries, that logic has to be reworked.\n- **No Node.js APIs.** No `fs`, `http`, `process`, `Buffer`, `path`, or other Node built-ins. The server runs in a sandbox, not in Node.\n- **No DOM or browser APIs**, because it is not a browser either.\n- **No connecting to your existing database.** There is no connection string and no driver. Your data moves to **ToilDB**, the built-in database.\n\n### What you use instead\n\ntoiljs replaces the common needs with built-in globals and decorators, so you rarely miss the missing libraries:\n\n| You used to reach for | On the toiljs server you use |\n| --- | --- |\n| `express` routes / a router | `@rest` controllers with `@get` / `@post` ([HTTP routes](../backend/rest.md)) |\n| A REST client between services | `@service` / `@remote` typed RPC ([RPC](../backend/rpc.md)) |\n| Postgres / Mongo / Redis | [ToilDB](../database/README.md) families (documents, counters, events, views) |\n| `jsonwebtoken`, session middleware | Built-in [auth](../auth/README.md) and cookies |\n| `crypto` from Node | the `crypto` global (synchronous Web Crypto) ([Crypto](../services/crypto.md)) |\n| `nodemailer` / an email SDK | the built-in email service ([Email](../services/email.md)) |\n| `process.env` | `Environment.get()` / `Environment.getSecure()` ([Environment](../services/environment.md)) |\n| `Date.now()`, timers | the `Time` global ([Time](../services/time.md)) |\n\n### Type system differences\n\ntoilscript uses precise, explicit numeric types instead of JavaScript's single `number`. You will write `i32`, `u64`, `f64`, and even `u256`, and be explicit about integer sizes. Values that must cross the wire are `@data` classes with concrete fields, not free-form objects. There is no `any`-style duck typing to lean on. The full rules, and how server types map to `bigint` and friends on the client, are in [Types](../concepts/types.md).\n\n### Two behaviors to design around\n\n- **Memory resets every request.** Each request gets a fresh `.wasm` instance and its memory is wiped afterward. A module-level variable does not persist. Anything durable goes in ToilDB. (This is the same rule you met in [Your first app](./first-app.md).)\n- **Reads and writes are split.** A `@get` is a read-only query; a `@post` is a write action. Operations that scan unbounded data are not allowed in a request handler; they move to a [`@derive`](../background/derive.md) or a background [daemon](../background/daemons.md).\n\n## Configuration\n\nTwo config files replace the various configs you may have had:\n\n- **`toil.config.ts`** is your client and build config: SEO defaults, image optimization, page transitions, and dev-server options. It uses `defineConfig`. See [Configuration](../concepts/config.md).\n- **`toilconfig.json`** is the low-level server (wasm) build config. The scaffold sets sensible defaults, and you usually leave it alone.\n\nEnvironment variables move from a `.env` you read with `process.env` to `.env` / `.env.secrets` files you read with `Environment.get()` / `Environment.getSecure()`. Secrets and plain vars are kept in separate buckets. See [Environment and secrets](../services/environment.md).\n\n## Step by step\n\n1. **Scaffold a fresh project** with `toiljs create` and get it running with `npm run dev`. Start from something that works.\n2. **Move the frontend.** Copy components into `client/components/`, turn each page into a file under `client/routes/`, move styles into `client/styles/`, and static assets into `client/public/`. Swap your router's `Link` for `Toil.Link`. Install your client-side npm dependencies.\n3. **Get the client rendering** against the routes, even if the data is still stubbed or points at your old backend. Fix routing and asset paths first.\n4. **Rebuild the backend, one route at a time.** For each old endpoint, add a `@data` model in `server/models/`, a `@rest` controller in `server/routes/`, and move its data into a ToilDB family. Import each new route in `server/main.ts`. This is the bulk of the effort; go endpoint by endpoint.\n5. **Move persistence to ToilDB.** Map each table or collection to a ToilDB family (a document store, a counter, an event log, or a view). See [Database overview](../database/README.md) for choosing a family.\n6. **Switch the client to the typed client.** Replace `fetch('/api/...')` calls with `Server.REST.*` (or `Server.<service>.*` for RPC). Now a backend change that breaks the contract shows up as a client type error.\n7. **Run the doctor.** `toiljs doctor` checks your wiring (routes, RPC generation, config, dependencies) and points at anything still off. Add `--fix` to let it repair what it can.\n\n## When toiljs is not the right move\n\nBe honest with yourself about the backend rewrite. Migration is a poor fit if:\n\n- Your server depends heavily on npm libraries or Node-only APIs that have no toiljs equivalent, and rewriting them is not worth it.\n- You must keep talking to an existing external database or service that toiljs cannot reach. (Daemons can make outbound HTTP calls in some cases, but general server-side networking is limited by design.)\n- You need long-lived in-process server state that does not fit the per-request, database-backed model.\n\nIn those cases, you can still adopt toiljs for the frontend and keep your existing backend, calling it with plain `fetch`. You just give up the single-repo, end-to-end-typed benefits for that part.\n\n## Gotchas and notes\n\n- **Do not try to `import` server code into the client or vice versa.** They compile with different rules. The only bridge is the generated `shared/server.ts`.\n- **One `@data` type per file** under `models/` matches the tooling's expectations.\n- **`shared/server.ts` is generated**, so it will not exist until your first build, and you never edit it.\n- **Plan the backend as a rewrite, not a port.** The frontend moves; the backend is re-expressed in toiljs's model. Budget for that.\n\n## Related\n\n- [Getting started overview](./README.md)\n- [Backend overview](../backend/README.md)\n- [Types](../concepts/types.md)\n- [Database overview](../database/README.md)\n- [The CLI reference](../cli/README.md)\n- [Configuration](../concepts/config.md)\n",
|
|
55
55
|
"getting-started/project-structure.md": "# Project structure\n\nA tour of every folder and file in a toiljs project, what each one is for, and the single most important question: **where does this code run, the browser or the edge?**\n\n## Why this matters\n\ntoiljs blends frontend and backend into one repo, so the same `.ts` file extension can mean two very different things. A file in `client/` becomes JavaScript that runs in your user's browser. A file in `server/` becomes WebAssembly that runs on the edge, with different rules and different available APIs. Knowing which folder you are in tells you what you are allowed to do. Keep this mental split and everything else falls into place.\n\n```mermaid\nflowchart TD\n subgraph Browser[\"Runs in the browser\"]\n C[\"client/*\"]\n end\n subgraph Edge[\"Runs on the edge (WebAssembly)\"]\n S[\"server/* -> release.wasm\"]\n end\n subgraph Generated[\"Generated glue (types + config)\"]\n SH[\"shared/server.ts\"]\n CFG[\"toil.config.ts / toilconfig.json\"]\n end\n C -. \"typed calls\" .-> SH\n SH -. \"HTTP / realtime\" .-> S\n S --> DB[(\"ToilDB\")]\n```\n\n## The top level\n\nThese files sit in your project root.\n\n| File | What it is | Runs where |\n| --- | --- | --- |\n| `package.json` | Scripts (`dev`, `build`, `lint`, `typecheck`, `format`) and dependencies (`toiljs`, `react`, `toilscript`, ...) | tooling only |\n| `toil.config.ts` | **Client and build config.** Uses `defineConfig` to set SEO, images, page transitions, and dev options. | tooling only |\n| `toilconfig.json` | **Server (wasm) build config** for toilscript: the entry file, the output `.wasm` path, and low-level compile options. You rarely edit this. | tooling only |\n| `tsconfig.json` | TypeScript config for the client (`client/`, `shared/`, `emails/`). Extends `toiljs/tsconfig`. | tooling only |\n| `eslint.config.js` | Linting preset (`toiljs/eslint`). | tooling only |\n| `.prettierrc` | Formatting preset (`toiljs/prettier`). | tooling only |\n| `.prettierignore` | Files Prettier should skip (generated files). | tooling only |\n| `.gitignore` | Ignores `build/`, `.toil/`, generated files, and your `.env` files. | tooling only |\n| `.vscode/settings.json` | Tells VS Code to use the project's TypeScript so the toilscript editor plugin loads. | editor only |\n| `toil-env.d.ts` | **Generated** editor types for client globals like `Toil.Link` and `Toil.Image`. Do not edit. | editor only |\n| `toil-routes.d.ts` | **Generated** list of your real route names, so `Toil.Link href=\"...\"` type-checks. Filled in on the first build. | editor only |\n| `README.md` | Your project's readme. | docs |\n| `CLAUDE.md`, `AGENTS.md`, etc. | Optional AI-assistant hint files that point tools at the toiljs docs. | docs |\n\nTwo folders you may also see at the root:\n\n- **`.toil/`** is a working directory toiljs manages (a build cache and a copy of the docs). It is gitignored. You never edit it.\n- **`.env` and `.env.secrets`** are files **you** create when you need local environment variables or secrets during `toiljs dev`. They are gitignored so you never commit them, and the edge loads their real values out of band in production. Your server reads them with `Environment.get(\"KEY\")` and `Environment.getSecure(\"KEY\")`. See [Environment and secrets](../services/environment.md).\n\n## `client/` (runs in the browser)\n\nThis is a normal React app. You can use React libraries and browser APIs here freely.\n\n```text\nclient/\n toil.tsx the entry point; mounts your app\n layout.tsx the root layout wrapping every page\n 404.tsx the not-found page\n global-error.tsx the top-level error page\n routes/ file-based pages\n components/ shared React components\n styles/ global stylesheets\n public/ static files served as-is\n```\n\n- **`toil.tsx`** is the entry file. It imports your global styles and calls `Toil.mount(...)` to start the app. You rarely change it beyond the style imports.\n- **`layout.tsx`** is your root layout: the header, footer, and page shell that wrap every route. Its `children` prop is the current page.\n- **`404.tsx`** renders when no route matches. **`global-error.tsx`** renders when a route throws.\n- **`routes/`** is where pages live, and the file name **is** the URL. `routes/index.tsx` is `/`, `routes/about.tsx` is `/about`, `routes/blog/[slug].tsx` is `/blog/:slug`. This is called **file-based routing**. See [Routing](../frontend/routing.md).\n- **`components/`** holds React components you reuse across pages. Nothing here is a route.\n- **`styles/`** holds your global CSS (or Sass, Less, or Stylus, if you chose one). See [Styling](../frontend/styling.md).\n- **`public/`** holds static files served exactly as they are: `favicon`, `robots.txt`, and an `images/` folder (reachable at `/images/...`). The `public/index.html` is the base HTML shell your app mounts into.\n\nA single global, `Toil`, is available in client code without an import (for `Toil.Link`, `Toil.Image`, and `Toil.Head`). It is typed by the generated `toil-env.d.ts`.\n\n## `server/` (runs on the edge, as WebAssembly)\n\nThis is your backend. It is compiled by toilscript into one `.wasm` file. Remember the two rules: **memory resets every request**, and **this is not Node.js** (a strict TypeScript subset, no arbitrary npm packages). See [Backend overview](../backend/README.md) and [Types](../concepts/types.md).\n\n```text\nserver/\n main.ts the entry: wires the handler + imports your modules\n tsconfig.json server-only TS config (loads the toilscript editor plugin)\n toil-server-env.d.ts generated editor types for server globals\n core/ your request handler and shared logic\n models/ @data classes\n routes/ @rest controllers (HTTP)\n services/ @service / @remote (typed RPC)\n migrations/ ToilDB schema migrations\n scheduled/ reserved for scheduled tasks\n```\n\n- **`main.ts`** is the entry the build compiles. It does three required things: it sets `Server.handler` (a factory that returns one fresh handler per request), it re-exports the wasm entry points (`export * from 'toiljs/server/runtime/exports'`), and it defines the `abort` hook. It also `import`s your other server modules so a direct toilscript run builds the same code.\n- **`core/`** holds your top-level `ToilHandler` (often `AppHandler.ts`): the first code that sees each request. It can dispatch to your `@rest` controllers and then fall through to any hand-written logic.\n- **`models/`** holds your `@data` classes, one type per file. A `@data` class is a typed message that can cross the wire between client and server (and into ToilDB). See [Data types](../backend/data.md).\n- **`routes/`** holds your `@rest` controllers: classes decorated with `@rest`, `@get`, and `@post` that expose HTTP endpoints. See [HTTP routes](../backend/rest.md).\n- **`services/`** holds `@service` classes and free `@remote` functions: typed remote calls the client makes as plain function calls (no URLs). See [Typed RPC](../backend/rpc.md).\n- **`migrations/`** holds ToilDB schema migrations. When you change the shape of a stored `@data` type, you add a `<Type>.migration.ts` here that carries old records forward. The compiler enforces this convention. See [Documents](../database/documents.md).\n- **`scheduled/`** is reserved for scheduled tasks. New decorated files anywhere under `server/` are picked up automatically by the build.\n- **`tsconfig.json`** and **`toil-server-env.d.ts`** are editor-support files. They teach your editor about the server globals (`crypto`, `Cookie`, `Environment`, and friends) so it stops flagging them. They do not affect the build.\n\n### How the build discovers your server code\n\nYou do not register routes in a config file. The compiler scans every `.ts` file under `server/` and picks up anything that declares a decorated surface (`@rest`, `@service`, `@remote`, `@data`, `@user`, `@database`, and so on). Importing those files from `main.ts` is still good practice: it keeps a direct `toilscript` run building the exact same server.\n\n## `shared/` (generated glue)\n\n```text\nshared/\n server.ts GENERATED typed client (do not edit)\n```\n\n**`shared/server.ts` is written for you** by the server build. It contains:\n\n- A typed `Server` object the browser uses to call your backend: `Server.REST.*` for HTTP routes and `Server.<service>.*` for RPC.\n- The client-side codecs for every `@data` class, so responses come back as real typed objects.\n- A `getUser()` helper for reading the signed-in user on the client.\n\nBecause it is generated, it does not exist in a fresh project and it is gitignored. It appears the first time you run `toiljs dev` or `toiljs build`. Never hand-edit it; change your server code and it regenerates.\n\n## `build/` (compiled output)\n\n```text\nbuild/\n server/release.wasm your compiled backend (+ release.wat, a readable text form)\n client/ the bundled React app (from Vite)\n```\n\nThis is what actually ships. It is gitignored and recreated by `toiljs build`. You do not edit anything here.\n\n## Putting it together: one request\n\n```mermaid\nsequenceDiagram\n participant B as Browser (client/)\n participant SH as shared/server.ts\n participant W as server.wasm (server/)\n participant DB as ToilDB\n B->>SH: Server.REST.likes.like()\n SH->>W: HTTP POST /likes\n W->>DB: counter.add(key, 1)\n W-->>SH: typed LikeCount response\n SH-->>B: { count }\n```\n\nThe browser calls a typed method, the generated client turns it into an HTTP request, your `.wasm` handles it and touches ToilDB, and a typed result comes back. You wrote both ends; the middle is generated.\n\n## Gotchas and notes\n\n- **A `.ts` file's folder decides its rules.** The same code that works in `client/` may not compile in `server/`, because the server is a strict subset without Node APIs.\n- **Do not edit generated files.** `shared/server.ts`, `toil-env.d.ts`, `toil-routes.d.ts`, and `toil-server-env.d.ts` are all regenerated by the build and will overwrite your changes.\n- **One `@data` type per file** under `models/` keeps things tidy and matches the convention the tooling expects.\n- **`build/` and `.toil/` are disposable.** Delete them and the next build recreates them.\n\n## Related\n\n- [Your first app](./first-app.md)\n- [Frontend overview](../frontend/README.md) and [Routing](../frontend/routing.md)\n- [Backend overview](../backend/README.md)\n- [Database overview](../database/README.md)\n- [Configuration](../concepts/config.md)\n- [Decorators reference](../concepts/decorators.md)\n",
|
|
56
56
|
"getting-started/README.md": "# Getting started\n\ntoiljs is a full-stack web framework: you write a React frontend and a TypeScript backend in one project, and toiljs ships both together. This section takes you from \"nothing installed\" to \"a small feature running end to end.\"\n\n## What toiljs is\n\nThink of a normal web app as two programs that have to agree with each other:\n\n- A **frontend**: the React code that runs in your user's browser.\n- A **backend**: the code that runs on a server, answers requests, and talks to a database.\n\nNormally these live in separate projects, speak to each other over hand-written HTTP calls, and drift apart until something breaks at runtime. toiljs puts both in one repository and wires them together with types, so a change on one side shows up as a compile error on the other side instead of a bug in production.\n\nThe twist is what your backend becomes. You write it in TypeScript, but toiljs does not run it in Node.js. Instead, a compiler called **toilscript** turns your backend into **WebAssembly** (often shortened to \"Wasm\"): a small, fast, sandboxed program that runs at the **edge**. \"Edge\" just means servers spread all over the world, close to your users, so requests do not have to travel to one far-away data center. A worldwide database called **ToilDB** is built in, so you can store and read data without setting up or connecting to a database yourself.\n\nNew terms, defined once:\n\n- **WebAssembly / Wasm**: a compact binary format that runs code in a locked-down sandbox at near-native speed. Your backend compiles to a single `.wasm` file.\n- **toilscript**: the compiler that turns your TypeScript backend into that `.wasm` file. It accepts a strict subset of TypeScript (more on that below).\n- **Dacely edge**: the global network of servers that runs your `.wasm` backend.\n- **ToilDB**: the built-in database that lives on the edge next to your code.\n\n## The client / server / shared mental model\n\nEvery toiljs project is organized into three folders. The most important thing to learn first is **where each piece of code actually runs**.\n\n```mermaid\nflowchart LR\n A[\"Your project<br/>(one repo)\"] -->|toiljs build| B[\"client bundle<br/>React + JS\"]\n A -->|toilscript compiles| C[\"server.wasm<br/>your backend\"]\n B --> U[\"User's browser\"]\n C --> E[\"Dacely edge<br/>(worldwide servers)\"]\n E --> D[(\"ToilDB<br/>global database\")]\n U <-->|\"HTTP / realtime\"| E\n```\n\n- **`client/`** is your React app: pages, components, and styles. It is bundled by [Vite](https://vitejs.dev) and runs **in the browser**.\n- **`server/`** is your backend: HTTP routes, database access, auth. It is compiled by toilscript to `build/server/release.wasm` and runs **on the edge** (and locally when you run the dev server).\n- **`shared/`** holds a file that toiljs **generates for you** (`shared/server.ts`). It is a fully typed client: the browser calls your backend through a `Server` object, and TypeScript checks every call. You do not write this file by hand.\n\nHere is the loop in one picture:\n\n```mermaid\nflowchart TD\n W[\"client/routes/page.tsx<br/>(browser)\"] -->|\"Server.REST.likes.like()\"| G[\"shared/server.ts<br/>(generated typed client)\"]\n G -->|\"HTTP request\"| S[\"server/routes/Likes.ts<br/>(your @rest route, in wasm)\"]\n S -->|\"read / write\"| DB[(\"ToilDB\")]\n S -->|\"typed response\"| W\n```\n\nYou write both ends in TypeScript, and the generated `shared/server.ts` in the middle keeps them in sync.\n\n## Two rules to keep in mind\n\nThese two facts explain most of how toiljs backends behave. They are covered in depth later, but it helps to meet them now.\n\n1. **The server runs one fresh instance per request.** Every request gets a brand-new copy of your `.wasm`, and its memory is wiped when the request ends. So a normal variable you set in one request is gone by the next request. Anything that must survive (accounts, counters, posts) has to go into **ToilDB** or another store. Nothing in a plain module-level variable persists.\n\n2. **The server is not Node.js.** toilscript compiles a strict subset of TypeScript, so you cannot `import` an arbitrary npm package into `server/` or use Node APIs like `fs`. Instead, toiljs gives you built-in globals for the common needs: `crypto`, cookies, email, the database, and more. See [Types](../concepts/types.md) and [Decorators](../concepts/decorators.md) for the details.\n\nThe client side, by contrast, is normal React. You can use React libraries and browser APIs there as usual.\n\n## The getting-started path\n\nWork through these pages in order:\n\n1. **[Installation](./installation.md)**: check your Node.js version and install the toiljs command-line tool.\n2. **[Create a project](./create-project.md)**: scaffold a new app and see what you get.\n3. **[Project structure](./project-structure.md)**: a tour of every folder and file, and where each one runs.\n4. **[Your first app](./first-app.md)**: build a tiny feature end to end, a page that calls a backend route and reads and writes one piece of ToilDB data.\n5. **[Migrating an existing app](./migrating.md)**: bring a React app you already have into toiljs.\n6. **[Deploy](./deploy.md)**: build for production and self-host it, and how the managed edge fits in.\n\n## Related\n\n- [Documentation home](../README.md)\n- [The CLI reference](../cli/README.md)\n- [Frontend overview](../frontend/README.md)\n- [Backend overview](../backend/README.md)\n- [Database overview](../database/README.md)\n- [Compute tiers (where code runs)](../concepts/tiers.md)\n",
|