toiljs 0.0.102 → 0.0.104

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.
@@ -1,41 +1,57 @@
1
1
  # Understanding toil
2
2
 
3
- This section is the "why." It explains what toil is, the problem it solves, how it works underneath, and why it is built the way it is. If you read nothing else first, read this: it is what turns "another framework" into "oh, that is the point."
3
+ toil is one framework for a whole web app: the frontend, the backend, and the database, in a single project, all running close to your users.
4
4
 
5
- ## The one big idea
5
+ You write React for the client and TypeScript for the server. toil compiles the server to WebAssembly and runs it at the edge, next to whoever is asking. Auth, database, email, realtime, and background jobs are already built in. Nothing to wire up, nothing to configure. A pizza site gets the same infrastructure a funded team would rent from ten separate vendors.
6
6
 
7
- Almost every website has a split personality. The pretty part (pages, buttons) is served from servers all over the world, close to you. The important part (the database, where your data lives and changes) sits in **one place**, one region, often one machine. Post a comment in Tokyo when the database is in Virginia and your click flies halfway around the planet and back before anything happens.
7
+ This section explains what that means and why it holds up. Start here, then follow the links at the end.
8
8
 
9
- toil removes the split. Your **frontend** (React) and **backend** (TypeScript, compiled to a tiny WebAssembly program) both run at the **edge**, near your users, and **ToilDB** is distributed too, so writes do not travel to one far-away box. One language, one project, one deploy, running close to everyone.
9
+ ## What toil is
10
+
11
+ A toil project has three parts in one folder:
12
+
13
+ - `client/` is your React app: file-based routing, data loaders, and a typed client for calling the server.
14
+ - `server/` is your backend, plain TypeScript marked up with decorators like `@rest`, `@data`, and `@auth`. toil compiles it to a small sandboxed WebAssembly module.
15
+ - ToilDB is your database. It is already there. No connection string, no instance to spin up.
16
+
17
+ Types tie the three together. Change a field on the server and the client stops compiling until you fix it. Edge deployment, post-quantum login, and asset tamper-proofing are on by default, not features you bolt on later.
18
+
19
+ ## The problem it solves
20
+
21
+ Most apps read fast and write slow. Pages load from caches everywhere, but a write, say a comment or an order, has to reach one database in one region. A user in Tokyo writing to Virginia waits for the trip there and back. That single region is also a single point of failure.
22
+
23
+ toil closes the gap in two moves. Your code runs at the edge instead of one origin. And ToilDB is built to distribute the writes, not just the reads: every key has a home region that orders its writes, while the data copies outward so reads stay local and quick.
24
+
25
+ Spreading reads is easy, and everyone does it. Spreading writes is the hard part, and it is why most "global" apps are only global for reading.
10
26
 
11
27
  ```mermaid
12
28
  flowchart TB
13
29
  subgraph Ordinary["The ordinary web"]
14
- U1["User in Tokyo"] -->|fast| E1["Edge (pretty part)"]
30
+ U1["User in Tokyo"] -->|fast| E1["Edge (pages)"]
15
31
  E1 -->|slow, one region| DB1[("Database in Virginia")]
16
32
  end
17
33
  subgraph Toil["toil"]
18
- U2["User in Tokyo"] -->|fast| E2["Edge near Tokyo<br/>(frontend + backend + data)"]
34
+ U2["User in Tokyo"] -->|fast| E2["Edge near Tokyo<br/>frontend + backend + data"]
19
35
  end
20
36
  ```
21
37
 
22
- That is the entire pitch. The rest of these pages is how toil pulls it off, and why almost nobody else does.
38
+ ## Where to go from here
23
39
 
24
- ## Read these in order
40
+ Read these in order. Each one builds on the last.
25
41
 
26
- 1. **[Why toil? Who is it for?](./why-toil.md)** The problem with today's stacks, who benefits most, and the honest cases against.
27
- 2. **[The modern stack](./modern-stack.md)** The full catalog of modern tech baked in with zero setup.
28
- 3. **[How toil works](./how-it-works.md)** The whole machine end to end: React client, WebAssembly backend, the edge, ToilDB, the four compute tiers.
29
- 4. **[What makes toil hyper-scalable](./hyperscale.md)** What "hyper-scale" means, and the mechanisms that let one small program serve the planet.
30
- 5. **[How toil is distributed](./distributed.md)** The hardest problem in web infrastructure, distributing the writes, and how ToilDB solves it.
31
- 6. **[toil versus other frameworks](./vs-other-frameworks.md)** An honest comparison with Next.js, Rails, Django, serverless, edge runtimes, and backend-as-a-service.
32
- 7. **[Why toil is built this way (the RSG bar)](./design-principles.md)** The rubric toil grades itself against.
42
+ 1. **[Why toil, and who it is for](./why-toil.md)** What is wrong with a modern stack, who gains most from toil, and the honest cases against it.
43
+ 2. **[What comes built in](./modern-stack.md)** The full list of what toil owns and runs for you, and what it does not.
44
+ 3. **[How toil works](./how-it-works.md)** The whole path, from a React click through WebAssembly to ToilDB and back.
45
+ 4. **[Why it scales cheaply](./hyperscale.md)** How one small program can serve the whole planet without a per-app server bill.
46
+ 5. **[How toil distributes writes](./distributed.md)** The hardest problem in web infrastructure, and how ToilDB is built to solve it.
47
+ 6. **[toil next to other stacks](./vs-other-frameworks.md)** A fair comparison with Next.js, Rails, serverless, and the rest, wins and losses both.
48
+ 7. **[The bar toil holds itself to](./design-principles.md)** The RSG rubric, and its one rule: your grade is your weakest part.
33
49
 
