toiljs 0.0.115 → 0.0.117

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.
Files changed (326) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +16 -7
  3. package/TYPESCRIPT_LAW.md +1946 -2073
  4. package/build/backend/.tsbuildinfo +1 -1
  5. package/build/cli/.tsbuildinfo +1 -1
  6. package/build/cli/index.js +2375 -258
  7. package/build/client/.tsbuildinfo +1 -1
  8. package/build/client/dev/devtools.js +1 -1
  9. package/build/client/dev/error-overlay.js +1 -1
  10. package/build/client/routing/mount.js +1 -1
  11. package/build/compiler/.tsbuildinfo +1 -1
  12. package/build/compiler/auth-emails.js +7 -3
  13. package/build/compiler/emails.js +1 -1
  14. package/build/compiler/index.d.ts +1 -0
  15. package/build/compiler/index.js +25 -4
  16. package/build/compiler/pages.js +2 -3
  17. package/build/compiler/prerender.d.ts +3 -8
  18. package/build/compiler/prerender.js +88 -127
  19. package/build/compiler/ssg.js +3 -4
  20. package/build/compiler/template-build.js +3 -4
  21. package/build/compiler/toil-docs.generated.js +6 -5
  22. package/build/compiler/vite.js +3 -6
  23. package/build/devserver/.tsbuildinfo +1 -1
  24. package/build/devserver/db/database.js +7 -3
  25. package/build/devserver/db/types.js +1 -1
  26. package/build/devserver/production.js +4 -3
  27. package/build/devserver/runtime/crypto.d.ts +1 -1
  28. package/build/io/.tsbuildinfo +1 -1
  29. package/build/logger/.tsbuildinfo +1 -1
  30. package/build/shared/.tsbuildinfo +1 -1
  31. package/examples/basic/client/routes/analytics.tsx +7 -5
  32. package/examples/basic/server/tsconfig.json +1 -3
  33. package/package.json +45 -44
  34. package/presets/no-uint8array-tostring.js +120 -153
  35. package/presets/oxlint.d.ts +5 -0
  36. package/presets/oxlint.js +224 -0
  37. package/server/runtime/tsconfig.json +1 -3
  38. package/tsconfig.json +2 -11
  39. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  40. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
  41. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  42. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  43. package/.github/PULL_REQUEST_TEMPLATE.md +0 -43
  44. package/.github/changelog-config.json +0 -45
  45. package/.github/dependabot.yml +0 -27
  46. package/.github/workflows/ci.yml +0 -222
  47. package/.vscode/settings.json +0 -10
  48. package/docs/README.md +0 -107
  49. package/docs/auth/README.md +0 -104
  50. package/docs/auth/configuration.md +0 -105
  51. package/docs/auth/emails.md +0 -92
  52. package/docs/auth/extending.md +0 -202
  53. package/docs/auth/how-it-works.md +0 -138
  54. package/docs/auth/usage.md +0 -188
  55. package/docs/backend/README.md +0 -143
  56. package/docs/backend/data.md +0 -351
  57. package/docs/backend/rest.md +0 -402
  58. package/docs/backend/rpc.md +0 -226
  59. package/docs/background/README.md +0 -114
  60. package/docs/background/daemons.md +0 -276
  61. package/docs/background/derive.md +0 -179
  62. package/docs/cli/README.md +0 -379
  63. package/docs/concepts/ai-guide.md +0 -132
  64. package/docs/concepts/config.md +0 -416
  65. package/docs/concepts/decorators.md +0 -127
  66. package/docs/concepts/security.md +0 -108
  67. package/docs/concepts/tiers.md +0 -166
  68. package/docs/concepts/types.md +0 -216
  69. package/docs/database/README.md +0 -143
  70. package/docs/database/capacity.md +0 -350
  71. package/docs/database/counters.md +0 -174
  72. package/docs/database/documents.md +0 -277
  73. package/docs/database/events.md +0 -307
  74. package/docs/database/membership.md +0 -246
  75. package/docs/database/setup.md +0 -155
  76. package/docs/database/unique.md +0 -216
  77. package/docs/database/views.md +0 -246
  78. package/docs/frontend/README.md +0 -104
  79. package/docs/frontend/components.md +0 -286
  80. package/docs/frontend/data-fetching.md +0 -260
  81. package/docs/frontend/images.md +0 -148
  82. package/docs/frontend/metadata.md +0 -344
  83. package/docs/frontend/navigation.md +0 -296
  84. package/docs/frontend/rendering.md +0 -236
  85. package/docs/frontend/routing.md +0 -346
  86. package/docs/frontend/scripts.md +0 -118
  87. package/docs/frontend/search.md +0 -191
  88. package/docs/frontend/styling.md +0 -147
  89. package/docs/frontend/toil-global.md +0 -216
  90. package/docs/getting-started/README.md +0 -81
  91. package/docs/getting-started/create-project.md +0 -131
  92. package/docs/getting-started/deploy.md +0 -101
  93. package/docs/getting-started/first-app.md +0 -215
  94. package/docs/getting-started/installation.md +0 -124
  95. package/docs/getting-started/migrating.md +0 -125
  96. package/docs/getting-started/project-structure.md +0 -163
  97. package/docs/introduction/README.md +0 -66
  98. package/docs/introduction/design-principles.md +0 -81
  99. package/docs/introduction/distributed.md +0 -91
  100. package/docs/introduction/how-it-works.md +0 -121
  101. package/docs/introduction/hyperscale.md +0 -70
  102. package/docs/introduction/modern-stack.md +0 -85
  103. package/docs/introduction/vs-other-frameworks.md +0 -64
  104. package/docs/introduction/why-toil.md +0 -99
  105. package/docs/llms.txt +0 -95
  106. package/docs/realtime/README.md +0 -102
  107. package/docs/realtime/channels.md +0 -211
  108. package/docs/realtime/streams.md +0 -369
  109. package/docs/services/README.md +0 -60
  110. package/docs/services/analytics.md +0 -279
  111. package/docs/services/caching.md +0 -175
  112. package/docs/services/cookies.md +0 -235
  113. package/docs/services/crypto.md +0 -209
  114. package/docs/services/email.md +0 -289
  115. package/docs/services/environment.md +0 -141
  116. package/docs/services/ratelimit.md +0 -174
  117. package/docs/services/time.md +0 -85
  118. package/eslint.config.js +0 -57
  119. package/examples/basic/client/routes/docs/[...slug].tsx +0 -12
  120. package/presets/eslint.js +0 -88
  121. package/src/backend/index.ts +0 -204
  122. package/src/cli/configure.ts +0 -363
  123. package/src/cli/create.ts +0 -924
  124. package/src/cli/db.ts +0 -158
  125. package/src/cli/diagnostics.ts +0 -665
  126. package/src/cli/doctor.ts +0 -982
  127. package/src/cli/features.ts +0 -170
  128. package/src/cli/index.ts +0 -326
  129. package/src/cli/notify.ts +0 -120
  130. package/src/cli/proc.ts +0 -56
  131. package/src/cli/ui.ts +0 -169
  132. package/src/cli/update.ts +0 -248
  133. package/src/cli/updates.ts +0 -92
  134. package/src/cli/validate.ts +0 -31
  135. package/src/cli/version-check.ts +0 -125
  136. package/src/client/auth.ts +0 -869
  137. package/src/client/channel/channel.ts +0 -146
  138. package/src/client/components/Form.tsx +0 -65
  139. package/src/client/components/Image.tsx +0 -160
  140. package/src/client/components/Script.tsx +0 -113
  141. package/src/client/components/Slot.tsx +0 -21
  142. package/src/client/dev/devtools.tsx +0 -1089
  143. package/src/client/dev/error-overlay.tsx +0 -225
  144. package/src/client/errors.ts +0 -11
  145. package/src/client/head/head.ts +0 -182
  146. package/src/client/head/metadata.ts +0 -110
  147. package/src/client/index.ts +0 -130
  148. package/src/client/navigation/Link.tsx +0 -108
  149. package/src/client/navigation/NavLink.tsx +0 -86
  150. package/src/client/navigation/navigation.ts +0 -235
  151. package/src/client/navigation/prefetch.ts +0 -169
  152. package/src/client/navigation/scroll.ts +0 -53
  153. package/src/client/routing/Router.tsx +0 -216
  154. package/src/client/routing/action.ts +0 -122
  155. package/src/client/routing/error-boundary.tsx +0 -43
  156. package/src/client/routing/hooks.ts +0 -140
  157. package/src/client/routing/lazy.ts +0 -77
  158. package/src/client/routing/loader.ts +0 -381
  159. package/src/client/routing/match.ts +0 -47
  160. package/src/client/routing/mount.tsx +0 -90
  161. package/src/client/routing/params-context.ts +0 -10
  162. package/src/client/routing/slot-context.ts +0 -7
  163. package/src/client/rpc.ts +0 -97
  164. package/src/client/search/search.ts +0 -189
  165. package/src/client/search/use-page-search.ts +0 -73
  166. package/src/client/ssr/markers.tsx +0 -172
  167. package/src/client/stream/client.ts +0 -237
  168. package/src/client/types.ts +0 -73
  169. package/src/compiler/auth-emails.ts +0 -312
  170. package/src/compiler/config.ts +0 -369
  171. package/src/compiler/docs.ts +0 -127
  172. package/src/compiler/email-preview.ts +0 -314
  173. package/src/compiler/emails.ts +0 -404
  174. package/src/compiler/fonts.ts +0 -89
  175. package/src/compiler/generate.ts +0 -471
  176. package/src/compiler/image-blur.ts +0 -42
  177. package/src/compiler/image-report.ts +0 -88
  178. package/src/compiler/index.ts +0 -1362
  179. package/src/compiler/pages.ts +0 -49
  180. package/src/compiler/plugin.ts +0 -391
  181. package/src/compiler/prerender.ts +0 -263
  182. package/src/compiler/routes.ts +0 -170
  183. package/src/compiler/seo.ts +0 -415
  184. package/src/compiler/sri.ts +0 -144
  185. package/src/compiler/ssg.ts +0 -177
  186. package/src/compiler/ssr-codegen.ts +0 -85
  187. package/src/compiler/template-build.ts +0 -676
  188. package/src/compiler/template.ts +0 -262
  189. package/src/compiler/toil-docs.generated.ts +0 -77
  190. package/src/compiler/vite.ts +0 -245
  191. package/src/devserver/analytics/index.ts +0 -317
  192. package/src/devserver/config/dotenv.ts +0 -102
  193. package/src/devserver/config/env.ts +0 -30
  194. package/src/devserver/config/ratelimit.ts +0 -123
  195. package/src/devserver/daemon/catalog.ts +0 -120
  196. package/src/devserver/daemon/cron.ts +0 -87
  197. package/src/devserver/daemon/host.ts +0 -134
  198. package/src/devserver/daemon/index.ts +0 -369
  199. package/src/devserver/daemon/runtime.ts +0 -48
  200. package/src/devserver/db/catalog.ts +0 -109
  201. package/src/devserver/db/database.ts +0 -1834
  202. package/src/devserver/db/derives.ts +0 -121
  203. package/src/devserver/db/index.ts +0 -20
  204. package/src/devserver/db/routeKinds.ts +0 -191
  205. package/src/devserver/db/types.ts +0 -168
  206. package/src/devserver/email/caps.ts +0 -0
  207. package/src/devserver/email/config.ts +0 -141
  208. package/src/devserver/email/console.ts +0 -348
  209. package/src/devserver/email/index.ts +0 -155
  210. package/src/devserver/email/providers.ts +0 -131
  211. package/src/devserver/email/status.ts +0 -23
  212. package/src/devserver/email/validate.ts +0 -37
  213. package/src/devserver/email/wire.ts +0 -55
  214. package/src/devserver/http/cache.ts +0 -0
  215. package/src/devserver/http/envelope.ts +0 -157
  216. package/src/devserver/http/proxy.ts +0 -172
  217. package/src/devserver/http/runtime.ts +0 -387
  218. package/src/devserver/index.ts +0 -43
  219. package/src/devserver/production-ipc.ts +0 -63
  220. package/src/devserver/production-worker.ts +0 -83
  221. package/src/devserver/production.ts +0 -718
  222. package/src/devserver/runtime/crypto.ts +0 -587
  223. package/src/devserver/runtime/host.ts +0 -427
  224. package/src/devserver/runtime/module.ts +0 -442
  225. package/src/devserver/server.ts +0 -251
  226. package/src/devserver/ssr.ts +0 -208
  227. package/src/devserver/stream/catalog.ts +0 -106
  228. package/src/devserver/stream/host.ts +0 -42
  229. package/src/devserver/stream/index.ts +0 -301
  230. package/src/devserver/stream/manager.ts +0 -167
  231. package/src/devserver/stream/router.ts +0 -101
  232. package/src/devserver/stream/wire.ts +0 -58
  233. package/src/devserver/stream/ws.ts +0 -78
  234. package/src/devserver/wasm/sections.ts +0 -59
  235. package/src/devserver/wasm/surface.ts +0 -115
  236. package/src/io/FastMap.ts +0 -151
  237. package/src/io/FastSet.ts +0 -113
  238. package/src/io/codec.ts +0 -362
  239. package/src/io/index.ts +0 -10
  240. package/src/io/lengths.ts +0 -14
  241. package/src/io/types.ts +0 -19
  242. package/src/logger/index.ts +0 -22
  243. package/src/shared/index.ts +0 -46
  244. package/test/analytics-dev.test.ts +0 -113
  245. package/test/assembly/aspect-shim.ts +0 -33
  246. package/test/assembly/cookie.spec.ts +0 -303
  247. package/test/assembly/example.spec.ts +0 -22
  248. package/test/assembly/ssr.spec.ts +0 -95
  249. package/test/assembly.test.ts +0 -59
  250. package/test/auth-emails.test.ts +0 -118
  251. package/test/built-ssr.test.ts +0 -148
  252. package/test/channel.test.ts +0 -21
  253. package/test/config-host-port.test.ts +0 -59
  254. package/test/configure.test.ts +0 -94
  255. package/test/daemon-build.test.ts +0 -278
  256. package/test/daemon-catalog.test.ts +0 -349
  257. package/test/daemon-emulation.test.ts +0 -256
  258. package/test/db.test.ts +0 -0
  259. package/test/devserver-database.test.ts +0 -1102
  260. package/test/devserver-email.test.ts +0 -306
  261. package/test/devserver-pqauth.test.ts +0 -93
  262. package/test/devserver-secrets.test.ts +0 -63
  263. package/test/devserver.test.ts +0 -259
  264. package/test/doctor.test.ts +0 -298
  265. package/test/dom/Image.test.tsx +0 -144
  266. package/test/dom/Link.test.tsx +0 -67
  267. package/test/dom/NavLink.test.tsx +0 -37
  268. package/test/dom/Script.test.tsx +0 -48
  269. package/test/dom/action.test.tsx +0 -146
  270. package/test/dom/error-overlay.test.tsx +0 -44
  271. package/test/dom/loader.test.tsx +0 -121
  272. package/test/dom/navigation.test.ts +0 -59
  273. package/test/dom/revalidate.test.tsx +0 -38
  274. package/test/dom/route-head.test.tsx +0 -56
  275. package/test/dom/router-loading.test.tsx +0 -44
  276. package/test/dom/scroll.test.ts +0 -56
  277. package/test/dom/slot.test.tsx +0 -131
  278. package/test/dom/use-metadata.test.tsx +0 -58
  279. package/test/dom/view-transitions.test.tsx +0 -53
  280. package/test/dynamic-routing.test.ts +0 -132
  281. package/test/email-console.test.ts +0 -86
  282. package/test/email-preview.test.ts +0 -73
  283. package/test/emails.test.ts +0 -58
  284. package/test/errors.test.ts +0 -21
  285. package/test/example-guestbook.test.ts +0 -123
  286. package/test/features.test.ts +0 -149
  287. package/test/fixtures/bignum-wire/spec.ts +0 -25
  288. package/test/fixtures/daemon-app.ts +0 -65
  289. package/test/fixtures/stream-echo.ts +0 -26
  290. package/test/fixtures/stream-gate.ts +0 -28
  291. package/test/fixtures/stream-trap.ts +0 -18
  292. package/test/fixtures/stream-typed.ts +0 -41
  293. package/test/fonts.test.ts +0 -28
  294. package/test/global-setup.ts +0 -17
  295. package/test/head.test.ts +0 -42
  296. package/test/image-blur.test.ts +0 -50
  297. package/test/io.test.ts +0 -117
  298. package/test/metadata.test.ts +0 -40
  299. package/test/navlink.test.ts +0 -28
  300. package/test/pages.test.ts +0 -105
  301. package/test/placeholder.test.ts +0 -9
  302. package/test/pqauth-e2e.test.ts +0 -216
  303. package/test/pqauth-email.test.ts +0 -801
  304. package/test/prerender.test.ts +0 -116
  305. package/test/prettier-plugin.test.ts +0 -46
  306. package/test/routes.test.ts +0 -76
  307. package/test/rpc-bignum-wire.test.ts +0 -164
  308. package/test/rpc-dispatch.test.ts +0 -132
  309. package/test/rpc-kinds.test.ts +0 -18
  310. package/test/rpc.test.ts +0 -66
  311. package/test/search.test.ts +0 -114
  312. package/test/seo.test.ts +0 -175
  313. package/test/slot-layouts.test.ts +0 -69
  314. package/test/sri.test.ts +0 -129
  315. package/test/ssg.test.ts +0 -36
  316. package/test/ssr-hydration.test.tsx +0 -122
  317. package/test/ssr-render.test.ts +0 -197
  318. package/test/ssr-template.test.tsx +0 -482
  319. package/test/stream-emulation.test.ts +0 -479
  320. package/test/ui.test.ts +0 -40
  321. package/test/update.test.ts +0 -58
  322. package/test/validate.test.ts +0 -42
  323. package/test/version-check.test.ts +0 -130
  324. package/tests/data-parity/generated-parity.ts +0 -99
  325. package/tests/data-parity/parity.ts +0 -80
  326. package/tests/data-parity/spec.ts +0 -46
