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,351 +0,0 @@
1
- # Data types (`@data`)
2
-
3
- `@data` turns a plain class into a typed value that can travel safely between your frontend and your WASM backend, and in and out of the database, with both a binary and a JSON codec generated for you.
4
-
5
- ## What `@data` is
6
-
7
- A **codec** is a pair of functions: one that turns a value into bytes (encode), and one that turns those bytes back into a value (decode). Any time data crosses a boundary (browser to server, server to database, one WASM call to another) it has to become bytes and back. Writing that by hand is tedious and easy to get wrong.
8
-
9
- `@data` writes it for you. You declare a class with typed fields, tag it `@data`, and the compiler synthesizes a deterministic binary codec and a JSON codec on the class. The exact same class is also generated into your `shared/server.ts`, so the browser and the WASM backend agree on the format down to the byte.
10
-
11
- ```ts
12
- @data
13
- class Player {
14
- username: string = '';
15
- admin: bool = false;
16
- score: u64 = 0;
17
- }
18
- ```
19
-
20
- That is a complete, serializable type. Note every field has a **default value**; that is required (the generated decoder and the client constructor use it).
21
-
22
- ## Why and when
23
-
24
- `@data` is the backbone of almost everything that moves in a toiljs app:
25
-
26
- - **Request and response bodies** for [`@rest`](./rest.md) routes.
27
- - **Arguments and return values** for [`@service` / `@remote`](./rpc.md) calls.
28
- - **Values stored in [ToilDB](../database/README.md)**.
29
- - **Sessions**, [stream](../realtime/README.md) messages, and any custom payload you design.
30
-
31
- Whenever a route, an RPC method, or the database needs a structured value, that value is a `@data` class. You will define these constantly.
32
-
33
- ```mermaid
34
- flowchart LR
35
- D["@data class<br/>(one definition)"] --> R["REST route body"]
36
- D --> P["RPC argument / result"]
37
- D --> B[("ToilDB value")]
38
- D --> S["Stream message"]
39
- ```
40
-
41
- ## What the compiler generates
42
-
43
- From your `@data` class, the compiler adds these members:
44
-
45
- | Member | What it does |
46
- | --- | --- |
47
- | `encode(): Uint8Array` | Serialize to bytes, with a 4-byte type-id prefix. |
48
- | `static decode(buf): T` | Rebuild a value from bytes produced by `encode()`. |
49
- | `encodeInto(w: DataWriter)` | Serialize without the type-id frame, for nesting inside another value. |
50
- | `static decodeFrom(r: DataReader): T` | The matching read, for nested values. |
51
- | `toJSON()` / `static fromJSON(v)` | The JSON codec (large integers become decimal strings, so `JSON.parse` keeps them exact). |
52
- | `static dataId(): u32` | A stable hash (FNV-1a) of the class name, written as the type-id prefix. |
53
-
54
- You mostly do not call these directly; routes, RPC, and the database call them for you. But they are there when you need them (for example `value.toJSON().toString()` to build a `Response.json`).
55
-
56
- ## Supported field types
57
-
58
- A `@data` field may be:
59
-
60
- - a scalar: `u8` through `u256`, `i8` through `i256`, `f32`, `f64`, `bool`;
61
- - a `string`;
62
- - a `Uint8Array` (a raw byte buffer);
63
- - a nested `@data` class;
64
- - an array `T[]` of any of the above.
65
-
66
- For the number types (why `u64` is a `bigint` on the client, when to reach for `u256`, and so on), see [Types](../concepts/types.md).
67
-
68
- Give every field a default. The layout is exactly the field declaration order (this matters, see [gotchas](#gotchas)).
69
-
70
- ## Nested `@data`, arrays, and bytes
71
-
72
- `@data` classes compose. A field can be another `@data` class, an array, or a byte buffer, and it all encodes and decodes as one value:
73
-
74
- ```ts
75
- @data
76
- class Tag {
77
- label: string = '';
78
- weight: f32 = 0;
79
- }
80
-
81
- @data
82
- class Document {
83
- id: u64 = 0;
84
- title: string = '';
85
- tags: Tag[] = []; // array of nested @data
86
- authors: string[] = []; // array of strings
87
- thumbnail: Uint8Array = new Uint8Array(0); // raw bytes
88
- }
89
- ```
90
-
91
- `Document.encode()` walks the whole tree: it writes `id`, `title`, each `Tag` (via `encodeInto`), each author string, and the raw `thumbnail` bytes, in field order. `Document.decode(bytes)` reads them back in the same order. On the JSON side, a `Uint8Array` field becomes a JSON array of byte numbers, and nested `@data` fields become nested JSON objects.
92
-
93
- ## Using `@data` in a route
94
-
95
- A route's body parameter and its return value are `@data` values. Which codec runs depends on the route's stream mode (see [REST bodies](./rest.md#request-and-response-bodies)):
96
-
97
- ```ts
98
- // JSON route (the default): body from JSON.parse, result via toJSON()
99
- @post('/')
100
- public create(input: NewPlayer): Player { /* ... */ }
101
-
102
- // Binary route: body via decode(), result via encode()
103
- @route({ method: Methods.POST, path: '/blob', stream: DataStream.Binary })
104
- public blob(input: FileData): FileResult { /* ... */ }
105
- ```
106
-
107
- - In a **JSON** route, the incoming body is `JSON.parse`d and revived with the type's `fromJSON`, and the returned value is serialized with `toJSON()`.
108
- - In a **Binary** route, the incoming body is `decode`d and the returned value is `encode`d.
109
-
110
- You do not call the codec yourself in either case; you just declare the types.
111
-
112
- ## The raw codec: `DataWriter` and `DataReader`
113
-
114
- Sometimes you want to lay out bytes by hand: a custom body, a session token, a challenge message, a wire format someone else defined. For that, use the codec directly. It lives in the `data` module:
115
-
116
- ```ts
117
- import { DataWriter, DataReader } from 'data';
118
- ```
119
-
120
- This is the same codec `@data` classes are built from, and it has a byte-for-byte identical TypeScript version in `toiljs/io` (`src/io/codec.ts`), so your browser code can read and write the exact same bytes the WASM backend does.
121
-
122
- ### `DataWriter`
123
-
124
- Every write method returns the writer, so calls chain.
125
-
126
- | Method | Signature | Wire format |
127
- | --- | --- | --- |
128
- | `writeU8` / `writeI8` | `(v): DataWriter` | 1 byte |
129
- | `writeU16` / `writeI16` | `(v): DataWriter` | 2 bytes, little-endian |
130
- | `writeU32` / `writeI32` | `(v): DataWriter` | 4 bytes, LE |
131
- | `writeU64` / `writeI64` | `(v): DataWriter` | 8 bytes, LE |
132
- | `writeF32` / `writeF64` | `(v): DataWriter` | 4 / 8 bytes, IEEE-754 LE |
133
- | `writeBool` | `(v): DataWriter` | 1 byte (`1` / `0`) |
134
- | `writeBytes` | `(b: Uint8Array): DataWriter` | `u32` length (LE) + the raw bytes |
135
- | `writeString` | `(s: string): DataWriter` | `u32` length (LE) + UTF-8 bytes |
136
- | `writeU128` / `writeI128` | `(v): DataWriter` | two `u64` limbs (lo, hi) |
137
- | `writeU256` / `writeI256` | `(v): DataWriter` | four `u64` limbs |
138
- | `length` | `(): i32` | bytes written so far |
139
- | `toBytes` | `(): Uint8Array` | an exact-length copy of the buffer |
140
-
141
- ### `DataReader`
142
-
143
- Reads are **bounds-safe**: reading past the end of the buffer never crashes. Instead the read returns a zero or empty default and flips the reader's public `ok` flag to `false`. Check `ok` after a sequence of reads to catch a truncated or malformed buffer.
144
-
145
- | Method | Signature | On over-read |
146
- | --- | --- | --- |
147
- | `readU8` / `readI8` | `(): integer` | `0` |
148
- | `readU16`..`readU64`, `readI16`..`readI64` | `(): integer` | `0` |
149
- | `readF32` / `readF64` | `(): float` | `0` |
150
- | `readBool` | `(): bool` | `false` |
151
- | `readBytes` | `(): Uint8Array` | empty array |
152
- | `readString` | `(): string` | `""` |
153
- | `readU128` / `readI128` / `readU256` / `readI256` | `(): bignum` | `0` |
154
- | `remaining` | `(): i32` | bytes left unread |
155
- | `ok` | `bool` (field) | `false` once any read over-ran |
156
-
157
- ### Encode and decode, both directions
158
-
159
- ```ts
160
- import { DataWriter, DataReader } from 'data';
161
-
162
- // Encode: a version byte, a name, a score, then a blob.
163
- const out = new DataWriter()
164
- .writeU8(1)
165
- .writeString('alice')
166
- .writeU64(1234)
167
- .writeBytes(payload)
168
- .toBytes();
169
-
170
- // Decode: read the fields back in the exact same order.
171
- const r = new DataReader(out);
172
- const version = r.readU8();
173
- const name = r.readString();
174
- const score = r.readU64();
175
- const blob = r.readBytes();
176
- if (!r.ok) return Response.badRequest('truncated');
177
- ```
178
-
179
- The order of reads must match the order of writes exactly. The layout is the format.
180
-
181
- ## JSON vs binary: which to use
182
-
183
- You usually pick this at the route level (see [REST bodies](./rest.md#request-and-response-bodies)), but the trade-off is the same everywhere:
184
-
185
- - **JSON** is human-readable and understood by every tool. Pick it for endpoints a browser or a third party calls directly. Large integers ride as decimal strings so they stay exact.
186
- - **Binary** is smaller, faster, and lossless for big numbers. Pick it for app-to-app traffic and anything performance sensitive. RPC always uses binary under the hood.
187
-
188
- ## Evolving a `@data` format: `@migrate`
189
-
190
- Once records are stored, changing a `@data` **value** type is a problem: as the [gotchas](#gotchas) explain, the binary layout *is* the field order, so adding or changing a field means the bytes already on disk no longer match the new shape and would fail to decode. `@migrate` is how you evolve a stored type without a backfill and without downtime.
191
-
192
- **What it is.** `@migrate` marks a plain (free) function that upgrades one record written under an **old** version of a value type into the **current** shape. The compiler weaves that function into the value type's decoder as a version dispatch: every stored record carries the schema version it was written under, so when a read hits an old record, the decoder decodes it as its old shape and runs your `@migrate` forward to today's shape. This happens **lazily**, per row, only when a record is actually read (nothing rewrites your whole collection up front).
193
-
194
- **When to use it.** Any time you change a `@data` type that is already stored in ToilDB: you added a field, renamed one, changed a type. New writes use the new layout; old rows keep working because the migration upgrades them on the way out.
195
-
196
- Here is the whole story. You shipped this value type:
197
-
198
- ```ts
199
- // server/models/GuestEntry.ts (version 1: what you shipped first)
200
- @data
201
- export class GuestEntry {
202
- author: string = '';
203
- message: string = '';
204
- }
205
- ```
206
-
207
- Later you add an `at` timestamp:
208
-
209
- ```ts
210
- // server/models/GuestEntry.ts (version 2: gained an `at` field)
211
- @data
212
- export class GuestEntry {
213
- author: string = '';
214
- message: string = '';
215
- at: u64 = 0; // NEW field
216
- }
217
- ```
218
-
219
- Every entry already stored was written without `at`, so its bytes cannot decode as the new `GuestEntry`. You add a migration file to bridge them:
220
-
221
- ```ts
222
- // server/migrations/GuestEntry.migration.ts
223
- import { GuestEntry } from '../models/GuestEntry';
224
-
225
- // Keep the ORIGINAL layout as its own class so old rows still decode.
226
- // One kept class per past version.
227
- @data
228
- export class GuestEntryV1 {
229
- author: string = '';
230
- message: string = '';
231
- }
232
-
233
- // Upgrade a v1 row to the current GuestEntry. This is the DELTA form:
234
- // (old, into). The compiler pre-copies the fields the two layouts share
235
- // (author, message), so your body fills only what is new.
236
- @migrate
237
- export function up(old: GuestEntryV1, into: GuestEntry): void {
238
- into.at = 0; // unknown for entries written before the timestamp existed
239
- }
240
- ```
241
-
242
- That is it. Old entries now surface as fully-formed `GuestEntry` values with `at = 0`; new entries carry a real timestamp; the same read path serves both.
243
-
244
- A `@migrate` function comes in two shapes, and you pick whichever reads better:
245
-
246
- - **Delta form** `up(old: OldType, into: NewType): void` (used above). The compiler copies every field the two layouts share by name and type, and your body fills only the changed or new fields. Least to write when most fields carry over.
247
- - **Full form** `up(old: OldType): NewType`. Your body builds and returns the whole new value itself. Use it when the transform is not a simple field-for-field copy.
248
-
249
- ```ts
250
- // The same migration written in the full form.
251
- @migrate
252
- export function up(old: GuestEntryV1): GuestEntry {
253
- const e = new GuestEntry();
254
- e.author = old.author;
255
- e.message = old.message;
256
- e.at = 0;
257
- return e;
258
- }
259
- ```
260
-
261
- Gotchas specific to `@migrate`:
262
-
263
- - **Location is enforced.** Every `@migrate` must live in a `migrations/<Type>.migration.ts` file (a `*.migration.ts` file under a `migrations/` folder). The build auto-discovers it (nothing imports it), and a `@migrate` placed anywhere else is a hard compile error, because it would silently never run.
264
- - **A migration is a pure value transform.** It may not touch the database or any host service; it only turns old fields into new ones. Trying to read or write ToilDB from inside a `@migrate` is a compile error.
265
- - **Migrations chain.** If you evolve a type more than once, keep one old class and one `@migrate` per step (`V0 -> V1`, `V1 -> V2`). The compiler walks the chain, so a row written under the oldest layout is carried all the way forward to the current shape, shortest path first.
266
- - **When it actually runs.** On any read that decodes an old row (`get`, `getMany`, a view read, events `latest`). It also runs when a [`@derive`](../background/derive.md) rebuilds a view on box load and re-reads old stored events (see [Views](../database/views.md)).
267
-
268
- ## Dynamic JSON on the server: the `JSON` value tree
269
-
270
- `@data` is for shapes you know ahead of time. Sometimes you do not: a webhook whose body varies, a third-party payload with optional fields, or a response you assemble on the fly. For those, toiljs gives you a `JSON` **value tree**: an in-memory value that can be a null, a bool, a number, a string, an array, or an object, which you read and build at runtime.
271
-
272
- **What it is.** `JSON` is an ambient global class (no import needed). It represents one dynamic JSON value. `JSON.parse(text)` turns JSON text into one of these trees, and a `@data` class's `toJSON()` actually returns one too. It is the **untyped** counterpart to `@data`'s typed, fixed-shape codec: reach for `@data` when the shape is known and you want type safety, and for `JSON` when the shape is dynamic.
273
-
274
- The statics that make a value:
275
-
276
- | Static | What it does |
277
- | --- | --- |
278
- | `JSON.parse(text: string): JSON` | Parse JSON text into a value tree (returns an error value on malformed input). |
279
- | `JSON.obj(): JSON` | A new empty object; fill it with `.set(key, value)`. |
280
- | `JSON.arr(): JSON` | A new empty array; fill it with `.push(value)`. |
281
- | `JSON.of<T>(value: T): JSON` | Wrap a scalar, string, bool, or array as a JSON value. |
282
- | `JSON.nul(): JSON` | A JSON null. |
283
- | `JSON.stringify<T>(value: T): string` | Serialize a scalar / string / bool / array value straight to a JSON string. |
284
-
285
- The instance methods that read and build a value:
286
-
287
- | Method | What it does |
288
- | --- | --- |
289
- | `.isObject()` / `.isArray()` / `.isString()` / `.isNumber()` / `.isBool()` / `.isNull(): bool` | Test the value's type before you read it. |
290
- | `.has(key: string): bool` | Whether an object has `key`. |
291
- | `.get(key: string): JSON` | The value for `key` on an object. |
292
- | `.objectKeys(): Array<string>` | The keys of an object. |
293
- | `.at(index: i32): JSON` | The element at `index` of an array. |
294
- | `.length(): i32` | The element count of an array (0 otherwise). |
295
- | `.asString(): string` | Read the value as a string. |
296
- | `.asF64(): f64` / `.asI64(): i64` / `.asU64(): u64` | Read the value as a number. |
297
- | `.asBool(): bool` | Read the value as a bool. |
298
- | `.set(key: string, value: JSON): JSON` | Set a key on an object; returns `this`, so calls chain. |
299
- | `.push(value: JSON): JSON` | Append to an array; returns `this`, so calls chain. |
300
- | `.toString(): string` | Serialize this tree back to a JSON string. |
301
-
302
- Reading an untyped body and building a reply, together:
303
-
304
- ```ts
305
- import { Response, RouteContext } from 'toiljs/server/runtime';
306
-
307
- @rest('echo')
308
- class Echo {
309
- // POST /echo with an arbitrary JSON body we do not model as a @data class.
310
- @post('/')
311
- public handle(ctx: RouteContext): Response {
312
- const body = JSON.parse(ctx.text()); // ctx.text() is the raw body as text
313
- if (!body.isObject() || !body.has('name')) {
314
- return Response.badRequest('expected an object with a "name" field');
315
- }
316
-
317
- // Read fields out of the tree, guarding types and optional keys.
318
- const name = body.get('name').asString();
319
- const age = body.has('age') ? body.get('age').asI64() : 0;
320
-
321
- // Build a fresh JSON object to send back (chaining .set).
322
- const out = JSON.obj()
323
- .set('greeting', JSON.of<string>('hello, ' + name))
324
- .set('age', JSON.of<i64>(age));
325
- return Response.json(out.toString());
326
- }
327
- }
328
- ```
329
-
330
- Gotchas specific to `JSON`:
331
-
332
- - **Check the type before you read.** `JSON.parse` never throws; a malformed input or a wrong-typed field yields an error or default value rather than a crash. Use `.isObject()` / `.has(...)` / `.isString()` and friends to validate untrusted input before you trust it.
333
- - **It is a value tree, not a typed struct.** You get no compile-time field checking. When a shape is stable, prefer a `@data` class so the compiler catches typos and the client gets a typed type for free.
334
- - **Big-integer care still applies.** As with any JSON, integers above 2^53 are best carried as strings; read them with `.asString()` when exactness matters.
335
-
336
- ## Gotchas
337
-
338
- - **Field order is the format.** The binary layout is exactly your field declaration order. Reordering fields, or changing a field's type, is a breaking change: old bytes will decode wrong. To evolve a format safely, add new fields at the **end** (and, for hand-rolled payloads, bump a leading version byte).
339
- - **Every field needs a default.** The generated decoder and the client constructor rely on it. A field with no default will not compile as `@data`.
340
- - **`encode()` carries a type id; `encodeInto` does not.** The 4-byte `dataId()` prefix lets a decoder confirm it is reading the type it expected. When nesting one `@data` inside another, `encodeInto` / `decodeFrom` skip that frame (the outer type already identifies the whole value).
341
- - **Endianness.** The WASM codec is little-endian. The `toiljs/io` codec defaults to little-endian too, and also accepts a per-call big-endian flag for network formats. Keep both ends on the same setting.
342
- - **Plain JSON numbers lose precision above 2^53.** That is why `@data` sends 64-bit-and-larger integers as decimal strings over JSON. If you hand-build JSON, do the same, or use the binary codec.
343
- - **`DataReader` never throws; check `ok`.** An over-read returns a default and sets `ok = false`. Always check `ok` after decoding untrusted bytes.
344
-
345
- ## Related
346
-
347
- - [Types](../concepts/types.md): `u64`, `u256`, `f64`, and how each maps to `number` or `bigint`.
348
- - [HTTP routes (`@rest`)](./rest.md): where `@data` bodies and return values are used, and the JSON vs binary route modes.
349
- - [Typed RPC](./rpc.md): `@data` as RPC arguments and results, and the generated client classes.
350
- - [The database](../database/README.md): storing `@data` values in ToilDB.
351
- - [Backend overview](./README.md): where `@data` fits in the request lifecycle.