34
- ## The short version
50
+ ## The short answer
35
51
 
36
- - **Who it is for:** people building real products who want global speed and reliability without a platform team or ten stitched-together vendors. See [Why toil](./why-toil.md).
37
- - **Why it is fast:** the code runs next to the user, with no slow trip to a central origin. See [Hyper-scale](./hyperscale.md).
38
- - **Why it is different:** it distributes the writes, not just the reads. See [Distributed](./distributed.md).
39
- - **Why it is safe:** the backend is a sandbox, passwords never reach the server in a usable form, secrets never ship in the code, and the browser verifies every asset it loads. See [Security](../concepts/security.md).
52
+ - **Who it is for:** anyone shipping a real product who wants global speed without a platform team or ten stitched-together services.
53
+ - **Why it is fast:** the code runs next to the user, with no trip to a distant origin.
54
+ - **Why it is different:** it distributes writes, not only reads.
55
+ - **Why it is safe:** the backend is sandboxed, passwords never reach the server in a usable form, secrets never ship in the code, and the browser checks every file it loads.
40
56
 
41
- When you are ready to build, jump to [Getting started](../getting-started/README.md).
57
+ Ready to build? Jump to [Getting started](../getting-started/README.md).
@@ -1,55 +1,81 @@
1
- # Why toil is built this way (the RSG bar)
1
+ # Why toil is built this way
2
2
 
3
- toil is opinionated on purpose. Almost every design decision traces back to one internal rubric it
4
- holds itself against, and this page shows the rubric and the choice each axis forces.
3
+ toil is opinionated on purpose. The purpose is one thing: reach top-tier infrastructure and ship it as the **default**. AAA-grade tech is normally something you assemble from a dozen vendors and keep alive with a team that understands distributed systems. toil makes the good version the version you get out of the box. One framework, zero configuration. A solo builder starts from the same baseline as a funded team.
5
4
 
6
- ## The rubric in a paragraph
5
+ Every decision on this page traces back to that goal. Below is the rubric toil grades itself against, and the choices that rubric forces.
7
6
 
8
- **RSG** (Resilience and Scale Grade) is toil's own internal rubric for how resilient, distributed,
9
- fast, lean, and secure an app really is, scored as a single letter from AAA down to D. It grades
10
- nine axes, and the one rule that matters is this: **your grade is your weakest axis**, never the
11
- average and never the best. To earn AAA, all nine must be AAA at the same time. A globally edged
12
- frontend on a single-region database is capped by the database. A worldwide system serving a
13
- one-second app is capped by latency. The lowest column sets the grade, every time. RSG is not an
14
- external certification and no auditor issues it; it is a design mirror the team holds up to find
15
- the weakest link and fix it first. The full rubric lives at the repository root in
16
- [`RSG.md`](../../RSG.md).
7
+ ## How RSG grades an app
17
8
 
18
- The reason for the weakest-link rule is the most common lie in this space: calling something
19
- "global scale" because the read path is global, while the write path is one box in one region.
20
- Averaging would let that one strong axis hide the weak one. The minimum refuses to.
9
+ RSG stands for Resilience and Scale Grade. It is toil's internal rubric for how resilient and scalable an app really is, scored as a single letter from **AAA** down to **D**. It grades nine axes. One rule runs the whole thing: **your grade is your weakest axis**. Never the average, never the best. To earn AAA, all nine axes must be AAA at once.
21
10
 
22
- ## The nine axes, and the one choice each one forces
11
+ A globally edged frontend on a single-region database is capped by the database. A worldwide system running slow code is capped by latency. The lowest column sets the grade, every time.
23
12
 
