toiljs 0.0.85 → 0.0.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +303 -293
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/config.d.ts +2 -0
- package/build/compiler/config.js +1 -0
- package/build/compiler/docs.js +8 -24
- package/build/compiler/generate.js +4 -2
- package/build/compiler/index.d.ts +1 -1
- package/build/compiler/index.js +69 -6
- package/build/compiler/toil-docs.generated.js +64 -22
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/analytics/index.js +7 -3
- package/build/devserver/db/database.d.ts +4 -0
- package/build/devserver/db/database.js +43 -1
- package/build/devserver/db/index.d.ts +1 -1
- package/build/devserver/db/index.js +1 -1
- package/build/devserver/db/types.d.ts +3 -0
- package/build/devserver/db/types.js +2 -0
- package/build/devserver/runtime/module.js +4 -1
- package/docs/README.md +104 -65
- package/docs/auth/README.md +102 -0
- package/docs/auth/configuration.md +94 -0
- package/docs/auth/extending.md +202 -0
- package/docs/auth/how-it-works.md +138 -0
- package/docs/auth/usage.md +188 -0
- package/docs/backend/README.md +143 -0
- package/docs/backend/data.md +351 -0
- package/docs/backend/rest.md +402 -0
- package/docs/backend/rpc.md +226 -0
- package/docs/background/README.md +114 -0
- package/docs/background/daemons.md +230 -0
- package/docs/background/derive.md +179 -0
- package/docs/cli/README.md +377 -0
- package/docs/concepts/config.md +416 -0
- package/docs/concepts/decorators.md +127 -0
- package/docs/concepts/security.md +108 -0
- package/docs/concepts/tiers.md +166 -0
- package/docs/concepts/types.md +216 -0
- package/docs/database/README.md +143 -0
- package/docs/database/capacity.md +350 -0
- package/docs/database/counters.md +174 -0
- package/docs/database/documents.md +255 -0
- package/docs/database/events.md +307 -0
- package/docs/database/membership.md +246 -0
- package/docs/database/setup.md +155 -0
- package/docs/database/unique.md +216 -0
- package/docs/database/views.md +246 -0
- package/docs/frontend/README.md +101 -0
- package/docs/frontend/data-fetching.md +243 -0
- package/docs/frontend/images.md +148 -0
- package/docs/frontend/metadata.md +344 -0
- package/docs/frontend/rendering.md +236 -0
- package/docs/frontend/routing.md +344 -0
- package/docs/frontend/scripts.md +118 -0
- package/docs/frontend/search.md +191 -0
- package/docs/frontend/styling.md +147 -0
- package/docs/getting-started/README.md +81 -0
- package/docs/getting-started/create-project.md +131 -0
- package/docs/getting-started/deploy.md +101 -0
- package/docs/getting-started/first-app.md +215 -0
- package/docs/getting-started/installation.md +106 -0
- package/docs/getting-started/migrating.md +125 -0
- package/docs/getting-started/project-structure.md +163 -0
- package/docs/introduction/README.md +41 -0
- package/docs/introduction/design-principles.md +55 -0
- package/docs/introduction/distributed.md +74 -0
- package/docs/introduction/how-it-works.md +74 -0
- package/docs/introduction/hyperscale.md +51 -0
- package/docs/introduction/modern-stack.md +36 -0
- package/docs/introduction/vs-other-frameworks.md +48 -0
- package/docs/introduction/why-toil.md +93 -0
- package/docs/llms.txt +90 -0
- package/docs/realtime/README.md +102 -0
- package/docs/realtime/channels.md +211 -0
- package/docs/realtime/streams.md +369 -0
- package/docs/services/README.md +60 -0
- package/docs/services/analytics.md +268 -0
- package/docs/services/caching.md +175 -0
- package/docs/services/cookies.md +235 -0
- package/docs/services/crypto.md +209 -0
- package/docs/services/email.md +289 -0
- package/docs/services/environment.md +141 -0
- package/docs/services/ratelimit.md +174 -0
- package/docs/services/time.md +85 -0
- package/examples/basic/client/routes/analytics.tsx +13 -12
- package/examples/basic/server/models/SiteAnalytics.ts +29 -17
- package/examples/basic/server/routes/Analytics.ts +15 -17
- package/examples/basic/server/routes/UserId.ts +22 -0
- package/package.json +15 -11
- package/scripts/gen-toil-docs.mjs +24 -35
- package/server/auth/AuthController.ts +336 -0
- package/server/auth/AuthUser.ts +23 -0
- package/server/auth/index.ts +16 -0
- package/server/globals/auth.ts +31 -0
- package/server/globals/userid.ts +107 -0
- package/src/compiler/config.ts +13 -0
- package/src/compiler/docs.ts +16 -33
- package/src/compiler/generate.ts +6 -2
- package/src/compiler/index.ts +114 -6
- package/src/compiler/toil-docs.generated.ts +64 -22
- package/src/devserver/analytics/index.ts +10 -4
- package/src/devserver/db/database.ts +67 -1
- package/src/devserver/db/index.ts +1 -0
- package/src/devserver/db/types.ts +13 -0
- package/src/devserver/runtime/module.ts +7 -0
- package/test/analytics-dev.test.ts +2 -1
- package/test/devserver-database.test.ts +113 -0
- package/docs/auth-todo.md +0 -149
- package/docs/auth.md +0 -322
- package/docs/caching.md +0 -115
- package/docs/cli.md +0 -17
- package/docs/client.md +0 -39
- package/docs/cookies.md +0 -457
- package/docs/crypto.md +0 -130
- package/docs/daemon.md +0 -123
- package/docs/data.md +0 -131
- package/docs/derive.md +0 -159
- package/docs/email.md +0 -326
- package/docs/environment.md +0 -97
- package/docs/getting-started.md +0 -128
- package/docs/index.md +0 -30
- package/docs/ratelimit.md +0 -95
- package/docs/routing.md +0 -259
- package/docs/rpc.md +0 -149
- package/docs/server.md +0 -61
- package/docs/ssr.md +0 -632
- package/docs/streams.md +0 -178
- package/docs/styling.md +0 -22
- package/docs/tiers.md +0 -133
- package/docs/time.md +0 -43
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Why toil? Who is it for?
|
|
2
|
+
|
|
3
|
+
toil is a full-stack framework: you write a React frontend and a TypeScript backend in one project, and toil runs both (plus a database) close to every user, worldwide.
|
|
4
|
+
|
|
5
|
+
The thesis in one line: toil is the modern full-stack tech a developer would actually want, AAA-grade from the very first line, and hyper-scalable at the same time. Even a simple pizza site gets top-tier infrastructure with zero setup. Distributed writes are one pillar of that. The modern stack that just works is the heart.
|
|
6
|
+
|
|
7
|
+
## The problem with today's stacks
|
|
8
|
+
|
|
9
|
+
Two problems, really.
|
|
10
|
+
|
|
11
|
+
**Read-global, write-central.** Your pages load fast from caches worldwide. But a *write* (a comment, a like, an order) usually travels to one database in one region. A user in Sydney writing to a database in Virginia pays for the round trip. That single region is also a single point of failure.
|
|
12
|
+
|
|
13
|
+
**The ten-vendor tax.** A typical production stack is stitched from rented services: a frontend host, serverless functions, a managed database, auth, email, a queue, a cache, analytics, realtime. Each is its own account, bill, SDK, and failure mode. You did not set out to be a systems integrator, but the stack hands you the job.
|
|
14
|
+
|
|
15
|
+
And every third-party service on your **critical path** (what must work for a request to succeed) is a black box you cannot inspect, patch, or fully secure. When it is slow, you are slow. When it is breached, part of you is breached.
|
|
16
|
+
|
|
17
|
+
The result: a solo builder and a funded startup hit the *same* wall. Good, safe, fast infrastructure means assembling and babysitting a lot of parts, so most people settle for less.
|
|
18
|
+
|
|
19
|
+
## What toil does instead
|
|
20
|
+
|
|
21
|
+
Close the gap, own the pieces, and make the good version the *default* version. Four pillars.
|
|
22
|
+
|
|
23
|
+
### 1. AAA-grade from the first line
|
|
24
|
+
|
|
25
|
+
Top-tier infrastructure on day one, on the smallest project, with zero setup: edge compute (your code runs near users worldwide), one-line post-quantum login, automatic tamper-proofing of your app's code, HTTP/3, and a global database already there.
|
|
26
|
+
|
|
27
|
+
A pizza site and a planet-scale app start from the same baseline. "AAA-grade" is the actual bar toil grades itself against; see [design principles](./design-principles.md).
|
|
28
|
+
|
|
29
|
+
### 2. Batteries-included, and owned
|
|
30
|
+
|
|
31
|
+
Auth, database, email, rate limiting, analytics, realtime streaming, and background jobs are all built in and are toil's own. Nothing third-party sits on your critical path.
|
|
32
|
+
|
|
33
|
+
Because they are one system, the parts already fit. You are not gluing ten SDKs together and praying they agree. Full catalog: [The modern stack](./modern-stack.md).
|
|
34
|
+
|
|
35
|
+
```mermaid
|
|
36
|
+
flowchart TB
|
|
37
|
+
subgraph assemble["Usual way: assemble ten vendors"]
|
|
38
|
+
direction TB
|
|
39
|
+
A1["Your app"] --> V1["host"]
|
|
40
|
+
A1 --> V2["functions"]
|
|
41
|
+
A1 --> V3["database"]
|
|
42
|
+
A1 --> V4["auth"]
|
|
43
|
+
A1 --> V5["email"]
|
|
44
|
+
A1 --> V6["rate limiter"]
|
|
45
|
+
A1 --> V7["analytics"]
|
|
46
|
+
A1 --> V8["realtime"]
|
|
47
|
+
A1 --> V9["jobs"]
|
|
48
|
+
end
|
|
49
|
+
subgraph toil["toil: one owned stack"]
|
|
50
|
+
direction TB
|
|
51
|
+
A2["Your app"] --> T["edge + database + auth + email +<br/>rate limiting + analytics +<br/>streaming + jobs, built in"]
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Honest boundary: "owned" means the *core* of a working app is toil's, not that outside services are banned. Call a payment provider or another API and you still can.
|
|
56
|
+
|
|
57
|
+
### 3. A modern DX that just works
|
|
58
|
+
|
|
59
|
+
TypeScript end to end, one repo, one deploy, wired by types: change a field on the server and the frontend stops compiling until you fix it (a compile error at your desk, not a production bug).
|
|
60
|
+
|
|
61
|
+
The toolchain is set up for you: ESLint, Prettier (with a plugin for toil's decorators), an editor plugin, one CLI, and a `doctor` that fixes common problems in place. The docs are even LLM-friendly, so an AI assistant reads your current conventions instead of guessing. More in [The modern stack](./modern-stack.md).
|
|
62
|
+
|
|
63
|
+
### 4. Hyper-scalable and distributed (one pillar, not the whole story)
|
|
64
|
+
|
|
65
|
+
Your backend compiles to a tiny sandboxed **WebAssembly** module (a compact, locked-down binary that runs at near-native speed), so one edge box safely runs many apps, which makes running near everyone affordable.
|
|
66
|
+
|
|
67
|
+
And the database, **ToilDB**, distributes the *writes*, not just the reads: every key has one **home** region that orders its writes, while data replicates outward for fast local reads. Distributing writes is the hard part almost nobody does. The trade is eventual consistency: a far read can lag a few milliseconds. See [How toil works](./how-it-works.md) and [How toil is distributed](./distributed.md).
|
|
68
|
+
|
|
69
|
+
## Who it is for
|
|
70
|
+
|
|
71
|
+
- **Solo builders and small teams:** a full, global, secure stack without hiring a platform team. The pizza site is first-class.
|
|
72
|
+
- **Latency-sensitive apps:** writes that resolve near the user, not across an ocean.
|
|
73
|
+
- **Global apps:** logic and data near users on every continent.
|
|
74
|
+
- **Realtime apps:** chat, presence, and live cursors on built-in streaming, not a bolted-on vendor.
|
|
75
|
+
|
|
76
|
+
Same install for the smallest project and the largest. You grow into the scale; you do not rebuild to reach it.
|
|
77
|
+
|
|
78
|
+
## When not to use toil
|
|
79
|
+
|
|
80
|
+
- **You need SQL or heavy joins.** ToilDB is seven purpose-built families, not a general SQL engine. See the [database overview](../database/README.md).
|
|
81
|
+
- **You lean on the Node ecosystem.** The server is a strict TypeScript subset compiled to WebAssembly: no arbitrary npm packages, no Node APIs, built-in globals instead.
|
|
82
|
+
- **You are happy single-region and simple.** If one region already fits, toil's distribution is effort you do not need.
|
|
83
|
+
- **You need a big integration catalog today.** toil is younger, and that catalog is smaller.
|
|
84
|
+
|
|
85
|
+
None of these are permanent, and the right tool is the one that fits the job in front of you.
|
|
86
|
+
|
|
87
|
+
## Related
|
|
88
|
+
|
|
89
|
+
- [The modern stack](./modern-stack.md): the full, verified catalog of what is built in.
|
|
90
|
+
- [How toil works](./how-it-works.md): the whole machine end to end, from React client to ToilDB.
|
|
91
|
+
- [toil versus other frameworks](./vs-other-frameworks.md): an honest, axis-by-axis comparison.
|
|
92
|
+
- [Security](../concepts/security.md): the defaults that are already on.
|
|
93
|
+
- [Getting started](../getting-started/README.md): install the tool and build a small feature.
|
package/docs/llms.txt
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# toiljs
|
|
2
|
+
|
|
3
|
+
> toiljs is a full-stack web framework: you write the frontend in React and the backend in TypeScript, and toiljs compiles the backend to a sandboxed WebAssembly module that runs on the worldwide Dacely edge, with a built-in global database (ToilDB), streaming, background jobs, and auth included.
|
|
4
|
+
|
|
5
|
+
This is the canonical documentation, organized by section. Each link points to a Markdown page (each folder's README.md is its landing page). If you are new, read the Understanding toil section first. A toiljs project has three parts: client/ (the React app in the browser), server/ (the backend, compiled to WebAssembly and run on the edge), and shared/ (a generated, fully typed client so the browser calls the server with end-to-end type safety). The backend is compiled by toilscript, a strict TypeScript-to-WebAssembly subset, so server code uses fixed-width number types (u64, i32, and so on) rather than the single JavaScript number type.
|
|
6
|
+
|
|
7
|
+
## Understanding toil
|
|
8
|
+
- [Understanding toil](introduction/README.md): 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.
|
|
9
|
+
- [Why toil is built this way (the RSG bar)](introduction/design-principles.md): toil is opinionated on purpose.
|
|
10
|
+
- [How toil is distributed](introduction/distributed.md): Distributing a website's reads is easy.
|
|
11
|
+
- [How toil works](introduction/how-it-works.md): What your project compiles into, and what happens when a user makes a request.
|
|
12
|
+
- [What makes toil hyper-scalable](introduction/hyperscale.md): **Hyper-scale** is serving very large, worldwide traffic at low latency without rebuilding your app as it grows.
|
|
13
|
+
- [The modern stack: what toil gives you that others do not](introduction/modern-stack.md): Most frameworks give you a way to write code and then send you shopping: a database, an auth provider, email, a rate limiter, analytics, a realtime service, a job runner, all wired together and kept in sync by you.
|
|
14
|
+
- [toil versus other stacks](introduction/vs-other-frameworks.md): An honest look at where each stack you already use hits a ceiling, and why toil bets on a different shape.
|
|
15
|
+
- [Why toil? Who is it for?](introduction/why-toil.md): toil is a full-stack framework: you write a React frontend and a TypeScript backend in one project, and toil runs both (plus a database) close to every user, worldwide.
|
|
16
|
+
|
|
17
|
+
## Getting started
|
|
18
|
+
- [Getting started](getting-started/README.md): toiljs is a full-stack web framework: you write a React frontend and a TypeScript backend in one project, and toiljs ships both together.
|
|
19
|
+
- [Create a project](getting-started/create-project.md): Scaffold a brand-new toiljs app with one command.
|
|
20
|
+
- [Deploying a toiljs app](getting-started/deploy.md): This page is an honest look at how you get a toiljs app in front of real users.
|
|
21
|
+
- [Your first app](getting-started/first-app.md): Build a tiny feature end to end: a page with a "Like" button that reads and writes a real number in ToilDB, so the count survives page reloads and restarts.
|
|
22
|
+
- [Installation](getting-started/installation.md): Install the toiljs command-line tool (CLI) so you can create and run projects.
|
|
23
|
+
- [Migrating an existing React app](getting-started/migrating.md): How to bring a React app you already have into toiljs.
|
|
24
|
+
- [Project structure](getting-started/project-structure.md): A tour of every folder and file in a toiljs project, what each one is for, and the single most important question: **where does this code run, the browser or the edge?**
|
|
25
|
+
|
|
26
|
+
## CLI
|
|
27
|
+
- [The toiljs CLI](cli/README.md): The `toiljs` command is how you scaffold, run, build, self-host, and diagnose a toiljs app.
|
|
28
|
+
|
|
29
|
+
## Frontend
|
|
30
|
+
- [Frontend](frontend/README.md): Your toiljs frontend is a React app with file-based routing that runs in the browser, and can also be rendered ahead of time on the server for a fast first paint and good SEO.
|
|
31
|
+
- [Fetching data](frontend/data-fetching.md): Your React frontend and your toiljs backend live in one project, so toiljs generates a **typed client** that lets the browser call your server with full type safety and no hand-written `fetch` boilerplate.
|
|
32
|
+
- [Images](frontend/images.md): `Toil.Image` is a drop-in replacement for `<img>` that stops layout shift, lazy-loads by default, and can fade in from a blurred placeholder.
|
|
33
|
+
- [Metadata and SEO](frontend/metadata.md): Metadata is the information in a page's `<head>`: its title, its description, and the tags that control how it looks when shared on social media or listed by a search engine.
|
|
34
|
+
- [Rendering and SSR](frontend/rendering.md): This page explains where your pages are built: in the browser, ahead of time at build, or on the server for each request.
|
|
35
|
+
- [Routing](frontend/routing.md): toiljs uses file-based routing: every file under `client/routes/` becomes a page, and its path on disk becomes its URL.
|
|
36
|
+
- [Scripts](frontend/scripts.md): `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.
|
|
37
|
+
- [Page search](frontend/search.md): toiljs 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.
|
|
38
|
+
- [Styling](frontend/styling.md): toiljs does not force a styling system on you.
|
|
39
|
+
|
|
40
|
+
## Backend
|
|
41
|
+
- [Backend overview](backend/README.md): Your backend is TypeScript that toilscript compiles into a small, sandboxed WebAssembly program, which runs on the Dacely edge and answers every request.
|
|
42
|
+
- [Data types (`@data`)](backend/data.md): `@data` turns a plain class into a typed value that can travel safely between your frontend and your WASM backend, and in and out of the database, with both a binary and a JSON codec generated for you.
|
|
43
|
+
- [HTTP routes (`@rest`)](backend/rest.md): Expose a real HTTP API by tagging a class `@rest` and its methods with an HTTP verb; toiljs generates the router for you.
|
|
44
|
+
- [Typed RPC (`@service` / `@remote`)](backend/rpc.md): Write a server function, tag it `@remote`, and call it from your React frontend like a local async function, with the argument and return types checked end to end.
|
|
45
|
+
|
|
46
|
+
## Database (ToilDB)
|
|
47
|
+
- [ToilDB](database/README.md): ToilDB is the database built into toiljs.
|
|
48
|
+
- [Capacity (limited stock without overselling)](database/capacity.md): A `Capacity` collection models a **finite resource that must never be oversold**: concert tickets, seats, limited-drop stock, rate grants.
|
|
49
|
+
- [Counters](database/counters.md): The **Counter** family is a distributed running total that many callers can increase at the same time, from anywhere in the world, without ever losing a count.
|
|
50
|
+
- [Documents](database/documents.md): The **Documents** family is ToilDB's general-purpose record store: you keep a value under a key and look it up, update it, or delete it by that key.
|
|
51
|
+
- [Events (append-only logs)](database/events.md): An `Events` collection is an **append-only log**: a growing list of immutable facts under a key.
|
|
52
|
+
- [Membership (sets and relationships)](database/membership.md): A `Membership` collection stores **sets**: unordered groups of members under a key.
|
|
53
|
+
- [Declaring a database](database/setup.md): You set up ToilDB by **declaring** it in code: a `@database` class listing your collections, plus the `@data` key and value types those collections use.
|
|
54
|
+
- [Unique](database/unique.md): The **Unique** family enforces that a value is claimed by only one owner across the entire world.
|
|
55
|
+
- [Views (materialized views)](database/views.md): A `View` collection is a **precomputed, read-optimized result** that you publish once and read many times.
|
|
56
|
+
|
|
57
|
+
## Auth
|
|
58
|
+
- [Authentication](auth/README.md): Toil ships a complete, **post-quantum password login** you turn on with one line.
|
|
59
|
+
- [Configuring auth for production](auth/configuration.md): Built-in auth runs locally with **zero configuration**, it falls back to published, insecure DEV secrets so `toiljs dev` Just Works.
|
|
60
|
+
- [Extending & integrating auth](auth/extending.md): Built-in auth is deliberately opinionated so the common case is one line.
|
|
61
|
+
- [How Toil auth works](auth/how-it-works.md): Toil auth is a **post-quantum, password-based, mutually-authenticated** login.
|
|
62
|
+
- [Using auth](auth/usage.md): Either the config flag (canonical) or a one-line import, both do the same thing (the build appends the framework's auth controller + user shape to your server as compiled entries, so `/auth/*` self-mounts):
|
|
63
|
+
|
|
64
|
+
## Realtime
|
|
65
|
+
- [Realtime](realtime/README.md): Realtime is how your app pushes data to the browser the instant it happens, instead of the browser having to ask again and again.
|
|
66
|
+
- [Channels (`useChannel`)](realtime/channels.md): `useChannel` is the client-side React hook for realtime.
|
|
67
|
+
- [Streams (`@stream`)](realtime/streams.md): A `@stream` is a server class that handles one live connection from start to finish.
|
|
68
|
+
|
|
69
|
+
## Background work
|
|
70
|
+
- [Background work](background/README.md): Background work is code that runs **without a user waiting for it**: on a timer, once globally, or automatically after your data changes.
|
|
71
|
+
- [Daemons (`@daemon` / `@scheduled`)](background/daemons.md): A `@daemon` is a single, long-lived background worker for your whole app.
|
|
72
|
+
- [Derived views (`@derive`)](background/derive.md): A `@derive` keeps a fast, precomputed **View** of your data in sync automatically.
|
|
73
|
+
|
|
74
|
+
## Platform services
|
|
75
|
+
- [Platform services](services/README.md): Your toiljs backend runs on the **Dacely edge**: a fleet of servers spread around the world that sit close to your users and run your compiled backend (a small, sandboxed WebAssembly program).
|
|
76
|
+
- [Analytics](services/analytics.md): Read your site's own usage numbers (requests, bytes, cache hits, database ops, errors, and more) straight from a route, with no extra code and no third-party service.
|
|
77
|
+
- [Caching](services/caching.md): **Caching lets the edge keep a copy of a response and hand it back instantly, without re-running your code.** You opt in per route, either with the `@cache` decorator or by calling `Response.cache(...)`.
|
|
78
|
+
- [Cookies](services/cookies.md): **Cookies let you store a small piece of state in the user's browser and read it back on the next request.** toiljs ships a complete cookie layer: read incoming cookies, set them on a response, and (optionally) sign or encrypt their values so they cannot be tampered with.
|
|
79
|
+
- [Crypto](services/crypto.md): A small, safe cryptography toolkit (hashing, random bytes, HMAC, symmetric encryption, key derivation, and signatures) available with no import through the ambient `crypto` global.
|
|
80
|
+
- [Email](services/email.md): Send transactional email (verification codes, password resets, receipts) straight from a route handler.
|
|
81
|
+
- [Environment and secrets](services/environment.md): **`Environment` gives your app configuration values and secrets that are set outside your code**, so your compiled backend (`.wasm`) never carries a password or an API key.
|
|
82
|
+
- [Rate limiting](services/ratelimit.md): **Rate limiting caps how often one client can call a route.** You add the `@ratelimit` decorator to a route, and the edge rejects a client that goes over the limit **before your code runs**.
|
|
83
|
+
- [Time](services/time.md): **`Time` is the edge's clock: the blessed way to read the current time in your backend.** It gives you the number of milliseconds or seconds since a fixed reference point, so you can stamp events and compute expiries.
|
|
84
|
+
|
|
85
|
+
## Concepts and reference
|
|
86
|
+
- [Configuration (`toil.config.ts`)](concepts/config.md): `toil.config.ts` is the one file that configures your whole toiljs app: the client (React/Vite) side and the server (WebAssembly) side.
|
|
87
|
+
- [Decorators reference](concepts/decorators.md): Every feature of a toiljs backend, an HTTP route, an RPC method, a database collection, a scheduled job, is switched on by a **decorator**.
|
|
88
|
+
- [Security](concepts/security.md): Security in toil is not a single feature you switch on.
|
|
89
|
+
- [Compute tiers](concepts/tiers.md): Your toiljs backend runs across four **compute tiers**, from a per-request handler at the very edge (L1) up to a single worldwide coordinator (L4).
|
|
90
|
+
- [The server type system](concepts/types.md): Your backend is TypeScript, but a stricter, faster dialect of it.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Realtime
|
|
2
|
+
|
|
3
|
+
Realtime is how your app pushes data to the browser the instant it happens, instead of the browser having to ask again and again. In toiljs you get realtime from two small pieces: a server class marked `@stream`, and a client React hook called `useChannel`.
|
|
4
|
+
|
|
5
|
+
## What "realtime" means
|
|
6
|
+
|
|
7
|
+
A normal web request is one round trip. The browser asks ("give me the todos"), the server answers, and the connection closes. If you want fresh data a second later, you have to ask again. That is fine for a page load or a form submit, but it is a poor fit for anything that changes on its own: a live chat, a game, a price ticker, a progress bar, a presence indicator ("3 people online").
|
|
8
|
+
|
|
9
|
+
Realtime flips it around. The browser opens **one long-lived connection** and keeps it open. After that, either side can send a message at any time, as many times as it likes, with no new handshake. That open connection is often called a **socket**.
|
|
10
|
+
|
|
11
|
+
## WebTransport in plain words
|
|
12
|
+
|
|
13
|
+
To hold that long-lived connection open, the browser and the Dacely edge speak a protocol called **WebTransport**.
|
|
14
|
+
|
|
15
|
+
Here is all you need to know as a beginner:
|
|
16
|
+
|
|
17
|
+
- WebTransport is a modern, built-in browser feature (like `fetch`, but for a persistent two-way connection).
|
|
18
|
+
- It runs on top of **HTTP/3** and **QUIC**, which are the newest, fastest versions of the web's plumbing. They are built for low latency (messages arrive quickly) and for surviving network changes (your phone switching from Wi-Fi to cellular without dropping the connection).
|
|
19
|
+
- You do not write any WebTransport code by hand. toiljs gives you a tiny client API, and it uses WebTransport underneath on the production edge.
|
|
20
|
+
|
|
21
|
+
One helpful detail for later: in local development (`toiljs dev`) the same client API runs over a **WebSocket** instead, because a WebSocket is simpler to serve from one local process. A WebSocket is the older, widely supported "keep a socket open" browser feature. The point is that **your code is identical** in dev and in production; only the transport underneath differs, and toiljs picks it for you.
|
|
22
|
+
|
|
23
|
+
## When to use realtime (and when not to)
|
|
24
|
+
|
|
25
|
+
Reach for realtime when **the server needs to talk first**, or when messages fly back and forth quickly:
|
|
26
|
+
|
|
27
|
+
| Use realtime when... | Use a plain HTTP request when... |
|
|
28
|
+
| --------------------------------------------- | ------------------------------------------------ |
|
|
29
|
+
| A chat or comment thread updates live. | You load a page or a list once. |
|
|
30
|
+
| A multiplayer game syncs moves and positions. | You submit a form and get one answer. |
|
|
31
|
+
| You show live presence or typing indicators. | You fetch data on a button click. |
|
|
32
|
+
| You stream progress of a long job. | The data rarely changes, or the user pulls it. |
|
|
33
|
+
|
|
34
|
+
**Games are a first-class use case, not an afterthought.** Realtime multiplayer (player moves, low-latency input, live presence, per-player state) is one of the hardest things to build on the plain web, and it is exactly what toil streaming is designed for. More on why, and how far it is built to scale, in [Built for massive fan-out and world-wide sync](#built-for-massive-fan-out-and-world-wide-sync) below.
|
|
35
|
+
|
|
36
|
+
If a single request-and-response does the job, prefer that: it is simpler, it caches well, and it needs no open connection. Plain requests in toiljs are [HTTP routes](../backend/rest.md) and [typed RPC](../backend/rpc.md). Reach for realtime only when the "ask again and again" model genuinely gets in your way.
|
|
37
|
+
|
|
38
|
+
## The two pieces
|
|
39
|
+
|
|
40
|
+
Realtime in toiljs is always a pair:
|
|
41
|
+
|
|
42
|
+
1. **The server: a `@stream` class.** You write a small class and mark it `@stream`. The edge turns it into a **resident box**: a live instance that is created when a connection opens, handles every message on that connection, and is torn down when it closes. It has four lifecycle hooks (`@connect`, `@message`, `@close`, `@disconnect`). See [Streams](./streams.md).
|
|
43
|
+
|
|
44
|
+
2. **The client: a hook.** From your React UI you open the connection and send or receive messages. The low-level way is the `useChannel` hook; the typed, generated way is `Server.Stream.<ClassName>.connect()`. See [Channels](./channels.md).
|
|
45
|
+
|
|
46
|
+
```mermaid
|
|
47
|
+
sequenceDiagram
|
|
48
|
+
participant B as Browser (React)
|
|
49
|
+
participant E as Dacely edge
|
|
50
|
+
participant S as Your @stream box
|
|
51
|
+
B->>E: open connection (WebTransport, or WebSocket in dev)
|
|
52
|
+
E->>S: create the box, run @connect
|
|
53
|
+
Note over S: the box is now resident for this connection
|
|
54
|
+
B->>E: send "hello"
|
|
55
|
+
E->>S: @message("hello")
|
|
56
|
+
S-->>B: reply "hi back"
|
|
57
|
+
B->>E: send "still here"
|
|
58
|
+
E->>S: @message("still here")
|
|
59
|
+
S-->>B: reply "yep"
|
|
60
|
+
B->>E: close
|
|
61
|
+
E->>S: run @close, then destroy the box
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Notice that the box lives across **many** messages in that diagram. That is the whole idea: because it is the same instance every time, it can remember things between messages (a counter, who you are, what room you joined). A normal HTTP handler forgets everything after each request; a stream box does not, for as long as the connection stays open.
|
|
65
|
+
|
|
66
|
+
## Built for massive fan-out and world-wide sync
|
|
67
|
+
|
|
68
|
+
Realtime gets exciting when a single event reaches **many** people at nearly the same moment. Picture a live sports app: a goal is scored, and every fan watching sees it light up together. Picture a multiplayer game: one player moves, and everyone in the match sees it happen right away. That shape, one event fanning out to a huge live audience, is what toil streaming is built for. We call it **world-wide packet sync**: everyone in the same live session gets the same update at nearly the same time, wherever they are on the planet.
|
|
69
|
+
|
|
70
|
+
```mermaid
|
|
71
|
+
flowchart TB
|
|
72
|
+
P["One event<br/>(a goal, a chat line, a game move)"] --> E["Dacely edge<br/>(spread across cores and cities)"]
|
|
73
|
+
E --> S1["Player in Tokyo"]
|
|
74
|
+
E --> S2["Player in Paris"]
|
|
75
|
+
E --> S3["Player in Sao Paulo"]
|
|
76
|
+
E --> Sn["...many more, at once"]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Why it can go so wide
|
|
80
|
+
|
|
81
|
+
Three design choices make massive scale possible, without any single machine or single lock becoming the ceiling.
|
|
82
|
+
|
|
83
|
+
1. **Connections spread across the fleet in parallel.** Every new connection is handled on its own, with no shared lock in the middle for connections to fight over. There is no central bottleneck that every connection has to pass through, so adding machines adds capacity in a straight line.
|
|
84
|
+
2. **The session follows the user.** A phone that switches from Wi-Fi to cellular keeps its exact session, in-memory game state and all. A network change does not drop the connection or reset the box.
|
|
85
|
+
3. **The session sits near the user.** The edge is a fleet of servers in many cities. A `@stream` runs at a regional or continental node (see [Compute tiers](../concepts/tiers.md)), so the round trip to a player stays short no matter where on the map they are.
|
|
86
|
+
|
|
87
|
+
### An honest word on numbers
|
|
88
|
+
|
|
89
|
+
The framework is **designed** for very large live audiences, aiming at the scale of millions of concurrent connections in a single live session as its ambition. That is the target the architecture is built toward, not a number measured in these docs. Real capacity depends entirely on your deployment: how many machines and cores you run, how big each message is, how often it is sent, and where your users are. We do not publish a benchmarked figure here. What we can say honestly is **why** it scales (the three design choices above), and that the design removes the usual single-machine and single-lock ceilings that cap other realtime stacks.
|
|
90
|
+
|
|
91
|
+
One more honest note. Fanning a single message out to **other** users' connections (true one-to-many broadcast, the arrows in the diagram above) is the job of `@channel`. The client side of that (`useChannel`, `Server.Stream`) is live today. The server-side broadcast that reaches every subscriber across the mesh is **planned, not shipped yet**. See [Channels](./channels.md) for exactly what works now and what is coming, so you can build on solid ground.
|
|
92
|
+
|
|
93
|
+
## Where to go next
|
|
94
|
+
|
|
95
|
+
- [Streams](./streams.md): the server side. How to declare a `@stream` class, the four lifecycle hooks, per-connection state, replying, and the separate `main.stream.ts` file.
|
|
96
|
+
- [Channels](./channels.md): the client side. The `useChannel` React hook, the generated typed client, and a chat-style example. Also covers the planned `@channel` broadcast feature.
|
|
97
|
+
|
|
98
|
+
## Related
|
|
99
|
+
|
|
100
|
+
- [Compute tiers (L1 to L4)](../concepts/tiers.md): where a stream box runs on the edge.
|
|
101
|
+
- [HTTP routes (`@rest`)](../backend/rest.md) and [Typed RPC](../backend/rpc.md): the plain request-and-response alternatives.
|
|
102
|
+
- [Daemons](../background/daemons.md): long-lived background work that is not tied to a browser connection.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Channels (`useChannel`)
|
|
2
|
+
|
|
3
|
+
`useChannel` is the client-side React hook for realtime. It opens a live connection from the browser to a server `@stream` box, tracks whether it is connected, collects the messages that arrive, and gives you a `send` function. It reconnects on its own if the connection drops.
|
|
4
|
+
|
|
5
|
+
## What a channel is
|
|
6
|
+
|
|
7
|
+
A **channel** is simply an open, two-way connection between one browser and the server, viewed from the client's side. On the server that connection is handled by a [`@stream`](./streams.md) box. On the client you hold the other end with `useChannel` (a React hook) or `connectChannel` (a plain function, no React).
|
|
8
|
+
|
|
9
|
+
You have already met the two-piece model in the [realtime overview](./README.md): a `@stream` on the server, a channel on the client. This page is the client half.
|
|
10
|
+
|
|
11
|
+
## The `useChannel` hook
|
|
12
|
+
|
|
13
|
+
`useChannel` is available on the global `Toil` object in your route files, so no import is needed:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
export default function Ping() {
|
|
17
|
+
const chan = Toil.useChannel({ path: '/echo' });
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<main>
|
|
21
|
+
<p>Status: <strong>{chan.connected ? 'connected' : 'offline'}</strong></p>
|
|
22
|
+
<button type="button" onClick={() => chan.send('ping')}>Send ping</button>
|
|
23
|
+
<ul>
|
|
24
|
+
{chan.messages.map((m, i) => (
|
|
25
|
+
<li key={i}><code>{typeof m === 'string' ? m : '(binary frame)'}</code></li>
|
|
26
|
+
))}
|
|
27
|
+
</ul>
|
|
28
|
+
</main>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### What it returns
|
|
34
|
+
|
|
35
|
+
`useChannel(options?)` returns an object with three things:
|
|
36
|
+
|
|
37
|
+
| Field | Type | What it is |
|
|
38
|
+
| ----------- | -------------------------- | ---------------------------------------------------------------------- |
|
|
39
|
+
| `connected` | `boolean` | `true` while the socket is open. Re-renders when it changes. |
|
|
40
|
+
| `messages` | `(string \| ArrayBuffer)[]`| every frame received so far, in order. A new frame re-renders. |
|
|
41
|
+
| `send` | `(data) => void` | send one frame to the server (a no-op until the socket is open). |
|
|
42
|
+
|
|
43
|
+
A frame is either a **string** (text) or an **`ArrayBuffer`** (binary). Send accepts a string or binary data. To send or read structured data, encode and decode it yourself:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
chan.send(new TextEncoder().encode('hello')); // send bytes
|
|
47
|
+
const text = (m: string | ArrayBuffer) =>
|
|
48
|
+
typeof m === 'string' ? m : new TextDecoder().decode(m); // read either kind as text
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Options
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
Toil.useChannel({
|
|
55
|
+
path: '/echo', // which @stream route to connect to. Default: '/_toil'
|
|
56
|
+
url: 'wss://...', // full ws(s):// override (wins over path). Usually omit this.
|
|
57
|
+
reconnect: true, // auto-reconnect after an unexpected close. Default: true
|
|
58
|
+
reconnectDelay: 1000 // ms to wait before each reconnect attempt. Default: 1000
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The most important option is **`path`**: it points the channel at a specific `@stream` route. `{ path: '/echo' }` connects to a stream mounted with `@stream('echo')`. If you omit `path`, it connects to the default `/_toil` channel.
|
|
63
|
+
|
|
64
|
+
You do not build the `ws://` or `wss://` URL yourself. The hook derives it from the current page (a page served over `https` uses `wss`, otherwise `ws`), and on the production edge the transport is upgraded to WebTransport for you. Your code stays the same.
|
|
65
|
+
|
|
66
|
+
### Lifecycle and cleanup
|
|
67
|
+
|
|
68
|
+
`useChannel` connects when the component mounts and closes when it unmounts, so a channel lives exactly as long as the page that uses it is on screen. React's rules apply: call it at the top level of your component, not inside a loop or condition.
|
|
69
|
+
|
|
70
|
+
### `connectChannel`: the non-React version
|
|
71
|
+
|
|
72
|
+
If you need a channel outside a React component (in a plain module, a store, an event handler), use `connectChannel`. It takes a callback for each incoming frame and returns a handle:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { connectChannel } from 'toiljs/client';
|
|
76
|
+
|
|
77
|
+
const chan = connectChannel(
|
|
78
|
+
(frame) => console.log('got', frame),
|
|
79
|
+
{ path: '/echo' }
|
|
80
|
+
);
|
|
81
|
+
chan.send('hello');
|
|
82
|
+
chan.close(); // stop and stop reconnecting
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`useChannel` is `connectChannel` wrapped for React (it manages the `connected` / `messages` state for you).
|
|
86
|
+
|
|
87
|
+
## The typed client: `Server.Stream`
|
|
88
|
+
|
|
89
|
+
`useChannel` deals in raw frames. For a typed experience, toiljs also generates a client per `@stream` class at `Server.Stream.<ClassName>`, described in full on the [Streams](./streams.md#reaching-a-stream-from-the-browser) page. Use whichever fits: `useChannel` for a quick reactive hook, `Server.Stream.<Name>.connect()` for typed messages and explicit `onMessage` / `onClose` callbacks. Both open the same kind of connection to the same box.
|
|
90
|
+
|
|
91
|
+
## A full chat-style example
|
|
92
|
+
|
|
93
|
+
Here is a chat UI wired end to end: a React page that sends what you type and shows every reply, plus the server box it talks to.
|
|
94
|
+
|
|
95
|
+
### Client (`client/routes/chat.tsx`)
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { useState } from 'react';
|
|
99
|
+
|
|
100
|
+
export default function Chat() {
|
|
101
|
+
const chan = Toil.useChannel({ path: '/room' });
|
|
102
|
+
const [draft, setDraft] = useState('');
|
|
103
|
+
|
|
104
|
+
const asText = (m: string | ArrayBuffer): string =>
|
|
105
|
+
typeof m === 'string' ? m : new TextDecoder().decode(m);
|
|
106
|
+
|
|
107
|
+
function submit(): void {
|
|
108
|
+
if (draft.length === 0) return;
|
|
109
|
+
chan.send(draft); // one send() becomes one @message on the server
|
|
110
|
+
setDraft('');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<main>
|
|
115
|
+
<h1>Chat</h1>
|
|
116
|
+
<p>Status: <strong>{chan.connected ? 'connected' : 'offline'}</strong></p>
|
|
117
|
+
<ul>
|
|
118
|
+
{chan.messages.map((m, i) => <li key={i}>{asText(m)}</li>)}
|
|
119
|
+
</ul>
|
|
120
|
+
<input
|
|
121
|
+
value={draft}
|
|
122
|
+
onChange={(e) => setDraft(e.target.value)}
|
|
123
|
+
onKeyDown={(e) => { if (e.key === 'Enter') submit(); }}
|
|
124
|
+
placeholder="Say something"
|
|
125
|
+
/>
|
|
126
|
+
<button type="button" onClick={submit}>Send</button>
|
|
127
|
+
</main>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Server (`server/streams/Room.ts`)
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
@stream('room')
|
|
136
|
+
class Room {
|
|
137
|
+
private seen: i32 = 0;
|
|
138
|
+
|
|
139
|
+
@connect
|
|
140
|
+
onConnect(): void {
|
|
141
|
+
this.seen = 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@message
|
|
145
|
+
onMessage(packet: StreamPacket): StreamOutbound {
|
|
146
|
+
this.seen = this.seen + 1;
|
|
147
|
+
const text = new TextDecoder().decode(packet.bytes());
|
|
148
|
+
const reply = '#' + this.seen.toString() + ': ' + text;
|
|
149
|
+
return StreamOutbound.reply(Uint8Array.wrap(String.UTF8.encode(reply)));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Do not forget to import the stream in `server/main.stream.ts` (see [Streams](./streams.md#the-mainstreamts-file-a-separate-tier)):
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import './streams/Room';
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Run `toiljs dev`, open the page, and every line you send comes back numbered, live.
|
|
161
|
+
|
|
162
|
+
### An honest limitation: this echoes, it does not broadcast
|
|
163
|
+
|
|
164
|
+
Read the example carefully. Each browser has its **own** `Room` box, and that box replies only to **its own** connection. So in this version, two people in "the same room" do **not** see each other's messages: each just sees their own, echoed back. That is enough for a private assistant, a progress feed, or a single-player game, but it is **not** a shared chat room where one message reaches everyone.
|
|
165
|
+
|
|
166
|
+
To send one message to **many** connected users (true broadcast, the heart of a group chat or a live feed), you need the server to **fan out** a message to every subscriber. That is what `@channel` is for.
|
|
167
|
+
|
|
168
|
+
## `@channel`: server broadcast (not yet available)
|
|
169
|
+
|
|
170
|
+
> **Status: planned, not live in the current runtime.** A `@stream` class that declares a `@channel` hook is **rejected** by the edge today ("stream channels are not a v1 runtime ABI"), and the compiler does not yet emit it. The information below describes the intended shape so you can plan for it, not an API you can call right now.
|
|
171
|
+
|
|
172
|
+
The idea is **publish/subscribe** ("pub/sub"), a standard pattern for broadcast:
|
|
173
|
+
|
|
174
|
+
- A **channel** is a named topic, for example a chat room id.
|
|
175
|
+
- A connection **subscribes** to a channel to start receiving everything sent to it.
|
|
176
|
+
- Anyone can **publish** a message to the channel, and every subscriber receives it.
|
|
177
|
+
- A connection **unsubscribes** (or disconnects) to stop.
|
|
178
|
+
|
|
179
|
+
With `@channel`, the server box would join a connection to a named topic and publish messages to it, and the edge would deliver each published message to every subscriber across all the connections in that topic, no matter which worker or node each one landed on. That is the missing "one to many" piece that turns the echo example above into a real shared room. The plan already reserves per-plan limits for it (a cap on subscribers per channel and on message size), so the surface is designed; it is the runtime delivery that is not shipped yet.
|
|
180
|
+
|
|
181
|
+
### The picture: world-wide sync (the design)
|
|
182
|
+
|
|
183
|
+
When `@channel` ships, one `publish` will fan out across the whole edge mesh, reaching every subscriber wherever they are, at nearly the same moment. That is the **world-wide sync** idea: a live session where everyone sees the same update together, whether they are in the same city or on opposite sides of the planet.
|
|
184
|
+
|
|
185
|
+
```mermaid
|
|
186
|
+
flowchart TB
|
|
187
|
+
Pub["One publish()<br/>(a chat line, a goal, a game tick)"] --> Mesh["Dacely edge mesh<br/>(routes to every subscriber's core)"]
|
|
188
|
+
Mesh --> A["Subscriber in Tokyo"]
|
|
189
|
+
Mesh --> B["Subscriber in Paris"]
|
|
190
|
+
Mesh --> C["Subscriber in New York"]
|
|
191
|
+
Mesh --> D["...every other subscriber, at once"]
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The mechanism meant to make this fast is the same one that already spreads plain connections across the edge: because the edge always knows where each subscriber's connection lives, a broadcast is a direct fan-out to a known set of destinations, not a search for who is listening. For the wider picture, see [Built for massive fan-out and world-wide sync](./README.md#built-for-massive-fan-out-and-world-wide-sync) in the overview.
|
|
195
|
+
|
|
196
|
+
Keep the status in mind: the diagram above is the **intended shape**. The delivery runtime is not shipped yet, so treat it as the plan, not an API you can call today.
|
|
197
|
+
|
|
198
|
+
**What you can build today:** anything where each user talks to their own box (assistants, per-user live updates, single-player sync, progress streams). **What needs `@channel`:** shared rooms and one-to-many broadcast. Until it ships, a common workaround is to persist messages to [the database](../database/README.md) and have clients poll or re-read, which is simpler but not instant.
|
|
199
|
+
|
|
200
|
+
## Gotchas
|
|
201
|
+
|
|
202
|
+
- **Frames are `string` or `ArrayBuffer`.** Decode binary frames with `TextDecoder`; there is no automatic parsing in `useChannel` (the typed `Server.Stream` client is the structured option).
|
|
203
|
+
- **`send` before "connected" is dropped.** It is a no-op until the socket is open. Guard on `chan.connected`, or expect the first eager sends to be lost.
|
|
204
|
+
- **`messages` grows forever.** It keeps every frame received. For a long-lived page, slice it or keep your own bounded list.
|
|
205
|
+
- **One box per connection.** `useChannel` does not broadcast between users; that is the `@channel` feature described above.
|
|
206
|
+
|
|
207
|
+
## Related
|
|
208
|
+
|
|
209
|
+
- [Realtime overview](./README.md): the two-piece model and when to use realtime.
|
|
210
|
+
- [Streams](./streams.md): the server `@stream` class this hook talks to.
|
|
211
|
+
- [The database (ToilDB)](../database/README.md): where to persist messages that must outlive a connection or reach users who are offline.
|