toiljs 0.0.116 → 0.0.118

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 (337) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +16 -7
  3. package/build/backend/.tsbuildinfo +1 -1
  4. package/build/cli/.tsbuildinfo +1 -1
  5. package/build/cli/index.js +2375 -258
  6. package/build/client/.tsbuildinfo +1 -1
  7. package/build/client/auth.d.ts +3 -0
  8. package/build/client/auth.js +19 -10
  9. package/build/client/dev/devtools.js +1 -1
  10. package/build/client/dev/error-overlay.js +1 -1
  11. package/build/client/index.d.ts +1 -1
  12. package/build/client/index.js +1 -1
  13. package/build/client/navigation/navigation.js +28 -3
  14. package/build/client/routing/Router.js +5 -2
  15. package/build/client/routing/mount.js +1 -1
  16. package/build/compiler/.tsbuildinfo +1 -1
  17. package/build/compiler/auth-emails.js +7 -3
  18. package/build/compiler/emails.js +1 -1
  19. package/build/compiler/index.d.ts +1 -0
  20. package/build/compiler/index.js +26 -5
  21. package/build/compiler/pages.js +2 -3
  22. package/build/compiler/prerender.d.ts +3 -8
  23. package/build/compiler/prerender.js +88 -127
  24. package/build/compiler/ssg.js +3 -4
  25. package/build/compiler/template-build.js +3 -4
  26. package/build/compiler/toil-docs.generated.js +8 -7
  27. package/build/compiler/vite.js +4 -6
  28. package/build/devserver/.tsbuildinfo +1 -1
  29. package/build/devserver/db/database.js +7 -3
  30. package/build/devserver/db/types.js +1 -1
  31. package/build/devserver/production.js +4 -3
  32. package/build/devserver/runtime/crypto.d.ts +1 -1
  33. package/build/io/.tsbuildinfo +1 -1
  34. package/build/logger/.tsbuildinfo +1 -1
  35. package/build/shared/.tsbuildinfo +1 -1
  36. package/examples/basic/client/routes/analytics.tsx +7 -5
  37. package/examples/basic/client/routes/pq.tsx +2 -1
  38. package/examples/basic/server/tsconfig.json +1 -3
  39. package/package.json +45 -44
  40. package/presets/no-uint8array-tostring.js +120 -153
  41. package/presets/oxlint.d.ts +5 -0
  42. package/presets/oxlint.js +224 -0
  43. package/server/auth/AuthController.ts +53 -14
  44. package/server/auth/FactorSettings.ts +41 -0
  45. package/server/auth/SessionEpochs.ts +58 -0
  46. package/server/auth/SessionRevocations.ts +13 -0
  47. package/server/globals/auth.ts +27 -2
  48. package/server/runtime/tsconfig.json +1 -3
  49. package/tsconfig.json +2 -11
  50. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  51. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
  52. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  53. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  54. package/.github/PULL_REQUEST_TEMPLATE.md +0 -43
  55. package/.github/changelog-config.json +0 -45
  56. package/.github/dependabot.yml +0 -27
  57. package/.github/workflows/ci.yml +0 -222
  58. package/.vscode/settings.json +0 -10
  59. package/docs/README.md +0 -107
  60. package/docs/auth/README.md +0 -104
  61. package/docs/auth/configuration.md +0 -105
  62. package/docs/auth/emails.md +0 -92
  63. package/docs/auth/extending.md +0 -202
  64. package/docs/auth/how-it-works.md +0 -138
  65. package/docs/auth/usage.md +0 -188
  66. package/docs/backend/README.md +0 -143
  67. package/docs/backend/data.md +0 -351
  68. package/docs/backend/rest.md +0 -402
  69. package/docs/backend/rpc.md +0 -226
  70. package/docs/background/README.md +0 -114
  71. package/docs/background/daemons.md +0 -276
  72. package/docs/background/derive.md +0 -179
  73. package/docs/cli/README.md +0 -379
  74. package/docs/concepts/ai-guide.md +0 -132
  75. package/docs/concepts/config.md +0 -416
  76. package/docs/concepts/decorators.md +0 -127
  77. package/docs/concepts/security.md +0 -108
  78. package/docs/concepts/tiers.md +0 -166
  79. package/docs/concepts/types.md +0 -216
  80. package/docs/database/README.md +0 -143
  81. package/docs/database/capacity.md +0 -350
  82. package/docs/database/counters.md +0 -174
  83. package/docs/database/documents.md +0 -277
  84. package/docs/database/events.md +0 -307
  85. package/docs/database/membership.md +0 -246
  86. package/docs/database/setup.md +0 -155
  87. package/docs/database/unique.md +0 -216
  88. package/docs/database/views.md +0 -246
  89. package/docs/frontend/README.md +0 -104
  90. package/docs/frontend/components.md +0 -286
  91. package/docs/frontend/data-fetching.md +0 -260
  92. package/docs/frontend/images.md +0 -148
  93. package/docs/frontend/metadata.md +0 -344
  94. package/docs/frontend/navigation.md +0 -296
  95. package/docs/frontend/rendering.md +0 -236
  96. package/docs/frontend/routing.md +0 -346
  97. package/docs/frontend/scripts.md +0 -118
  98. package/docs/frontend/search.md +0 -191
  99. package/docs/frontend/styling.md +0 -147
  100. package/docs/frontend/toil-global.md +0 -216
  101. package/docs/getting-started/README.md +0 -81
  102. package/docs/getting-started/create-project.md +0 -131
  103. package/docs/getting-started/deploy.md +0 -101
  104. package/docs/getting-started/first-app.md +0 -215
  105. package/docs/getting-started/installation.md +0 -124
  106. package/docs/getting-started/migrating.md +0 -125
  107. package/docs/getting-started/project-structure.md +0 -163
  108. package/docs/introduction/README.md +0 -66
  109. package/docs/introduction/design-principles.md +0 -81
  110. package/docs/introduction/distributed.md +0 -91
  111. package/docs/introduction/how-it-works.md +0 -121
  112. package/docs/introduction/hyperscale.md +0 -70
  113. package/docs/introduction/modern-stack.md +0 -85
  114. package/docs/introduction/vs-other-frameworks.md +0 -64
  115. package/docs/introduction/why-toil.md +0 -99
  116. package/docs/llms.txt +0 -95
  117. package/docs/realtime/README.md +0 -102
  118. package/docs/realtime/channels.md +0 -211
  119. package/docs/realtime/streams.md +0 -369
  120. package/docs/services/README.md +0 -60
  121. package/docs/services/analytics.md +0 -279
  122. package/docs/services/caching.md +0 -175
  123. package/docs/services/cookies.md +0 -235
  124. package/docs/services/crypto.md +0 -209
  125. package/docs/services/email.md +0 -289
  126. package/docs/services/environment.md +0 -141
  127. package/docs/services/ratelimit.md +0 -174
  128. package/docs/services/time.md +0 -85
  129. package/eslint.config.js +0 -57
  130. package/examples/basic/client/routes/docs/[...slug].tsx +0 -12
  131. package/presets/eslint.js +0 -88
  132. package/src/backend/index.ts +0 -204
  133. package/src/cli/configure.ts +0 -363
  134. package/src/cli/create.ts +0 -924
  135. package/src/cli/db.ts +0 -158
  136. package/src/cli/diagnostics.ts +0 -665
  137. package/src/cli/doctor.ts +0 -982
  138. package/src/cli/features.ts +0 -170
  139. package/src/cli/index.ts +0 -326
  140. package/src/cli/notify.ts +0 -120
  141. package/src/cli/proc.ts +0 -56
  142. package/src/cli/ui.ts +0 -169
  143. package/src/cli/update.ts +0 -248
  144. package/src/cli/updates.ts +0 -92
  145. package/src/cli/validate.ts +0 -31
  146. package/src/cli/version-check.ts +0 -125
  147. package/src/client/auth.ts +0 -869
  148. package/src/client/channel/channel.ts +0 -146
  149. package/src/client/components/Form.tsx +0 -65
  150. package/src/client/components/Image.tsx +0 -160
  151. package/src/client/components/Script.tsx +0 -113
  152. package/src/client/components/Slot.tsx +0 -21
  153. package/src/client/dev/devtools.tsx +0 -1089
  154. package/src/client/dev/error-overlay.tsx +0 -225
  155. package/src/client/errors.ts +0 -11
  156. package/src/client/head/head.ts +0 -182
  157. package/src/client/head/metadata.ts +0 -110
  158. package/src/client/index.ts +0 -130
  159. package/src/client/navigation/Link.tsx +0 -108
  160. package/src/client/navigation/NavLink.tsx +0 -86
  161. package/src/client/navigation/navigation.ts +0 -235
  162. package/src/client/navigation/prefetch.ts +0 -169
  163. package/src/client/navigation/scroll.ts +0 -53
  164. package/src/client/routing/Router.tsx +0 -216
  165. package/src/client/routing/action.ts +0 -122
  166. package/src/client/routing/error-boundary.tsx +0 -43
  167. package/src/client/routing/hooks.ts +0 -140
  168. package/src/client/routing/lazy.ts +0 -77
  169. package/src/client/routing/loader.ts +0 -381
  170. package/src/client/routing/match.ts +0 -47
  171. package/src/client/routing/mount.tsx +0 -90
  172. package/src/client/routing/params-context.ts +0 -10
  173. package/src/client/routing/slot-context.ts +0 -7
  174. package/src/client/rpc.ts +0 -97
  175. package/src/client/search/search.ts +0 -189
  176. package/src/client/search/use-page-search.ts +0 -73
  177. package/src/client/ssr/markers.tsx +0 -172
  178. package/src/client/stream/client.ts +0 -237
  179. package/src/client/types.ts +0 -73
  180. package/src/compiler/auth-emails.ts +0 -312
  181. package/src/compiler/config.ts +0 -369
  182. package/src/compiler/docs.ts +0 -127
  183. package/src/compiler/email-preview.ts +0 -314
  184. package/src/compiler/emails.ts +0 -404
  185. package/src/compiler/fonts.ts +0 -89
  186. package/src/compiler/generate.ts +0 -471
  187. package/src/compiler/image-blur.ts +0 -42
  188. package/src/compiler/image-report.ts +0 -88
  189. package/src/compiler/index.ts +0 -1362
  190. package/src/compiler/pages.ts +0 -49
  191. package/src/compiler/plugin.ts +0 -391
  192. package/src/compiler/prerender.ts +0 -263
  193. package/src/compiler/routes.ts +0 -170
  194. package/src/compiler/seo.ts +0 -415
  195. package/src/compiler/sri.ts +0 -144
  196. package/src/compiler/ssg.ts +0 -177
  197. package/src/compiler/ssr-codegen.ts +0 -85
  198. package/src/compiler/template-build.ts +0 -676
  199. package/src/compiler/template.ts +0 -262
  200. package/src/compiler/toil-docs.generated.ts +0 -77
  201. package/src/compiler/vite.ts +0 -245
  202. package/src/devserver/analytics/index.ts +0 -317
  203. package/src/devserver/config/dotenv.ts +0 -102
  204. package/src/devserver/config/env.ts +0 -30
  205. package/src/devserver/config/ratelimit.ts +0 -123
  206. package/src/devserver/daemon/catalog.ts +0 -120
  207. package/src/devserver/daemon/cron.ts +0 -87
  208. package/src/devserver/daemon/host.ts +0 -134
  209. package/src/devserver/daemon/index.ts +0 -369
  210. package/src/devserver/daemon/runtime.ts +0 -48
  211. package/src/devserver/db/catalog.ts +0 -109
  212. package/src/devserver/db/database.ts +0 -1834
  213. package/src/devserver/db/derives.ts +0 -121
  214. package/src/devserver/db/index.ts +0 -20
  215. package/src/devserver/db/routeKinds.ts +0 -191
  216. package/src/devserver/db/types.ts +0 -168
  217. package/src/devserver/email/caps.ts +0 -0
  218. package/src/devserver/email/config.ts +0 -141
  219. package/src/devserver/email/console.ts +0 -348
  220. package/src/devserver/email/index.ts +0 -155
  221. package/src/devserver/email/providers.ts +0 -131
  222. package/src/devserver/email/status.ts +0 -23
  223. package/src/devserver/email/validate.ts +0 -37
  224. package/src/devserver/email/wire.ts +0 -55
  225. package/src/devserver/http/cache.ts +0 -0
  226. package/src/devserver/http/envelope.ts +0 -157
  227. package/src/devserver/http/proxy.ts +0 -172
  228. package/src/devserver/http/runtime.ts +0 -387
  229. package/src/devserver/index.ts +0 -43
  230. package/src/devserver/production-ipc.ts +0 -63
  231. package/src/devserver/production-worker.ts +0 -83
  232. package/src/devserver/production.ts +0 -718
  233. package/src/devserver/runtime/crypto.ts +0 -587
  234. package/src/devserver/runtime/host.ts +0 -427
  235. package/src/devserver/runtime/module.ts +0 -442
  236. package/src/devserver/server.ts +0 -251
  237. package/src/devserver/ssr.ts +0 -208
  238. package/src/devserver/stream/catalog.ts +0 -106
  239. package/src/devserver/stream/host.ts +0 -42
  240. package/src/devserver/stream/index.ts +0 -301
  241. package/src/devserver/stream/manager.ts +0 -167
  242. package/src/devserver/stream/router.ts +0 -101
  243. package/src/devserver/stream/wire.ts +0 -58
  244. package/src/devserver/stream/ws.ts +0 -78
  245. package/src/devserver/wasm/sections.ts +0 -59
  246. package/src/devserver/wasm/surface.ts +0 -115
  247. package/src/io/FastMap.ts +0 -151
  248. package/src/io/FastSet.ts +0 -113
  249. package/src/io/codec.ts +0 -362
  250. package/src/io/index.ts +0 -10
  251. package/src/io/lengths.ts +0 -14
  252. package/src/io/types.ts +0 -19
  253. package/src/logger/index.ts +0 -22
  254. package/src/shared/index.ts +0 -46
  255. package/test/analytics-dev.test.ts +0 -113
  256. package/test/assembly/aspect-shim.ts +0 -33
  257. package/test/assembly/cookie.spec.ts +0 -303
  258. package/test/assembly/example.spec.ts +0 -22
  259. package/test/assembly/ssr.spec.ts +0 -95
  260. package/test/assembly.test.ts +0 -59
  261. package/test/auth-emails.test.ts +0 -118
  262. package/test/built-ssr.test.ts +0 -148
  263. package/test/channel.test.ts +0 -21
  264. package/test/config-host-port.test.ts +0 -59
  265. package/test/configure.test.ts +0 -94
  266. package/test/daemon-build.test.ts +0 -278
  267. package/test/daemon-catalog.test.ts +0 -349
  268. package/test/daemon-emulation.test.ts +0 -256
  269. package/test/db.test.ts +0 -0
  270. package/test/devserver-database.test.ts +0 -1102
  271. package/test/devserver-email.test.ts +0 -306
  272. package/test/devserver-pqauth.test.ts +0 -93
  273. package/test/devserver-secrets.test.ts +0 -63
  274. package/test/devserver.test.ts +0 -259
  275. package/test/doctor.test.ts +0 -298
  276. package/test/dom/Image.test.tsx +0 -144
  277. package/test/dom/Link.test.tsx +0 -67
  278. package/test/dom/NavLink.test.tsx +0 -37
  279. package/test/dom/Script.test.tsx +0 -48
  280. package/test/dom/action.test.tsx +0 -146
  281. package/test/dom/error-overlay.test.tsx +0 -44
  282. package/test/dom/loader.test.tsx +0 -121
  283. package/test/dom/navigation.test.ts +0 -59
  284. package/test/dom/revalidate.test.tsx +0 -38
  285. package/test/dom/route-head.test.tsx +0 -56
  286. package/test/dom/router-loading.test.tsx +0 -44
  287. package/test/dom/scroll.test.ts +0 -56
  288. package/test/dom/slot.test.tsx +0 -131
  289. package/test/dom/use-metadata.test.tsx +0 -58
  290. package/test/dom/view-transitions.test.tsx +0 -53
  291. package/test/dynamic-routing.test.ts +0 -132
  292. package/test/email-console.test.ts +0 -86
  293. package/test/email-preview.test.ts +0 -73
  294. package/test/emails.test.ts +0 -58
  295. package/test/errors.test.ts +0 -21
  296. package/test/example-guestbook.test.ts +0 -123
  297. package/test/features.test.ts +0 -149
  298. package/test/fixtures/bignum-wire/spec.ts +0 -25
  299. package/test/fixtures/daemon-app.ts +0 -65
  300. package/test/fixtures/stream-echo.ts +0 -26
  301. package/test/fixtures/stream-gate.ts +0 -28
  302. package/test/fixtures/stream-trap.ts +0 -18
  303. package/test/fixtures/stream-typed.ts +0 -41
  304. package/test/fonts.test.ts +0 -28
  305. package/test/global-setup.ts +0 -17
  306. package/test/head.test.ts +0 -42
  307. package/test/image-blur.test.ts +0 -50
  308. package/test/io.test.ts +0 -117
  309. package/test/metadata.test.ts +0 -40
  310. package/test/navlink.test.ts +0 -28
  311. package/test/pages.test.ts +0 -105
  312. package/test/placeholder.test.ts +0 -9
  313. package/test/pqauth-e2e.test.ts +0 -216
  314. package/test/pqauth-email.test.ts +0 -801
  315. package/test/prerender.test.ts +0 -116
  316. package/test/prettier-plugin.test.ts +0 -46
  317. package/test/routes.test.ts +0 -76
  318. package/test/rpc-bignum-wire.test.ts +0 -164
  319. package/test/rpc-dispatch.test.ts +0 -132
  320. package/test/rpc-kinds.test.ts +0 -18
  321. package/test/rpc.test.ts +0 -66
  322. package/test/search.test.ts +0 -114
  323. package/test/seo.test.ts +0 -175
  324. package/test/slot-layouts.test.ts +0 -69
  325. package/test/sri.test.ts +0 -129
  326. package/test/ssg.test.ts +0 -36
  327. package/test/ssr-hydration.test.tsx +0 -122
  328. package/test/ssr-render.test.ts +0 -197
  329. package/test/ssr-template.test.tsx +0 -482
  330. package/test/stream-emulation.test.ts +0 -479
  331. package/test/ui.test.ts +0 -40
  332. package/test/update.test.ts +0 -58
  333. package/test/validate.test.ts +0 -42
  334. package/test/version-check.test.ts +0 -130
  335. package/tests/data-parity/generated-parity.ts +0 -99
  336. package/tests/data-parity/parity.ts +0 -80
  337. package/tests/data-parity/spec.ts +0 -46