@@ -1,246 +0,0 @@
1
- # Views (materialized views)
2
-
3
- A `View` collection is a **precomputed, read-optimized result** that you publish
4
- once and read many times. Reads are a single cheap keyed lookup, no scanning, no
5
- recomputing.
6
-
7
- ## What and why
8
-
9
- A "materialized view" is a fancy name for a simple idea: instead of computing an
10
- answer every time someone asks, you compute it **once**, store the finished
11
- answer, and just hand it out on each request. "Materialized" means the result is
12
- made real and saved (as opposed to computed on the fly). "View" means it is a
13
- read-friendly projection of your underlying data.
14
-
15
- Think of a leaderboard. Computing "the top 10 players by score" means scanning
16
- every player and sorting. You do not want to do that on every page load. So you
17
- compute it in the background, save the finished top-10 list as a view, and every
18
- page load just reads that saved list. Fast, cheap, and it does not get slower as
19
- you add players.
20
-
21
- Use a `View` when:
22
-
23
- - A page needs a result that is **expensive to compute** (a scan, a sort, a fold
24
- over many rows).
25
- - That result is **read far more often than it changes** (a home page, a feed, a
26
- leaderboard, a "latest N" list, a rendered summary).
27
-
28
- The rule of thumb: if a route wants "the newest N of something" or "the top N of
29
- something", that is a scan, and scans are barred on the request path. A `View`
30
- is how you serve that result without scanning per request.
31
-
32
- ```mermaid
33
- flowchart LR
34
- subgraph off["Off the request path (@derive)"]
35
- S[("Source data<br/>events / counters / records")] --> D["@derive: scan + compute"]
36
- D --> P["publish(key, result)"]
37
- end
38
- P --> V[("View")]
39
- subgraph on["On the request path (@get)"]
40
- G["view.get(key)"] --> V
41
- end
42
- ```
43
-
44
- ## The type
45
-
46
- A `View` has two type parameters: the **key** (which view) and the **value**
47
- (the precomputed result).
48
-
49
- ```ts
50
- View<K, V>
51
- ```
52
-
53
- - `K` is the key type: it picks *which* view to read or publish. For a single
54
- global leaderboard, the key can be a fixed constant like `'main'`.
55
- - `V` is the value type: the finished result you serve. Both are
56
- [`@data`](../concepts/types.md) classes.
57
-
58
- Declare it as a `@collection` field inside a `@database` class, alongside the
59
- sources it is built from:
60
-
61
- ```ts
62
- @database
63
- class BoardDb {
64
- @collection static scores: Events<GameKey, ScoreEvent>; // a source
65
- @collection static board: View<GameKey, Leaderboard>; // the view
66
- }
67
- ```
68
-
69
- ## Operations
70
-
71
- A `View` has exactly two operations you use directly (plus `require`, a
72
- convenience wrapper). Exact signatures:
73
-
74
- | Operation | Signature | What it does |
75
- | --- | --- | --- |
76
- | `get` | `get(key: K): V \| null` | Read the published view, or `null` if nothing has been published yet. |
77
- | `require` | `require(key: K): V` | Like `get`, but traps (aborts the request) if nothing is published. |
78
- | `publish` | `publish(key: K, value: V): void` | Overwrite the view for `key` with a fresh result. |
79
-
80
- ### `get`
81
-
82
- A plain keyed read. It is cheap and is allowed from **any** handler, including a
83
- read-only `@get` route.
84
-
85
- ```ts
86
- const board = BoardDb.board.get(new GameKey('main'));
87
- if (board == null) {
88
- // Nothing published yet (e.g. brand-new game). Serve an empty default.
89
- return new Leaderboard();
90
- }
91
- return board;
92
- ```
93
-
94
- Always handle the `null` case: until a `@derive` (or `@job`) has published at
95
- least once, `get` returns `null`.
96
-
97
- ### `publish`
98
-
99
- Overwrites the stored view with a new value. This is how the view gets its
100
- content.
101
-
102
- ```ts
103
- BoardDb.board.publish(new GameKey('main'), freshBoard);
104
- ```
105
-
106
- `publish` is **restricted**: you may only call it from a
107
- [`@derive`](../background/derive.md) or a `@job` (a background task), never from a
108
- request handler (`@get`/`@post`/...). The compiler enforces this, and so does the
109
- edge at runtime. The reason: a view is meant to be maintained off the request
110
- path from the source of truth, not written ad hoc by whichever request happens to
111
- run.
112
-
113
- ### How `publish` differs from a Documents write
114
-
115
- A `View`'s `publish` looks like writing a value, but it is not the same as a
116
- [Documents](./documents.md) `patch`/`create`. The differences matter:
117
-
118
- | | `Documents` write (`create` / `patch`) | `View` `publish` |
119
- | --- | --- | --- |
120
- | Who may call it | Any action handler (`@post`, ...) | Only a `@derive` or `@job` |
121
- | Meaning | The record IS the source of truth | The view is a **copy** derived from a source |
122
- | Version control | You may version-check (optimistic concurrency) | The host assigns the version; a later publish always wins (last writer wins) |
123
- | Read path | Standard keyed read | A read-optimized fast path (views are heavily read) |
124
- | Who owns correctness | You (each writer edits the true value) | The derive (it recomputes from the source, so the view always converges) |
125
-
126
- In short: a Document is the truth; a View is a saved snapshot of a computation
127
- over the truth. If the view is ever wrong or stale, the derive just recomputes and
128
- republishes it. You never hand-edit a view from a route.
129
-
130
- ## Automatic maintenance with `@derive`
131
-
132
- You rarely call `publish` by hand. The normal pattern is a
133
- [`@derive`](../background/derive.md): a method on your `@database` class that
134
- reads the sources, builds the value, and publishes it. The runtime runs it for
135
- you:
136
-
137
- - **Right after a write to a source.** When a request writes one of the
138
- database's source collections (an `append`, a `counter.add`, a record write),
139
- the database's derives run right after the response is produced. So the view
140
- reflects the new data on the next read.
141
- - **On box load.** When the server starts or reloads, the views are rebuilt from
142
- their sources before the first read is served.
143
-
144
- You never call a derive yourself, and a derive's own `publish` never re-triggers
145
- it. See [`@derive`](../background/derive.md) for the full rules.
146
-
147
- ## Worked example: a leaderboard
148
-
149
- A game appends score events; a derive folds them into a top-10 list; a route
150
- serves that list with a single `get`.
151
-
152
- ```ts
153
- import { ScoreEvent } from '../models/ScoreEvent';
154
- import { Leaderboard } from '../models/Leaderboard';
155
- import { GameKey } from '../models/GameKey';
156
- import { NewScore } from '../models/NewScore';
157
-
158
- @database
159
- class BoardDb {
160
- // The source of truth: every score, appended as it happens.
161
- @collection static scores: Events<GameKey, ScoreEvent>;
162
- // The materialized view: the current top entries, ready to serve.
163
- @collection static board: View<GameKey, Leaderboard>;
164
-
165
- // Off the request path: scan the recent scores, sort, publish the top 10.
166
- @derive
167
- rebuild(): void {
168
- const key = new GameKey('main');
169
- const recent = BoardDb.scores.latest(key, 500); // a scan, allowed in a derive
170
- const board = new Leaderboard();
171
- board.entries = topTen(recent); // your own sort/aggregate
172
- BoardDb.board.publish(key, board);
173
- }
174
- }
175
-
176
- @rest('leaderboard')
177
- class LeaderboardRoutes {
178
- // GET reads the precomputed view: one keyed read, no scan.
179
- @get('/')
180
- public top(): Leaderboard {
181
- const board = BoardDb.board.get(new GameKey('main'));
182
- return board == null ? new Leaderboard() : board;
183
- }
184
-
185
- // POST records a score. The @derive rebuilds `board` right after.
186
- @post('/')
187
- public submit(input: NewScore): Leaderboard {
188
- BoardDb.scores.append(new GameKey('main'), new ScoreEvent(input.player, input.score));
189
- return new Leaderboard(); // ack; the GET serves the updated board from the view
190
- }
191
- }
192
- ```
193
-
194
- The models:
195
-
196
- ```ts
197
- @data
198
- export class ScoreEvent {
199
- player: string = '';
200
- score: u64 = 0;
201
- }
202
-
203
- @data
204
- export class Leaderboard {
205
- entries: ScoreEvent[] = [];
206
- }
207
- ```
208
-
209
- A "latest N" view is the same shape: the derive calls `latest(key, N)` and
210
- publishes the list. See the [Events](./events.md) page for that variant end to
211
- end.
212
-
213
- ## Consistency
214
-
215
- - **Last writer wins.** The host assigns each `publish` a version, and a later
216
- publish always supersedes an earlier one. Because a derive recomputes the whole
217
- view from the source of truth, the view converges to a correct snapshot; you do
218
- not need to coordinate concurrent publishes.
219
- - **A view can be briefly stale.** ToilDB is worldwide, and a view is published
220
- at its home then copied to other regions in the background (asynchronous
221
- replication). A read from a far region can lag the newest publish by a moment.
222
- This is usually fine for the things views hold (feeds, leaderboards, summaries):
223
- a leaderboard that is a second behind is still a good leaderboard.
224
- - **`get` returns `null` until the first publish.** Always default the empty case.
225
-
226
- ## Gotchas
227
-
228
- - **`publish` is derive/job only.** You cannot publish from a route. If you need
229
- to update a view in response to a request, write the *source* (append an event,
230
- add to a counter) from the action and let the `@derive` republish.
231
- - **Handle `null` from `get`.** A never-published view reads as `null`, not as an
232
- empty value. Return a sensible default.
233
- - **A view is a copy, not the truth.** Never store data *only* in a view. Keep
234
- the real data in a source family (Documents, Events, Counters) and treat the
235
- view as a disposable, recomputable projection.
236
- - **Keep views bounded.** A view value is read whole on every request, so build
237
- it from a bounded read (top N, latest N, a total), not the entire history.
238
-
239
- ## Related
240
-
241
- - [`@derive`](../background/derive.md): the normal way a view is maintained.
242
- - [Events](./events.md): the append-only log a view is often folded from.
243
- - [Counters](./counters.md): another common source for a view (totals).
244
- - [Documents](./documents.md): mutable source-of-truth records (and how a write
245
- differs from a publish).
246
- - [Data types (`@data`)](../concepts/types.md): how view keys and values are stored.
@@ -1,104 +0,0 @@
1
- # Frontend
2
-
3
- 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.
4
-
5
- If you have written React before, everything here is familiar React: components, hooks, JSX. toiljs adds the parts a plain React app makes you wire up yourself: a router, data loading, `<head>` and SEO management, an image component, and a typed client for calling your backend. You get them for free and you do not import most of them, they live on a global called `Toil`.
6
-
7
- ## What "frontend" means here
8
-
9
- A toiljs project has three top-level folders. The frontend is the first two:
10
-
11
- - **`client/`** is your React app: pages, components, and styles. This is what runs in the user's browser.
12
- - **`shared/`** is a typed bridge that toiljs generates for you. It lets the browser call your backend with full type safety (see [Fetching data](./data-fetching.md)).
13
- - **`server/`** is your backend. It compiles to WebAssembly and runs on the edge. That is a separate section (see [Backend](../backend/README.md)).
14
-
15
- Inside `client/`, the important pieces are:
16
-
17
- | Path | What it is |
18
- | --- | --- |
19
- | `client/toil.tsx` | The entry file. It imports your routes and global styles and mounts the app. |
20
- | `client/routes/` | Your pages. One file per URL (file-based routing). See [Routing](./routing.md). |
21
- | `client/layout.tsx` | The root layout that wraps every page (a header, a footer, and so on). |
22
- | `client/components/` | Your own reusable React components. |
23
- | `client/styles/` | Your CSS. See [Styling](./styling.md). |
24
- | `client/public/` | Static files served as-is (`favicon.ico`, images, `robots.txt`). |
25
-
26
- The entry file is tiny, and you rarely touch it:
27
-
28
- ```tsx
29
- // client/toil.tsx
30
- import { routes, layout, notFound, globalError, slots } from 'toiljs/routes';
31
- import './styles/main.css';
32
-
33
- Toil.mount(routes, layout, notFound, globalError, slots);
34
- ```
35
-
36
- `toiljs/routes` is a virtual module: the compiler scans `client/routes/` and generates the route table for you, so you never hand-maintain a list of pages. Adding a file under `client/routes/` adds a page.
37
-
38
- ## SPA, but with a server head start
39
-
40
- By default a toiljs app is a single-page application (SPA). The word "SPA" means the browser loads one HTML shell, then JavaScript builds every page and swaps between them without a full reload. That makes navigation instant, but it has a classic weakness: the very first load shows a blank page until the JavaScript runs, and simple crawlers that do not run JavaScript see nothing useful.
41
-
42
- toiljs closes that gap in two ways, both optional and both explained in [Rendering and SSR](./rendering.md):
43
-
44
- 1. **Build-time prerendering.** At build time toiljs bakes each route's `<head>` (title, description, Open Graph, and so on) into real HTML, so crawlers and link-preview bots see correct metadata even without running your code.
45
- 2. **Edge server-side rendering (SSR).** For routes you opt in with `export const ssr = true`, the edge fills in real first-paint HTML for the page body, then the browser "hydrates" it (attaches React to the already-drawn markup instead of redrawing it).
46
-
47
- Here is the full life of one request, from a cold link click to a warm client-side app:
48
-
49
- ```mermaid
50
- sequenceDiagram
51
- participant U as User's browser
52
- participant E as Dacely edge
53
- U->>E: GET /some-page
54
- E-->>U: HTML (baked head + first-paint body if ssr=true)
55
- Note over U: First paint appears immediately
56
- U->>E: fetch the JS bundle + route chunk
57
- Note over U: React "hydrates": attaches to the existing HTML
58
- Note over U: Page is now interactive
59
- U->>U: Click a link -> client-side navigation (no reload)
60
- Note over U: Only the new route's data + chunk are fetched
61
- ```
62
-
63
- The key idea: the server gets you a correct first paint fast, and from then on the app runs entirely in the browser, fetching only small route chunks and data as you navigate.
64
-
65
- ## The `Toil` global
66
-
67
- Most of the client API lives on a global object called `Toil`, so route files need no imports for the common things. A few examples you will meet across these pages:
68
-
69
- ```tsx
70
- Toil.Link // a client-side navigation link
71
- Toil.NavLink // a Link that knows when it is "active"
72
- Toil.useParams() // read dynamic URL params, e.g. { id } for /blog/[id]
73
- Toil.useLoaderData // read data your route's loader fetched
74
- Toil.Image // an <img> that avoids layout shift and lazy-loads
75
- Toil.useHead // set the <title> and <meta> tags
76
- Server.REST.* // the typed fetch client for your backend
77
- ```
78
-
79
- `Server` is also global (it is the typed backend client, see [Fetching data](./data-fetching.md)). Everything on `Toil` is fully typed: your editor autocompletes it, because toiljs generates a `toil-env.d.ts` that maps `Toil` onto the `toiljs/client` package.
80
-
81
- The same fast data utilities your backend uses are available in client code too, as bare globals with no import: `FastMap` and `FastSet` (high-performance map and set collections), and `DataWriter` / `DataReader` (a compact binary codec for encoding and decoding buffers). They are handed to you the same way `Toil` and `Server` are, so you can write `new DataWriter()` straight in a component. See [Data types](../backend/data.md) for the codec and when to reach for it.
82
-
83
- ## The frontend pages
84
-
85
- Read them in roughly this order:
86
-
87
- - **[Routing](./routing.md)**: turn files into URLs. Index, nested, and dynamic pages; layouts and templates.
88
- - **[Navigation](./navigation.md)**: move between pages. Links and active state, navigating in code, typed hrefs, prefetching, scroll restoration, and animated transitions.
89
- - **[Components](./components.md)**: use your own React components, plus the toiljs primitives (`Image`, `Script`, `Form`, `Slot`, `Head`, and the SSR markers).
90
- - **[Rendering and SSR](./rendering.md)**: what renders on the server versus in the browser, how hydration works, and the current SSR limitations.
91
- - **[Styling](./styling.md)**: plain CSS, preprocessors (Sass / Less / Stylus), and Tailwind.
92
- - **[Images](./images.md)**: the `Toil.Image` component, automatic blur placeholders, and how it stops layout shift.
93
- - **[Metadata and SEO](./metadata.md)**: set the page title, description, and social-share tags per route.
94
- - **[Fetching data](./data-fetching.md)**: call your backend with the generated typed clients, submit forms, and read who is logged in.
95
- - **[Scripts](./scripts.md)**: load external or inline `<script>` tags with a loading strategy, using `Toil.Script`.
96
- - **[Search](./search.md)**: the built-in, statically-baked page search and command palette (`usePageSearch`).
97
- - **[The Toil global (reference)](./toil-global.md)**: a complete, grouped list of everything on the `Toil` object.
98
-
99
- ## Related
100
-
101
- - [Getting started](../getting-started/README.md): install toiljs and create a project.
102
- - [Project structure](../getting-started/project-structure.md): the full folder layout.
103
- - [Backend overview](../backend/README.md): the `server/` side your frontend talks to.
104
- - [The CLI](../cli/README.md): `toiljs dev`, `toiljs build`, and every flag.
@@ -1,286 +0,0 @@
1
- # Components
2
-
3
- A toiljs frontend is a normal React app, so the components you write are just React components: functions that return JSX, hold state with hooks, and compose however you like. There is no toiljs-specific base class, no decorator, and no registration step. On top of your own components, toiljs ships a small set of ready-made ones on the `Toil` global (an image, a script loader, a form, and a few more) for the jobs a plain React app makes you wire up by hand. This page covers both: how your components fit in, and a reference for every `Toil.*` component.
4
-
5
- ## Writing your own components
6
-
7
- Anything you already know about writing React components applies unchanged. You write ordinary functions, use `useState` / `useEffect` / `useMemo` and any hooks you like, and return JSX:
8
-
9
- ```tsx
10
- // client/components/Counter.tsx
11
- import { useState } from 'react';
12
-
13
- export default function Counter({ start = 0 }: { start?: number }) {
14
- const [n, setN] = useState(start);
15
- return <button onClick={() => setN(n + 1)}>Clicked {n} times</button>;
16
- }
17
- ```
18
-
19
- Your reusable components live in `client/components/`, and you import them the normal way from anywhere in `client/`:
20
-
21
- ```tsx
22
- // client/routes/index.tsx
23
- import Counter from '../components/Counter';
24
-
25
- export default function Home() {
26
- return (
27
- <main>
28
- <h1>Welcome</h1>
29
- <Counter start={10} />
30
- </main>
31
- );
32
- }
33
- ```
34
-
35
- A few things are worth spelling out, because they are the parts that are handled for you rather than by you:
36
-
37
- - **Route files must `export default` a component.** A file under `client/routes/` becomes a page only if it default-exports a component (see [Routing](./routing.md)). Files under `client/components/` have no such rule; export them however you please (default or named).
38
- - **The `Toil.*` globals need no import.** `Toil.Link`, `Toil.useParams()`, `Toil.Image`, and everything else on `Toil` are ambient. The compiler generates a `toil-env.d.ts` that types the global, so your editor autocompletes `Toil.` and type-checks it with no `import` line. The same goes for `Server` (the typed backend client) and the `FastMap` / `DataWriter` data utilities. See the [Frontend overview](./README.md) for the whole ambient surface.
39
- - **The JSX runtime is configured for you.** You do not write `import React from 'react'` at the top of every file. The build sets up the automatic JSX runtime, so JSX just works. Import named hooks and types from `react` when you need them (`import { useState, type ReactNode } from 'react'`), but the bare React import for JSX is unnecessary.
40
-
41
- In other words, a component in a toiljs app is indistinguishable from a component in any React app. Two things are genuinely special, and both are opt-in.
42
-
43
- ### Special case 1: components inside an SSR route
44
-
45
- If a route opts into server rendering with `export const ssr = true`, its component tree is rendered once at build time into a template, then filled per request on the edge (see [Rendering and SSR](./rendering.md)). For that to work, any part of the JSX that changes per request (a value from the URL, a list from a loader, a block of user HTML) has to be wrapped in one of the [SSR marker primitives](#ssr-marker-primitives) below, or isolated in a `Toil.Island`. Anything you leave unwrapped gets frozen into the template at its build-time value.
46
-
47
- You do not have to get this perfect: if an SSR route (or a layout above it) cannot render on the server, toiljs skips SSR for that route at build, prints a warning telling you what to move, and falls back to plain client rendering. So the route still works, it just loses its server first paint until you address the warning.
48
-
49
- ### Special case 2: importing images and other assets
50
-
51
- Importing an image with the `?toil` suffix gives you an object carrying the resolved URL, the intrinsic width and height, and an auto-generated blur placeholder (a `blurDataURL`), ready to hand straight to `Toil.Image`:
52
-
53
- ```tsx
54
- import hero from './hero.webp?toil';
55
- // hero is { src, width, height, blurDataURL }
56
-
57
- <Toil.Image src={hero} alt="Our office" />;
58
- ```
59
-
60
- Passing the whole object lets `Toil.Image` reserve the correct aspect ratio and paint the blur while the real image loads, with no extra props. See [Images](./images.md) for the full treatment.
61
-
62
- Plain asset imports are typed for you as well. A bare `import logo from './logo.svg'` (or `.png`, `.webp`, and so on) resolves to the hashed URL string, and the vite-imagetools query forms (`?url`, `?as=srcset`, `?as=metadata`) are typed too, so you get autocomplete and type-checking on all of them without declaring any modules yourself.
63
-
64
- ## The toiljs component primitives
65
-
66
- These are the components toiljs provides on the `Toil` global. They cover the framework-level jobs a plain React app leaves to you. All are ambient (no import), and all are fully typed.
67
-
68
- | Component | What it is | Renders |
69
- | --- | --- | --- |
70
- | `Toil.Image` | Layout-shift-free `<img>` replacement with lazy-load and blur placeholder. | An `<img>` (optionally wrapped in a sizing box). |
71
- | `Toil.Script` | One-time external or inline `<script>` loader with a load strategy. | Nothing (`null`). |
72
- | `Toil.Form` | A `<form>` that runs an action on submit and revalidates loader data. | A `<form>`. |
73
- | `Toil.Slot` | Renders a named parallel-route slot for the current URL. | The slot's route tree, or a fallback. |
74
- | `Toil.Head` | Declarative `<head>` contribution (title, meta, link). | Nothing (`null`). |
75
- | `Toil.Metadata` | Declarative route-style metadata applied from any component. | Nothing (`null`). |
76
-
77
- The [SSR marker primitives](#ssr-marker-primitives) (`Toil.Hole`, `Toil.Repeat`, `Toil.RawHtml`, `Toil.attr`, `Toil.Island`) are a separate group, covered in their own section below.
78
-
79
- ### `Toil.Image`
80
-
81
- A drop-in `<img>` replacement that prevents layout shift and lazy-loads by default. You give it `width` and `height` (or `fill`) so it reserves space before the image arrives, it decodes asynchronously, it lazy-loads unless you mark it `priority`, and it can fade in from a blur placeholder. It accepts either a string URL or a `?toil` import object (which auto-fills the size and blur):
82
-
83
- ```tsx
84
- import hero from './hero.webp?toil';
85
-
86
- export default function Home() {
87
- return (
88
- <>
89
- <Toil.Image src={hero} alt="Our office" priority placeholder="blur" />
90
- <Toil.Image src="/team.jpg" alt="The team" width={800} height={600} />
91
- </>
92
- );
93
- }
94
- ```
95
-
96
- Key props are `src`, `alt` (required, pass `alt=""` for a decorative image), `width` / `height` or `fill`, `priority` (for an above-the-fold hero), and `placeholder` (`'empty'` or `'blur'`). It also accepts any standard `<img>` attribute. This is kept brief on purpose: [Images](./images.md) covers sizing, `fill`, blur placeholders, and how it stops layout shift in full.
97
-
98
- ### `Toil.Script`
99
-
100
- Loads an external or inline `<script>` exactly once for the whole life of the app (even across client-side navigations), and lets you choose when it runs with a `strategy` prop. Use it instead of a hand-written `<script>` tag for third-party snippets like analytics or chat widgets, which a plain `<script>` in a single-page app runs unreliably or twice:
101
-
102
- ```tsx
103
- // client/layout.tsx
104
- export default function Layout({ children }: { children?: React.ReactNode }) {
105
- return (
106
- <div className="app">
107
- <Toil.Script src="https://cdn.example-analytics.com/analytics.js" />
108
- {children}
109
- </div>
110
- );
111
- }
112
- ```
113
-
114
- The `strategy` is `afterInteractive` (default), `lazyOnload`, or `beforeInteractive`, and there are `onLoad` / `onReady` / `onError` callbacks. `Toil.Script` renders nothing. See [Scripts](./scripts.md) for the strategies, inline scripts, dedup rules, and the full prop table.
115
-
116
- ### `Toil.Form`
117
-
118
- A `<form>` that submits without reloading the page. On submit it runs your `action` (which receives the form's `FormData`), tracks pending and error state, and on success revalidates the current route's loader data so the page reflects the write. It is the convenient front end of the loader/action data loop:
119
-
120
- ```tsx
121
- // A guestbook that refreshes its list after a successful sign.
122
- export default function Guestbook() {
123
- const entries = Toil.useLoaderData<typeof loader>();
124
-
125
- const sign = async (data: FormData) => {
126
- const author = String(data.get('author'));
127
- const message = String(data.get('message'));
128
- await Server.REST.guestbook.sign({ body: new NewMessage(author, message) });
129
- };
130
-
131
- return (
132
- <Toil.Form action={sign} resetOnSuccess>
133
- {({ pending, error }) => (
134
- <>
135
- <input name="author" placeholder="Your name" />
136
- <textarea name="message" />
137
- <button disabled={pending}>{pending ? 'Signing...' : 'Sign'}</button>
138
- {error ? <p className="err">Could not sign, try again.</p> : null}
139
- </>
140
- )}
141
- </Toil.Form>
142
- );
143
- }
144
- ```
145
-
146
- Pass a render function as the child to read the live submit state: it receives `{ pending, error, data }`, which is how you disable the button while pending or show an error. The props:
147
-
148
- | Prop | Type | Default | What it does |
149
- | --- | --- | --- | --- |
150
- | `action` | `(data: FormData) => void \| Promise<void>` | (required) | Runs on submit, receiving the form's `FormData`. May be async. |
151
- | `revalidate` | `RevalidateTarget` | `true` | Which loader data to refetch after a successful submit. `true` is the current route. |
152
- | `onSuccess` | `() => void` | (none) | Called after a successful submit. |
153
- | `onError` | `(error: unknown) => void` | (none) | Called when the action throws. |
154
- | `resetOnSuccess` | `boolean` | `false` | Reset the form fields after a successful submit. |
155
- | `className` | `string` | (none) | Class on the `<form>` element. |
156
- | `children` | `ReactNode` or `(state) => ReactNode` | (none) | Form contents. A function child receives `{ pending, error, data }`. |
157
-
158
- For writes that are not form submits (a delete button, a like toggle), reach for the underlying `Toil.useAction` hook instead. Both are covered in [Fetching data](./data-fetching.md).
159
-
160
- ### `Toil.Slot`
161
-
162
- Renders the named parallel-route slot for the current URL. A folder starting with `@` (like `@modal`) under `client/routes/` is a whole second route tree that matches the URL independently of the main page; placing `<Toil.Slot name="modal" />` is where that tree renders. If no slot route matches the current URL, it renders the `fallback` (nothing by default):
163
-
164
- ```tsx
165
- // client/routes/gallery/layout.tsx
166
- export default function GalleryLayout({ children }: { children?: React.ReactNode }) {
167
- return (
168
- <div>
169
- {children}
170
- <Toil.Slot name="modal" fallback={null} />
171
- </div>
172
- );
173
- }
174
- ```
175
-
176
- | Prop | Type | Default | What it does |
177
- | --- | --- | --- | --- |
178
- | `name` | `string` | (required) | The slot name: the `@name` directory under `routes/`, without the `@`. |
179
- | `fallback` | `ReactNode` | `null` | Rendered when no slot route matches the current URL. |
180
-
181
- Parallel slots and the intercepting routes that fill them (the "click a photo, open it in a modal" pattern) are explained in [Routing](./routing.md).
182
-
183
- ### `Toil.Head`
184
-
185
- A declarative way for any component (a page, a layout, a deep child) to contribute to the document `<head>`: a title, `<meta>` tags, and `<link>` tags. It renders nothing; it just applies its head entries for the lifetime of the component and reverts them on unmount. Entries compose across the tree, with later or deeper ones winning per key:
186
-
187
- ```tsx
188
- export default function ArticlePage() {
189
- const post = Toil.useLoaderData<typeof loader>();
190
- return (
191
- <>
192
- <Toil.Head
193
- title={post.title}
194
- meta={[
195
- { name: 'description', content: post.summary },
196
- { property: 'og:title', content: post.title },
197
- ]}
198
- link={[{ rel: 'canonical', href: `https://example.com/blog/${post.id}` }]}
199
- />
200
- <article>{/* ... */}</article>
201
- </>
202
- );
203
- }
204
- ```
205
-
206
- `<Toil.Head>` takes `title`, `meta` (an array of `{ name | property, content }` tags), and `link` (an array of `{ rel, href }` tags). The hook form `Toil.useHead(spec)` and the shorthand `Toil.useTitle(title)` do the same job imperatively. See [Metadata and SEO](./metadata.md).
207
-
208
- ### `Toil.Metadata`
209
-
210
- The declarative, convenience-shaped cousin of `Toil.Head`. Instead of raw meta and link arrays, you pass a route-style metadata object (the same shape a route file's `export const metadata` uses), and toiljs expands the convenience fields (`description`, `keywords`, `canonical`, `openGraph`, and so on) into the right tags. It renders nothing, and applies for the component's lifetime. Use it to set metadata from a component that is not itself a route file (a rendered article, a widget):
211
-
212
- ```tsx
213
- <Toil.Metadata
214
- title="Our pricing"
215
- description="Simple, flat pricing for teams of any size."
216
- openGraph={{ title: 'Pricing', image: 'https://example.com/og/pricing.png' }}
217
- />
218
- ```
219
-
220
- Because a route's own `metadata` export is applied last (highest priority), `Toil.Metadata` fills in for routes that declare none and yields to a route that sets the same key. The full field list lives in [Metadata and SEO](./metadata.md).
221
-
222
- ## SSR marker primitives
223
-
224
- These five primitives matter only for routes with `export const ssr = true`. They are how you tell the build which parts of a server-rendered page are dynamic (filled per request) versus static (baked into the template once). On a client-only route they do nothing special, so you never need them unless you opt a route into SSR.
225
-
226
- **The mental model.** In the browser these markers are transparent: `<Toil.Hole>` renders its children, `<Toil.Repeat>` maps its rows, `<Toil.RawHtml>` renders a raw-HTML wrapper, and `Toil.attr(id, value)` returns the value unchanged. Your client-side app runs exactly as written. But under the build-time SSR extractor, each marker instead emits a sentinel token that marks an insertion point. The extractor strips those tokens and records their positions, producing a static template with numbered holes. Per request, your compiled backend fills only the hole values, and the edge splices them into the template. Because the static scaffold around each hole is React's own rendering output and the hole values are escaped exactly as React escapes them, the browser hydrates the result byte-for-byte, with no mismatch.
227
-
228
- | Marker | Use it for | Shape |
229
- | --- | --- | --- |
230
- | `Toil.Hole` | A single dynamic **text** value. | `<Toil.Hole id="...">{value}</Toil.Hole>` |
231
- | `Toil.Repeat` | A **list**: one row template repeated over an `each` array. | `<Toil.Repeat id="..." each={rows}>{(item, i) => ...}</Toil.Repeat>` |
232
- | `Toil.RawHtml` | A block of **trusted, pre-rendered HTML**. | `<Toil.RawHtml id="..." html={s} as="section" />` |
233
- | `Toil.attr` | A dynamic value in **attribute position** (an `href`, a `class`). | `href={Toil.attr('id', value)}` (a function call) |
234
- | `Toil.Island` | Content that must render **only in the browser** (escape hatch). | `<Toil.Island>{children}</Toil.Island>` |
235
-
236
- A few rules that keep the template valid:
237
-
238
- - **Every marker needs a stable `id`**, a short name unique within the page. The build maps each id to a numbered slot, so keep the ids constant across builds.
239
- - **`Toil.Repeat` needs at least one row at build time.** It captures that first row as the sub-template for every row, so the build render must see a sample with one or more items. An empty `each` gives it nothing to capture.
240
- - **`Toil.RawHtml` renders inside a wrapper element** (a `<div>` by default, `as="section"` to change the tag), and you own sanitising that HTML, exactly like React's `dangerouslySetInnerHTML`.
241
- - **`Toil.attr` is a function, not an element.** An attribute is not a child node, so it cannot be a JSX element. You call `Toil.attr(id, value)` right where the attribute value goes, and it composes with literal text around it (`` className={`btn ${Toil.attr('kind', d.kind)}`} ``).
242
- - **`Toil.Island` renders nothing on the server and on the first (hydration) render**, then reveals its children after mount. So an island gets no server first paint and no SEO, by design. It is the place for anything that genuinely cannot run on the server (reads `window`, calls `Date.now()`, depends on the live URL).
243
-
244
- Here is a compact SSR route wiring several of them together. The title, tag list, and author link all come from the route's `loader`:
245
-
246
- ```tsx
247
- // client/routes/blog/[id].tsx
248
- export const ssr = true;
249
-
250
- export const loader = async ({ params }: Toil.LoaderArgs) => {
251
- // Illustrative shape: { title, tags, authorUrl }.
252
- return Server.REST.blog.get({ params: { id: params.id } });
253
- };
254
-
255
- export default function BlogPost() {
256
- const post = Toil.useLoaderData<typeof loader>();
257
- return (
258
- <article>
259
- <h1>
260
- <Toil.Hole id="title">{post.title}</Toil.Hole>
261
- </h1>
262
-
263
- {/* A dynamic attribute: call attr() in attribute position. */}
264
- <a href={Toil.attr('authorUrl', post.authorUrl)}>By the author</a>
265
-
266
- {/* A list: one row template, stamped once per item on the server. */}
267
- <ul>
268
- <Toil.Repeat id="tags" each={post.tags}>
269
- {(tag) => <li key={tag}>{tag}</li>}
270
- </Toil.Repeat>
271
- </ul>
272
- </article>
273
- );
274
- }
275
- ```
276
-
277
- For the whole SSR story (the template extraction flow, keeping a route SSR-safe, and the current SSR limitations), see [Rendering and SSR](./rendering.md).
278
-
279
- ## Related
280
-
281
- - [Rendering and SSR](./rendering.md): how SSR routes render, hydrate, and use the marker primitives.
282
- - [Images](./images.md): the full `Toil.Image` reference, blur placeholders, and layout shift.
283
- - [Scripts](./scripts.md): `Toil.Script` strategies, inline scripts, and dedup.
284
- - [Fetching data](./data-fetching.md): `Toil.Form`, `useAction`, loaders, and the typed backend clients.
285
- - [Metadata and SEO](./metadata.md): `Toil.Head`, `Toil.Metadata`, and per-route metadata.
286
- - [Routing](./routing.md): pages, layouts, and the parallel slots `Toil.Slot` renders.