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,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).