24
- Each axis names a way a system can be weak. Each row is the single design decision toil makes so
25
- that axis cannot be the thing that caps it.
13
+ That rule kills the most common lie in this space: calling a system "global scale" because the read path is global, while the write path is one box in one region. Averaging lets the strong axis hide the weak one. The minimum refuses to.
14
+
15
+ RSG is not an external certification, and no auditor issues it. It is a mirror the team holds up to find the weakest link and fix it first. The full rubric lives at the repository root in [`RSG.md`](../../RSG.md).
16
+
17
+ ## What the bar forces
18
+
19
+ Aiming for AAA as the default is not a slogan. It forces a specific set of decisions, and they are why toil looks the way it does.
20
+
21
+ ### The strong version is the default
22
+
23
+ On most stacks the resilient path is a paid tier and the secure path is a checklist for later. toil flips that. The strong version is what you get before you configure anything.
24
+
25
+ - **Post-quantum login in about one line.** Enable auth and a password is stretched through an OPRF (ristretto255) and Argon2id into a deterministic ML-DSA-44 keypair. Only the public key ever leaves the device. Login runs ML-KEM-768 mutual auth, so the client verifies the server too. The password never reaches the server in a usable form. ML-DSA and ML-KEM are the NIST-standardized algorithms built to survive a quantum computer. You do not assemble any of it. You switch it on.
26
+ - **Asset tamper-proofing is on by default.** Every shipped asset carries SHA-384 Subresource Integrity plus importmap integrity. A modified script is rejected by the browser instead of run. There is nothing to remember to turn on.
27
+
28
+ ### Everything is built in and first-party
29
+
30
+ Auth, [ToilDB](../database/README.md), email, rate limiting, analytics, realtime streaming, background jobs, materialized views, scheduled jobs, sessions and cookies, an environment and secrets store. toil builds and owns all of it. This is the Dependencies axis, where zero third-party code on the critical path is what earns AAA. Nothing you cannot inspect or fix sits between a request and its response.
31
+
32
+ ### One framework instead of ten vendors
33
+
34
+ You write a React frontend and a TypeScript backend in one project. toil runs both, plus the database, near your users worldwide. The browser calls the backend through a generated, fully typed `Server` client. There is no hand-written fetch and no drift between the two halves. One project, one deploy, one mental model. The alternative is a frontend host plus an API host plus a database vendor plus an auth vendor plus a queue, each glued to the next.
35
+
36
+ ### Modern foundations
37
+
38
+ toil bets on current technology instead of propping up old tooling.
39
+
40
+ - **WebAssembly backends.** Your TypeScript compiles to a small, sandboxed WASM module. Because the sandbox is real, one edge box safely runs many tenants at once. That multi-tenancy is what makes running near everyone affordable.
41
+ - **Post-quantum crypto** as the login default, not classical crypto you swap out later.
42
+ - **End-to-end types** from the generated `Server` client. A backend change that breaks the frontend is a compile error, not a production 500.
43
+ - **QUIC and WebTransport** carry realtime ([@stream](../concepts/tiers.md)).
44
+
45
+ ### Honest limits
46
+
47
+ toil grades itself on honesty, so the docs state the limits plainly.
48
+
49
+ - **Distributed writes** are real in design and tested in the core. Every key has one home region that orders its writes, while data replicates outward for fast local reads (see [How toil is distributed](./distributed.md)). But live multi-region deployment is configuration-gated, not on by default, and the local dev database is a single in-process store. toil is built to distribute writes worldwide, and the mechanism is real. The claim is not that every app is already running a live global write cluster today.
50
+ - **Compute tiers.** The per-request edge (L1) is live and real. Regional, continental, and global-daemon tiers are opt-in and deployment-gated, not always-on for every app.
51
+ - **Analytics** is real on the edge. The dev server returns sample data.
52
+ - **Auth secrets** ship as clearly insecure dev placeholders so `toiljs dev` just works. A real deployment must set its own.
53
+ - toil is younger than the incumbents, and its integration catalog is smaller. [vs other frameworks](./vs-other-frameworks.md) covers when not to reach for it.
54
+
55
+ ## The nine axes
56
+
57
+ Each axis names a way a system can be weak. Each row is the single design choice toil makes so that axis cannot be the thing that caps it.
26
58
 
27
59
  | RSG axis | What it grades | The toil design choice that hits it |
28
60
  | --- | --- | --- |
29
- | **Topology + distribution** | How close your code runs to users, and in how many places | Edge compute: your frontend and backend both run on nodes next to users, worldwide, not in one origin region. |
61
+ | **Topology + distribution** | How close your code runs to users, and in how many places | Edge compute: frontend and backend both run on nodes next to users, worldwide, not in one origin region. |
30
62
  | **Availability** | What survives a failure | Cross-region failover with no single point of failure, so losing a node or a region does not take the app down. |
31
63
  | **Data path** | Where data is read and *written* (the hard one) | ToilDB's per-key-home model distributes the **writes**, not just the reads. See [How toil is distributed](./distributed.md). |
32
- | **Delivered p99 latency** | The end-to-end time the user actually feels (measured, under 100ms = AAA) | An allocation-free hot path, measured rather than assumed, so the response is fast for real, not just on paper. |
33
- | **Program performance + efficiency** | Hot-path code quality, and cost per request (no brute-forcing latency with a big server bill) | No blocking work on the request path; the fast path does no wasted work, so speed comes from the code, not from overprovisioning. |
64
+ | **Delivered p99 latency** | The end-to-end time the user actually feels | An allocation-free hot path, measured rather than assumed, so the response is fast for real, not just on paper. |
65
+ | **Program performance + architecture** | Hot-path code quality and cost per request (no brute-forcing latency with a big server bill) | No blocking work on the request path; speed comes from the code, not from overprovisioning. |
34
66
  | **Dependencies** | How much of your critical path you own (zero third-party on it = AAA) | An owned, batteries-included stack: nothing third-party sits on the critical request path for you to be unable to inspect or fix. |
35
- | **Security** | How hard the system is to break, and how bad a breach would be | Post-quantum password login (the password never reaches the server in usable form), sandboxed WebAssembly backends, and Subresource Integrity on every asset. See [Security](../concepts/security.md). |
67
+ | **Security** | How hard the system is to break, and how bad a breach would be | Post-quantum password login, sandboxed WebAssembly backends, and Subresource Integrity on every asset. See [Security](../concepts/security.md). |
36
68
  | **Client performance + reach** | How well the shipped app runs on old and low-end devices as data grows | A lean React client: a small bundle and linear-or-better hot paths, so it stays smooth on weak hardware, not just new flagships. |
