toiljs 0.0.123 → 0.0.124
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 +9 -0
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +472 -228
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/toil-docs.generated.js +2 -2
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@ export const TOIL_DOCS = {
|
|
|
13
13
|
"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",
|
|
14
14
|
"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",
|
|
15
15
|
"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",
|
|
16
|
-
"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 test` | Run Vitest unit or real-browser tests, installing missing tooling automatically. |\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 generate auth` | Generate private auth keys and the matching client public key, preserving existing credentials. |\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 generate auth`\n\n```bash\ntoiljs generate auth\ntoiljs generate auth --root ./apps/marketing\n```\n\nSaves private authentication keys in `.env.secrets` (mode `0600`) and\n`VITE_AUTH_KEM_PUBLIC_KEY` in `.env`, and adds both files to `.gitignore`. It preserves\nunrelated settings and existing credentials, and can recover a missing public key from the\nexisting private key. It refuses invalid or mismatched keys rather than rotating them.\nNo secret values are printed. Use this for initial setup or completing an existing key set.\n\nPin the public key in your client's auth options, build with that `.env` on the build machine,\nand install the private settings in your backend's secure env store. See\n[auth configuration](../auth/configuration.md) for the client snippet and toil-backend deployment steps.\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 7, Oxlint + tsgolint, Prettier, and Vitest unit/browser/coverage presets, file-based routing, a `toil.config.ts`, a `toilconfig.json` (the server compiler settings), a `.gitignore`, and the native TypeScript 7 editor settings. 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` | `oxlint --type-aware client` |\n| `npm run typecheck` | `tsc --noEmit` |\n| `npm test` | `toiljs test` |\n| `npm run test:watch` | `toiljs test --watch` |\n| `npm run test:browser` | `toiljs test --browser` |\n| `npm run test:all` | `toiljs test --all` |\n| `npm run test:coverage` | `toiljs test --all --coverage` |\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 test`\n\nRuns unit tests by default. All templates include the dependencies, configuration,\nand runnable unit and React browser examples. Existing projects get missing tooling\ninstalled automatically; custom configs, scripts, and existing files are preserved.\n\n```bash\nnpx toiljs test\nnpx toiljs test --browser\nnpx toiljs test --all --coverage\nnpx toiljs test --watch\nnpx toiljs test Greeting --reporter=verbose\n```\n\n| Flag | Meaning |\n| --- | --- |\n| `--browser` | Run the native browser project with Chrome and WebdriverIO. |\n| `--all` | Run both native projects. Cannot be combined with `--browser`. |\n| `--watch`, `-w` | Watch for changes instead of running once. |\n| `--coverage` | Collect V8 coverage; reports go to `coverage/`. |\n| `--root <dir>` | Run against a project in another directory. |\n\nOther options and file filters pass through to Vitest. An explicit `--project`\noverrides native selection. Custom configs keep their own project selection, with\n`--browser` forwarded to Vitest. Test failures preserve a nonzero shell exit status.\nSee [Testing and coverage](../frontend/testing.md) for setup, browser binaries,\nframework globals, coverage reports, and configuration.\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 TypeScript 7 is installed and its declared range stays within 7.x, that Oxlint and tsgolint are configured for type-aware linting, 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` migrates unsupported or missing TypeScript declarations to `^7.0.2` and enables the native TypeScript editor 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- removes the legacy toilscript JavaScript language-service plugin, which cannot run in the TypeScript 7 native language server; `toiljs build --server` provides the WebAssembly dialect diagnostics,\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\nTypeScript updates must stay within supported **7.x** releases. Older and future compiler majors,\nunbounded ranges, and prereleases are withheld. An older project is offered the latest 7.x migration\neven with `--target patch`. Select that migration before applying other updates. The command writes\nthe exact reviewed versions before installing, so a second registry query cannot substitute a\ndifferent compiler major.\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",
|
|
16
|
+
"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 test` | Run Vitest unit or real-browser tests, installing missing tooling automatically. |\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 generate auth` | Generate private auth keys and the matching client public key, preserving existing credentials. |\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 generate auth`\n\n```bash\ntoiljs generate auth\ntoiljs generate auth --root ./apps/marketing\n```\n\nSaves private authentication keys in `.env.secrets` (mode `0600`) and\n`VITE_AUTH_KEM_PUBLIC_KEY` in `.env`, and adds both files to `.gitignore`. It preserves\nunrelated settings and existing credentials, and can recover a missing public key from the\nexisting private key. It refuses invalid or mismatched keys rather than rotating them.\nNo secret values are printed. Use this for initial setup or completing an existing key set.\n\nPin the public key in your client's auth options, build with that `.env` on the build machine,\nand install the private settings in your backend's secure env store. See\n[auth configuration](../auth/configuration.md) for the client snippet and toil-backend deployment steps.\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 7, Oxlint + tsgolint, Prettier, and Vitest unit/browser/coverage presets, file-based routing, a `toil.config.ts`, a `toilconfig.json` (the server compiler settings), a `.gitignore`, and the native TypeScript 7 editor settings. 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` | `oxlint --type-aware client` |\n| `npm run typecheck` | `tsc --noEmit` |\n| `npm test` | `toiljs test` |\n| `npm run test:watch` | `toiljs test --watch` |\n| `npm run test:browser` | `toiljs test --browser` |\n| `npm run test:all` | `toiljs test --all` |\n| `npm run test:coverage` | `toiljs test --all --coverage` |\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 test`\n\nRuns unit tests by default. All templates include the dependencies, configuration,\nand runnable unit and React browser examples. Existing projects get missing tooling\ninstalled automatically; custom configs, scripts, and existing files are preserved.\n\n```bash\nnpx toiljs test\nnpx toiljs test --browser\nnpx toiljs test --all --coverage\nnpx toiljs test --watch\nnpx toiljs test Greeting --reporter=verbose\n```\n\n| Flag | Meaning |\n| --- | --- |\n| `--browser` | Run the native browser project with Chrome and WebdriverIO. |\n| `--all` | Run both native projects. Cannot be combined with `--browser`. |\n| `--watch`, `-w` | Watch for changes instead of running once. |\n| `--coverage` | Collect V8 coverage; reports go to `coverage/`. |\n| `--root <dir>` | Run against a project in another directory. |\n\nOther options and file filters pass through to Vitest. An explicit `--project`\noverrides native selection. Custom configs keep their own project selection, with\n`--browser` forwarded to Vitest. Test failures preserve a nonzero shell exit status.\nSee [Testing and coverage](../frontend/testing.md) for setup, browser binaries,\nframework globals, coverage reports, and configuration.\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 TypeScript 7 is installed and its declared range stays within 7.x, that Oxlint and tsgolint are configured for type-aware linting, 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| **Testing** | Actual installed Vitest, browser provider, WebdriverIO, and V8 coverage versions; compatible declared ranges; matching Vitest/coverage releases; config presence and unit/browser/watch/coverage scripts. Missing testing is a warning for legacy apps, while broken configured tooling fails. |\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` migrates unsupported or missing TypeScript declarations to `^7.0.2`, enables the native TypeScript editor, and repairs testing setup in any project. Testing repairs add missing packages and scripts, migrate unsupported registry versions, align Vitest and coverage, add the preset and examples when there is no custom config, include tests in plain JSON TypeScript configs, and ignore test reports. Existing custom configs, nonempty scripts, explicit package sources, and existing files are preserved; commented TypeScript configs get a manual repair note. It does not install packages or launch a browser. 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- removes the legacy toilscript JavaScript language-service plugin, which cannot run in the TypeScript 7 native language server; `toiljs build --server` provides the WebAssembly dialect diagnostics,\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 testing, TypeScript, editor, and 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 and adds missing testing setup when updates are applied. `--yes` repairs and installs missing testing even when no dependency upgrades are available. Custom configs and scripts are preserved; declining updates leaves testing untouched. Native test updates stay within supported versions, and selecting either Vitest or V8 coverage updates both to a matching release. `npm-check-updates` runs via `npx`, so it never becomes a permanent dependency of your project.\n\nTypeScript updates must stay within supported **7.x** releases. Older and future compiler majors,\nunbounded ranges, and prereleases are withheld. An older project is offered the latest 7.x migration\neven with `--target patch`. Select that migration before applying other updates. The command writes\nthe exact reviewed versions before installing, so a second registry query cannot substitute a\ndifferent compiler major.\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",
|
|
17
17
|
"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",
|
|
18
18
|
"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## Route execution and transport limits\n\n`server.daemon.tickBudgetMs` applies only to scheduled daemon ticks. It is an overrun warning emitted after a tick returns; it does not cancel the tick or set a REST/RPC route deadline. There is no route execution timeout setting in `toil.config.ts`.\n\nFor `toiljs dev` and the Node self-host (`toiljs start`):\n\n| Limit | Default | What it bounds |\n| --- | --- | --- |\n| HTTP request body | 8 MiB | The body accepted by the front server, before handler dispatch. |\n| HTTP idle timeout | 60 seconds | An idle connection. |\n| HTTP response timeout | 120 seconds | A response stalled while sending under backpressure. |\n| WASM handler execution | No elapsed-time cutoff or gas metering | Synchronous `handle`/`render` execution on its worker. |\n\nThe HTTP timeouts do not interrupt a WASM handler that is still running. A busy loop can occupy its worker indefinitely, so a browser abort or reverse-proxy timeout must not be treated as proof that a write was cancelled. The low-level `startDevServer` / `startBuiltServer` APIs accept `maxBodyLength`; the CLI uses the default above.\n\nThe Dacely production edge meters execution with a per-request **gas budget** (a charge for WASM instructions and host operations), determined by the host's plan and operator configuration. Gas exhaustion traps the request. Gas is a work budget, not a guaranteed number of milliseconds, and the local Node hosts do not emulate it.\n\nProduction asynchronous database waits have their own deadlines: the current defaults are **50 ms for a Query**, **250 ms for an Action**, and **5 seconds for a Job or Derive**. The edge operator configures these through `db_quota`, separately from `toil.config.ts`. They bound DB waits rather than the whole route's execution time, and the local emulator does not enforce them. Deployed transport and database budgets are controlled by the host; there is no universal route time limit in seconds to infer from the daemon setting.\n\nKeep each upload request bounded by bytes, chunk count, and database operations. Continue large jobs across requests or background tasks rather than looping until a wall-clock deadline. See [Documents size limits and file uploads](../database/documents.md#size-limits-and-file-uploads) and [Time](../services/time.md#upload-expiry-versus-elapsed-deadlines).\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",
|
|
19
19
|
"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",
|
|
@@ -40,7 +40,7 @@ export const TOIL_DOCS = {
|
|
|
40
40
|
"frontend/scripts.md": "# Scripts\n\n`Toil.Script` loads an external or inline `<script>` for you, with control over *when* it runs and a guarantee it runs only *once* across your whole app. Use it instead of a hand-written `<script>` tag for third-party snippets (analytics, chat widgets, embeds). It is the toiljs analog of Next.js `next/script`.\n\n## Why not just write a `<script>` tag?\n\nDropping a raw `<script>` into your JSX is unreliable in a single-page app (an app where the browser loads one HTML shell and JavaScript swaps pages in place, with no full reload). Two problems:\n\n1. **It may not execute.** When React inserts a `<script>` element into the page, the browser does not always run it the way it runs scripts present in the original HTML.\n2. **It runs too often.** As the user navigates between routes that both render that script, React can mount it more than once, so an analytics library or a widget initialises twice.\n\n`Toil.Script` fixes both: it injects a real `<script>` into `<head>` so the browser runs it, and it **deduplicates** by a key so a given script executes at most once for the whole life of the app, even across client-side navigations. It renders nothing into your layout.\n\n## The simplest usage: an external script\n\nGive it a `src`. By default it loads once the app is interactive, which is right for most third-party scripts:\n\n```tsx\n// client/layout.tsx\nimport { type ReactNode } from 'react';\n\nexport default function Layout({ children }: { children?: ReactNode }) {\n return (\n <div className=\"app\">\n <Toil.Script src=\"https://cdn.example-analytics.com/analytics.js\" />\n {children}\n </div>\n );\n}\n```\n\nPutting it in the root layout means it loads once for the whole app and stays loaded as the user navigates. For an external script, the dedup key defaults to its `src`, so you never need an `id`.\n\n## Load strategies\n\nThe `strategy` prop decides *when* the script is injected, relative to your app becoming interactive:\n\n| Strategy | When it runs | Use it for |\n| --- | --- | --- |\n| `afterInteractive` (default) | On mount, once the app is running. | Analytics, chat widgets, most third-party scripts. |\n| `lazyOnload` | Deferred until the browser is idle, after the page's `load` event. | Low-priority extras (a feedback button, a social embed) that should not compete with the initial render. |\n| `beforeInteractive` | As early as possible: injected immediately on first mount. | A script other code depends on immediately. |\n\nOne honest caveat about `beforeInteractive`: a toiljs frontend is a client-only single-page app, so there is no server-rendered `<script>` to run before hydration. `beforeInteractive` therefore still runs *after* hydration, just as early and eagerly as possible on the first mount. It is a priority hint, not a true \"before the page is interactive\" guarantee.\n\n```tsx\n<Toil.Script\n src=\"https://widget.example.com/embed.js\"\n strategy=\"lazyOnload\"\n/>\n```\n\n## Inline scripts\n\nTo run a snippet of code instead of loading a URL, put the code in `children` (as a **string**) and give the script an `id`. An inline script has no `src`, so the `id` is what identifies it for dedup, and it is required:\n\n```tsx\n<Toil.Script id=\"init-theme\" strategy=\"beforeInteractive\">\n {`document.documentElement.dataset.theme =\n localStorage.getItem('theme') ?? 'light';`}\n</Toil.Script>\n```\n\nWithout an `id` (and no `src`), an inline script has nothing to dedup on, so `Toil.Script` does nothing at all. This is a deliberate no-op, not an error, so remember the `id`.\n\n## Reacting to load\n\nThree optional callbacks let you run code around the script's lifecycle:\n\n```tsx\n<Toil.Script\n src=\"https://widget.example.com/embed.js\"\n strategy=\"lazyOnload\"\n onReady={() => {\n // The global the script defines is now available.\n window.MyWidget?.init();\n }}\n onError={(err) => {\n console.warn('widget failed to load', err);\n }}\n/>\n```\n\n- `onLoad` fires **once**, when an external script finishes loading (or an inline script is inserted).\n- `onReady` fires after load **and on every later mount** once the script is already loaded. So if a route that renders the `Toil.Script` is left and revisited, `onReady` runs again, which is the right place to re-initialise a widget.\n- `onError` fires if an **external** script fails to load. After an error the script's key is cleared, so a later remount retries the load.\n\n## All props\n\nRead them straight from the source (`src/client/components/Script.tsx`):\n\n| Prop | Type | Default | What it does |\n| --- | --- | --- | --- |\n| `src` | `string` | (none) | URL of an external script. Omit it when you provide an inline body via `children`. |\n| `children` | `string` | (none) | Inline script body. Mutually exclusive with `src`. |\n| `strategy` | `'beforeInteractive' \\| 'afterInteractive' \\| 'lazyOnload'` | `'afterInteractive'` | When to inject the script (see above). |\n| `id` | `string` | `src` for external scripts | Stable identity for dedup. **Required** for inline scripts. |\n| `type` | `string` | (none) | The `type` attribute, e.g. `'module'` or `'application/json'`. |\n| `onLoad` | `() => void` | (none) | Fired once the script has loaded (external) or been inserted (inline). |\n| `onReady` | `() => void` | (none) | Fired after load, and on every later mount once the script is already loaded. |\n| `onError` | `(error: unknown) => void` | (none) | Fired if an external script fails to load. |\n\nExternal scripts are injected with `async` set, so they never block other work while downloading.\n\n## Gotchas\n\n- **Inline scripts need an `id`.** No `id` and no `src` means nothing to dedup on, so the component quietly does nothing.\n- **Dedup is app-wide and survives navigation.** The same `Toil.Script` rendered on two different routes runs a total of once, keyed by `id` (or `src`). This is the point, but it means you cannot use two copies of the same key to run something twice.\n- **Props are read at injection time, and the script re-injects only if its key or strategy changes.** Changing a handler or an inline body *without* changing the `id` will not re-run the script. If you truly need to re-inject a changed inline script, change its `id`.\n- **`onReady` versus `onLoad`.** Use `onLoad` for one-time setup that must happen exactly once; use `onReady` for setup that should also run each time a component remounts against an already-loaded script.\n- **`beforeInteractive` is not truly before hydration** in a client-only app (see the caveat above). Do not rely on it running before your React tree mounts.\n- **`Toil.Script` renders nothing.** It returns `null`, so you can place it anywhere in your component tree; a layout is the usual home for app-wide scripts.\n\n## Related\n\n- [Rendering and SSR](./rendering.md): how the client-only app and its first paint fit together.\n- [Metadata and SEO](./metadata.md): for `<head>` tags (title, meta, Open Graph), which is a different job from loading scripts.\n- [Frontend overview](./README.md): the `Toil` global and the rest of the client API.\n",
|
|
41
41
|
"frontend/search.md": "# Page search\n\ntoiljs ships a built-in search over your own pages: at build time it indexes every route's metadata (title, description, keywords, Open Graph), and at runtime you query that index to build a search box or a command palette that jumps straight to a page. It is entirely static (no server, no search service), and there is no equivalent in Next.js, so this page explains it from scratch.\n\n## What it is\n\nEvery route in your app can export `metadata` (its title, description, and so on, see [Metadata and SEO](./metadata.md)). The toiljs compiler reads that metadata from all your routes at build time and bakes a small **page index** into your bundle: a list of `{ path, title, description, keywords, ... }` for every page. When your app starts, that index is registered in the browser. You can then search it instantly, offline, with zero network calls, and turn a match into a navigation.\n\nThis is what powers a \"jump to any page\" box or a `Cmd+K` command palette without you maintaining a list of pages or standing up a search backend.\n\n```mermaid\nflowchart TD\n A[\"Your routes<br/>each with export const metadata\"] -->|build: scan + extract| B[\"Static page index<br/>{ path, dynamic, metadata } per page\"]\n B -->|baked into the bundle| C[\"App startup: Toil.registerPages(index)\"]\n C --> D[\"Browser: usePageSearch / searchPages query the index\"]\n D --> E[\"Ranked results -> goTo() navigates\"]\n```\n\nOnly **statically-known** metadata is indexed. A route whose `<head>` comes from a dynamic `generateMetadata` (a per-request title) has nothing to index by default; to include it anyway, export `searchHints` (see below).\n\n## The `usePageSearch` hook\n\n`usePageSearch` is the React way in. Give it the current query string, and it hands back ranked results plus a helper to navigate:\n\n```tsx\nconst { results, pages, goTo } = Toil.usePageSearch(query);\n```\n\nIt returns an object with three fields:\n\n| Field | Type | What it is |\n| --- | --- | --- |\n| `results` | `readonly PageSearchResult[]` | The ranked matches for `query`, best first. Empty when the query is blank. |\n| `pages` | `readonly PageMeta[]` | The full page index, handy for showing an \"all pages\" listing. |\n| `goTo` | `(target, options?) => void` | Navigates to a result, a page, or a raw path string. A stable reference (safe to pass to a child or destructure). |\n\nEach item in `results` is a `PageSearchResult`:\n\n- `page`: the matched `PageMeta`, which is `{ path, dynamic, metadata }`. `path` is the route URL (`'/about'`), `dynamic` says whether it has `:param` segments, and `metadata` is the indexed title/description/etc.\n- `score`: a relevance number (higher is better; always above zero for a returned result).\n- `matches`: which fields matched, for example `['title', 'keywords']`.\n\nThe results are memoised, so they recompute only when the query (or options) change, not on every render.\n\n### A full search box\n\nHere is a complete search page: an input, a ranked result list, and a click that navigates to the chosen page.\n\n```tsx\n// client/routes/search.tsx\nimport { useState } from 'react';\n\nexport const metadata: Toil.Metadata = {\n title: 'Search',\n description: 'Find any page and jump straight to it.',\n keywords: ['search', 'find', 'pages'],\n};\n\nexport default function SearchPage() {\n const [query, setQuery] = useState('');\n const { results, pages, goTo } = Toil.usePageSearch(query);\n\n return (\n <main>\n <h1>Search</h1>\n <input\n type=\"search\"\n value={query}\n onChange={(e) => {\n setQuery(e.target.value);\n }}\n placeholder={`Search ${pages.length} pages...`}\n aria-label=\"Search pages\"\n autoFocus\n />\n\n {query.trim() !== '' && (\n <ul>\n {results.length === 0 && <li>No pages match \"{query}\".</li>}\n {results.map((r) => (\n <li key={r.page.path}>\n <button\n type=\"button\"\n onClick={() => {\n goTo(r);\n }}\n >\n <strong>{r.page.metadata.title ?? r.page.path}</strong>{' '}\n <code>{r.page.path}</code>\n {r.page.metadata.description !== undefined && (\n <p>{r.page.metadata.description}</p>\n )}\n </button>\n </li>\n ))}\n </ul>\n )}\n </main>\n );\n}\n```\n\n`goTo` accepts a result (as above), a `PageMeta`, or a plain path string, and takes the same options as `navigate` (see [Routing](./routing.md)). Passing a result is the common case.\n\n### Options\n\n`usePageSearch` takes a second argument to tune the search:\n\n```tsx\nconst { results } = Toil.usePageSearch(query, {\n limit: 8, // cap the number of results (after ranking)\n includeDynamic: true, // include :param routes (see below); default false\n fields: ['title', 'keywords'], // only match these fields; default all fields\n});\n```\n\n| Option | Type | Default | Effect |\n| --- | --- | --- | --- |\n| `limit` | `number` | no cap | Keep only the top N results after ranking. |\n| `includeDynamic` | `boolean` | `false` | Include dynamic (`:param` / `*catch-all`) routes. Off by default because you cannot navigate to them without filling in the params. |\n| `fields` | array of field names | all fields | Restrict matching to a subset of `'title'`, `'description'`, `'keywords'`, `'path'`, `'openGraph'`. |\n\n## Making dynamic routes searchable with `searchHints`\n\nA dynamic route like `client/routes/blog/[id].tsx` usually produces its `<head>` with a per-post `generateMetadata`, so there is nothing static to index, and the route is missing from search. To surface it anyway, export `searchHints`: a small static object the compiler merges into the index for that route (winning ties against any static `metadata`).\n\n```tsx\n// client/routes/blog/[id].tsx\n\n// The per-post <title> is dynamic, so it cannot be indexed. These static hints\n// put the blog into the search index so it shows up when someone searches \"blog\".\nexport const searchHints: Toil.SearchHints = {\n title: 'Blog',\n description: 'Articles and updates.',\n keywords: ['blog', 'posts', 'articles'],\n};\n```\n\n`SearchHints` has three optional fields: `title`, `description`, and `keywords` (a string or an array of strings).\n\nTwo things to keep in mind. First, `searchHints` only affects the index; the route is still dynamic, so it appears in results only when you pass `includeDynamic: true`. Second, you cannot `goTo` a dynamic page directly (it needs concrete params), so `goTo` is a no-op for one unless you hand it a real, filled-in path string. In practice you use `searchHints` to make a *section* discoverable (typing \"blog\" surfaces the blog), and point the user at a concrete landing URL.\n\n## The lower-level API\n\nThe hook is a thin wrapper over a small, framework-agnostic core you can use directly (outside React, in tests, or to build your own UI). All of these are on the `Toil` global:\n\n- `Toil.searchPages(query, options?)`: the pure ranking function behind the hook. Returns `PageSearchResult[]`. Same options as the hook.\n- `Toil.getPages()`: the full registered index (`readonly PageMeta[]`), including dynamic routes. Empty before registration.\n- `Toil.pagePath(target)`: normalises a result, a page, or a raw string down to its path string.\n- `Toil.registerPages(pages)`: replaces the live index. toiljs calls this for you at startup with the compiler-built index, so you rarely call it yourself. It exists for tests and advanced setups that build a custom index.\n\n```tsx\nimport { searchPages } from 'toiljs/client';\n\n// Outside a component: the top 5 title/keyword matches for \"billing\".\nconst hits = searchPages('billing', { limit: 5, fields: ['title', 'keywords'] });\n```\n\n## How ranking works\n\nThe matcher is simple and predictable:\n\n- The query is lower-cased and split on whitespace into terms. **Every** term must match somewhere (AND semantics), or the page is dropped. A blank query returns nothing.\n- Matching is case-insensitive and substring-based, with bonuses: an exact field match ranks highest, then a match at the start of the field, then a match at the start of a word inside the field, then a plain mid-word substring.\n- Each field carries a weight, so a hit in the title counts for much more than a hit in the description:\n\n| Field | Weight |\n| --- | --- |\n| `title` | 10 |\n| `path` | 6 |\n| `keywords` | 5 |\n| `description` | 3 |\n| `openGraph` | 2 |\n\n- The `path` field is made word-searchable: `/get-started` matches \"get\" or \"started\".\n- Results are sorted by score (highest first), ties broken alphabetically by path for a stable order, then cut to `limit` if you set one.\n\n## Gotchas\n\n- **Only static metadata is indexed.** A route whose title comes from `generateMetadata` is invisible to search until you add `searchHints`.\n- **Dynamic routes are excluded by default.** They need `includeDynamic: true` to appear, and even then `goTo` cannot navigate to one without concrete params.\n- **The index is built, not live.** It reflects the metadata at build time. Add a route or change a title and you must rebuild for search to see it.\n- **A blank query returns no results**, not every page. If you want an \"all pages\" listing for an empty box, render `pages` yourself.\n- **The type names** `PageSearchResult`, `PageSearchOptions`, and `SearchField` are importable from `'toiljs/client'` if you need to annotate them; `PageMeta` and `SearchHints` are also available as `Toil.PageMeta` / `Toil.SearchHints`.\n\n## Related\n\n- [Metadata and SEO](./metadata.md): the `metadata` the search index is built from.\n- [Routing](./routing.md): route paths, dynamic segments, and `navigate` (what `goTo` calls).\n- [Rendering and SSR](./rendering.md): where `searchHints` fits alongside `generateStaticParams` for dynamic routes.\n- [Frontend overview](./README.md): the `Toil` global and the rest of the client API.\n",
|
|
42
42
|
"frontend/styling.md": "# Styling\n\ntoiljs does not force a styling system on you. It builds with Vite, so you style your app the way you would any Vite + React project: plain CSS, CSS Modules, a preprocessor like Sass, or Tailwind. This page shows the practical options.\n\n## The one import that matters\n\nYour app pulls in global styles from the entry file, `client/toil.tsx`:\n\n```tsx\n// client/toil.tsx\nimport { routes, layout, notFound, globalError, slots } from 'toiljs/routes';\nimport './styles/main.css'; // <- your global stylesheet\n\nToil.mount(routes, layout, notFound, globalError, slots);\n```\n\nThat one import is where your global CSS (resets, CSS variables, base element styles) lives. A typical `client/styles/main.css` sets up theme variables and base styles:\n\n```css\n/* client/styles/main.css */\n:root {\n --accent: #2563ff;\n --bg: #080d11;\n --text: #f5f6fa;\n}\n\n*, *::before, *::after {\n box-sizing: border-box;\n}\n\nbody {\n margin: 0;\n background: var(--bg);\n color: var(--text);\n font-family: system-ui, -apple-system, sans-serif;\n line-height: 1.6;\n}\n```\n\n## Importing CSS anywhere\n\nYou are not limited to the one global sheet. Any component can import its own CSS, and Vite bundles it in:\n\n```tsx\n// client/components/Card.tsx\nimport './card.css';\n\nexport default function Card() {\n return <div className=\"card\">...</div>;\n}\n```\n\nThe class names in a plain `.css` file are global (they apply everywhere), so name them carefully to avoid clashes, or reach for CSS Modules.\n\n## CSS Modules (scoped class names)\n\nIf you name a file `*.module.css`, Vite treats it as a **CSS Module**: the class names are locally scoped to the component that imports them, so two components can both use `.title` without colliding. You import the generated names as an object:\n\n```tsx\n// client/components/Card.tsx\nimport styles from './card.module.css';\n\nexport default function Card() {\n return <div className={styles.card}>...</div>;\n}\n```\n\n```css\n/* client/components/card.module.css */\n.card {\n padding: 1rem;\n border: 1px solid var(--border);\n}\n```\n\nThis is the simplest way to get component-scoped styles with no extra tooling.\n\n## Preprocessors: Sass, Less, Stylus\n\nWhen you create a project (`toiljs create`) you can pick a CSS preprocessor, or add one later to an existing project with the `configure` command:\n\n```sh\ntoiljs configure # interactive: choose preprocessor + Tailwind\ntoiljs configure --style sass # switch the preprocessor to Sass\n```\n\n`configure` installs the right packages and rewrites your style imports for you. After that, `.scss` / `.sass` / `.less` / `.styl` files import exactly like `.css` files, and Vite compiles them.\n\n## Tailwind\n\nTo use Tailwind (a utility-class CSS framework), turn it on at create time or add it later:\n\n```sh\ntoiljs configure --tailwind\n```\n\nTailwind lives in its own stylesheet, `client/styles/tailwind.css`, which is just:\n\n```css\n@import \"tailwindcss\";\n```\n\n`configure` wires that import in for you. From then on you use Tailwind's utility classes directly in your JSX:\n\n```tsx\nexport default function Cta() {\n return (\n <button className=\"rounded-lg bg-blue-600 px-4 py-2 font-semibold text-white\">\n Get started\n </button>\n );\n}\n```\n\n## Inline styles and the `style` prop\n\nStandard React inline styles work as always, and are handy for one-off, dynamic values:\n\n```tsx\n<div style={{ padding: 24, background: 'var(--surface)' }}>...</div>\n```\n\nPrefer classes for anything reused; keep inline styles for the truly per-instance case (a computed width, a dynamic color).\n\n## How types work for style and asset imports\n\ntoiljs generates a `toil-env.d.ts` in your project that declares the import types, so TypeScript is happy importing stylesheets and images. It covers `.css`, `.scss`, `.sass`, `.less`, `.styl` (and friends), plus image formats (`.svg`, `.png`, `.jpg`, `.webp`, and so on), which import as a URL string:\n\n```tsx\nimport logoUrl from './logo.svg'; // logoUrl is a string (a hashed URL)\n\n<img src={logoUrl} alt=\"Logo\" />\n```\n\nFor images specifically, prefer the `Toil.Image` component and its `?toil` import, which also handle layout shift and blur placeholders (see [Images](./images.md)).\n\n## Gotchas\n\n- **Global CSS is global.** A plain `.css` import puts its class names in one shared namespace. If two files both define `.button`, the last one loaded wins. Use `*.module.css` (CSS Modules) or unique prefixes to scope styles.\n- **Do not hand-edit `toil-env.d.ts`.** It is generated. If a new file type is not recognized, re-run the build so it regenerates, rather than editing it.\n- **Preprocessor packages are managed by `configure`.** Add or switch preprocessors through `toiljs configure` so the packages and imports stay in sync; do not install them by hand.\n\n## Related\n\n- [Images](./images.md): the `Toil.Image` component and image imports.\n- [The CLI](../cli/README.md): `toiljs configure` and every flag.\n- [Frontend overview](./README.md): where styles fit in the `client/` folder.\n",
|
|
43
|
-
"frontend/testing.md": "# Testing and coverage\n\nEvery new toiljs project includes Vitest 5, real Chrome tests through WebdriverIO,\nand V8 coverage. The `app`, `minimal`, and `agent` templates install the tooling\nalongside the other development dependencies. Tests run without starting or building\nany backend.\n\n## Run the starter examples\n\n```bash\nnpm test # Node unit tests\nnpm run test:watch # Watch unit tests\nnpm run test:browser # Real Chrome component tests\nnpm run test:all # Unit and browser projects together\nnpm run test:coverage # Both projects with coverage\n```\n\nThe starter includes `client/lib/Greeting.ts` and `tests/Greeting.test.ts`:\n\n```ts\nimport { expect, test } from 'vitest';\nimport { Greeting } from '../client/lib/Greeting';\n\ntest('trims the supplied name', (): void => {\n expect(Greeting.message(' Ada ')).toBe('Hello, Ada!');\n});\n```\n\n`tests/TestCounter.browser.test.tsx` renders a React component, clicks its button\nwith `page` from `vitest/browser`, and checks that its count changes. It also uses\n`Toil.Link`, so the example verifies framework globals in a real browser.\n\n## Configuration and globals\n\nThe generated `vitest.config.ts` uses the framework preset:\n\n```ts\nimport { defineTestConfig } from 'toiljs/vitest';\n\nexport default defineTestConfig();\n```\n\nThe preset installs the React Vite plugin, `client` and `shared` aliases, and two\nindependent projects:\n\n| Project | Files | Runtime |\n| --- | --- | --- |\n| `unit` | `*.test.ts`, `*.test.tsx`, `*.spec.ts`, `*.spec.tsx` under `client/`, `tests/`, or `test/`, excluding browser files | Node |\n| `browser` | `*.browser.test.ts`, `*.browser.test.tsx`, `*.browser.spec.ts`, `*.browser.spec.tsx` under those folders | Chrome, headless by default |\n\nThe setup registers `Toil`, `Server`, `FastMap`, `FastSet`, `DataWriter`, `DataReader`,\nand `parseError`. It does not mount the app or launch a server. Mock typed backend\ncalls with Vitest mocks or `vi.stubGlobal('Server', yourTypedMock)` when a component\nneeds data. AssemblyScript backend code requires separate WebAssembly integration\ntests; this preset covers JavaScript and React code.\n\nPass `root`, `headless`, `chromeArgs`, `unitInclude`, or `browserInclude` to\n`defineTestConfig` to change its defaults. Use `mergeConfig` from `vitest/config`\nfor other settings, such as coverage thresholds or additional aliases. A custom\nconfiguration can import `toiljs/vitest/setup` as a setup file to register globals.\n\nThe preset uses WebDriver Classic for compatibility with Vitest iframe control on\nWebdriverIO 10. WebdriverIO discovers Chrome and manages ChromeDriver automatically. To use specific\nbinaries, set `TOIL_TEST_BROWSER_BINARY` and `TOIL_TEST_CHROMEDRIVER` independently.\nBrowser tests need Chrome and its OS libraries installed; downloads need network\naccess on the first run. A Linux container running as root may need explicit\n`chromeArgs: ['--no-sandbox']` in its test config.\n\n## Coverage reports\n\n`npm run test:coverage` produces terminal, HTML, JSON, and LCOV reports under\n`coverage/`. Open `coverage/index.html` for the HTML report. Client and shared\nTypeScript files are included, with declarations, tests, and generated\n`shared/server.ts` excluded. The `coverage/` directory is gitignored.\n\n```bash\nnpx toiljs test --coverage # Unit coverage only\nnpx toiljs test --browser --coverage # Browser coverage only\nnpx toiljs test --all --coverage --coverage.thresholds.lines=80\n```\n\n## Existing projects\n\nAfter updating toiljs, run `npx toiljs test`. It adds missing test scripts and installs\nmissing Vitest, WebdriverIO, and coverage packages using your project's package\nmanager. If no Vitest or Vite config exists, it also writes the preset and examples,\npreserving any files already at those paths. Existing custom configs and scripts\nare preserved. Keep Vitest and `@vitest/coverage-v8` on matching versions; the native\npreset requires Vitest 5.\n\nOther Vitest options and file filters pass through:\n\n```bash\nnpx toiljs test Greeting\nnpx toiljs test --browser TestCounter\nnpx toiljs test --all --reporter=verbose\nnpx toiljs test --root ./apps/dashboard --watch\n```\n\nWith the native preset, `toiljs test` selects `unit`, `--browser` selects `browser`,\nand `--all` runs both. An explicit `--project` selects your requested project.\nA custom configuration keeps its own project selection; `--browser` is forwarded\nto Vitest. Failed tests return a nonzero exit status for CI.\n",
|
|
43
|
+
"frontend/testing.md": "# Testing and coverage\n\nEvery new toiljs project includes Vitest 5, real Chrome tests through WebdriverIO,\nand V8 coverage. The `app`, `minimal`, and `agent` templates install the tooling\nalongside the other development dependencies. Tests run without starting or building\nany backend.\n\n## Run the starter examples\n\n```bash\nnpm test # Node unit tests\nnpm run test:watch # Watch unit tests\nnpm run test:browser # Real Chrome component tests\nnpm run test:all # Unit and browser projects together\nnpm run test:coverage # Both projects with coverage\n```\n\nThe starter includes `client/lib/Greeting.ts` and `tests/Greeting.test.ts`:\n\n```ts\nimport { expect, test } from 'vitest';\nimport { Greeting } from '../client/lib/Greeting';\n\ntest('trims the supplied name', (): void => {\n expect(Greeting.message(' Ada ')).toBe('Hello, Ada!');\n});\n```\n\n`tests/TestCounter.browser.test.tsx` renders a React component, clicks its button\nwith `page` from `vitest/browser`, and checks that its count changes. It also uses\n`Toil.Link`, so the example verifies framework globals in a real browser.\n\n## Configuration and globals\n\nThe generated `vitest.config.ts` uses the framework preset:\n\n```ts\nimport { defineTestConfig } from 'toiljs/vitest';\n\nexport default defineTestConfig();\n```\n\nThe preset installs the React Vite plugin, `client` and `shared` aliases, and two\nindependent projects:\n\n| Project | Files | Runtime |\n| --- | --- | --- |\n| `unit` | `*.test.ts`, `*.test.tsx`, `*.spec.ts`, `*.spec.tsx` under `client/`, `tests/`, or `test/`, excluding browser files | Node |\n| `browser` | `*.browser.test.ts`, `*.browser.test.tsx`, `*.browser.spec.ts`, `*.browser.spec.tsx` under those folders | Chrome, headless by default |\n\nThe setup registers `Toil`, `Server`, `FastMap`, `FastSet`, `DataWriter`, `DataReader`,\nand `parseError`. It does not mount the app or launch a server. Mock typed backend\ncalls with Vitest mocks or `vi.stubGlobal('Server', yourTypedMock)` when a component\nneeds data. AssemblyScript backend code requires separate WebAssembly integration\ntests; this preset covers JavaScript and React code.\n\nPass `root`, `headless`, `chromeArgs`, `unitInclude`, or `browserInclude` to\n`defineTestConfig` to change its defaults. Use `mergeConfig` from `vitest/config`\nfor other settings, such as coverage thresholds or additional aliases. A custom\nconfiguration can import `toiljs/vitest/setup` as a setup file to register globals.\n\nThe preset uses WebDriver Classic for compatibility with Vitest iframe control on\nWebdriverIO 10. WebdriverIO discovers Chrome and manages ChromeDriver automatically. To use specific\nbinaries, set `TOIL_TEST_BROWSER_BINARY` and `TOIL_TEST_CHROMEDRIVER` independently.\nBrowser tests need Chrome and its OS libraries installed; downloads need network\naccess on the first run. A Linux container running as root may need explicit\n`chromeArgs: ['--no-sandbox']` in its test config.\n\n## Coverage reports\n\n`npm run test:coverage` produces terminal, HTML, JSON, and LCOV reports under\n`coverage/`. Open `coverage/index.html` for the HTML report. Client and shared\nTypeScript files are included, with declarations, tests, and generated\n`shared/server.ts` excluded. The `coverage/` directory is gitignored.\n\n```bash\nnpx toiljs test --coverage # Unit coverage only\nnpx toiljs test --browser --coverage # Browser coverage only\nnpx toiljs test --all --coverage --coverage.thresholds.lines=80\n```\n\n## Existing projects\n\nAfter updating toiljs, run `npx toiljs test`. It adds missing test scripts and installs\nmissing Vitest, WebdriverIO, and coverage packages using your project's package\nmanager. If no Vitest or Vite config exists, it also writes the preset and examples,\npreserving any files already at those paths. Existing custom configs and scripts\nare preserved. Keep Vitest and `@vitest/coverage-v8` on matching versions; the native\npreset requires Vitest 5.\n\nOther Vitest options and file filters pass through:\n\n```bash\nnpx toiljs test Greeting\nnpx toiljs test --browser TestCounter\nnpx toiljs test --all --reporter=verbose\nnpx toiljs test --root ./apps/dashboard --watch\n```\n\nWith the native preset, `toiljs test` selects `unit`, `--browser` selects `browser`,\nand `--all` runs both. An explicit `--project` selects your requested project.\nA custom configuration keeps its own project selection; `--browser` is forwarded\nto Vitest. Failed tests return a nonzero exit status for CI.\n\n## Diagnose and repair setup\n\n`npx toiljs doctor` has a Testing report covering actual installed versions, declared\nranges, matching Vitest and coverage releases, config presence, and scripts.\nUnconfigured legacy projects get warnings; broken configured packages fail the check.\n`--json` includes the same Testing group for CI.\n\n`npx toiljs doctor --fix` repairs missing tooling, scripts, config, and examples;\nadds tests to plain JSON TypeScript includes; and ignores test reports. It preserves\ncustom configs, existing files, nonempty scripts, and explicit dependency sources.\nInstall afterward, or run `npx toiljs test` to install missing packages. Doctor never\nlaunches a browser or installs packages itself.\n\n`npx toiljs update --yes` includes testing setup and installation even when no package\nupdates are available. Native updates retain supported runner/browser majors and\nkeep Vitest and V8 coverage on matching releases. Interactive updates add setup only\nwhen updates are accepted.\n",
|
|
44
44
|
"frontend/toil-global.md": "# The Toil global (reference)\n\nAlmost the entire toiljs client API hangs off one global object, `Toil`, so your route files need no imports for the everyday things. This page is the full index: every `Toil.*` member, its TypeScript signature, and a one-line description, grouped by area. Reach for it when you know roughly what you want and just need the exact name or shape; the deeper guides (linked per section) explain the concepts.\n\n`Toil` is not a hand-written object. It is the module namespace of the `toiljs/client` package, exposed as a global. The compiler writes a `toil-env.d.ts` at your project root containing `declare const Toil: typeof import('toiljs/client')`, so `Toil` is typed as the whole package and every member autocompletes with no import. Put another way: anything you could `import { X } from 'toiljs/client'` is reachable as `Toil.X`.\n\nA few members are also bare globals, handed to you the same way. `Server` (the typed backend client) and `parseError` are global on their own and additionally live under `Toil` (so `Server` and `Toil.Server` are the same value, likewise `parseError`). `FastMap`, `FastSet`, `DataWriter`, and `DataReader` (the fast collections and compact binary codec from `toiljs/io`, see [Data types](../backend/data.md)) are bare globals only: they are not under `Toil`, so write `new DataWriter()`, never `new Toil.DataWriter()`.\n\n## Components (JSX)\n\nDrop-in components you render in JSX. See [Images](./images.md), [Scripts](./scripts.md), [Fetching data](./data-fetching.md), and [Metadata and SEO](./metadata.md) for the detail.\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `Toil.Image` | `Image(props: ImageProps): ReactNode` | A drop-in `<img>` that reserves space (no layout shift), lazy-loads, and can fade in from a blur placeholder. See [Images](./images.md). |\n| `Toil.Script` | `Script(props: ScriptProps): ReactNode` | Loads an external or inline `<script>` with a load `strategy`, deduplicated so it runs at most once. Renders nothing. See [Scripts](./scripts.md). |\n| `Toil.Form` | `Form(props: FormProps): ReactNode` | A `<form>` that runs an action on submit (no reload) and revalidates loader data on success. See [Fetching data](./data-fetching.md). |\n| `Toil.Slot` | `Slot(props: SlotProps): ReactNode` | Renders the parallel-route slot named `props.name` (`{ name: string; fallback?: ReactNode }`) for the current URL. See [Routing](./routing.md). |\n| `Toil.Head` | `Head(props: HeadSpec): null` | Declarative form of `useHead`: `<Toil.Head title=\"...\" meta={[...]} />`. Renders nothing. See [Metadata and SEO](./metadata.md). |\n| `Toil.Metadata` | `Metadata(props: Metadata): null` | Declarative form of `useMetadata`: `<Toil.Metadata title=\"...\" openGraph={...} />`. Renders nothing. See [Metadata and SEO](./metadata.md). |\n| `Toil.Router` | `Router(props: { routes; layout?; notFound?; globalError?; slots? }): ReactNode` | The app router element. `Toil.mount` renders this for you, so you rarely use it directly. |\n\n## SSR marker primitives\n\nThese mark a route's dynamic bits so the edge can server-render it (the compiler's template extractor finds them deterministically). They are transparent at runtime: in the browser they render exactly the normal tree. See [Components](./components.md) and [Rendering and SSR](./rendering.md).\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `Toil.Hole` | `Hole(props: HoleProps): ReactNode` | A scalar text hole (`{ id: string; children?: ReactNode }`); renders `children` in the browser, a text insertion point under the SSR extractor. |\n| `Toil.Repeat` | `Repeat<T>(props: RepeatProps<T>): ReactNode` | A repeat region (`{ id; each: readonly T[]; children: (item: T, index: number) => ReactNode }`); `each.map(children)` in the browser. |\n| `Toil.RawHtml` | `RawHtml(props: RawHtmlProps): ReactNode` | A raw-HTML block hole (`{ id; html; as? }`); wraps `dangerouslySetInnerHTML` in a host element (default `div`). |\n| `Toil.attr` | `attr(id: string, value: string): string` | An attribute-value hole, used in attribute position (`href={Toil.attr('u', d.url)}`); returns `value` unchanged in the browser. |\n| `Toil.Island` | `Island(props: IslandProps): ReactNode` | A client-only escape hatch (`{ children? }`); renders nothing on the server and first paint, then reveals `children` after mount (so no SSR / SEO, by design). |\n\n## Navigation\n\nClient-side links and imperative navigation. Detailed guide: [Navigation](./navigation.md).\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `Toil.Link` | `Link(props: LinkProps): ReactNode` | A client-side navigation `<a>`: no full reload, prefetches on hover/focus, falls through to the browser for external / `target` / `download` / `#hash` links. |\n| `Toil.NavLink` | `NavLink(props: NavLinkProps): ReactNode` | A `Link` that adds an active class (default `\"active\"`) and `aria-current=\"page\"` when it points at the current page. |\n| `Toil.matchActive` | `matchActive(linkPath: string, currentPath: string, end: boolean): boolean` | Whether a link to `linkPath` is active for `currentPath` (the pure rule behind `NavLink`). |\n| `Toil.navigate` | `navigate(href: Href, options?: NavigateOptions): void` | Navigate in code (no hook needed), pushing history or replacing with `{ replace: true }`. |\n| `Toil.back` | `back(): void` | Go back one history entry. |\n| `Toil.forward` | `forward(): void` | Go forward one history entry. |\n| `Toil.refresh` | `refresh(): void` | Re-render the current route and re-run its loader (its `loading.tsx` shows while it re-fetches). |\n| `Toil.href` | `href(path: string): Href` | Assert a runtime-built string is a valid `Href`. Escape hatch for a path assembled from data. |\n| `Toil.prefetch` | `prefetch(href: string): void` | Warm a route's chunk ahead of navigation; a no-op for external, unknown, or already-warmed targets. |\n| `Toil.setViewTransitions` | `setViewTransitions(enabled: boolean): void` | Enable animated View Transitions for navigation (normally set once from `client.viewTransitions`). |\n| `Toil.setTransitions` | `setTransitions(enabled: boolean): void` | Keep the current page visible until the next route is ready (normally set once from `client.transitions`). |\n\n## Routing and location hooks\n\nHooks a component uses to read where it is and to get a router handle.\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `Toil.useParams` | `useParams<T extends RouteParams = RouteParams>(): T` | Read the dynamic route params, e.g. `{ id }` for `/blog/[id]`. Values are always strings. |\n| `Toil.useNavigate` | `useNavigate(): (href: Href, options?: NavigateOptions) => void` | Returns the bare `navigate` function. |\n| `Toil.useRouter` | `useRouter(): RouterInstance` | Returns the router handle (`push` / `replace` / `back` / `forward` / `refresh` / `revalidate` / `prefetch`). |\n| `Toil.useLocation` | `useLocation(): string` | The current pathname, re-read on each navigation (an alias of `usePathname`). |\n| `Toil.usePathname` | `usePathname(): string` | The current pathname, e.g. `\"/blog/42\"`. |\n| `Toil.useSearchParams` | `useSearchParams(): URLSearchParams` | The query string as a `URLSearchParams`, re-read on each navigation. |\n| `Toil.useNavigationPending` | `useNavigationPending(): boolean` | `true` while a navigation is in flight (drives a top loading bar). |\n| `Toil.matchRoute` | `matchRoute(pattern: string, pathname: string): RouteParams \\| null` | Pure route matcher: extract params, or `null` if the pattern does not match. |\n\n## Route data and mutations\n\nLoaders read data on navigation, actions write it. See [Fetching data](./data-fetching.md).\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `Toil.useLoaderData` | `useLoaderData<L extends LoaderFunction>(loader: L): Awaited<ReturnType<L>>` | Read the data the current route's `loader` returned. Passing `loader` infers the type; the no-arg `useLoaderData<T>()` returns `unknown` unless you supply `T`. |\n| `Toil.revalidate` | `revalidate(href?: string): void` | Invalidate loader data and re-render so the active route (or a given href) re-fetches. Call after a mutation; usable outside React. |\n| `Toil.invalidateLoaderData` | `invalidateLoaderData(href?: string): void` | Drop cached loader data (all routes, or one href) without re-rendering. |\n| `Toil.useAction` | `useAction<TInput = void, TData = unknown>(fn: (input: TInput) => TData \\| Promise<TData>, options?: UseActionOptions<TData>): ActionHandle<TInput, TData>` | Run a mutation with pending / error / result tracking; revalidates loader data on success. |\n\n## Head and metadata\n\nSet the document `<head>` from a component or resolve a route's metadata. See [Metadata and SEO](./metadata.md).\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `Toil.useHead` | `useHead(spec: HeadSpec): void` | Apply a title / `<meta>` / `<link>` contribution for the component's lifetime (reverts on unmount, composes across the tree). |\n| `Toil.useTitle` | `useTitle(title: string): void` | Set `document.title` for the component's lifetime. |\n| `Toil.mergeHead` | `mergeHead(specs: readonly HeadSpec[]): ResolvedHead` | Merge head specs in order: the last `title` wins, `meta` dedupes by name/property, `link` by rel+href. |\n| `Toil.useMetadata` | `useMetadata(metadata: Metadata): void` | Apply a route-style `Metadata` object from inside any component (the runtime counterpart of a route's `metadata` export). |\n| `Toil.resolveMetadata` | `resolveMetadata(metadata: Metadata): HeadSpec` | Expand a `Metadata` object into a title plus concrete `<meta>` / `<link>` tags. |\n\n## Page search\n\nQuery the statically-baked index of your pages' metadata. See [Search](./search.md).\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `Toil.searchPages` | `searchPages(query: string, options?: PageSearchOptions): PageSearchResult[]` | Rank the registered page index against a query (pure, framework-agnostic; AND semantics across terms). |\n| `Toil.usePageSearch` | `usePageSearch(query: string, options?: PageSearchOptions): PageSearch` | React binding for `searchPages`, memoized, with a `goTo` helper that navigates to a match. |\n| `Toil.registerPages` | `registerPages(pages: readonly PageMeta[]): void` | Replace the live page index. Called once at startup by the generated bundle; rarely called by user code. |\n| `Toil.getPages` | `getPages(): readonly PageMeta[]` | The registered page index (every page, including dynamic ones). Empty before registration. |\n| `Toil.pagePath` | `pagePath(target: string \\| PageMeta \\| PageSearchResult): string` | Normalize a result / page / raw path to its route path string. |\n\n## Realtime\n\nOpen a channel or a typed stream to the backend. See [Channels](../realtime/channels.md) and [Streams](../realtime/streams.md).\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `Toil.connectChannel` | `connectChannel(onMessage: (data: ChannelData) => void, options?: ChannelOptions): Channel` | Open a WebSocket channel to the backend, invoking `onMessage` per frame; returns `send` / `close`. |\n| `Toil.useChannel` | `useChannel(options?: ChannelOptions): ChannelHook` | React hook over `connectChannel`: connects on mount, tracks `connected` + `messages`, exposes `send`. |\n| `Toil.resolveChannelUrl` | `resolveChannelUrl(path?: string, location?: { protocol: string; host: string }): string` | Derive the channel's `ws(s)://` URL from the current page location. |\n| `Toil.makeStreamClient` | `makeStreamClient(routes: Record<string, string>, origin?: string, encoders?: Record<string, (msg: never) => Uint8Array>): StreamClient` | Build the `Server.Stream` client from the generated route map. Used by generated code; rarely called directly. |\n\n## Backend client, errors, bootstrap\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `Toil.Server` | `Server.REST.<controller>.<route>(args)` / `Server.Stream.<class>.connect(path?)` / `Server.<service>.<method>(args)` | The typed backend surface. Its shape is generated into `shared/server.ts`; this global is the runtime behind it. See [Fetching data](./data-fetching.md). |\n| `Toil.parseError` | `parseError(err: unknown): string` | Extract a human-readable message from an unknown thrown value: `Error.message`, else `String(err)`. |\n| `Toil.mount` | `mount(routes: RouteDef[], layout?: LayoutLoader, notFound?: NotFoundLoader, globalError?: ErrorComponentLoader, slots?: Record<string, RouteDef[]>): void` | Boot the app into `#root` and start idle link prefetching. Called by the generated entry file; you rarely call it yourself. |\n\n## Auth\n\nThe whole password-auth client lives under `Toil.Auth`, and its error family is exposed on `Toil.*` (and mirrored under `Toil.Auth.*`) so you can branch on it. The password never leaves the browser. See [Auth](../auth/README.md).\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `Toil.Auth.register` | `register(username: string, password: string, email: string, opts?: AuthOptions): Promise<void>` | Create an account (only a derived public key is ever sent). Throws `UsernameTakenError` / `EmailInUseError` on conflict. |\n| `Toil.Auth.login` | `login(username: string, password: string, opts?: AuthOptions): Promise<Uint8Array>` | Log in with mutual authentication; resolves to the opaque session token. |\n| `Toil.Auth.confirmEmail` | `confirmEmail(token: string, opts?: AuthOptions): Promise<void>` | Confirm an account from the one-time token in the emailed link. |\n| `Toil.Auth.resendConfirmation` | `resendConfirmation(email: string, opts?: AuthOptions): Promise<void>` | Ask the server to re-send the confirmation email (never reveals whether the address exists). |\n| `Toil.Auth.requestPasswordReset` | `requestPasswordReset(email: string, opts?: AuthOptions): Promise<void>` | Begin a password reset by emailing a link (anti-enumeration: always resolves). |\n| `Toil.Auth.resetPassword` | `resetPassword(token: string, newPassword: string, opts?: AuthOptions): Promise<void>` | Complete a reset from the token plus a new password. |\n| `Toil.Auth.verifyTwoFactor` | `verifyTwoFactor(twoFaId: string, code: string, opts?: AuthOptions): Promise<Uint8Array>` | Finish a 2FA login by submitting the code for `twoFaId`; resolves to the session token. |\n| `Toil.Auth.setupTwoFactor` | `setupTwoFactor(method: number, opts?: AuthOptions): Promise<void>` | Begin enabling or disabling 2FA for the current session user (pass a `TwoFactorMethod` value). |\n| `Toil.Auth.confirmTwoFactorSetup` | `confirmTwoFactorSetup(code: string, opts?: AuthOptions): Promise<void>` | Confirm a pending `setupTwoFactor` with the delivered code. |\n| `Toil.Auth.twoFactorStatus` | `twoFactorStatus(opts?: AuthOptions): Promise<number>` | The current user's 2FA method (a `TwoFactorMethod` value; `0` means off). |\n| `Toil.Auth.TwoFactorMethod` | `{ None: 0, Email: 1 }` | The 2FA method values, mirroring the server enum. |\n\nThe typed error surface (each error carries a stable `code`, and every subclass maps 1:1 to an `AuthErrorCode`):\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `Toil.AuthError` | `class AuthError extends Error { readonly code: AuthErrorCode }` | Base class for every auth error. `err instanceof Toil.AuthError` narrows the whole family; `err.code` discriminates within it. |\n| `Toil.AuthErrorCode` | `enum AuthErrorCode` (string values) | The stable, machine-readable discriminant carried as `err.code`. Branch on this, never on `err.message` or `err.name`. |\n| `Toil.UsernameTakenError` | `class UsernameTakenError extends AuthError` | register: the username is already registered (`AuthErrorCode.UsernameTaken`). |\n| `Toil.EmailInUseError` | `class EmailInUseError extends AuthError` | register: the email is already in use (`AuthErrorCode.EmailInUse`). |\n| `Toil.InvalidCredentialsError` | `class InvalidCredentialsError extends AuthError` | login: wrong username or password (`AuthErrorCode.InvalidCredentials`). |\n| `Toil.EmailNotConfirmedError` | `class EmailNotConfirmedError extends AuthError` | login: valid credential, but the email is not confirmed (`AuthErrorCode.EmailNotConfirmed`). |\n| `Toil.TwoFactorRequiredError` | `class TwoFactorRequiredError extends AuthError { readonly twoFaId: string }` | login: a second factor is required; echo `err.twoFaId` to `verifyTwoFactor` (`AuthErrorCode.TwoFactorRequired`). |\n| `Toil.ServerAuthFailedError` | `class ServerAuthFailedError extends AuthError` | login/2fa: the server failed to prove its identity, possible MITM (`AuthErrorCode.ServerAuthFailed`). |\n| `Toil.TwoFactorCodeError` | `class TwoFactorCodeError extends AuthError` | 2fa: the code was wrong, expired, or already used (`AuthErrorCode.TwoFactorCodeInvalid`). |\n| `Toil.ConfirmationInvalidError` | `class ConfirmationInvalidError extends AuthError` | confirm: the confirmation link was invalid or expired (`AuthErrorCode.ConfirmationInvalid`). |\n| `Toil.PasswordResetInvalidError` | `class PasswordResetInvalidError extends AuthError` | reset: the reset link was invalid or expired (`AuthErrorCode.PasswordResetInvalid`). |\n\nThe `AuthErrorCode` string values:\n\n```ts\nenum AuthErrorCode {\n RequestFailed = 'request_failed',\n ProtocolError = 'protocol_error',\n UsernameTaken = 'username_taken',\n EmailInUse = 'email_in_use',\n RegistrationRejected = 'registration_rejected',\n InvalidCredentials = 'invalid_credentials',\n EmailNotConfirmed = 'email_not_confirmed',\n TwoFactorRequired = 'two_factor_required',\n ServerAuthFailed = 'server_auth_failed',\n TwoFactorCodeInvalid = 'two_factor_code_invalid',\n TwoFactorSetupFailed = 'two_factor_setup_failed',\n ConfirmationInvalid = 'confirmation_invalid',\n PasswordResetInvalid = 'password_reset_invalid',\n}\n```\n\nCatch by class for the common branches, or on `err.code` for a precise check:\n\n```tsx\ntry {\n const token = await Toil.Auth.login(username, password);\n // ... persist the session token and redirect\n} catch (err) {\n if (err instanceof Toil.EmailNotConfirmedError) return promptEmailConfirmation();\n if (err instanceof Toil.TwoFactorRequiredError) return promptTwoFactorCode(err.twoFaId);\n if (err instanceof Toil.AuthError && err.code === Toil.AuthErrorCode.InvalidCredentials) {\n return setError('Incorrect username or password.');\n }\n throw err;\n}\n```\n\n## A note on types\n\nEvery member above is a value, and values on `Toil.*` need no import: `Toil.Link`, `Toil.useParams`, `Toil.Auth.login`. Types are different. In type position, only a small set are namespaced as `Toil.<Type>`, because the generated `toil-env.d.ts` aliases exactly these under a `declare namespace Toil` block:\n\n`LoaderArgs`, `LoaderFunction`, `Revalidate`, `Metadata`, `GenerateMetadata`, `GenerateStaticParams`, `StaticParams`, `RouteErrorProps`, `Href`, `RoutePath`, `PageMeta`, `SearchHints`.\n\nSo a loader can annotate its argument with no import:\n\n```tsx\nexport const loader = async ({ params }: Toil.LoaderArgs) => { /* ... */ };\n```\n\nEvery other exported type (for example `LinkProps`, `NavLinkState`, `NavigateOptions`, `RouterInstance`, `ImageProps`, `ScriptProps`, `FormProps`, `ActionState`) is not in that namespace, so it must be imported from the package:\n\n```tsx\nimport type { ImageProps } from 'toiljs/client';\n```\n\n## Related\n\n- [Navigation](./navigation.md): links, active state, and navigating in code.\n- [Components](./components.md): the built-in components and the SSR marker primitives.\n- [Fetching data](./data-fetching.md): the `Server` client, loaders, actions, and forms.\n- [Metadata and SEO](./metadata.md): titles, descriptions, and social-share tags per route.\n- [Auth](../auth/README.md): the full password-auth client and its error handling.\n",
|
|
45
45
|
"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 7, Oxlint + tsgolint, 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 oxlint.config.ts 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 (dialect checked by toilscript builds)\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\nInstall the **Oxc** plugin for VS Code or WebStorm using the [editor setup instructions](./installation.md#editor-setup).\nThe generated README includes the steps. WebStorm requires **Enable type aware rules** to be\nchecked under **Settings → Tools → Oxlint**, even though the project preset enables them.\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",
|
|
46
46
|
"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",
|
package/package.json
CHANGED