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,235 +0,0 @@
1
- # Cookies
2
-
3
- **Cookies let you store a small piece of state in the user's browser and read it back on the next request.** toiljs ships a complete cookie layer: read incoming cookies, set them on a response, and (optionally) sign or encrypt their values so they cannot be tampered with.
4
-
5
- ## What a cookie is
6
-
7
- A **cookie** is a tiny named value the browser holds for your site. The browser sends it back on every request to your site (in a `Cookie` request header), and you can change or add cookies by putting a `Set-Cookie` header on your response. Cookies are how a site remembers things between requests: a theme preference, a feature flag, a session id.
8
-
9
- Two directions to keep straight:
10
-
11
- - **Reading** happens on the `Request` (the browser sent its cookies to you).
12
- - **Writing** happens on the `Response` (you tell the browser to store or clear a cookie).
13
-
14
- ```mermaid
15
- sequenceDiagram
16
- participant B as Browser
17
- participant S as Your handler
18
- B->>S: Request<br/>Cookie: theme=dark
19
- Note over S: req.cookie('theme') returns "dark"
20
- S-->>B: Response<br/>Set-Cookie: theme=light
21
- Note over B: browser stores the new cookie<br/>and sends it next time
22
- ```
23
-
24
- The cookie types (`Cookie`, `Cookies`, `CookieMap`, `SecureCookies`, and the `SameSite` / `CookieEncoding` / `CookiePrefix` enums) are **ambient globals**: use them with no import, like `crypto`. They are also exported from `toiljs/server/runtime` if you prefer an explicit import.
25
-
26
- ## Reading incoming cookies
27
-
28
- On the `Request`, two methods cover almost everything:
29
-
30
- ```ts
31
- const theme = req.cookie('theme'); // one value: string | null
32
- const jar = req.cookies(); // all of them, parsed once and cached
33
- jar.get('theme'); // "dark", or null
34
- jar.has('theme'); // true / false
35
- ```
36
-
37
- `req.cookies()` returns a `CookieMap`, an ordered name-to-value view. It is parsed once per request and cached, so calling it repeatedly is cheap.
38
-
39
- ## Setting a cookie on a response
40
-
41
- Build a cookie with the fluent `Cookie` builder, then attach it with `setCookie`. Every setter returns the cookie, so calls chain:
42
-
43
- ```ts
44
- import { Response } from 'toiljs/server/runtime';
45
-
46
- return Response.json('{"ok":true}').setCookie(
47
- Cookie.create('theme', 'dark')
48
- .path('/')
49
- .maxAge(60 * 60 * 24 * 365) // one year, in seconds
50
- .sameSite(SameSite.Lax)
51
- .secure(),
52
- );
53
- ```
54
-
55
- Shorthands for the common cases:
56
-
57
- ```ts
58
- resp.setCookieKV('theme', 'dark'); // name=value, no attributes
59
- resp.clearCookie('theme'); // delete it (empty value, expired)
60
- ```
61
-
62
- Each `setCookie` adds its own `Set-Cookie` header; cookies are never folded into one header.
63
-
64
- ### The attributes you will actually use
65
-
66
- | Method | Sets | What it means |
67
- | --- | --- | --- |
68
- | `path('/')` | `Path` | Which URL paths the cookie applies to. `/` means the whole site. |
69
- | `maxAge(seconds)` | `Max-Age` | How long the browser keeps it. `0` or negative deletes it now. |
70
- | `secure()` | `Secure` | Only send over HTTPS. Use it for anything that matters. |
71
- | `httpOnly()` | `HttpOnly` | Hide it from JavaScript in the browser (blocks theft via XSS). Use it for session cookies. |
72
- | `sameSite(SameSite.Lax)` | `SameSite` | Controls whether the cookie is sent on cross-site requests. `Lax` is a good default; `Strict` is tighter; `None` allows cross-site (and forces `Secure`). |
73
-
74
- You rarely need the rest, but they exist: `domain`, `expires` (an absolute time instead of a duration), `partitioned` (CHIPS), `priority`, and `extension` for anything custom.
75
-
76
- ## The `__Host-` and `__Secure-` prefixes
77
-
78
- Two special name prefixes give the browser extra guarantees. They are not magic strings you invent; browsers recognize them and **refuse** to accept a cookie that carries the prefix without meeting its rules.
79
-
80
- - **`__Secure-`**: the cookie must be `Secure` (HTTPS only).
81
- - **`__Host-`**: the cookie must be `Secure`, have `Path=/`, and have **no** `Domain`. This locks the cookie to exactly your host, so a sibling subdomain cannot set or read it. It is the strongest option and the right choice for session cookies.
82
-
83
- You do not spell the prefix yourself. Two helpers apply it and enforce the rules for you:
84
-
85
- ```ts
86
- Cookie.create('sid', 'abc123')
87
- .httpOnly()
88
- .sameSite(SameSite.Lax)
89
- .maxAge(3600)
90
- .asHostPrefixed(); // prepends __Host-, forces Secure + Path=/ + no Domain
91
- ```
92
-
93
- `asSecurePrefixed()` is the lighter version (prepends `__Secure-` and forces `Secure`).
94
-
95
- > Under `toiljs dev`, browsers treat `http://localhost` as a secure context, so `Secure` and `__Host-` cookies work over plain HTTP locally. You do not need HTTPS to test them.
96
-
97
- ## Worked example: a theme preference cookie
98
-
99
- A tiny handler that reads a `theme` cookie and lets the user flip it. This is the canonical "remember a preference" pattern: a plain, non-secret value.
100
-
101
- ```ts
102
- import { Response, RouteContext } from 'toiljs/server/runtime';
103
-
104
- @rest('prefs')
105
- class Prefs {
106
- // GET /prefs/theme -> current theme (defaults to "light")
107
- @get('/theme')
108
- read(ctx: RouteContext): Response {
109
- const theme = ctx.request.cookie('theme');
110
- return Response.text((theme != null ? theme : 'light') + '\n');
111
- }
112
-
113
- // POST /prefs/theme/dark -> remember "dark" for a year
114
- @post('/theme/dark')
115
- setDark(ctx: RouteContext): Response {
116
- return Response.text('saved\n').setCookie(
117
- Cookie.create('theme', 'dark')
118
- .path('/')
119
- .maxAge(60 * 60 * 24 * 365)
120
- .sameSite(SameSite.Lax)
121
- .secure(),
122
- );
123
- }
124
- }
125
- ```
126
-
127
- A preference like this does not need to be secret or tamper-proof (the worst a user can do is change their own theme), so a plain cookie is fine. When the value **does** matter, reach for `SecureCookies`.
128
-
129
- ## Secure cookies: signing and encryption
130
-
131
- Sometimes a cookie value must not be forged or read by the user. `SecureCookies` covers both, built on the `crypto` global (no extra setup):
132
-
133
- - **Signed** (`SecureCookies.signed(key)`): the value stays readable but is stamped with a signature (HMAC-SHA256) bound to the cookie's name. The user can see it but cannot change it or move it to another cookie without the signature failing. Use this for a value the client may read but must not tamper with.
134
- - **Encrypted** (`SecureCookies.encrypted(key)`): the value is scrambled (AES-GCM) so the user cannot read **or** change it. Use this for something confidential.
135
-
136
- The `key` is raw secret bytes you supply (load it from a secret via [`Environment.getSecure`](./environment.md), never hard-code it):
137
-
138
- ```ts
139
- // In a real app, load this from a secret, do not inline it.
140
- const key = Uint8Array.wrap(String.UTF8.encode('0123456789abcdef0123456789abcdef'));
141
-
142
- // Signed: readable but tamper-proof.
143
- const signer = SecureCookies.signed(key);
144
- const sealed = signer.sign('session', 'user-42');
145
- const user = signer.unsign('session', sealed); // "user-42", or null if tampered
146
-
147
- // Encrypted: unreadable and authenticated.
148
- const box = SecureCookies.encrypted(key);
149
- resp.setCookie(box.seal(Cookie.create('secret', 'top-secret').httpOnly()));
150
- const secret = box.open(req.cookies(), 'secret'); // "top-secret", or null
151
- ```
152
-
153
- Two important safety properties:
154
-
155
- - **Verification never crashes.** `unsign` and `decrypt` return `null` on a tampered, truncated, or wrong-key value; they never throw. That makes them safe to call directly on attacker-controlled input.
156
- - **Values are bound to the cookie name.** A sealed value made for cookie `a` will not verify or decrypt under cookie `b`.
157
-
158
- For HMAC keys, use 32 or more bytes. For AES, the key must be exactly 16 or 32 bytes (a wrong length is rejected immediately). You can rotate keys by sealing with a new key while still accepting an old one:
159
-
160
- ```ts
161
- const signer = SecureCookies.signed(newKey).addKey(oldKey); // seal with new, open with either
162
- ```
163
-
164
- ## Encoding is not encryption
165
-
166
- One common mix-up. **Encoding** (the `CookieEncoding` on a `Cookie`, default percent-encoding) only makes a value safe to put on the wire; anyone can reverse it. **Signing / encryption** (`SecureCookies`) is the cryptographic protection and needs a secret key. If you want a value the user cannot forge, you need `SecureCookies`, not a fancier encoding.
167
-
168
- ## Relationship to auth session cookies
169
-
170
- You do not manage login cookies by hand. The [auth system](../auth/usage.md) sets and reads its own hardened, `__Host-` prefixed, signed session cookie for you, and enforces access with the `@auth` decorator. This page is for **your own** cookies (preferences, flags, small bits of state). If you find yourself building a login cookie from scratch, use auth instead; it already does the hard, security-sensitive parts correctly.
171
-
172
- ## Advanced reference: `Cookie` and `Cookies` helpers
173
-
174
- Most handlers only need the builders above. These extra members are here for lower-level work: validating a cookie before you send it, parsing a `Set-Cookie` header back into a `Cookie` (for a proxy or a test), or controlling the exact wire encoding.
175
-
176
- ### More `Cookie` methods
177
-
178
- | Method | Returns | What it does |
179
- | --- | --- | --- |
180
- | `validate()` | `CookieValidation` | Check the cookie against RFC 6265bis (name is a token, name+value within the 4096-byte cap, `Path` form, prefix rules, `SameSite=None` / `Partitioned` imply `Secure`, the 400-day lifetime cap) **without** sending it. Never throws. |
181
- | `serialize(strict)` | `string` | Build the `Set-Cookie` value. Lenient by default (always emits a best-effort cookie); pass `serialize(true)` to **throw** on a hard violation instead. `setCookie(...)` calls this for you. |
182
- | `withEncoding(enc)` | `Cookie` | Choose how the value goes on the wire: `CookieEncoding.Percent` (default), `.Raw`, or `.Base64Url`. Chains like the other setters. |
183
- | `detectedPrefix()` | `CookiePrefix` | Report which special prefix the name carries (`CookiePrefix.Host`, `.Secure`, or `.None`), detected case-insensitively. |
184
- | `encodedValue()` | `string` | The value after the chosen encoding is applied, exactly as it will appear on the wire. |
185
- | `expiresRaw(date)` | `Cookie` | Set `Expires` to a verbatim date string (an escape hatch; it is **not** validated, unlike `expires(epochSeconds)`). |
186
-
187
- `validate()` returns a `CookieValidation`, a small result object:
188
-
189
- - `valid: bool` is `true` when there were no problems.
190
- - `errors: Array<string>` holds one human-readable message per problem (empty when valid).
191
-
192
- ```ts
193
- // A __Host- cookie must be Secure with Path=/; here we forgot both.
194
- const c = Cookie.create('__Host-sid', 'abc');
195
- const check = c.validate();
196
- if (!check.valid) {
197
- // check.errors includes "__Host- prefix requires the Secure attribute"
198
- // (asHostPrefixed() would have set those attributes for you)
199
- }
200
- ```
201
-
202
- ### `Cookies` static helpers
203
-
204
- `Cookies` is the read side, plus a couple of codec shortcuts. Every method is static, so call them as `Cookies.xxx(...)`.
205
-
206
- | Call | Returns | What it does |
207
- | --- | --- | --- |
208
- | `Cookies.parse(header)` | `CookieMap` | Parse a request `Cookie` header (`a=1; b=2`) into a name-to-value map. Values are percent-decoded; malformed pairs are skipped, never thrown. |
209
- | `Cookies.get(header, name)` | `string \| null` | Shorthand: parse `header` and return one value (or `null`). |
210
- | `Cookies.serialize(name, value)` | `string` | One-shot `Set-Cookie` value for `name=value` with no attributes. For attributes, build a `Cookie` and call `serialize()`. |
211
- | `Cookies.parseSetCookie(header)` | `Cookie` | Parse a `Set-Cookie` field value back into a `Cookie` (handy for proxies or tests). The value is kept verbatim (`CookieEncoding.Raw`) so re-serializing reproduces the original. |
212
- | `Cookies.encodeValue(raw)` | `string` | Percent-encode a value the way the default `Cookie` encoding would. |
213
- | `Cookies.decodeValue(enc)` | `string` | Percent-decode a value (the inverse of `encodeValue`). |
214
-
215
- ```ts
216
- // Round-trip a Set-Cookie header (for example, inspecting an upstream response in a proxy):
217
- const cookie = Cookies.parseSetCookie('sid=abc123; Path=/; HttpOnly; SameSite=Lax');
218
- cookie.name; // "sid"
219
- cookie.serialize(); // "sid=abc123; Path=/; SameSite=Lax; HttpOnly"
220
- ```
221
-
222
- ## Gotchas
223
-
224
- - **Read on the `Request`, write on the `Response`.** Setting a cookie does not change what `req.cookie(...)` returns for the current request; it takes effect on the browser's next request.
225
- - **`maxAge` is in seconds.** `maxAge(3600)` is one hour, not one millisecond.
226
- - **To delete a cookie, the `path` (and `domain`) must match** the ones you set it with. Use `clearCookie(name, path, domain)`.
227
- - **A `Set-Cookie` opts a response out of edge caching.** By design, a response that sets a cookie is never edge-cached, because a cookie is per-user state. See [Caching](./caching.md).
228
- - **Do not put secrets in a plain cookie value.** Use `SecureCookies.encrypted(...)`, and load the key from a [secret](./environment.md).
229
-
230
- ## Related
231
-
232
- - [Auth, sessions, and `@user`](../auth/usage.md): the built-in, hardened session cookie.
233
- - [Crypto](./crypto.md): the primitives under `SecureCookies`.
234
- - [Environment and secrets](./environment.md): where to store your signing / encryption key.
235
- - [Caching](./caching.md): why setting a cookie disables edge caching.
@@ -1,209 +0,0 @@
1
- # Crypto
2
-
3
- A small, safe cryptography toolkit (hashing, random bytes, HMAC, symmetric encryption, key derivation, and signatures) available with no import through the ambient `crypto` global.
4
-
5
- Reach for `crypto` to fingerprint a value (hashing), generate an unguessable token or ID (random bytes), prove a value has not been tampered with (HMAC or a signature), or encrypt data you store yourself (AES). Skip it for **login passwords**: use [auth](../auth/README.md), which is purpose-built and does the hard parts for you. `crypto` is also the engine under [signed cookies](./cookies.md) and auth, so most apps use it indirectly without ever calling it.
6
-
7
- ## The shape of the API
8
-
9
- `crypto` mirrors the browser Web Crypto API, with one big difference: **there are no Promises**. ToilScript (the language your backend is written in) has no `async`, so every call returns its result **directly**, right away.
10
-
11
- ```ts
12
- const digest = crypto.sha256Text('hello'); // Uint8Array, no await
13
- const id = crypto.randomUUID(); // string, no await
14
- ```
15
-
16
- There are two layers:
17
-
18
- - **`crypto.*`**: ergonomic one-call helpers (hash this string, give me a UUID, HMAC these bytes). Start here.
19
- - **`crypto.subtle`**: the full primitive surface (import a key, encrypt, sign, derive bits) for when you need more control.
20
-
21
- Both are ambient globals (no import). The small helper classes and the `ALG_*` / `FMT_*` / `USAGE_*` / `CURVE_*` constants you pass to `subtle` are imported from the `'crypto'` module.
22
-
23
- ## Quick helpers (`crypto.*`)
24
-
25
- Every helper is synchronous and returns its value directly.
26
-
27
- | Helper | Signature | What it does |
28
- | --- | --- | --- |
29
- | `getRandomValues` | `(array: Uint8Array): void` | Fill the array with cryptographically strong random bytes. |
30
- | `randomUUID` | `(): string` | A random RFC 4122 v4 UUID string. |
31
- | `sha1` / `sha256` / `sha384` / `sha512` | `(data: Uint8Array): Uint8Array` | Hash raw bytes. |
32
- | `sha1Text` … `sha512Text` | `(s: string): Uint8Array` | UTF-8 encode the string, then hash. |
33
- | `hmacSha256` | `(key: Uint8Array, msg: Uint8Array): Uint8Array` | Keyed fingerprint (HMAC-SHA-256) of bytes. |
34
- | `hmacSha256Text` | `(key: Uint8Array, msg: string): Uint8Array` | HMAC-SHA-256 over a string. |
35
- | `toHex` | `(bytes: Uint8Array): string` | Lowercase hex string (for display or storage). |
36
- | `subtle` | `SubtleCrypto` | The full primitive surface (below). |
37
-
38
- **Hashing** turns any input into a fixed-size fingerprint. The same input always gives the same output, and you cannot work backward from the fingerprint to the input. Use it to compare or index a value without storing the original (for example, keying a cache by the hash of a URL).
39
-
40
- **Random bytes** come from a CSPRNG (a cryptographically secure random generator), which means the output is unpredictable and safe for tokens, session IDs, and IVs. Never use `Math.random()` for anything security-related.
41
-
42
- **HMAC** is a fingerprint computed *with a secret key*. Anyone can hash a value, but only someone who holds the key can produce the correct HMAC, so it proves a value came from you and was not altered. This is exactly what [`TwoFactor`](./email.md) tokens and [signed cookies](./cookies.md) use.
43
-
44
- ## Worked example: hash a value and generate a token
45
-
46
- ```ts
47
- import { RouteContext } from 'toiljs/server/runtime';
48
-
49
- @rest('demo')
50
- class Demo {
51
- @get('/')
52
- public example(ctx: RouteContext): string {
53
- // Hash a value to a hex fingerprint (safe to log or use as a key).
54
- const digest = crypto.sha256Text('alice@example.com');
55
- const fingerprint = crypto.toHex(digest); // 64 hex chars
56
-
57
- // Generate a 128-bit unguessable token as hex.
58
- const bytes = new Uint8Array(16);
59
- crypto.getRandomValues(bytes);
60
- const token = crypto.toHex(bytes); // 32 hex chars
61
-
62
- // A ready-made unique id.
63
- const id = crypto.randomUUID();
64
-
65
- return `fp=${fingerprint} token=${token} id=${id}`;
66
- }
67
- }
68
- ```
69
-
70
- To fingerprint a value *with a secret* (so only your server can produce or check it), use HMAC:
71
-
72
- ```ts
73
- const key = Uint8Array.wrap(String.UTF8.encode(Environment.getSecure('SIGNING_KEY')!));
74
- const mac = crypto.hmacSha256Text(key, 'order:42');
75
- const macHex = crypto.toHex(mac);
76
- ```
77
-
78
- ## The primitive surface (`crypto.subtle`)
79
-
80
- Use `subtle` when the helpers are not enough: symmetric encryption, signatures, or key derivation. Every method is synchronous.
81
-
82
- | Method | Signature |
83
- | --- | --- |
84
- | `digest` | `digest(algorithm: i32, data: Uint8Array): Uint8Array` |
85
- | `importKey` | `importKey(format: i32, keyData: Uint8Array, algorithm: AlgorithmParams, extractable: bool, usages: i32): CryptoKey` |
86
- | `exportKey` | `exportKey(format: i32, key: CryptoKey): Uint8Array` |
87
- | `encrypt` | `encrypt(algorithm: AlgorithmParams, key: CryptoKey, data: Uint8Array): Uint8Array` |
88
- | `decrypt` | `decrypt(algorithm: AlgorithmParams, key: CryptoKey, data: Uint8Array): Uint8Array` |
89
- | `sign` | `sign(algorithm: AlgorithmParams, key: CryptoKey, data: Uint8Array): Uint8Array` |
90
- | `verify` | `verify(algorithm: AlgorithmParams, key: CryptoKey, signature: Uint8Array, data: Uint8Array): bool` |
91
- | `deriveBits` | `deriveBits(algorithm: AlgorithmParams, baseKey: CryptoKey, length: i32): Uint8Array` |
92
- | `deriveKey` | `deriveKey(algorithm, baseKey, lengthBits, derivedKeyAlgorithm, extractable, usages): CryptoKey` |
93
-
94
- Two things differ from the browser API, both because of the missing-Promise design:
95
-
96
- - **`algorithm` and `format` are integer constants, not strings.** You pass `ALG_SHA_256` (not `"SHA-256"`) and `FMT_RAW` (not `"raw"`). The constants are listed below.
97
- - **`verify` returns a `bool`.** A signature mismatch returns `false` (it does not throw). A real error, like an invalid key, does throw.
98
-
99
- ### Keys are per-request handles
100
-
101
- You never hold raw key bytes in your code for long. `importKey` gives you back a **`CryptoKey`**, which is an opaque handle into a host-side keystore. That keystore is **wiped when the request ends**, so a `CryptoKey` is valid only within the request that created it. Never cache one across requests; import it again each time.
102
-
103
- ```mermaid
104
- sequenceDiagram
105
- participant G as Your handler
106
- participant H as Host keystore (per request)
107
- G->>H: importKey(...) -> a CryptoKey handle
108
- G->>H: sign(params, key, data)
109
- H-->>G: signature bytes
110
- Note over H: the keystore is wiped when the request ends
111
- ```
112
-
113
- ### Algorithm parameter classes
114
-
115
- Each algorithm takes a small parameters object you build and pass in. Import the class you need from `'crypto'`:
116
-
117
- ```ts
118
- import { AesGcmParams, HmacImportParams, ALG_SHA_256, USAGE_ENCRYPT, USAGE_DECRYPT } from 'crypto';
119
- ```
120
-
121
- | Class | Constructor | Used for |
122
- | --- | --- | --- |
123
- | `AesGcmParams` | `(iv, additionalData?, tagLength = 128)` | AES-GCM encrypt/decrypt |
124
- | `AesCbcParams` | `(iv)` | AES-CBC |
125
- | `AesCtrParams` | `(counter, length = 128)` | AES-CTR |
126
- | `HmacImportParams` | `(hash)` | importing an HMAC key |
127
- | `HmacParams` | `()` | HMAC sign/verify (hash comes from the key) |
128
- | `Pbkdf2Params` | `(hash, salt, iterations)` | deriving a key from a password |
129
- | `HkdfParams` | `(hash, salt, info?)` | deriving a key from key material |
130
- | `EcdsaParams` | `(hash)` | ECDSA sign/verify |
131
- | `EcKeyImportParams` | `(alg, namedCurve)` | importing an EC key |
132
- | `Ed25519Params` | `()` | Ed25519 sign/verify |
133
- | `X25519ImportParams` | `()` | importing an X25519 key |
134
- | `EcdhParams` | `(alg, publicKeyHandle)` | ECDH / X25519 key agreement |
135
-
136
- ### Constants
137
-
138
- - **Hashes and algorithms:** `ALG_SHA_1`, `ALG_SHA_256`, `ALG_SHA_384`, `ALG_SHA_512`, `ALG_SHA3_256`, `ALG_SHA3_384`, `ALG_SHA3_512`, `ALG_AES_GCM`, `ALG_AES_CBC`, `ALG_AES_CTR`, `ALG_AES_KW`, `ALG_HMAC`, `ALG_ECDSA`, `ALG_ED25519`, `ALG_ECDH`, `ALG_X25519`, `ALG_HKDF`, `ALG_PBKDF2`.
139
- - **Key formats:** `FMT_RAW`, `FMT_PKCS8`, `FMT_SPKI`. (`FMT_JWK` is rejected.)
140
- - **Key usages (a bitmask, OR them together):** `USAGE_ENCRYPT`, `USAGE_DECRYPT`, `USAGE_SIGN`, `USAGE_VERIFY`, `USAGE_DERIVE_KEY`, `USAGE_DERIVE_BITS`, `USAGE_WRAP_KEY`, `USAGE_UNWRAP_KEY`.
141
- - **Named curves:** `CURVE_P256`, `CURVE_P384`. (`CURVE_P521` is not supported.)
142
-
143
- The `digest` selector also accepts the SHA3 constants, so `crypto.subtle.digest(ALG_SHA3_256, data)` works even though there is no `sha3` quick helper.
144
-
145
- ### `CryptoKey`
146
-
147
- A `CryptoKey` is a handle plus metadata: `handle: i32`, `type: string` (`"secret"`, `"public"`, or `"private"`), `extractable: bool`, `algorithm: i32`, and `usages: i32`, with `algorithmName()` and `hasUsage(u)` helpers. Remember it is only valid within the current request.
148
-
149
- ## Example: AES-256-GCM encrypt and decrypt
150
-
151
- AES-GCM is authenticated encryption: it both hides the data and detects tampering. It needs a 32-byte key and a fresh 12-byte IV (a "number used once"). **Never reuse an IV with the same key**; generate a new one every time.
152
-
153
- ```ts
154
- import { AesGcmParams, ALG_AES_GCM, FMT_RAW, USAGE_ENCRYPT, USAGE_DECRYPT } from 'crypto';
155
-
156
- // 32-byte key and a fresh 12-byte IV.
157
- const rawKey = new Uint8Array(32); crypto.getRandomValues(rawKey);
158
- const iv = new Uint8Array(12); crypto.getRandomValues(iv);
159
-
160
- // Import the key once; grant it both usages so it can round-trip.
161
- const key = crypto.subtle.importKey(
162
- FMT_RAW, rawKey, new AesGcmParams(iv), false, USAGE_ENCRYPT | USAGE_DECRYPT,
163
- );
164
-
165
- const plaintext = Uint8Array.wrap(String.UTF8.encode('secret note'));
166
- const ciphertext = crypto.subtle.encrypt(new AesGcmParams(iv), key, plaintext);
167
- const recovered = crypto.subtle.decrypt(new AesGcmParams(iv), key, ciphertext);
168
- // recovered == plaintext
169
- ```
170
-
171
- Store the `iv` next to the ciphertext (it is not secret); you need the same IV to decrypt.
172
-
173
- ## Example: HMAC with `subtle`
174
-
175
- The quick `crypto.hmacSha256` helper does this in one line, but here is the explicit form, which mirrors how [`TwoFactor`](./email.md) signs its tokens:
176
-
177
- ```ts
178
- import { HmacImportParams, HmacParams, ALG_SHA_256, FMT_RAW, USAGE_SIGN, USAGE_VERIFY } from 'crypto';
179
-
180
- const key = crypto.subtle.importKey(
181
- FMT_RAW, rawKeyBytes, new HmacImportParams(ALG_SHA_256), false, USAGE_SIGN | USAGE_VERIFY,
182
- );
183
-
184
- const mac = crypto.subtle.sign(new HmacParams(), key, message);
185
- const ok: bool = crypto.subtle.verify(new HmacParams(), key, mac, message); // true
186
-
187
- // verify compares in constant time and returns false on a mismatch (it does not throw).
188
- ```
189
-
190
- ## Limitations
191
-
192
- - **No Promises.** Every call is synchronous.
193
- - **No RSA.** It was dropped because the only pure-Rust implementation had an unfixable timing weakness. Use ECDSA or Ed25519 for signatures.
194
- - **No JWK key format.** Use `FMT_RAW`, `FMT_PKCS8`, or `FMT_SPKI`.
195
- - **No on-host key generation.** Keys are always **imported**, never generated on the server. Generate them elsewhere and import the bytes.
196
- - **No P-521.** `CURVE_P256` and `CURVE_P384` are supported.
197
- - **Keys do not survive the request.** A `CryptoKey` is a per-request handle; re-import each request.
198
- - **Every call is metered.** Crypto operations cost compute (charged up front from the sizes involved), so an over-budget call fails cleanly instead of burning CPU.
199
-
200
- ### Post-quantum signature verify
201
-
202
- Auth's login uses a post-quantum signature scheme (ML-DSA) that the host can **verify** (it never holds a private key). This underpins the [auth login stack](../auth/how-it-works.md); you reach it through `AuthService`, not through `crypto` directly, so it is documented there rather than here.
203
-
204
- ## Related
205
-
206
- - [Auth: how it works](../auth/how-it-works.md), the post-quantum login stack built on host-side verify.
207
- - [Cookies](./cookies.md), signed and encrypted cookies built on `crypto`.
208
- - [Email](./email.md), whose `TwoFactor` codes are HMAC tokens.
209
- - [Environment and secrets](./environment.md), where you keep signing and encryption keys.