37
- | **Modern stack + compatibility** | Current protocols, *with* graceful fallback for older clients | HTTP/3, QUIC, and WebTransport where they fit, negotiating down cleanly so nobody one version behind gets a blank screen. |
69
+ | **Modern stack + compatibility** | Current foundations, *with* graceful fallback for older clients | WebAssembly backends, post-quantum auth, and QUIC/WebTransport realtime, negotiating down cleanly so a client one version behind still works. |
38
70
 
39
- ## The punchline
71
+ ## Why every axis has to hold
40
72
 
41
- toil is opinionated because being AAA on **every** axis at once forces these choices. You cannot
42
- reach the top grade with a fast frontend on a centralized database, or a global system running
43
- slow code, or a modern stack that only works on the newest browser. The weakest-link rule closes
44
- every one of those escape hatches. Any framework that lets a single axis slip is, by its own
45
- honest scoring, not AAA. Holding all nine at once is the whole reason toil looks the way it does.
73
+ toil is opinionated because being AAA on every axis at once forces these choices. It delivers them as the default, so you do not have to earn each one by hand. You cannot reach the top grade with a fast frontend on a centralized database. You cannot reach it with a global system running slow code, or a modern stack that only works on the newest browser. The weakest-link rule closes every one of those escape hatches. Any framework that lets a single axis slip is, by its own honest scoring, not AAA. Holding all nine at once, out of the box, is why toil looks the way it does.
46
74
 
47
75
  ## Related
48
76
 
49
- - [How toil is distributed](./distributed.md): the data-path axis in depth, and why distributing
50
- writes is the hard one.
51
- - [What makes toil hyper-scalable](./hyperscale.md): the topology, latency, and program axes in
52
- practice.
77
+ - [How toil is distributed](./distributed.md): the data-path axis in depth, and why distributing writes is the hard one.
78
+ - [What makes toil hyper-scalable](./hyperscale.md): the topology, latency, and program axes in practice.
53
79
  - [Security](../concepts/security.md): the security axis and its hard caps.
54
- - [`RSG.md`](../../RSG.md) at the repository root: the full rubric, the internal mirror this page
55
- summarizes.
80
+ - [vs other frameworks](./vs-other-frameworks.md): the honest "when not to use toil".
81
+ - [`RSG.md`](../../RSG.md) at the repository root: the full rubric, the internal mirror this page summarizes.
@@ -1,35 +1,34 @@
1
1
  # How toil is distributed
2
2
 
3
- Distributing a website's reads is easy. Distributing its writes is the hard part almost nobody solves, and it is why "global" apps are usually only half global. Here is the problem, and how ToilDB (the database built into toil) actually distributes the writes.
3
+ Spreading a website's reads across the world is easy. Spreading its writes is hard, and almost nobody solves it. That is why most "global" apps are only global for reading. This page covers the problem and how ToilDB, the database built into toil, is built to distribute the writes.
4
4
 
5
- ## Reads are easy, writes are hard
5
+ ## Why writes are the hard part
6
6
 
7
- A read never changes anything, so you copy your data to servers worldwide and let each user read the nearest copy. Every copy agrees: a reader in Tokyo and one in Paris both get a fast, local answer.
7
+ A read changes nothing, so you copy your data to servers worldwide and let each user read the nearest copy. Every copy agrees. A reader in Tokyo and one in Paris both get a fast, local answer.
8
8
 
9
- A write is a change, and two writes to the *same thing* can collide. A counter says `10`. Tokyo and Paris both read `10`, both add one, both write `11`. The real answer was `12`, so one add vanished with no error. That is a **write conflict**.
9
+ A write is a change, and two writes to the same key can collide. A counter says `10`. Tokyo and Paris both read `10`, both add one, both write `11`. The real answer was `12`. One add vanished, with no error. That is a **write conflict**.
10
10
 
11
- You could make every copy agree before accepting a write, but the network will eventually split (a **partition**), and the **CAP tradeoff** says that during a split you keep only two of consistency, availability, and partition tolerance. You either refuse the write (correct but unavailable) or accept it on one side and reconcile later (available but briefly inconsistent). Distributing writes is a real tradeoff to design around.
11
+ You could make every copy agree before accepting a write. The network, though, will eventually split into a **partition**. During a split the **CAP tradeoff** says you keep only two of three: consistency, availability, and partition tolerance. You either refuse the write, which is correct but unavailable, or accept it on one side and reconcile later, which is available but briefly inconsistent. Distributing writes is a tradeoff to design around. There is no patch that removes it.
12
12
 
13
+ ## The usual fix: one write database
13
14
 
14
- ## So almost everyone centralizes the write database
15
+ So nearly every stack keeps **one** primary write database in **one** region and spreads only read replicas worldwide. Every write funnels to that one box, one at a time, so conflicts cannot happen.
15
16
 
16
- Faced with that, nearly every stack keeps **one** primary write database in **one** region and spreads only read replicas worldwide. All writes funnel to that one box, one at a time, so conflicts cannot happen.
17
+ It is a reasonable choice. It also hides two costs the [RSG rubric](./design-principles.md) flags on its data-path axis:
17
18
 