@@ -1,279 +0,0 @@
1
- # Analytics
2
-
3
- Read your site's own usage numbers (requests, bytes, cache hits, database ops, errors, and more) straight from a route, with no extra code and no third-party service.
4
-
5
- Reach for analytics to build a status or usage dashboard for a site: how much traffic it serves, how effective its cache is, how many database operations it runs. It is **infrastructure metering per domain**, not per-user product analytics. There are no custom events; you read a fixed catalog of counters the edge already keeps for you.
6
-
7
- `Analytics` is an **ambient global**: use it with no import, like [`crypto`](./crypto.md) or `Time`.
8
-
9
- ## What "metering" means
10
-
11
- Every time the edge (the Dacely server running your code) serves a request, runs a database operation, opens a stream, or sends an email, it **counts** that into a set of per-domain counters. Those counters live in the same worldwide, distributed database that backs [ToilDB](../database/README.md), so the total for your site is correct across every edge location, not just the one machine that happened to serve a request.
12
-
13
- `Analytics.self()` reads a **snapshot** of your site's current counter values. You do not record anything yourself; the numbers are already there.
14
-
15
- ```mermaid
16
- flowchart LR
17
- R["Every request, DB op,<br/>stream, email"] --> C["Edge counts it into<br/>per-domain counters"]
18
- C --> D[("Distributed counters,<br/>worldwide")]
19
- D --> S["Analytics.self()<br/>reads a snapshot"]
20
- S --> Y["Your route returns<br/>the numbers"]
21
- ```
22
-
23
- ## Read your site's stats
24
-
25
- `Analytics.self()` returns a `TenantStats` object. Every counter is a **named getter** that returns a `u64` (an unsigned 64-bit integer, always zero or positive), so mapping the numbers into a response needs no casts at all.
26
-
27
- ```ts
28
- import { RouteContext } from 'toiljs/server/runtime';
29
-
30
- @rest('metrics')
31
- class Metrics {
32
- @get('/')
33
- public overview(ctx: RouteContext): string {
34
- const s = Analytics.self();
35
- // Every getter is a u64. cacheRatio is the one f64 (a fraction 0..1).
36
- return `requests=${s.requests} cacheHits=${s.cacheHits} ratio=${s.cacheRatio}`;
37
- }
38
- }
39
- ```
40
-
41
- You can also read any counter by its numeric id with `s.metric(MetricId.Requests)`, which returns `0` for an unknown id. The named getters are preferred: they are self-documenting and there are no magic strings to mistype.
42
-
43
- ### The counter catalog
44
-
45
- Every getter below is a lifetime running total: it only ever goes up, and reading it never resets it. They are grouped by area. (`MetricId` is the matching numeric id, an ambient enum you can use with no import.)
46
-
47
- **Requests and the L1 edge**
48
-
49
- | Getter | What it counts |
50
- | --- | --- |
51
- | `requests` | HTTP requests served |
52
- | `bytesOutL1` | Response bytes sent |
53
- | `bytesInL1` | Request bytes received |
54
- | `status2xx` | 2xx responses (success) |
55
- | `status3xx` | 3xx responses (redirects) |
56
- | `status4xx` | 4xx responses (client errors) |
57
- | `status5xx` | 5xx responses (server errors) |
58
- | `staticHits` | Static assets served without running your code |
59
- | `wasmDispatches` | Times your compiled handler ran |
60
- | `executorFullRejects` | Requests rejected because the worker pool was full |
61
- | `unknownHostRejects` | Requests rejected for an unknown host |
62
- | `rateLimitedRejects` | Requests rejected by rate limiting |
63
- | `gasUsed` | Total compute "gas" your handlers consumed |
64
-
65
- **Database (ToilDB)**
66
-
67
- | Getter | What it counts |
68
- | --- | --- |
69
- | `dbOps` | Database operations issued |
70
- | `dbReads` | Read operations |
71
- | `dbWrites` | Write operations |
72
- | `dbErrors` | Operations that errored |
73
- | `dbLatencyNsSum` | Summed operation latency, in nanoseconds |
74
- | `meanDbLatencyNs` | Derived: `dbLatencyNsSum / dbOps` (0 with no ops) |
75
-
76
- **Streams (WebTransport realtime)**
77
-
78
- | Getter | What it counts |
79
- | --- | --- |
80
- | `streamAccepts` | Stream connections accepted |
81
- | `streamRejectWrongNode` | Streams that reached the wrong node |
82
- | `streamRejectCapacity` | Streams rejected for capacity |
83
- | `streamRejectArtifact` | Streams rejected for a missing or invalid build |
84
- | `streamRejectGuest` | Streams your code rejected |
85
- | `streamTraps` | Stream handler crashes |
86
- | `streamIdleTimeouts` | Streams closed for being idle |
87
- | `streamBytesIn` / `streamBytesOut` | Bytes received / sent on streams |
88
- | `streamBackpressureEvents` | Times a stream had to slow down |
89
- | `streamCloses` | Clean closes |
90
- | `streamDisconnects` | Abrupt disconnects |
91
-
92
- **Daemons (L4 background jobs)**
93
-
94
- | Getter | What it counts |
95
- | --- | --- |
96
- | `daemonStarts` / `daemonStartFailures` | Daemon starts / failed starts |
97
- | `daemonTicksFired` | Scheduled ticks that ran |
98
- | `daemonTicksSkippedNotLeader` | Ticks skipped because this node was not the leader |
99
- | `daemonTicksFailed` | Ticks that failed |
100
- | `daemonLeaderAcquires` / `daemonLeaderFenced` | Leadership gained / lost |
101
- | `daemonHttpCallAttempts` / `daemonHttpCallFailures` | Outbound HTTP calls attempted / failed |
102
-
103
- **Memory, email, and cache**
104
-
105
- | Getter | What it counts |
106
- | --- | --- |
107
- | `memGrownBytes` | Wasm memory grown, in bytes |
108
- | `emails` | Emails sent |
109
- | `cacheHits` | Responses served from the edge cache |
110
- | `cacheMisses` | Cacheable responses that missed the cache |
111
- | `cacheRatio` | Derived **`f64`**: `cacheHits / (cacheHits + cacheMisses)`, a fraction from 0 to 1 (0 when there were no cacheable responses) |
112
-
113
- ### Live gauges and request meters
114
-
115
- A few fields are not lifetime totals. They still read as `u64` (except the derived `sustainedRpsCap`, an `f64`).
116
-
117
- **Live gauges** are the current level right now, not a running total:
118
-
119
- - `connectedStreams`: streams connected at this moment.
120
- - `committedMemory`: wasm memory committed right now, in bytes.
121
-
122
- **Request meters** show where you sit against your plan. The model is a **sustained-rate ("unmetered pipe")** one, not a fixed per-minute ceiling. Your plan has a **sustained request rate** `X` (requests per second), measured over a **3-minute rolling window**. Two important consequences:
123
-
124
- - **Instantaneous spikes are free.** There is no per-tier cap on how fast you may burst inside a window; you may spike right up to the box's own hardware ceiling (the `firewall.global_pps` DDoS backstop, shared across all tenants). Nothing throttles you for a brief peak.
125
- - **Only sustained overuse throttles.** You get an HTTP `429` once your fleet-global request count over the *trailing* 3 minutes exceeds `X * 180` (the sustained rate integrated over the window), or once your 30-day quota is exhausted. "Fleet-global" means the count is summed across every edge location, not just the one machine serving you.
126
-
127
- Because the window **rolls**, a burst ages out gradually over the following 3 minutes rather than being forgiven all at once on a boundary. The `Retry-After` on a `429` tells you exactly how long until enough of it has decayed.
128
-
129
- The fields:
130
-
131
- - `reqBurstUsed` / `reqBurstCap`: fleet-global requests in the **trailing 3-minute rolling window**, and its allowance. This is exactly the number the edge compares against, so it never disagrees with why you were throttled. The allowance is `X * 180` (the window is `BURST_WINDOW_SECS = 180` seconds). A cap of `0` means **unmetered** (no sustained cap).
132
- - `req30dUsed` / `req30dCap`: fleet-global requests in the **current 30-day window**, and the 30-day quota. This one still tumbles, because it is a billing period rather than a rate. A quota of `0` means **unmetered**.
133
- - `sustainedRpsCap`: a convenience `f64` derived from the burst cap, `reqBurstCap / BURST_WINDOW_SECS`. This is your plan's sustained rate `X` in requests/second (`0.0` when unmetered), so you can show "1000 rps sustained" without doing the division yourself.
134
-
135
- There is also `nowMs`, the edge wall-clock time (in milliseconds) when the snapshot was taken, so you can show "as of" and compute how fresh the numbers are.
136
-
137
- ## Time series (for graphs)
138
-
139
- The totals above are single numbers. To draw a graph over time, use `Analytics.series(metric, range)`, which returns a `Series`: a list of per-bucket totals from oldest to newest.
140
-
141
- ```ts
142
- @get('/requests-24h')
143
- public requests24h(ctx: RouteContext): string {
144
- const s = Analytics.series(MetricId.Requests, AnalyticsRange.H24);
145
- // s.points -> per-bucket totals, oldest to newest
146
- // s.bucketSecs -> seconds each bucket covers
147
- // s.headMs -> end time (ms) of the newest bucket
148
- // s.ratePerSec(i) -> requests/second for bucket i (points[i] / bucketSecs)
149
- return `buckets=${s.points.length} bucketSecs=${s.bucketSecs}`;
150
- }
151
- ```
152
-
153
- `AnalyticsRange` picks the window and the resolution. Short ranges use per-minute buckets for a smooth line; the rest use per-hour buckets (kept for 30 days):
154
-
155
- | Range | Span | Bucket width |
156
- | --- | --- | --- |
157
- | `H1` | 1 hour | 1 minute |
158
- | `H6` | 6 hours | 1 minute |
159
- | `H12` | 12 hours | 1 hour |
160
- | `H24` | 24 hours | 1 hour |
161
- | `D3` | 3 days | 1 hour |
162
- | `D7` | 7 days | 1 hour |
163
- | `D14` | 14 days | 1 hour |
164
- | `D30` | 30 days | 1 hour |
165
-
166
- The series stores **totals per bucket**, never rates. A rate is derived: `ratePerSec(i)` divides bucket `i` by its width for you, or you can compute `points[i] / bucketSecs` yourself. Most metrics you will chart are counters (`MetricId.Requests`, `MetricId.CacheHits`, and so on).
167
-
168
- ## Permissions: who can read what
169
-
170
- - **Your own site.** `Analytics.self()` and `Analytics.series(...)` always read the site the request landed on. The calling domain is decided by the edge; your code cannot forge it.
171
- - **Cross-site reads are for `dacely.com` only.** The privileged `dacely.com` domain (the platform dashboard) may read any site with `Analytics.site(domain)`, `Analytics.siteSeries(domain, ...)`, and `Analytics.listSites(...)`. For any other caller, or an unknown domain, `site` and `siteSeries` return `null` and `listSites` returns an empty page. There is no way for a normal site to read another site's numbers.
172
-
173
- `Analytics.listSites(cursor, limit)` enumerates site names for the dashboard, one page at a time. It returns a `SiteList` with `sites: string[]` and `hasMore: bool`. When `hasMore` is true, pass the last name back as the next `cursor` to get the following page.
174
-
175
- ```ts
176
- // Only meaningful when the calling domain is dacely.com.
177
- const page = Analytics.listSites('', 100); // first 100 site names
178
- for (let i = 0; i < page.sites.length; i++) {
179
- const other = Analytics.site(page.sites[i]); // TenantStats | null
180
- if (other != null) {
181
- // read other.requests, other.cacheRatio, ...
182
- }
183
- }
184
- ```
185
-
186
- ## Worked example: return everything
187
-
188
- This route reads the full snapshot and maps it into a typed response. Mapping is mechanical: nearly every getter is a `u64` and maps to a `u64` field with no cast; the only `f64` values are the derived `cacheRatio` and `sustainedRpsCap`. The example groups a representative field from each area; add the rest of the getters from the catalog above the same way.
189
-
190
- ```ts
191
- import { RouteContext } from 'toiljs/server/runtime';
192
-
193
- /** The shape returned to the client. All fields are u64 except the two derived f64s (cacheRatio, sustainedRpsCap). */
194
- @data
195
- export class UsageReport {
196
- // requests / L1
197
- requests: u64 = 0;
198
- bytesOut: u64 = 0;
199
- status2xx: u64 = 0;
200
- status5xx: u64 = 0;
201
- gasUsed: u64 = 0;
202
- // database
203
- dbOps: u64 = 0;
204
- dbErrors: u64 = 0;
205
- meanDbLatencyNs: u64 = 0;
206
- // cache
207
- cacheHits: u64 = 0;
208
- cacheMisses: u64 = 0;
209
- cacheRatio: f64 = 0;
210
- // live gauges (current level, not a total)
211
- connectedStreams: u64 = 0;
212
- committedMemory: u64 = 0;
213
- // request meters (cap 0 = unmetered)
214
- reqBurstUsed: u64 = 0; // requests in the current 3-minute sustained-rate bucket
215
- reqBurstCap: u64 = 0; // per-bucket cap = sustained_rps * 180
216
- req30dUsed: u64 = 0; // requests in the current 30-day window
217
- req30dCap: u64 = 0; // 30-day quota
218
- sustainedRpsCap: f64 = 0; // derived: reqBurstCap / 180 (the plan's sustained rps)
219
- // when the snapshot was read (edge ms)
220
- nowMs: u64 = 0;
221
- }
222
-
223
- @rest('usage')
224
- class Usage {
225
- @get('/')
226
- public report(ctx: RouteContext): UsageReport {
227
- const s = Analytics.self();
228
- const out = new UsageReport();
229
-
230
- out.requests = s.requests;
231
- out.bytesOut = s.bytesOutL1;
232
- out.status2xx = s.status2xx;
233
- out.status5xx = s.status5xx;
234
- out.gasUsed = s.gasUsed;
235
-
236
- out.dbOps = s.dbOps;
237
- out.dbErrors = s.dbErrors;
238
- out.meanDbLatencyNs = s.meanDbLatencyNs;
239
-
240
- out.cacheHits = s.cacheHits;
241
- out.cacheMisses = s.cacheMisses;
242
- out.cacheRatio = s.cacheRatio;
243
-
244
- out.connectedStreams = s.connectedStreams;
245
- out.committedMemory = s.committedMemory;
246
-
247
- out.reqBurstUsed = s.reqBurstUsed;
248
- out.reqBurstCap = s.reqBurstCap;
249
- out.req30dUsed = s.req30dUsed;
250
- out.req30dCap = s.req30dCap;
251
- out.sustainedRpsCap = s.sustainedRpsCap;
252
-
253
- out.nowMs = s.nowMs;
254
- return out;
255
- }
256
- }
257
- ```
258
-
259
- Because this is a `@data` return type, the client gets a fully typed result from `Server.REST.usage.report()`. See [structured data types](../backend/data.md) for how `@data` works.
260
-
261
- ## Local dev behavior
262
-
263
- Under `toiljs dev`, the real per-domain metering does not exist (there is only one machine and no fleet), so the dev server returns a **fixed sample** `TenantStats` and a gentle synthetic ramp for `series(...)`. That lets you build and style a dashboard locally. Dev is also permissive: it returns sample data for any domain, because the real `dacely.com`-only check is enforced on the edge. Your code is unchanged; only the numbers differ.
264
-
265
- ## Gotchas and limits
266
-
267
- - **Totals never reset.** The counter getters are lifetime running totals. To see "requests in the last hour", use `series(...)` and sum the buckets, not the totals.
268
- - **Values are `u64`.** Map them into `u64` fields with no casts. The non-integer fields are `cacheRatio` and `sustainedRpsCap` (and `Series.ratePerSec`), which are `f64`.
269
- - **Spikes are free; only sustained overuse throttles.** The request meters follow a sustained-rate model: you may burst up to the box's hardware ceiling, and throttling (`429`) starts only when the current 3-minute bucket passes `sustainedRpsCap * 180`, or when the 30-day quota runs out. A cap or quota of `0` means unmetered.
270
- - **Not per-user analytics.** These are infrastructure counters per domain. There is no way to record a custom event or attribute a number to a specific user.
271
- - **Cross-site reads need `dacely.com`.** `Analytics.site(...)` returns `null` for everyone else. Do not build a feature that reads another site's stats.
272
- - **Snapshots, not live streams.** Each call reads the current values once. `nowMs` tells you when.
273
-
274
- ## Related
275
-
276
- - [Caching](./caching.md), the source of `cacheHits`, `cacheMisses`, and `cacheRatio`.
277
- - [ToilDB database](../database/README.md), the source of the `db*` counters and where these counters are stored.
278
- - [Structured data types](../backend/data.md), for the `@data` return type in the worked example.
279
- - [Compute tiers](../concepts/tiers.md), for what "L1", "streams", and "daemons" mean in the catalog.
@@ -1,175 +0,0 @@
1
- # Caching
2
-
3
- **Caching lets the edge keep a copy of a response and hand it back instantly, without re-running your code.** You opt in per route, either with the `@cache` decorator or by calling `Response.cache(...)`.
4
-
5
- ## What "cache" means here
6
-
7
- A **cache** is a fast, temporary store of a previous answer. When a request comes in, the edge can check: "have I already computed this exact response recently?" If yes, that is a **cache hit**: it returns the saved copy in microseconds and never wakes up your WebAssembly program. If no, that is a **cache miss**: it runs your handler, sends the result, and (if you asked it to) saves a copy for next time.
8
-
9
- There are two places a response can be cached:
10
-
11
- - **The edge cache** is shared: one saved copy on an edge server answers many users. This is where the big speedups come from.
12
- - **The browser cache** is private to one user's browser. You control it with a standard `Cache-Control` header.
13
-
14
- You can use one or both.
15
-
16
- ## When to cache (and when not)
17
-
18
- Cache a response only when it is the **same for everyone** and safe to reuse for a little while:
19
-
20
- - Good: a public leaderboard, a product catalog, a blog index, an exchange-rate snapshot, a "server status" endpoint.
21
- - Bad: anything personalized (a user's dashboard, their cart, "hello, Alice"). If you cached that, one user could be served another user's data. The edge has strong safety rails against this (below), but the simplest rule is: **do not cache per-user data on the shared edge.**
22
-
23
- Caching is **always opt-in**. A response you do not mark is never stored. There is no "cache every GET" mode, because the edge cannot tell a personalized response from a public one on its own.
24
-
25
- ## How: the `@cache` decorator
26
-
27
- A **decorator** is an annotation you put right above a route method. `@cache` tells the edge how long it may reuse the response. The numbers are positional:
28
-
29
- ```ts
30
- import { Response } from 'toiljs/server/runtime';
31
-
32
- @rest('leaderboard')
33
- class Leaderboard {
34
- @cache(60) // 60 MINUTES at the edge
35
- @get('/')
36
- public top(): Response {
37
- // ...expensive query...
38
- return Response.json(standings);
39
- }
40
- }
41
- ```
42
-
43
- The full form is `@cache(edgeTtlMinutes, browserTtlSeconds?, privateScope?, allowAuth?)`:
44
-
45
- ```ts
46
- @cache(60) // 60 minutes at the edge, browser does not cache
47
- @cache(60, 300) // + tell the browser to cache for 5 minutes (300s)
48
- @cache(60, 300, true) // + mark it "private": browser only, never the shared edge
49
- @cache(60, 300, true, true) // + allow caching even for logged-in requests (see rails)
50
- ```
51
-
52
- **TTL** means "time to live": how long a saved copy stays fresh before the edge throws it away and recomputes. Notice the units differ on purpose: the edge TTL is in **minutes**, the browser TTL is in **seconds**.
53
-
54
- Every argument must be a plain number or `true`/`false` literal. If you pass something computed (a variable, a function call), the decorator safely degrades to "not cached" instead of misbehaving.
55
-
56
- ## How: `Response.cache(...)`
57
-
58
- `@cache` is just a shorthand. You can do the same thing by hand on any `Response`, which is handy when you decide at runtime:
59
-
60
- ```ts
61
- public cache(
62
- edgeTtlMinutes: u16,
63
- browserTtlSeconds: u32 = 0,
64
- privateScope: bool = false,
65
- allowAuth: bool = false,
66
- ): Response
67
- ```
68
-
69
- ```ts
70
- return Response.json(body).cache(60, 300);
71
- ```
72
-
73
- `cache(...)` returns the same `Response`, so it chains. There is also a shorthand for "edge only, no browser caching":
74
-
75
- ```ts
76
- return Response.bytes(blob).cacheFor(5); // edge-cache 5 minutes
77
- ```
78
-
79
- Under the hood, both set one header, `Dacely-Cache-Control`, that the edge reads and then strips (it never reaches the client).
80
-
81
- ## The parameters, in detail
82
-
83
- | Parameter | What it does |
84
- | --- | --- |
85
- | `edgeTtlMinutes` | How long the shared edge may serve the saved copy. Clamped to a 24-hour maximum, no matter what you pass. `0` means no edge caching. |
86
- | `browserTtlSeconds` | The `max-age` sent to the browser. `0` (default) means the browser is told not to cache. |
87
- | `privateScope` | Marks the response `private`: it may only live in the browser's own cache, never the shared edge or a CDN. |
88
- | `allowAuth` | Permits caching a response to a logged-in request. Off by default (see safety rails). |
89
-
90
- ## A hit vs a miss, visually
91
-
92
- ```mermaid
93
- flowchart TD
94
- R["Request arrives<br/>at the edge"] --> Q{"Saved copy<br/>still fresh?"}
95
- Q -->|Yes: cache HIT| H["Return the saved response<br/>(microseconds, no code runs)"]
96
- Q -->|No: cache MISS| M["Run your WebAssembly handler"]
97
- M --> D{"Marked cacheable<br/>and eligible?"}
98
- D -->|Yes| S["Save a copy for the TTL,<br/>then return it"]
99
- D -->|No| X["Return it, save nothing"]
100
- ```
101
-
102
- Every response the edge sends carries a `Dacely-Cache` header so you can see what happened while debugging:
103
-
104
- | `Dacely-Cache` value | Meaning |
105
- | --- | --- |
106
- | `HIT` | Served from the cache; your code did not run. |
107
- | `MISS` | Your code ran, and the response was saved (the next identical request should `HIT`). |
108
- | `DYNAMIC` | Your code ran, and the response was not cached (no directive, or not eligible). |
109
- | `BYPASS` | A fallback path (for example the compute layer was momentarily unavailable); not a cached answer. |
110
-
111
- ## Safety rails (why cross-user leaks cannot happen)
112
-
113
- The edge refuses to store certain responses even if you asked it to. These rules exist because a shared cache is exactly where one user's data could accidentally leak to another:
114
-
115
- - **5xx responses are never cached.** A server error is temporary; caching a `500` would keep serving the failure for the whole TTL. (2xx, 3xx, and 4xx are cacheable: a redirect or a `404`/`410` is a predictable function of the request.)
116
- - **A response with a `Set-Cookie` is never cached.** A cookie is per-user state.
117
- - **A response to a logged-in request is not cached** unless you pass `allowAuth = true`. This stops one user's personalized response from being handed to someone else. Even with `allowAuth = true`, such an entry is only ever served back to other **authenticated** requests, never to an anonymous one (which would skip the auth check entirely).
118
- - **The edge TTL is clamped to 24 hours.**
119
-
120
- The cache is also keyed precisely: an entry is identified by the **host, method, path, and the exact request body**. So a `POST` with body `A` never returns the copy saved for body `B`, and a `GET` never returns a `POST`'s copy.
121
-
122
- Because auth checks and body decoding run **before** the cache directive is applied, an unauthorized request is rejected with `401` before anything is stored, and a cached copy only ever comes from a handler that actually ran successfully.
123
-
124
- ## ETag, Last-Modified, and 304 (static files)
125
-
126
- The `@cache` decorator above is for **dynamic** responses (ones your code computes). Your project's **static files** (the built HTML, JS, CSS, and images) get a second, automatic layer of caching that you do not configure. It is worth understanding because it is what makes repeat page loads cheap.
127
-
128
- Every static file the edge serves carries three headers:
129
-
130
- - **`ETag`**: a short fingerprint of the file's contents (a "version tag"). toiljs uses a *weak* ETag (it starts with `W/`), which survives compression by a CDN in front of the edge.
131
- - **`Last-Modified`**: when the file last changed.
132
- - **`Cache-Control`**: content-hashed assets (files whose name contains their content hash, like `app-CfvHW67Y.js`) get `immutable` with a one-year max-age, because the URL itself changes whenever the content does. Everything else (HTML, non-hashed assets) gets `must-revalidate`, so the browser always checks freshness.
133
-
134
- **Revalidation** is the trick. Instead of re-downloading a file it already has, a browser asks "has this changed?" by echoing the tag back in an `If-None-Match` header (or the date in `If-Modified-Since`). If the file is unchanged, the edge replies **`304 Not Modified`** with an empty body, and the browser reuses its copy. A `304` is tiny and fast.
135
-
136
- ```mermaid
137
- sequenceDiagram
138
- participant B as Browser
139
- participant E as Dacely edge
140
- B->>E: GET /app.js<br/>If-None-Match: W/"1a2b-c3d4"
141
- alt file unchanged
142
- E-->>B: 304 Not Modified<br/>(no body)
143
- else file changed
144
- E-->>B: 200 OK<br/>(new bytes + new ETag)
145
- end
146
- ```
147
-
148
- The precedence follows the HTTP spec (RFC 7232): if the request has `If-None-Match`, that decides it; only when it is absent does `If-Modified-Since` apply. You get all of this for free; there is nothing to turn on.
149
-
150
- ## Where cached bodies live (memory bounds)
151
-
152
- You do not need this to use caching, but it explains the limits you may hit.
153
-
154
- The edge response cache is bounded and hard-capped so it can never exhaust a node's memory:
155
-
156
- - **RAM tier**: small, short-lived responses live in memory, within a bounded budget (on the order of ~128 MB) plus a cap on the number of entries. A single response over ~256 KB does not go in RAM.
157
- - **Disk tier (optional)**: when the operator enables an on-disk spill directory, a **big** response (over ~256 KB) or a **long-lived** one (TTL of 10 minutes or more) is written to disk and served back with near-zero RAM, the same way static files are. If disk spill is not enabled, a big response is simply not cached (you will see `Dacely-Cache: DYNAMIC`).
158
-
159
- Expired entries are dropped on read (a stale entry is treated as a miss) and reclaimed when the cache needs room. Nothing survives a process restart.
160
-
161
- ## Gotchas
162
-
163
- - **Units differ.** Edge TTL is in minutes; browser TTL is in seconds. `@cache(60)` is one hour at the edge, not one minute.
164
- - **Do not cache personalized data on the shared edge.** If a response depends on who is asking, either do not cache it, or use `privateScope` so it is browser-only.
165
- - **A `Set-Cookie` disables edge caching for that response.** If you set a cookie, that response will not be edge-cached, by design.
166
- - **Very large or slow-to-change bodies may not be cached** unless disk spill is enabled on the node. Check the `Dacely-Cache` header if a response you expected to cache shows `DYNAMIC`.
167
- - **Nothing persists across restarts.** The cache is a speed optimization, never a source of truth.
168
-
169
- ## Related
170
-
171
- - [Rate limiting](./ratelimit.md): protect a route before it runs.
172
- - [Analytics](./analytics.md): see your cache hit ratio per site.
173
- - [Cookies](./cookies.md): why a `Set-Cookie` opts a response out of edge caching.
174
- - [Auth, sessions, and `@user`](../auth/usage.md): why logged-in responses are not cached by default.
175
- - [Every decorator](../concepts/decorators.md).