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,416 @@
|
|
|
1
|
+
# Configuration (`toil.config.ts`)
|
|
2
|
+
|
|
3
|
+
`toil.config.ts` is the one file that configures your whole toiljs app: the client (React/Vite) side and the server (WebAssembly) side. Every field is optional and has a sensible default, so most projects keep it tiny.
|
|
4
|
+
|
|
5
|
+
## The shortest possible config
|
|
6
|
+
|
|
7
|
+
A scaffolded project ships something like this:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
// toil.config.ts
|
|
11
|
+
import { defineConfig } from 'toiljs/compiler';
|
|
12
|
+
|
|
13
|
+
export default defineConfig({
|
|
14
|
+
client: {
|
|
15
|
+
// Optimize images at build time (resize and compress imported images).
|
|
16
|
+
images: true,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`defineConfig` does not do anything at runtime: it is an identity helper that gives you full editor autocomplete and type-checking for the config shape. Always wrap your config in it.
|
|
22
|
+
|
|
23
|
+
An empty config is valid too. `export default defineConfig({})` gives you a working app with all defaults.
|
|
24
|
+
|
|
25
|
+
## Where the file lives
|
|
26
|
+
|
|
27
|
+
toiljs looks in your project root for the first file that matches, in this order:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
toil.config.ts toil.config.mts toil.config.js toil.config.mjs
|
|
31
|
+
toiljs.config.ts toiljs.config.mts toiljs.config.js toiljs.config.mjs
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Use whichever you like. `toil.config.ts` is the convention.
|
|
35
|
+
|
|
36
|
+
## Config vs. environment variables
|
|
37
|
+
|
|
38
|
+
There are two very different places settings live, and it matters which one you use.
|
|
39
|
+
|
|
40
|
+
| | `toil.config.ts` (this page) | Environment variables |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| **When it applies** | Build time and dev time | Runtime, per request |
|
|
43
|
+
| **What it holds** | Framework and build options (routing, styling, SEO, which features to compile) | Values and secrets your running server reads (API keys, feature flags, connection info) |
|
|
44
|
+
| **Committed to git?** | Yes, it is source | No. Local values go in `.env` / `.env.secrets` (both gitignored); production values live on your deploy target |
|
|
45
|
+
| **Read in code with** | Not read from your app code | `Environment.get(...)` / `Environment.getSecure(...)` on the server |
|
|
46
|
+
|
|
47
|
+
Rule of thumb: if it is a **secret** (a password, an API key, a session key), it does **not** go in `toil.config.ts`. It goes in the environment. See [Environment and secrets](../services/environment.md) for the full story.
|
|
48
|
+
|
|
49
|
+
There is also a **third** file, `toilconfig.json`, which is a different thing entirely. See [`toil.config.ts` is not `toilconfig.json`](#toilconfigts-is-not-toilconfigjson) at the bottom.
|
|
50
|
+
|
|
51
|
+
## The top-level shape
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
interface ToilConfig {
|
|
55
|
+
root?: string; // project root (defaults to the current working directory)
|
|
56
|
+
client?: ClientConfig; // the React / Vite frontend
|
|
57
|
+
server?: ServerConfig; // the toilscript / WebAssembly backend
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Everything else lives under `client` or `server`.
|
|
62
|
+
|
|
63
|
+
## `client` reference
|
|
64
|
+
|
|
65
|
+
Configures the frontend: source folders, the dev server, and build-time optimizations.
|
|
66
|
+
|
|
67
|
+
| Field | Type | Default | What it does |
|
|
68
|
+
| --- | --- | --- | --- |
|
|
69
|
+
| `srcDir` | `string` | `"client"` | Your frontend source directory, relative to the project root. |
|
|
70
|
+
| `routesDir` | `string` | `"routes"` | Your file-based routes directory, relative to `srcDir`. |
|
|
71
|
+
| `publicDir` | `string` | `"<srcDir>/public"` | Static assets directory. Holds `index.html` (which you own) plus files served as-is (favicons, images). |
|
|
72
|
+
| `outDir` | `string` | `"build/client"` | Where the production client bundle is written. |
|
|
73
|
+
| `base` | `string` | `"/"` | The public base path. Use this if you serve the app under a sub-path (a non-root base should start and end with `/`, like `"/app/"`). |
|
|
74
|
+
| `port` | `number` | `3000` | The dev server port. `--port` on the CLI overrides it. |
|
|
75
|
+
| `images` | `boolean` | `true` | Optimize imported images at build time (resize and convert). See [Images](../frontend/images.md). |
|
|
76
|
+
| `fonts` | `boolean` | `true` | Preload bundled fonts (inject `<link rel="preload">` for each `@font-face`) so text paints faster. |
|
|
77
|
+
| `viewTransitions` | `boolean` | `false` | Animate page navigations with the browser View Transitions API (a crossfade by default). Respects `prefers-reduced-motion`. |
|
|
78
|
+
| `transitions` | `boolean` | `false` | Wrap navigations in a React transition, keeping the current page visible while the next route's loader runs (instead of showing its loading state immediately). |
|
|
79
|
+
| `devtools` | `boolean` or object | `true` | The floating dev toolbar (route/build info, errors, live controls). It is dev-only and never ships in production. Set `false` to disable, or pass an object to configure its AI integration. |
|
|
80
|
+
| `seo` | object | (off) | Build-time SEO: bakes site-level metadata into the HTML and generates `robots.txt`, `sitemap.xml`, `llms.txt`. See [`client.seo`](#clientseo) below. |
|
|
81
|
+
| `vite` | Vite `InlineConfig` | `{}` | An escape hatch: raw Vite options, deep-merged over the framework's own Vite setup. toiljs owns the Vite config; use this only to override specific options. |
|
|
82
|
+
|
|
83
|
+
### Example
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { defineConfig } from 'toiljs/compiler';
|
|
87
|
+
|
|
88
|
+
export default defineConfig({
|
|
89
|
+
client: {
|
|
90
|
+
images: true,
|
|
91
|
+
fonts: true,
|
|
92
|
+
viewTransitions: true,
|
|
93
|
+
// Serve the app under https://example.com/app/
|
|
94
|
+
base: '/app/',
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### `client.devtools`
|
|
100
|
+
|
|
101
|
+
The dev toolbar is on by default. To turn it off, or to give it an AI provider (so its "explain this error" helpers can call a model), pass an object:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { defineConfig, AiProvider } from 'toiljs/compiler';
|
|
105
|
+
|
|
106
|
+
export default defineConfig({
|
|
107
|
+
client: {
|
|
108
|
+
devtools: {
|
|
109
|
+
ai: {
|
|
110
|
+
provider: AiProvider.Anthropic, // 'anthropic' or 'openai'
|
|
111
|
+
model: 'claude-sonnet-4-6',
|
|
112
|
+
// The name of the env var holding the API key. It is read
|
|
113
|
+
// server-side by the dev process and never sent to the browser.
|
|
114
|
+
apiKeyEnv: 'ANTHROPIC_API_KEY',
|
|
115
|
+
// Optional: a custom POST endpoint ({ prompt } in, { text } out)
|
|
116
|
+
// that overrides `provider` entirely.
|
|
117
|
+
// endpoint: 'http://localhost:5000/ai',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
| `devtools.ai` field | Type | What it does |
|
|
125
|
+
| --- | --- | --- |
|
|
126
|
+
| `provider` | `AiProvider` | Built-in provider: `AiProvider.Anthropic` (`'anthropic'`) or `AiProvider.OpenAI` (`'openai'`). |
|
|
127
|
+
| `model` | `string` | The model id, for example `'claude-sonnet-4-6'` or `'gpt-4o'`. |
|
|
128
|
+
| `apiKeyEnv` | `string` | The **name** of the environment variable that holds the API key. The key stays server-side and never reaches the browser. |
|
|
129
|
+
| `endpoint` | `string` | A custom POST endpoint. When set, it takes precedence over `provider`. |
|
|
130
|
+
|
|
131
|
+
The toolbar always offers hand-off links to Claude and ChatGPT even without any AI config; this only enables the in-toolbar helpers.
|
|
132
|
+
|
|
133
|
+
### `client.seo`
|
|
134
|
+
|
|
135
|
+
`client.seo` turns on build-time search-engine and social metadata. It bakes tags into your HTML `<head>` (so crawlers and link-preview bots that do not run JavaScript still see real metadata) and generates `robots.txt`, `sitemap.xml`, and `llms.txt`. This is a large section with its own guide; see [Metadata and SEO](../frontend/metadata.md) for the complete field list and examples. The one thing worth knowing here: set `seo.url` to your absolute site URL (like `"https://example.com"`), because the sitemap and canonical links need it.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
export default defineConfig({
|
|
139
|
+
client: {
|
|
140
|
+
seo: {
|
|
141
|
+
url: 'https://example.com',
|
|
142
|
+
title: 'My App',
|
|
143
|
+
description: 'A toiljs app.',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## `server` reference
|
|
150
|
+
|
|
151
|
+
Configures the backend: which platform features to compile in, and how the dev server and self-host behave.
|
|
152
|
+
|
|
153
|
+
| Field | Type | Default | What it does |
|
|
154
|
+
| --- | --- | --- | --- |
|
|
155
|
+
| `auth` | `boolean` | `false` | Opt into the framework's built-in post-quantum login. See [`server.auth`](#serverauth) below. |
|
|
156
|
+
| `email` | object | (off) | The non-secret email backend config for the dev server and self-host. See [`server.email`](#serveremail) below. |
|
|
157
|
+
| `daemon` | object | (defaults) | Background-job (L4 daemon) settings for dev and self-host. See [`server.daemon`](#serverdaemon) below. |
|
|
158
|
+
| `nodeMode` | string | `"all"` | Which compute layer the single dev/self-host process emulates. See [`server.nodeMode`](#servernodemode) below. |
|
|
159
|
+
| `threads` | `number` or `"auto"` | `"auto"` | HTTP worker count for `toiljs start`. `"auto"` uses one per CPU; `1` disables the worker pool. `--threads` on the CLI overrides it. |
|
|
160
|
+
| `srcDir` | `string` | `"server"` | Declarative: your server source directory. See the note below. |
|
|
161
|
+
| `outDir` | `string` | `"build/server"` | Declarative: the server build output directory. See the note below. |
|
|
162
|
+
|
|
163
|
+
> **Note on `server.srcDir` / `server.outDir`.** These fields exist in the config type, but the actual location of your server source and its compiled output is driven by `toilconfig.json` (the toilscript compiler config), not by these two fields. Change your server paths in `toilconfig.json`. These fields are currently declarative and left for forward compatibility. See [not `toilconfig.json`](#toilconfigts-is-not-toilconfigjson) below.
|
|
164
|
+
|
|
165
|
+
### `server.auth`
|
|
166
|
+
|
|
167
|
+
Set `auth: true` to get a complete post-quantum login system with no boilerplate. The build appends a shipped `@rest('auth')` controller and its `@user` shape to your server, giving you `/auth/register`, `/auth/login`, `/auth/me`, and `/auth/logout` plus sessions.
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
export default defineConfig({
|
|
171
|
+
server: {
|
|
172
|
+
auth: true,
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Two things to know:
|
|
178
|
+
|
|
179
|
+
- If you opt in, your app must **not** declare its own `@user` type. The built-in auth owns the single per-program one.
|
|
180
|
+
- There is an escape hatch: adding `import 'toiljs/server/auth'` in `server/main.ts` turns on the same built-in auth surface without this flag.
|
|
181
|
+
|
|
182
|
+
Auth has its own configuration (session secrets, the OPRF and KEM keys) that lives in the **environment**, not here. See [Auth configuration](../auth/configuration.md).
|
|
183
|
+
|
|
184
|
+
### `server.email`
|
|
185
|
+
|
|
186
|
+
The **non-secret** part of your email setup: which provider, the "from" address, and send caps. The dev server and self-host read it. The API key or SMTP password is a **secret** and never goes here; it comes from `.env.secrets` (`TOIL_EMAIL_API_KEY`). Any `TOIL_EMAIL_*` environment variable overrides the matching field here.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
export default defineConfig({
|
|
190
|
+
server: {
|
|
191
|
+
email: {
|
|
192
|
+
provider: 'resend',
|
|
193
|
+
from: 'hello@example.com',
|
|
194
|
+
maxPerMin: 60,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
| Field | Type | Default | What it does |
|
|
201
|
+
| --- | --- | --- | --- |
|
|
202
|
+
| `provider` | `'resend'` \| `'gmail'` \| `'smtp'` | `'resend'` | Which email backend to use. |
|
|
203
|
+
| `from` | `string` | (none) | The "from" address. Validated (single address, no line breaks). |
|
|
204
|
+
| `maxPerMin` | `number` | `60` | Per-process send ceiling per minute (rolling). `0` means unlimited. |
|
|
205
|
+
| `maxPerDay` | `number` | `0` | Per-process send ceiling per day (rolling). `0` means unlimited. |
|
|
206
|
+
| `maxPerRecipientPerHour` | `number` | `5` | Per-recipient hourly cap (anti-abuse). |
|
|
207
|
+
| `smtp` | object | (none) | Connection details for the `gmail` / `smtp` providers: `host`, `port` (defaults to 587 STARTTLS; 465 is implicit TLS), and `user` (defaults to `from`). |
|
|
208
|
+
|
|
209
|
+
See the full guide at [Email and 2FA](../services/email.md).
|
|
210
|
+
|
|
211
|
+
### `server.daemon`
|
|
212
|
+
|
|
213
|
+
Settings for the daemon (L4) background layer, used by the dev process and self-host. In dev, the local process is always the leader, so region fields are informational.
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
export default defineConfig({
|
|
217
|
+
server: {
|
|
218
|
+
daemon: {
|
|
219
|
+
defaultIntervalMs: 60000,
|
|
220
|
+
maxTasks: 64,
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
| Field | Type | Default | What it does |
|
|
227
|
+
| --- | --- | --- | --- |
|
|
228
|
+
| `region` | `string` | (none) | Region the daemon is pinned to (informational in dev). |
|
|
229
|
+
| `standbyRegion` | `string` | (none) | Warm standby region (informational in dev). |
|
|
230
|
+
| `defaultIntervalMs` | `number` | `60000` | Default interval for a `@scheduled` task that declares none. Values below `1000` are clamped up to `1000` (a sub-second loop would flood the console). |
|
|
231
|
+
| `tickBudgetMs` | `number` | `30000` | Per-tick wall-clock budget before the dev scheduler logs an overrun. |
|
|
232
|
+
| `gasTick` | `number` | `0` | Per-tick gas cap (a dev stub: charged then ignored). |
|
|
233
|
+
| `maxTasks` | `number` | `64` | Maximum number of `@scheduled` tasks. Clamped to the range 1 to 1024. |
|
|
234
|
+
|
|
235
|
+
See [Daemons and scheduled jobs](../background/daemons.md).
|
|
236
|
+
|
|
237
|
+
### `server.nodeMode`
|
|
238
|
+
|
|
239
|
+
Which compute layer the single local process emulates. This is a dev and self-host knob only; in production the Dacely edge decides each server's role. Valid values are `hot`, `regional`, `continental`, `daemon`, and `all`. The default, `all`, runs every surface (requests, streams, and daemons) in one process, which is what you want for a full local run. An invalid value falls back to `all` with a warning rather than failing. See [Compute tiers (L1 to L4)](../concepts/tiers.md) for what each layer means.
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
export default defineConfig({
|
|
243
|
+
server: {
|
|
244
|
+
nodeMode: 'all', // run everything locally (the default)
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Defaults at a glance
|
|
250
|
+
|
|
251
|
+
If you write nothing, this is what you get.
|
|
252
|
+
|
|
253
|
+
| Setting | Default |
|
|
254
|
+
| --- | --- |
|
|
255
|
+
| `client.srcDir` | `"client"` |
|
|
256
|
+
| `client.routesDir` | `"routes"` |
|
|
257
|
+
| `client.publicDir` | `"client/public"` |
|
|
258
|
+
| `client.outDir` | `"build/client"` |
|
|
259
|
+
| `client.base` | `"/"` |
|
|
260
|
+
| `client.port` | `3000` |
|
|
261
|
+
| `client.images` | `true` |
|
|
262
|
+
| `client.fonts` | `true` |
|
|
263
|
+
| `client.viewTransitions` | `false` |
|
|
264
|
+
| `client.transitions` | `false` |
|
|
265
|
+
| `client.devtools` | on |
|
|
266
|
+
| `client.seo` | off |
|
|
267
|
+
| `server.auth` | `false` |
|
|
268
|
+
| `server.email` | off |
|
|
269
|
+
| `server.nodeMode` | `"all"` |
|
|
270
|
+
| `server.threads` | `"auto"` |
|
|
271
|
+
| `server.daemon.defaultIntervalMs` | `60000` |
|
|
272
|
+
| `server.daemon.tickBudgetMs` | `30000` |
|
|
273
|
+
| `server.daemon.maxTasks` | `64` |
|
|
274
|
+
|
|
275
|
+
## A fuller example
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import { defineConfig, AiProvider } from 'toiljs/compiler';
|
|
279
|
+
|
|
280
|
+
export default defineConfig({
|
|
281
|
+
client: {
|
|
282
|
+
images: true,
|
|
283
|
+
fonts: true,
|
|
284
|
+
viewTransitions: true,
|
|
285
|
+
seo: {
|
|
286
|
+
url: 'https://example.com',
|
|
287
|
+
title: 'Example',
|
|
288
|
+
description: 'Built with toiljs.',
|
|
289
|
+
},
|
|
290
|
+
devtools: {
|
|
291
|
+
ai: { provider: AiProvider.Anthropic, model: 'claude-sonnet-4-6', apiKeyEnv: 'ANTHROPIC_API_KEY' },
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
server: {
|
|
295
|
+
auth: true,
|
|
296
|
+
email: { provider: 'resend', from: 'hello@example.com' },
|
|
297
|
+
threads: 'auto',
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## `toil.config.ts` is not `toilconfig.json`
|
|
303
|
+
|
|
304
|
+
These two look almost the same and are easy to confuse. They are not the same file.
|
|
305
|
+
|
|
306
|
+
| File | What it is |
|
|
307
|
+
| --- | --- |
|
|
308
|
+
| `toil.config.ts` | **This page.** The framework config: client and server options, styling, SEO, which features to build. You edit it often. |
|
|
309
|
+
| `toilconfig.json` | The **toilscript compiler** config: which server files are entry points, where the `.wasm` is written, and low-level WebAssembly options (optimization level, memory layout, enabled wasm features). It is scaffolded for you and you rarely touch it. Its presence is also what tells toiljs "this project has a server." |
|
|
310
|
+
|
|
311
|
+
If you ever need to change where your server source lives or what the compiled artifact is named, that is `toilconfig.json`, not `toil.config.ts`.
|
|
312
|
+
|
|
313
|
+
## The `toilconfig.json` reference
|
|
314
|
+
|
|
315
|
+
`toilconfig.json` is the **toilscript compiler** config. toilscript is the compiler that turns your `server/` TypeScript into a `.wasm` file (WebAssembly, the sandboxed binary your backend ships as). This file tells toilscript which server files to compile, where to write the output, and which low-level WebAssembly options to use.
|
|
316
|
+
|
|
317
|
+
`toiljs create` scaffolds it for you and most projects never touch it. You only edit it if you want to rename the compiled artifact, move your server entry, or hand-tune the WebAssembly codegen. Its presence at the project root is also the signal toiljs uses to decide "this project has a server" (a project with no `toilconfig.json` is a client-only app).
|
|
318
|
+
|
|
319
|
+
A scaffolded file looks like this:
|
|
320
|
+
|
|
321
|
+
```json
|
|
322
|
+
{
|
|
323
|
+
"entries": ["server/main.ts"],
|
|
324
|
+
"targets": {
|
|
325
|
+
"release": {
|
|
326
|
+
"outFile": "build/server/release.wasm",
|
|
327
|
+
"textFile": "build/server/release.wat"
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
"options": {
|
|
331
|
+
"sourceMap": false,
|
|
332
|
+
"optimizeLevel": 3,
|
|
333
|
+
"shrinkLevel": 1,
|
|
334
|
+
"converge": true,
|
|
335
|
+
"noAssert": false,
|
|
336
|
+
"enable": [
|
|
337
|
+
"sign-extension",
|
|
338
|
+
"mutable-globals",
|
|
339
|
+
"nontrapping-f2i",
|
|
340
|
+
"bulk-memory",
|
|
341
|
+
"simd",
|
|
342
|
+
"reference-types",
|
|
343
|
+
"multi-value"
|
|
344
|
+
],
|
|
345
|
+
"runtime": "stub",
|
|
346
|
+
"lib": ["node_modules/toiljs/server/globals"],
|
|
347
|
+
"memoryBase": 65536,
|
|
348
|
+
"initialMemory": 4,
|
|
349
|
+
"debug": false,
|
|
350
|
+
"trapMode": "allow"
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### `entries`
|
|
356
|
+
|
|
357
|
+
An array of your server entry files: the toilscript starting points for the compile. The scaffold lists just `server/main.ts`, and `main.ts` imports your other surface modules so they all get pulled in. (Under `toiljs build`, toiljs compiles every decorated server file it finds, not only the entries, so a `@rest` or `@data` file you drop in is picked up even if `main.ts` does not import it.)
|
|
358
|
+
|
|
359
|
+
A project that also has a streams tier or a daemon tier lists their entry files here too, so each tier can compile into its own artifact:
|
|
360
|
+
|
|
361
|
+
```json
|
|
362
|
+
"entries": ["server/main.ts", "server/main.stream.ts", "server/main.daemon.ts"]
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### `targets`
|
|
366
|
+
|
|
367
|
+
A map of named build targets. Each target names its output files. The scaffold has one target, `release`.
|
|
368
|
+
|
|
369
|
+
| Field | Type | What it does |
|
|
370
|
+
| --- | --- | --- |
|
|
371
|
+
| `outFile` | `string` | Where the compiled `.wasm` is written. The scaffold uses `build/server/release.wasm`. |
|
|
372
|
+
| `textFile` | `string` | Where the `.wat` (WebAssembly **text** format, the human-readable text form of the same module) is written. Handy for inspecting the output; not needed at runtime. |
|
|
373
|
+
|
|
374
|
+
### `options`
|
|
375
|
+
|
|
376
|
+
Low-level WebAssembly codegen options passed straight to toilscript. The defaults are already tuned for production, so change these only if you know you need to.
|
|
377
|
+
|
|
378
|
+
| Field | Type | Scaffold value | What it controls |
|
|
379
|
+
| --- | --- | --- | --- |
|
|
380
|
+
| `sourceMap` | `boolean` | `false` | Emit a source map alongside the `.wasm` so a debugger can map machine code back to your TypeScript. Off by default (it makes the build bigger). |
|
|
381
|
+
| `optimizeLevel` | `number` | `3` | How hard the optimizer works on **speed**, from `0` (none) to `3` (most). `3` is a production release setting. |
|
|
382
|
+
| `shrinkLevel` | `number` | `1` | How hard the optimizer works on **size**, from `0` to `2`. `1` trades a little speed for a smaller binary. |
|
|
383
|
+
| `converge` | `boolean` | `true` | Re-run the optimizer until the output stops getting better. Squeezes out a bit more at the cost of a slower build. |
|
|
384
|
+
| `noAssert` | `boolean` | `false` | Strip `assert(...)` checks from the output (replace them with just their value, no trap). `false` keeps the safety checks in. |
|
|
385
|
+
| `enable` | `string[]` | (see below) | Which modern WebAssembly features the compiled module is allowed to use. |
|
|
386
|
+
| `runtime` | `string` | `"stub"` | The memory-management runtime baked into the module. `"stub"` is a minimal runtime that never frees memory, which fits toiljs's model exactly: the edge runs one fresh instance per request and throws its whole memory away when the request ends, so there is nothing to garbage-collect. |
|
|
387
|
+
| `lib` | `string[]` | `["node_modules/toiljs/server/globals"]` | Extra library paths whose top-level exports become **ambient globals** (usable with no `import`). This is what makes toiljs's server globals (like `crypto` and the auth primitives) available everywhere in `server/`. |
|
|
388
|
+
| `memoryBase` | `number` | `65536` | The byte offset where your server's static data starts. toiljs reserves the first 64 KiB (`[0, 65536)`) for the **request envelope** the edge writes at offset 0, so a large request body can never overwrite your program's state. Raise it to accept larger request bodies (it costs a little more initial memory). |
|
|
389
|
+
| `initialMemory` | `number` | `4` | How much linear memory the module starts with, in **pages**. One WebAssembly page is 64 KiB, so `4` is 256 KiB. It grows on demand past this. |
|
|
390
|
+
| `debug` | `boolean` | `false` | Include debug information (names and the like) in the binary. Off for production. |
|
|
391
|
+
| `trapMode` | `string` | `"allow"` | What happens on a trapping operation (like a bad float-to-int conversion). `"allow"` lets it trap (the default and correct choice); `"clamp"` replaces traps with clamping instead. |
|
|
392
|
+
|
|
393
|
+
The `enable` array turns on WebAssembly features that are off by default in the compiler. The scaffold enables the modern set the compiled server relies on: `sign-extension`, `mutable-globals`, `nontrapping-f2i` (non-trapping float-to-int conversions), `bulk-memory` (fast `memory.copy` / `memory.fill`), `simd` (vector operations), `reference-types`, and `multi-value` (functions that return more than one value). Leave this list as scaffolded unless you have a specific reason to change it; removing an entry can make the module fail to compile or run.
|
|
394
|
+
|
|
395
|
+
### The `--rpcModule` build flag
|
|
396
|
+
|
|
397
|
+
One toilscript flag is worth knowing even though it lives in your npm scripts, not in `toilconfig.json`: `--rpcModule shared/server.ts`. It tells the compiler to also emit `shared/server.ts`, the fully typed client the browser imports to call your server (the `@data` codec plus the typed `Server` surface). toiljs adds this flag for you on the request build. If your `build:server` script is missing it (older projects predate it), `toiljs doctor --fix` injects it. See [the CLI reference](../cli/README.md#what---fix-repairs).
|
|
398
|
+
|
|
399
|
+
## Gotchas
|
|
400
|
+
|
|
401
|
+
- **Wrap the config in `defineConfig`.** Without it you lose autocomplete and type errors on typos.
|
|
402
|
+
- **Secrets never go here.** API keys, session secrets, and passwords belong in the environment, not in `toil.config.ts` (which is committed to git). See [Environment and secrets](../services/environment.md).
|
|
403
|
+
- **`server.srcDir` / `server.outDir` do not move your server.** The server source location is governed by `toilconfig.json` entries. Editing these two config fields has no effect today.
|
|
404
|
+
- **`nodeMode` and `daemon` are dev/self-host only.** In production the edge assigns each server its role; these settings never override that.
|
|
405
|
+
- **An invalid `nodeMode` does not crash the build.** It warns and falls back to `all`.
|
|
406
|
+
|
|
407
|
+
## Related
|
|
408
|
+
|
|
409
|
+
- [The CLI](../cli/README.md): the commands that read this config.
|
|
410
|
+
- [Environment and secrets](../services/environment.md): runtime values and how they differ from build-time config.
|
|
411
|
+
- [Auth configuration](../auth/configuration.md): the auth-related environment settings behind `server.auth`.
|
|
412
|
+
- [Email and 2FA](../services/email.md): the full email setup behind `server.email`.
|
|
413
|
+
- [Metadata and SEO](../frontend/metadata.md): the full `client.seo` field list.
|
|
414
|
+
- [Daemons and scheduled jobs](../background/daemons.md): the background layer behind `server.daemon`.
|
|
415
|
+
- [Compute tiers (L1 to L4)](../concepts/tiers.md): what `server.nodeMode` selects.
|
|
416
|
+
- [Images](../frontend/images.md) and [Styling](../frontend/styling.md): the features `client.images` and `toiljs configure` control.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Decorators reference
|
|
2
|
+
|
|
3
|
+
Every feature of a toiljs backend, an HTTP route, an RPC method, a database collection, a scheduled job, is switched on by a **decorator**. This page lists them all, grouped by what they do, so you can find the right one at a glance and jump to the page that covers it in depth.
|
|
4
|
+
|
|
5
|
+
## What a decorator is
|
|
6
|
+
|
|
7
|
+
A **decorator** is the `@name` you write on the line just above a class, a method, or a field. It attaches meaning to that code without changing what the code itself does. You are labelling it so the compiler knows how to wire it up.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
@rest('users') // <- a class decorator: "this class is an HTTP controller"
|
|
11
|
+
class Users {
|
|
12
|
+
@get('/:id') // <- a method decorator: "this method answers GET /users/:id"
|
|
13
|
+
public byId(): Response { /* ... */ }
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Three things to notice, because they decide where each decorator can go:
|
|
18
|
+
|
|
19
|
+
- Some decorators apply to a **class** (`@rest`, `@service`, `@stream`, `@daemon`, `@database`, `@data`, `@user`).
|
|
20
|
+
- Some apply to a **method** or a free **function** (`@get`, `@remote`, `@scheduled`, `@query`).
|
|
21
|
+
- One applies to a **field** (`@collection`).
|
|
22
|
+
|
|
23
|
+
Some take arguments, like `@get('/:id')` or `@cache(60)`; the bare ones, like `@rest` or `@daemon`, do not. You never register anything by hand: tagging the code is enough, and the build discovers it.
|
|
24
|
+
|
|
25
|
+
Each decorator also belongs to a **tier** (where its code runs) or is **shared** (compiled into every tier). If tiers are new to you, read [Compute tiers](./tiers.md) first; the short version is L1 = per-request at the edge, L2 / L3 = long-lived stream connections, L4 = one global daemon.
|
|
26
|
+
|
|
27
|
+
## Routing and HTTP (L1)
|
|
28
|
+
|
|
29
|
+
Turn a class into an HTTP controller and its methods into routes. All run on the L1 request tier. Covered in [REST](../backend/rest.md).
|
|
30
|
+
|
|
31
|
+
| Decorator | Applies to | What it does |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `@rest` | class | Marks the class an HTTP controller, mounted at a prefix (`@rest('users')` -> `/users`). |
|
|
34
|
+
| `@route` | method | Declares a route with an explicit method + path: `@route({ method: Methods.GET, path: '/' })`. |
|
|
35
|
+
| `@get` | method | Shorthand for a `GET` route: `@get('/:id')`. |
|
|
36
|
+
| `@post` | method | Shorthand for a `POST` route. |
|
|
37
|
+
| `@put` | method | Shorthand for a `PUT` route. |
|
|
38
|
+
| `@del` | method | Shorthand for a `DELETE` route (named `del` because `delete` is a reserved word). |
|
|
39
|
+
| `@patch` | method | Shorthand for a `PATCH` route. |
|
|
40
|
+
| `@head` | method | Shorthand for a `HEAD` route. |
|
|
41
|
+
| `@options` | method | Shorthand for an `OPTIONS` route. |
|
|
42
|
+
|
|
43
|
+
## RPC (L1)
|
|
44
|
+
|
|
45
|
+
Expose server functions that your own frontend calls like typed async functions. Run on L1. Covered in [RPC](../backend/rpc.md).
|
|
46
|
+
|
|
47
|
+
| Decorator | Applies to | What it does |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `@service` | class | Marks an RPC service; its `@remote` methods are namespaced under the generated client as `Server.<service>.<method>()`. |
|
|
50
|
+
| `@remote` | method / function | Marks a method (of a `@service`) or a top-level function as a client-callable RPC endpoint. |
|
|
51
|
+
|
|
52
|
+
## Guards and policy (L1)
|
|
53
|
+
|
|
54
|
+
Attach a rule to a route (or a whole controller). These stack above a route method. Run on L1.
|
|
55
|
+
|
|
56
|
+
| Decorator | Applies to | What it does | Covered in |
|
|
57
|
+
| --- | --- | --- | --- |
|
|
58
|
+
| `@auth` | class / method | Requires a valid session; returns `401` otherwise. On a class it guards every route. | [Auth usage](../auth/usage.md) |
|
|
59
|
+
| `@cache` | method | Caches the response at the edge and browser: `@cache(edgeMinutes, browserSeconds?, privateScope?, allowAuth?)`. | [Caching](../services/caching.md) |
|
|
60
|
+
| `@ratelimit` | method | Rate-limits a route: `@ratelimit(strategy, limit, window)`. | [Rate limiting](../services/ratelimit.md) |
|
|
61
|
+
|
|
62
|
+
## Realtime streams (L2 / L3)
|
|
63
|
+
|
|
64
|
+
Handle a long-lived connection, keeping state per connected client. The `@stream` class runs on the L2 / L3 stream tier; its lifecycle-hook methods fire as events arrive. Covered in [Realtime streams](../realtime/streams.md).
|
|
65
|
+
|
|
66
|
+
| Decorator | Applies to | What it does |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| `@stream` | class | Marks a stream protocol handler. `@stream('name')` sets the mount name; `@stream({ scope })` picks Regional (L2) or Continental (L3). |
|
|
69
|
+
| `@connect` | method | Lifecycle hook: fires when a client connects (returns a `StreamOutbound` accept/reject). |
|
|
70
|
+
| `@message` | method | Lifecycle hook: fires on each inbound packet. |
|
|
71
|
+
| `@close` | method | Lifecycle hook: fires on a graceful close. |
|
|
72
|
+
| `@disconnect` | method | Lifecycle hook: fires on an abrupt transport loss. |
|
|
73
|
+
|
|
74
|
+
A server-side broadcast hook, `@channel`, is planned but not live in the current runtime. See [Channels](../realtime/channels.md) for the status and the working client-side `useChannel` hook you can use today.
|
|
75
|
+
|
|
76
|
+
## Background and daemon (L4)
|
|
77
|
+
|
|
78
|
+
Recurring, run-once-globally background work. `@daemon` runs on the single L4 leader. Covered in [Daemons](../background/daemons.md).
|
|
79
|
+
|
|
80
|
+
| Decorator | Applies to | What it does |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| `@daemon` | class | Marks the single daemon class (at most one per project). May declare a zero-arg `onStart()` run once at boot. |
|
|
83
|
+
| `@scheduled` | method | Runs the method on a cadence: an interval (`'30s'`, `'5m'`, `'1h'`, `'1d'`) or a 5-field cron string (`'15 9 * * 1-5'`). |
|
|
84
|
+
|
|
85
|
+
## Database structure (shared)
|
|
86
|
+
|
|
87
|
+
Declare your database schema. These carry no tier of their own; they are compiled into every artifact so any tier can use them. Covered in the [database section](../database/README.md).
|
|
88
|
+
|
|
89
|
+
| Decorator | Applies to | What it does |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| `@database` | class | Marks a class as a ToilDB database; each `@collection` field becomes a typed handle (`App.users.get(...)`). |
|
|
92
|
+
| `@collection` | field | Declares a field as a collection handle (`Documents` / `View` / `Unique` / `Counter` / `Events` / `Membership` / `Capacity`). |
|
|
93
|
+
| `@data` | class | Marks a class serializable: the compiler generates a binary codec so it can cross the wire and the database. See [Data types](../backend/data.md). |
|
|
94
|
+
| `@migrate` | function | Schema migration for a `@data` type: a free function that upgrades a record written under an old layout to the current shape (lives in `migrations/<Type>.migration.ts`). See [Data types](../backend/data.md). |
|
|
95
|
+
|
|
96
|
+
## Database function kinds (data-access policy)
|
|
97
|
+
|
|
98
|
+
A **function kind** labels a function or method with *which* database operations it is allowed to issue, and the compiler enforces it (a read-only `@query` that tries to write is a compile error). Covered across the [database section](../database/README.md).
|
|
99
|
+
|
|
100
|
+
| Decorator | Applies to | What it does | Covered in |
|
|
101
|
+
| --- | --- | --- | --- |
|
|
102
|
+
| `@query` | function / method | Read-only data access. | [Database](../database/README.md) |
|
|
103
|
+
| `@action` | function / method | Read plus bounded writes and claims. | [Database](../database/README.md) |
|
|
104
|
+
| `@derive` | function / method | Publishes materialized views and rollups (a background materializer). | [@derive](../background/derive.md) |
|
|
105
|
+
| `@job` | function / method | Background work. | [Background work](../background/README.md) |
|
|
106
|
+
| `@admin` | function / method | Control-plane only operations. | [Database](../database/README.md) |
|
|
107
|
+
|
|
108
|
+
## Auth and structure
|
|
109
|
+
|
|
110
|
+
| Decorator | Applies to | What it does | Covered in |
|
|
111
|
+
| --- | --- | --- | --- |
|
|
112
|
+
| `@user` | class | Declares the authenticated-user shape; enables the typed `AuthService.getUser()`. At most one per project. | [Auth usage](../auth/usage.md) |
|
|
113
|
+
| `@main` | function | Marks a single top-level function as the module entry point (exported as the WASM `main`). Rarely written by hand: toiljs supplies the entry glue. | [Project structure](../getting-started/project-structure.md) |
|
|
114
|
+
|
|
115
|
+
## A note on low-level decorators
|
|
116
|
+
|
|
117
|
+
AssemblyScript (the language toilscript is built on) has its own low-level decorators such as `@inline`, `@unsafe`, and `@operator`. They tune how a symbol compiles and are for advanced library authors, not everyday app code. You will not need them to build a normal toiljs app, and they are out of scope here.
|
|
118
|
+
|
|
119
|
+
## Related
|
|
120
|
+
|
|
121
|
+
- [Compute tiers](./tiers.md): where each decorator's code runs (L1 to L4).
|
|
122
|
+
- [The type system](./types.md): the types (`u64`, `string`, `@data`) these decorators work with.
|
|
123
|
+
- [REST](../backend/rest.md) and [RPC](../backend/rpc.md): the L1 surfaces.
|
|
124
|
+
- [Realtime streams](../realtime/streams.md): the `@stream` surface.
|
|
125
|
+
- [Daemons](../background/daemons.md) and [@derive](../background/derive.md): L4 and background work.
|
|
126
|
+
- [The database (ToilDB)](../database/README.md): `@database`, `@collection`, and the function kinds.
|
|
127
|
+
- [Auth](../auth/README.md): `@auth` and `@user`.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
Security in toil is not a single feature you switch on. It is a set of defaults that are already on, spread across the whole stack, so that the safe path is also the default path.
|
|
4
|
+
|
|
5
|
+
## The model
|
|
6
|
+
|
|
7
|
+
Toil is built for **defense in depth**: many independent layers, so that one weak spot is not one breach. The goal is the top of our internal security bar, the Resilience and Scale Grade (see [../../RSG.md](../../RSG.md)), where each of these holds at the same time:
|
|
8
|
+
|
|
9
|
+
- All traffic is encrypted in transit with modern TLS (Transport Layer Security, the encryption every `https://` connection uses).
|
|
10
|
+
- The login design never lets the server see a password it could reuse.
|
|
11
|
+
- Secrets never live in your code.
|
|
12
|
+
- The browser verifies every asset it loads before running it.
|
|
13
|
+
- There is no implicit trust between the parts of the system (zero trust): each request is checked on its own merits.
|
|
14
|
+
|
|
15
|
+
The rest of this page walks through each layer and points to where it is documented in full.
|
|
16
|
+
|
|
17
|
+
## The backend is a sandbox
|
|
18
|
+
|
|
19
|
+
Every site (every tenant) on toil runs inside a **sandbox**: a walled-off compartment that can only touch what the host explicitly hands it, and can never reach the host itself or another tenant. Your server code is compiled to **WebAssembly** (wasm, a portable binary instruction format that runs in a tightly controlled virtual machine), and the edge loads each tenant as its own isolated wasm module.
|
|
20
|
+
|
|
21
|
+
That isolation is backed by **hard per-host limits**, enforced by the host and not by your code:
|
|
22
|
+
|
|
23
|
+
- **A committed-memory cap.** Each tenant may commit only a bounded amount of real memory (on the order of tens of MiB). A tenant cannot grow without bound and starve its neighbors.
|
|
24
|
+
- **A compute cap.** Each request gets a bounded compute budget, and when a request exceeds it, the host stops it. A tenant cannot spin forever on the request path.
|
|
25
|
+
- **Rate limits.** How often one client may call a route is capped (see [Transport and rate limiting](#transport-and-rate-limiting) below).
|
|
26
|
+
- **Hostile-wasm containment.** The runtime is hardened to run untrusted, even deliberately malicious, wasm without letting it escape.
|
|
27
|
+
|
|
28
|
+
The point of all of this: a buggy or malicious tenant can crash or hang only itself. It cannot escape its sandbox, exhaust the host, or read or corrupt another tenant's data.
|
|
29
|
+
|
|
30
|
+
## Passwords: post-quantum login
|
|
31
|
+
|
|
32
|
+
Toil ships a complete login system you turn on with one line, and it is built so the server never handles a usable password. See [../auth/README.md](../auth/README.md) for the overview and [../auth/how-it-works.md](../auth/how-it-works.md) for the mechanism.
|
|
33
|
+
|
|
34
|
+
Here is why that matters. The pattern almost the whole internet uses is "send the password over TLS, then hash it on the server." That is the accepted baseline, but it caps security below the top grade, because for a moment the server holds a live, replayable credential. Anything that reads server memory or a stray log line at the wrong instant walks away with real passwords, and that is one of the most common breach paths in practice.
|
|
35
|
+
|
|
36
|
+
Toil designs that surface away. The proof that you know your password happens without the password ever crossing the wire in a form anyone can replay, so a fully breached server still yields no usable credentials.
|
|
37
|
+
|
|
38
|
+
## Subresource Integrity (SRI) on the client bundle
|
|
39
|
+
|
|
40
|
+
This is the layer that protects the code running in your users' browsers.
|
|
41
|
+
|
|
42
|
+
### The risk
|
|
43
|
+
|
|
44
|
+
Your app's JavaScript and CSS are static files served over the network, often through a CDN (Content Delivery Network, the fleet of edge caches that sits between your server and the browser). If any point in that path is compromised (the host, a cache, or the network itself), an attacker can serve **tampered** JavaScript or CSS: a modified bundle that steals sessions, skims form input, or rewrites the page. The browser, on its own, has no way to know the bytes it received are not the bytes you shipped.
|
|
45
|
+
|
|
46
|
+
### What toil does at build time
|
|
47
|
+
|
|
48
|
+
**Subresource Integrity (SRI)** is a browser feature that closes this gap. You attach a cryptographic fingerprint of a file to the tag that loads it, and the browser refuses to use the file unless its bytes match that fingerprint.
|
|
49
|
+
|
|
50
|
+
At build time (verified in [../../src/compiler/sri.ts](../../src/compiler/sri.ts)), toil adds that fingerprint to every **local** asset tag in the built HTML:
|
|
51
|
+
|
|
52
|
+
- every `<script src=...>`,
|
|
53
|
+
- every `<link rel="modulepreload" href=...>`,
|
|
54
|
+
- every `<link rel="stylesheet" href=...>`.
|
|
55
|
+
|
|
56
|
+
To each it adds two attributes: `integrity="sha384-<base64>"` and `crossorigin="anonymous"`.
|
|
57
|
+
|
|
58
|
+
- **`sha384`** is the hash algorithm: the file's bytes are run through SHA-384 (a member of the SHA-2 family), producing a fixed fingerprint that changes if even one byte changes. The `integrity` value is that fingerprint, base64-encoded, with a `sha384-` prefix.
|
|
59
|
+
- **`crossorigin="anonymous"`** is required for the browser to actually verify the fingerprint. It is a no-op when the asset is same-origin, so adding it is always safe.
|
|
60
|
+
|
|
61
|
+
Tags that are not toil's to hash are left completely untouched: external URLs (anything with a scheme like `https:`, a protocol-relative `//cdn/...`, or a `data:` URI), inline scripts (a `<script>` with no `src`), and any tag that already carries its own `integrity`.
|
|
62
|
+
|
|
63
|
+
### Why an import map is also emitted
|
|
64
|
+
|
|
65
|
+
A per-tag `integrity` only protects the **entry** script: the one file named directly in the tag. But a modern app is an ES module graph (ECMAScript modules, the browser's native `import` system). The entry script statically imports other chunks (for example the shared React vendor chunk), and it lazily pulls in each route with a dynamic `import()` as the user navigates. Those follow-on files are fetched by the browser's **module loader**, which does **not** inherit the entry tag's `integrity`. Without more, everything past the entry point would be unverified.
|
|
66
|
+
|
|
67
|
+
So toil also emits an **import map**: a `<script type="importmap">` tag (a standard browser mechanism for describing module resolution). Its `integrity` section maps every emitted JavaScript chunk to its own `sha384` fingerprint. This is the platform mechanism that extends verification to the module loader's fetches, so the **full** dynamic `import()` graph is covered, not just the entry tag. Browsers that do not yet support the `integrity` key simply ignore it (progressive enhancement: nothing breaks on older browsers, they just get one fewer check).
|
|
68
|
+
|
|
69
|
+
### Why it stays stable
|
|
70
|
+
|
|
71
|
+
A fingerprint is only useful if it keeps matching the bytes actually served. Toil guarantees that by **content-hashing** every asset it puts SRI on: the build tool (Vite) names each JavaScript chunk and each CSS file with a hash of its own contents baked into the URL (verified in [../../src/compiler/vite.ts](../../src/compiler/vite.ts)). Because the URL is pinned to the exact bytes, the fingerprint baked into the HTML can never falsely mismatch.
|
|
72
|
+
|
|
73
|
+
This prevents a real failure mode. Imagine a stylesheet kept a stable URL but its bytes changed on a new deploy. A user still holding an older HTML page (with the old baked fingerprint) would fetch the new bytes, the hashes would disagree, and the browser would **block** the stylesheet, breaking the page for that user. Content-hashing sidesteps this: new bytes get a new URL, so old HTML keeps pointing at the old bytes, and every fingerprint always matches. (Images and fonts keep stable names on purpose: they carry no SRI and are often referenced by fixed path.)
|
|
74
|
+
|
|
75
|
+
### What the browser does
|
|
76
|
+
|
|
77
|
+
For each asset that carries an `integrity` value (from a tag or from the import map), the browser hashes the bytes it received and compares them to the expected fingerprint. If they match, it runs the script or applies the stylesheet. If they do not match, it refuses: the asset is blocked and tampered code never runs.
|
|
78
|
+
|
|
79
|
+
```mermaid
|
|
80
|
+
flowchart TD
|
|
81
|
+
A["Browser loads the built page HTML"] --> B["Reads integrity tags plus the importmap integrity section"]
|
|
82
|
+
B --> C["Fetches an asset: entry script, vendor chunk, route chunk, or stylesheet"]
|
|
83
|
+
C --> D["Computes the sha384 hash of the received bytes"]
|
|
84
|
+
D --> E{"Hash matches the baked fingerprint?"}
|
|
85
|
+
E -->|yes| F["Runs the script or applies the stylesheet"]
|
|
86
|
+
E -->|no| G["Refuses: blocks the asset, tampered code never runs"]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### When it applies
|
|
90
|
+
|
|
91
|
+
SRI is part of the **production build output**. The dev server hands the browser un-hashed modules straight from Vite, where SRI would be meaningless, so it is not applied there. You get the protection automatically in what you ship, with nothing to configure.
|
|
92
|
+
|
|
93
|
+
## Secrets
|
|
94
|
+
|
|
95
|
+
Secrets (API keys, signing keys, and the like) are **never compiled into the wasm**. They are provided per-host at runtime through the secure environment store and read with `Environment.getSecure(key)`, which is kept strictly separate from the plain-variable `Environment.get(key)`: a secret can never leak out through a code path that only reads plain config. Because secrets live outside your code, they are never in your repository or its history. See [../services/environment.md](../services/environment.md) for the full model.
|
|
96
|
+
|
|
97
|
+
## Transport and rate limiting
|
|
98
|
+
|
|
99
|
+
All traffic runs over modern **TLS**, so nothing sensitive travels in the clear. The edge speaks **HTTP/3** (built on QUIC, a faster, head-of-line-blocking-free transport) with graceful fallback to HTTP/2 and HTTP/1.1, so newer clients get the modern path and older clients still connect and work.
|
|
100
|
+
|
|
101
|
+
On top of that, you can cap how often a client may hit any given route. Adding the `@ratelimit` decorator to a route makes the edge reject a client that goes over the limit **before your code runs**, which blunts brute-force and abuse and helps absorb bursts and denial-of-service (DDoS) pressure at the edge rather than inside your handler. Only routes that opt in pay any cost; everything else stays on the untouched fast path. See [../services/ratelimit.md](../services/ratelimit.md) for the strategies and per-user (keyed) limits.
|
|
102
|
+
|
|
103
|
+
## Related
|
|
104
|
+
|
|
105
|
+
- [Authentication](../auth/README.md): the post-quantum login system and `@auth`-guarded routes.
|
|
106
|
+
- [Environment](../services/environment.md): plain variables versus secrets, and where they live.
|
|
107
|
+
- [Rate limiting](../services/ratelimit.md): the `@ratelimit` decorator, strategies, and keyed limits.
|
|
108
|
+
- [Design principles](../introduction/design-principles.md): the ideas the framework is built on.
|