18
- It is a reasonable choice, and it hides two costs the [RSG rubric](./design-principles.md) flags on its data-path axis:
19
+ - **Far writes are slow.** Post from Tokyo to a primary in Virginia, and the write crosses the planet and back before anything saves. The page loaded locally. The write did not.
20
+ - **The primary is a single point of failure.** One region holds every write. If it has a bad day, nothing anywhere can change.
19
21
 
20
- - **Far writes are slow.** Post from Tokyo to a primary in Virginia and your write crosses the planet and back before anything saves. The page was local; the action was not.
21
- - **The primary is a single point of failure.** One region holds every write, so if it has a bad day, nothing anywhere can be changed.
22
+ The read path is global. The write path is one machine in one city. Under RSG's weakest-link rule, that single data path caps the whole system.
22
23
 
23
- The read path is global; the write path is one machine in one city. Under RSG's weakest-link rule, that single data path caps the whole system.
24
+ ## ToilDB's model: one home per key
24
25
 
25
- ## ToilDB's answer: every key has a home
26
+ ToilDB gives **every key its own home**. A key is the label you store data under: a user id, a username, a room name. See the [database overview](../database/README.md). Each key gets one home: the single source of truth that orders its writes.
26
27
 
27
- ToilDB gives **every key its own home**. A key is the label you store data under (a user id, a username, a room name; see the [database overview](../database/README.md)). Each key is assigned one home: the single source of truth that orders its writes.
28
+ Two things follow from that:
28
29
 
29
- Two things follow, and together they are the whole trick:
30
-
31
- - **Writes to one key are safe.** Every write to a key travels to that key's home, which **serializes** them (applies them one at a time, in order). Both counter adds are ordered at the counter's home, so the result is `12`. No global lock over the whole database.
32
- - **Writes spread worldwide.** Different keys get different homes, so total write load spreads out. Tokyo users' data can home near Tokyo, Paris users' near Paris. No single box every write funnels through, so no single bottleneck.
30
+ - **Writes to one key are safe.** Every write to a key travels to that key's home, which **serializes** them: it applies them one at a time, in order. Both counter adds are ordered at the counter's home, so the result is `12`. No global lock over the whole database.
31
+ - **Writes spread worldwide.** Different keys get different homes, so total write load spreads out. Tokyo users' data can home near Tokyo, Paris users' near Paris. No single box carries every write, so no single bottleneck.
33
32
 
34
33
  ```mermaid
35
34
  flowchart TB
@@ -40,13 +39,13 @@ flowchart TB
40
39
  KA -.-> KC
41
40
  ```
42
41
 
43
- Reads stay local: each key still replicates out, so a reader anywhere gets a nearby copy. Those copies are **eventually consistent**, meaning that for a brief moment (usually milliseconds) after a write lands at the home, a far read can lag before it catches up. For almost all app data this is invisible; the [database overview](../database/README.md) has the full picture.
42
+ Reads stay local. Each key still replicates outward, so a reader anywhere gets a nearby copy. Those copies are **eventually consistent**. For a brief moment after a write lands at the home, a far read can lag before it catches up. That moment is usually a few milliseconds, and for almost all app data it is invisible. The [database overview](../database/README.md) has the full picture.
44
43
 
45
- Which location owns a key is decided by a shared formula (rendezvous hashing) every node computes the same way, so any node routes a write to the right home with no central coordinator. A key's home can move to follow demand, without rehashing the database.
44
+ A shared formula decides which location owns a key. It is called rendezvous hashing, and every node computes it the same way, so any node routes a write to the right home with no central coordinator. A key's home can move to follow demand, without rehashing the database.
46
45
 
47
- ## The seven families pick the right consistency tool
46
+ ## The seven families
48
47
 
49
- One "home orders the writes" rule fixes the counter, but different jobs want different guarantees. So ToilDB ships **seven families**, each a collection type tuned for one shape of data, each exposing only the operations that are safe and fast for it:
48
+ One "home orders the writes" rule fixes the counter. Different jobs, though, want different guarantees. So ToilDB ships **seven families**. Each is a collection type tuned for one shape of data, and each exposes only the operations that are safe and fast for it.
50
49
 
51
50
  | Family | What it gives |
52
51
  | --- | --- |
@@ -58,13 +57,31 @@ One "home orders the writes" rule fixes the counter, but different jobs want dif
58
57
  | [Membership](../database/membership.md) | Sets of who belongs to what |
59
58
  | [View](../database/views.md) | A read-optimized result a background job builds |
60
59
 
61
- Distributing writes is not one problem with one answer; each family is the right tool for one shape.
60
+ Distributing writes has no single answer. Each family is the right tool for one shape of data.
61
+
62
+ ## The machinery underneath
63
+
64
+ The per-key-home model only works if a lot of unglamorous machinery runs reliably across a flaky network. ToilDB owns all of it:
65
+
66
+ - Per-key **placement** and safe **rehoming**: a rising epoch plus a fencing token, so the old owner stops the instant the new one takes over.
67
+ - Ordered **cross-region replication** with per-stream cursors that detect and backfill gaps.
68
+ - **Idempotent apply**, so a redelivered write cannot double-count.
69
+ - **Capacity escrow** and **tenant quotas**.
70
+ - **Failover** with snapshot re-seeding for a cell that has fallen too far behind.
71
+
72
+ Getting all of these right at once is why truly distributed websites are rare.
73
+
74
+ ## What is real today
75
+
76
+ This is the hard part that almost nobody ships, so here is a straight account of where it stands.
77
+
78
+ The per-key-home model and all its core logic are **built and tested**: placement, rehoming, replication, idempotent apply, escrow, quotas, failover. This is running code, and it passes its tests.
62
79
 
63
- ## The hard machinery toil provides so you do not have to
80
+ The **live multi-region deployment** is not on by default. Wiring many real regions into one running mesh (the WAN routing) and the [ScyllaDB](https://www.scylladb.com/) storage that backs a production cluster are **configuration-gated**. You switch them on for a real deployment. You do not get a live global write cluster automatically. The full database-level **leader fencing** on the write path is also still landing, and a host-side leader gate is the current version. The design is settled. What remains is the last-mile host wiring.
64
81
 
65
- The per-key-home model only works if a lot of unglamorous machinery runs reliably across a flaky network, and ToilDB owns it: per-key **placement** and safe **rehoming** (a rising epoch plus a fencing token so the old owner stops the instant the new one takes over), ordered **cross-region replication** with per-stream cursors that detect and backfill gaps, **idempotent apply** so redelivered writes cannot double-count, **capacity escrow** and **tenant quotas**, and **failover** with snapshot re-seeding for a cell that has fallen too far behind. Getting all of these right at once is exactly why truly distributed websites are rare.
82
+ On your laptop, `toiljs dev` runs a single **in-process** database. Everything homes in one place, so your code behaves exactly as it will worldwide. The distribution only spreads out once you deploy with the multi-cell backing configured. You write your app the same way either way.
66
83
 
67
- **Still being finished:** live multi-cell **WAN routing** (wiring many real regions into one running mesh) and the full database-level **leader fencing** on the write path (the host-side leader gate is the current version). The design is settled; the last-mile host wiring is what remains.
84
+ toil is **built** to distribute writes worldwide. The mechanism runs today. Turning it on across live regions is a deployment step, not a rewrite of your app.
68
85
 
69
86
  ## Related
70
87
 
@@ -1,74 +1,121 @@
1
1
  # How toil works
2
2
 
3
- What your project compiles into, and what happens when a user makes a request. Every term is defined as it appears.
3
+ This page walks one app from source to response. What you write, what the build produces, and what happens when a request arrives. Every term is defined where it first appears. You configure none of the machinery below. This page just explains it.
4
4
 
5
- ## What "build" produces
5
+ ## The client and the server
6
6
 
7
- `toiljs build` turns your one TypeScript project into three outputs, because your code has two homes: the browser and the edge.
7
+ A toil app is one project. Your code lives in two folders.
8
8
 
9
- - **Client bundle.** Your React app plus any pages toil renders ahead of time, packaged as ordinary web files (HTML, JS, CSS, images). Runs in the browser.
10
- - **`server.wasm`.** Your backend. You write it as normal TypeScript classes in `server/`, and the **toilscript** compiler turns it into WebAssembly. It runs on the edge, not in the browser and not in Node.
11
- - **Generated typed client (`shared/`).** A small browser-side client toil generates from the shape of your backend. Your React code calls a normal-looking async function, and the types line up end to end: rename a field on the server and the frontend stops compiling until you fix it.
9
+ - **`client/`** is a **React** frontend built with Vite. File-based routing, data loaders, the usual components. It runs in the user's browser.
10
+ - **`server/`** is your backend, written in **TypeScript with decorators**. Mark a class `@rest` for HTTP endpoints, `@service` for typed RPC, `@stream` for realtime, `@daemon` for background jobs, `@database` or `@collection` for stored data, `@auth` or `@user` for login. There are no servers to provision and no routes to wire by hand. The decorator is the wiring.
12
11
 
13
- **WebAssembly** (WASM), in two sentences: a compact binary format that runs at close to native speed with no interpreter warm-up, and runs **sandboxed**, in a locked box that cannot open files, reach the operating system, or make network calls on its own. It touches the outside world only through the small, fixed set of functions the host hands it (read the request, build a response, query the database), which is what makes it safe to pack many apps onto one shared box. ([Why that matters for scale](./hyperscale.md).)
12
+ That is the whole surface. One repo, one language family, one `toiljs dev`.
14
13
 
15
- ## The request lifecycle
14
+ ## What the build produces
16
15
 
17
- It all happens on the **edge node nearest the user**, with no trip to a central origin.
16
+ `toiljs build` turns that one project into three kinds of output. The browser and the edge need different things.
18
17
 
19
- Two terms first:
18
+ **The client bundle.** Your React app, plus any pages toil renders ahead of time. It ships as ordinary web files that run in the browser: HTML, JS, CSS, images. Every asset is fingerprinted with SHA-384 Subresource Integrity, so the browser refuses a file that was tampered with in transit.
20
19
 
21
- - **The edge** is a fleet of servers spread across many cities. A request is served by whichever node is physically closest, which means lower latency (the delay before something happens).
22
- - An **origin server** is the single far machine a traditional site calls back to for anything real. toil has none: your backend and its database are replicated out to the edge, so there is nothing far away to call.
20
+ **The server WASM.** Your `server/` backend, compiled by the **toilscript** compiler into a small sandboxed **WebAssembly** module. It runs on the edge, not in the browser and not in Node. The build emits three artifacts, one per compute tier.
21
+
22
+ | Artifact | Holds | Runs |
23
+ | --- | --- | --- |
24
+ | `release.wasm` | the hot request path (`@rest`, `@service`) | on every request |
25
+ | `release-stream.wasm` | realtime handlers (`@stream`) | for the life of a connection |
26
+ | `release-cold.wasm` | background and scheduled jobs (`@daemon`) | on a timer, worldwide-singleton |
27
+
28
+ **The generated typed client, `shared/server.ts`.** toil reads the shape of your backend and generates a small browser-side client from it. Your React code calls a normal async function, `Server.REST.*` or `Server.Stream.*` or `Server.<service>`, and the types line up end to end. Rename a field on the server and the frontend stops compiling until you fix it. No hand-written `fetch`, no drift.
29
+
30
+ A word on **WebAssembly**. It is a compact binary format that runs at close to native speed with no interpreter warm-up. It runs inside a **sandbox**: a locked box that cannot open files, reach the operating system, or make network calls on its own. The guest touches the outside world only through a small fixed set of functions the host hands it. Read the request, build a response, query the database. That narrow surface is what makes it safe to pack many apps onto one shared machine. ([Why that matters for scale](./hyperscale.md).)
31
+
32
+ toilscript is a strict TypeScript dialect: explicit value types, no `any`, no runtime `RegExp`. The [decorators reference](../concepts/decorators.md) covers the full set.
33
+
34
+ ## The edge nodes
35
+
36
+ Your WASM does not run on a laptop or in one central data center. It runs on the **edge**: a fleet of Rust runtime nodes spread across many cities. Each user is served by whichever node is physically closest. There is no **origin server**, no single far machine every request funnels back to. Your backend and its data are already at the edge.
37
+
38
+ Two things make this work at the density it needs.
39
+
40
+ - **Multi-tenant on shared boxes.** Each app is a tiny sandboxed module, so one edge box safely runs many apps at once. That shared density is what makes putting compute near everyone affordable instead of a luxury.
41
+ - **A custom userspace datapath.** The node handles network packets itself in userspace, multi-queue, bypassing the operating system's network stack. This keeps the per-request cost low as traffic grows. **QUIC** and **WebTransport** ride on top to power realtime `@stream` connections.
42
+
43
+ ## What happens on a request
44
+
45
+ A request never leaves the neighborhood. It lands on the nearest edge node, and everything happens right there.
23
46
 
24
47
  ```mermaid
25
48
  sequenceDiagram
26
49
  participant U as User's browser
27
- participant E as Nearest edge node
28
- participant W as server.wasm (your handler)
50
+ participant E as Nearest edge node (Rust)
51
+ participant W as WASM host
52
+ participant G as Your guest module
29
53
  participant DB as ToilDB (local copy)
30
54
 
31
- U->>E: Request (page or API call)
32
- alt Path is a page/asset it can serve
33
- E-->>U: Prerendered / SSR page or static file
34
- else Path is a dynamic API call
35
- E->>W: Decode bytes, call your handler
36
- W->>DB: Read / write (a local, nearby copy)
37
- DB-->>W: Result
38
- W-->>E: Response
55
+ U->>E: Request over the network
56
+ alt Page or static asset
57
+ E-->>U: Prerendered / SSR page or file
58
+ else Dynamic API call
59
+ E->>W: Decode raw bytes into a Request
60
+ W->>G: Call your handler
61
+ G->>DB: Read / write nearby data
62
+ DB-->>G: Result
63
+ G-->>W: Response object
64
+ W-->>E: Encode to bytes
39
65
  E-->>U: Response
40
66
  end
41
67
  ```
42
68
 
43
69
  1. **The request lands on the closest edge node.** The network routes the user there automatically.
44
- 2. **Page or code?** If the path is a prerendered page, a server-rendered page, or a static asset, the edge serves it and never wakes your backend. This is the fast path for most page loads.
45
- 3. **Otherwise it runs your backend.** The edge decodes the raw bytes into a `Request` object and calls the single entry point of your `server.wasm`, which routes to your handler.
46
- 4. **Your handler reads and writes locally.** When it needs stored data it talks to [ToilDB](../database/README.md), which has a copy right there at the edge. No ocean crossing.
47
- 5. **Your handler returns a `Response`,** toil encodes it back to bytes, and the edge sends it to the browser.
70
+ 2. **Page or dynamic call.** A prerendered page, a server-rendered page, or a static asset is served directly by the edge, which never wakes your backend. This is the fast path for most page loads.
71
+ 3. **Otherwise the host runs your guest.** The **WASM host**, trusted Rust, decodes the raw bytes into a `Request`. It calls into your **guest module**, your sandboxed WASM, at its single entry point, which routes to the handler you wrote.
72
+ 4. **Your handler reads and writes locally.** When it needs stored data it talks to [ToilDB](../database/README.md), which keeps a copy right there at the edge. No ocean crossing.
73
+ 5. **Your handler returns a `Response`.** The host encodes it back to bytes and the edge sends it to the browser.
74
+
75
+ The mental model for your backend is a function of the request. Bytes in, bytes out, one request at a time.
76
+
77
+ ### Why the backend is stateless
78
+
79
+ A fresh copy of your handler serves each request. The next request might land on a node on the other side of the planet, so anything you set on an instance field does not survive. This is deliberate. Interchangeable copies with nothing to coordinate are what let the backend scale worldwide by adding more nodes. When you need something to persist, write it to ToilDB. See the [backend overview](../backend/README.md#stateless-by-default).
80
+
81
+ ## The database, ToilDB
48
82
 
49
- The mental model for your backend: a function of the request. Bytes in, bytes out, one request at a time.
83
+ State lives in [ToilDB](../database/README.md), toil's own database, replicated next to your code at the edge. It offers seven purpose-built families: Documents, Unique, Counter, Events, Membership, Capacity, View. You pick the shape that fits instead of forcing everything into one table.
50
84
 
51
- ### Stateless by default
85
+ The hard part is **distributed writes**. Every key has one **home region** that orders its writes, while the data replicates outward so reads stay fast and local everywhere. Most stacks distribute reads but pin writes to one region. toil is built to distribute both.
86
+
87
+ Where this stands today. The home-region model and its core logic are real and tested. A live multi-region deployment, meaning WAN routing over the ScyllaDB backing, is configuration-gated. It is not switched on for every app by default. A far read can also lag its home by a few milliseconds, the eventual-consistency trade-off. Your local `toiljs dev` database is a single in-process store. The [distributed writes page](./distributed.md) covers the write model and its trade-off in full.
88
+
89
+ ## The four compute tiers
90
+
91
+ Not all backend code has the same lifespan. toil sorts it into four tiers, and the build assigns each piece from its decorator automatically.
92
+
93
+ | Tier | What runs here | Status |
94
+ | --- | --- | --- |
95
+ | **L1** | the stateless per-request hot path (`@rest`, `@service`) | live, the default |
96
+ | **L2 / L3** | longer-lived regional and continental work, `@stream` realtime | opt-in, deployment-gated |
97
+ | **L4** | a single worldwide `@daemon` for background and scheduled jobs | opt-in, deployment-gated |
52
98
 
53
- A fresh copy of your handler serves each request, and the next request might be served by a node on the other side of the planet. So anything you set on a field does not survive. This is a feature: interchangeable copies with nothing to coordinate are what let the backend scale worldwide. When you need something to persist, write it to ToilDB. See the [backend overview](../backend/README.md#stateless-by-default).
99
+ Most apps live entirely on **L1**, which is real and running today. L2 through L4 are built and opt-in, not always-on for every app. Full detail is on the [tiers page](../concepts/tiers.md).
54
100
 
55
- ## The pieces
101
+ ## The parts of a running app
56
102
 
57
- Five parts make up a running toil app; you have now met all of them.
103
+ Five parts make up a running toil app. You have now met all of them.
58
104
 
59
105
  | Piece | What it is | Where it runs |
60
106
  | --- | --- | --- |
61
- | **React client** | Your frontend UI, the client bundle from the build. | The user's browser |
62
- | **toilscript backend** | Your TypeScript backend compiled to `server.wasm`. | The edge |
63
- | **The Dacely edge** | The Rust runtime that terminates the connection, serves pages, and runs your WASM. | Servers in many cities |
64
- | **ToilDB** | The globally distributed database, replicated next to your code. | The edge |
65
- | **The four tiers** | Where and for how long a piece of backend code lives. | L1 nearest, up to L4 worldwide |
107
+ | **React client** | your frontend UI, the client bundle from the build | the user's browser |
108
+ | **toilscript backend** | your `server/` code compiled to sandboxed WASM | the edge |
109
+ | **The edge node** | the Rust runtime that terminates the connection, serves pages, and runs your WASM | servers in many cities |
110
+ | **ToilDB** | the database with distributed writes, replicated next to your code | the edge |
111
+ | **The four tiers** | where and for how long a piece of backend code lives | L1 nearest, up to L4 worldwide |
66
112
 
67
- Most of your backend is the stateless, per-request handler above (tier **L1**). Long-lived connections (L2/L3) and single-worldwide scheduled jobs (L4 daemons) run on other tiers. Full detail, and how the build assigns them, is on the [tiers page](../concepts/tiers.md).
113
+ None of these need configuring to get the good version. You write React and decorated TypeScript. The build, the edge, and the database come as the default, not a stack you assemble and babysit.
68
114
 
69
115
  ## Related
70
116
 
71
117
  - [Backend overview](../backend/README.md): the request/response model and the sandbox in depth.
72
- - [The database (ToilDB)](../database/README.md): where persistent, shared state lives.
118
+ - [The database (ToilDB)](../database/README.md): families, home regions, and eventual consistency.
73
119
  - [Compute tiers](../concepts/tiers.md): L1 request, L2/L3 stream, L4 daemon.
120
+ - [Realtime streams](../realtime/streams.md) and [background daemons](../background/daemons.md): the other two artifacts.
74
121
  - [What makes toil hyper-scalable](./hyperscale.md): why this design serves the planet cheaply.