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,30 +0,0 @@
1
- /**
2
- * Dev-only source for `Environment.get` / `getSecure`, mirroring the edge's
3
- * per-tenant env store. Reads two optional dotenv files at the project root via
4
- * the shared loader (`./dotenv.ts`):
5
- *
6
- * - `.env` -> plain vars (`Environment.get`); `process.env` overlays
7
- * - `.env.secrets` -> secrets (`Environment.getSecure`); keep gitignored
8
- *
9
- * DISJOINT like the edge: `get` sees only vars, `getSecure` only secrets, so a
10
- * secret never comes back through `get`. Framework-reserved `TOIL_*` keys are
11
- * host-only (the email backend config reads them) and never a tenant var/secret.
12
- *
13
- * Cached after first read; restart `toiljs dev` to pick up edits. The edge
14
- * resolves this PER TENANT from `$TOIL_ENV_DIR/<host>.env` + `<host>.env.secrets`
15
- * (and the edge DB later) through a lazy, bounded cache; dev has a single
16
- * project, so two files are enough and no eviction is needed.
17
- */
18
- import { loadEnvFiles } from './dotenv.js';
19
-
20
- /** A plain var by exact key, or `null`. Reads ONLY the `.env` (vars) bucket. */
21
- export function devEnvGet(key: string): string | null {
22
- const v = loadEnvFiles(process.cwd()).vars.get(key);
23
- return v === undefined ? null : v;
24
- }
25
-
26
- /** A secret by exact key, or `null`. Reads ONLY the `.env.secrets` bucket. */
27
- export function devEnvGetSecure(key: string): string | null {
28
- const v = loadEnvFiles(process.cwd()).secrets.get(key);
29
- return v === undefined ? null : v;
30
- }
@@ -1,123 +0,0 @@
1
- /**
2
- * Dev-server rate limiter: a single-process mirror of the edge's
3
- * `toil-backend/src/ratelimit.rs` strategies, so a tenant using the
4
- * `@ratelimit(...)` decorator behaves the same under `toiljs dev` as on the
5
- * edge. The edge runs an EXACT limiter shared across 14 workers; dev is one
6
- * process, so a plain module-level registry is already "global".
7
- *
8
- * State lives here (module scope), NOT in the per-request DispatchState, because
9
- * a limiter must persist across requests (the dev server builds a fresh wasm
10
- * instance per request, exactly like the edge pools).
11
- */
12
-
13
- /** Mirror of the host's strategy tags (`Strategy` in `ratelimit.rs`). */
14
- export const STRATEGY_FIXED = 0;
15
- export const STRATEGY_SLIDING = 1;
16
- export const STRATEGY_TOKEN_BUCKET = 2;
17
-
18
- interface KeyState {
19
- /** Token bucket: tokens * 1000 (sub-token refill without floats). */
20
- tokensMilli: number;
21
- /** Window strategies: aligned index of the current bucket. */
22
- window: number;
23
- cur: number;
24
- prev: number;
25
- lastMs: number;
26
- }
27
-
28
- interface RouteLimiter {
29
- strategy: number;
30
- /** `limit`/`window_secs` for windows; `burst`/`refill_per_sec` for the bucket. */
31
- a: number;
32
- b: number;
33
- keys: Map<string, KeyState>;
34
- }
35
-
36
- /** `(routeId) -> limiter`, created lazily with the route's first-seen params. */
37
- const registry = new Map<number, RouteLimiter>();
38
-
39
- /** Drop all limiter state (tests). Each dev wasm dispatch shares this module-level
40
- * registry, so a test that fires many `@ratelimit`-decorated routes in one file
41
- * would otherwise carry counts across cases; reset between cases for isolation. */
42
- export function __resetRatelimitForTests(): void {
43
- registry.clear();
44
- }
45
-
46
- export interface DevDecision {
47
- allowed: boolean;
48
- /** Whole seconds to wait before retrying (>= 1 when denied, 0 when allowed). */
49
- retryAfterSecs: number;
50
- }
51
-
52
- /**
53
- * Account one event for `identity` against route `routeId` and return the
54
- * verdict. Mirrors `SharedLimiter::check` semantics. `now` is wall-clock ms
55
- * (`Date.now()`), which is fine for a single dev process.
56
- */
57
- export function ratelimitCheck(
58
- routeId: number,
59
- strategy: number,
60
- limit: number,
61
- window: number,
62
- identity: string,
63
- now: number,
64
- ): DevDecision {
65
- let rl = registry.get(routeId);
66
- if (rl === undefined) {
67
- rl = { strategy, a: Math.max(1, limit), b: Math.max(1, window), keys: new Map() };
68
- registry.set(routeId, rl);
69
- }
70
- if (rl.strategy === STRATEGY_TOKEN_BUCKET) return checkBucket(rl, identity, now);
71
- return checkWindow(rl, identity, now, rl.strategy === STRATEGY_SLIDING);
72
- }
73
-
74
- function checkBucket(rl: RouteLimiter, key: string, now: number): DevDecision {
75
- const capMilli = rl.a * 1000;
76
- const refillPerSec = rl.b;
77
- let st = rl.keys.get(key);
78
- if (st === undefined) {
79
- st = { tokensMilli: capMilli, window: 0, cur: 0, prev: 0, lastMs: now };
80
- rl.keys.set(key, st);
81
- }
82
- const elapsed = Math.max(0, now - st.lastMs);
83
- st.tokensMilli = Math.min(capMilli, st.tokensMilli + elapsed * refillPerSec);
84
- st.lastMs = now;
85
- if (st.tokensMilli >= 1000) {
86
- st.tokensMilli -= 1000;
87
- return { allowed: true, retryAfterSecs: 0 };
88
- }
89
- const needed = 1000 - st.tokensMilli;
90
- const waitMs = Math.ceil(needed / refillPerSec);
91
- return { allowed: false, retryAfterSecs: Math.max(1, Math.ceil(waitMs / 1000)) };
92
- }
93
-
94
- function checkWindow(rl: RouteLimiter, key: string, now: number, sliding: boolean): DevDecision {
95
- const limit = rl.a;
96
- const windowMs = rl.b * 1000;
97
- const curWindow = Math.floor(now / windowMs);
98
- let st = rl.keys.get(key);
99
- if (st === undefined) {
100
- st = { tokensMilli: 0, window: curWindow, cur: 0, prev: 0, lastMs: now };
101
- rl.keys.set(key, st);
102
- }
103
- if (curWindow === st.window + 1) {
104
- st.prev = st.cur;
105
- st.cur = 0;
106
- st.window = curWindow;
107
- } else if (curWindow > st.window) {
108
- st.prev = 0;
109
- st.cur = 0;
110
- st.window = curWindow;
111
- }
112
- st.lastMs = now;
113
- const posInWindow = now % windowMs;
114
- const effective = sliding
115
- ? Math.floor((st.prev * (windowMs - posInWindow)) / windowMs) + st.cur
116
- : st.cur;
117
- if (effective < limit) {
118
- st.cur += 1;
119
- return { allowed: true, retryAfterSecs: 0 };
120
- }
121
- const waitMs = windowMs - posInWindow;
122
- return { allowed: false, retryAfterSecs: Math.max(1, Math.ceil(waitMs / 1000)) };
123
- }
@@ -1,120 +0,0 @@
1
- /**
2
- * Parse a compiled COLD server wasm's `toildaemon.catalog` custom section into the
3
- * dev daemon scheduler's task list. Emitted by the toilscript cold pass
4
- * (`buildToilDaemonCatalog`) into `release-cold.wasm` when a `@daemon` class
5
- * exists. Cron schedules are PRECOMPUTED BITMASKS (RECONCILIATION F6): the reader
6
- * does bit tests, NEVER a runtime cron-string parse.
7
- *
8
- * Byte layout (RECONCILIATION Part 5, all little-endian; mirrors the toilscript
9
- * `CatWriter` emitter byte-for-byte):
10
- *
11
- * u16 format_version = 1
12
- * u8 has_daemon
13
- * u16 n_scheduled
14
- * per task:
15
- * str name (u32 len + UTF-8)
16
- * u16 task_index (the scheduled_tick(task_id) argument)
17
- * u8 schedule_kind (0 = interval, 1 = cron)
18
- * u64 interval_ms (used when schedule_kind = 0, else 0)
19
- * u64 cron_minute_mask (bits 0..59)
20
- * u32 cron_hour_mask (bits 0..23)
21
- * u32 cron_dom_mask (bits 1..31)
22
- * u16 cron_month_mask (bits 1..12)
23
- * u8 cron_dow_mask (bits 0..6)
24
- * u8 overlap_policy (0 = skip-if-running)
25
- * u8 catchup_policy (0 = no-backfill)
26
- * u64 gas_hint
27
- *
28
- * Fails closed via `DataReader.ok`: a short read mid-record stops the loop and
29
- * yields only the cleanly-decoded prefix, never over-reading. An absent or wholly
30
- * unparseable section returns `null`, so the daemon emulator simply does not start
31
- * (fail-closed = never run an unknown daemon).
32
- */
33
-
34
- import { DataReader } from 'toiljs/io';
35
-
36
- import { customSection } from '../wasm/sections.js';
37
-
38
- /** The five precomputed cron field bitmasks (RECONCILIATION Part 5 / F6). */
39
- export interface CronMasks {
40
- /** u64, bits 0..59 (one per minute). */
41
- readonly minute: bigint;
42
- /** u32, bits 0..23 (one per hour). */
43
- readonly hour: number;
44
- /** u32, bits 1..31 (one per day-of-month). */
45
- readonly dom: number;
46
- /** u16, bits 1..12 (one per month). */
47
- readonly month: number;
48
- /** u8, bits 0..6 (one per day-of-week, 0 = Sunday). */
49
- readonly dow: number;
50
- }
51
-
52
- /** One `@scheduled` task the dev scheduler drives. */
53
- export interface ScheduledTask {
54
- readonly name: string;
55
- /** The `scheduled_tick(task_id)` argument; equals declaration order. */
56
- readonly taskIndex: number;
57
- readonly schedule:
58
- | { readonly kind: 'interval'; readonly ms: number }
59
- | { readonly kind: 'cron'; readonly masks: CronMasks };
60
- /** 0 = skip-if-running (the at-most-once default). */
61
- readonly overlapPolicy: number;
62
- /** 0 = no-backfill (the at-most-once default). */
63
- readonly catchupPolicy: number;
64
- readonly gasHint: bigint;
65
- }
66
-
67
- export interface DaemonCatalog {
68
- readonly hasDaemon: boolean;
69
- readonly tasks: readonly ScheduledTask[];
70
- }
71
-
72
- export function parseDaemonCatalog(wasm: Buffer): DaemonCatalog | null {
73
- let sec: Buffer | null;
74
- try {
75
- sec = customSection(wasm, 'toildaemon.catalog');
76
- } catch {
77
- return null; // garbage section table (mid-rebuild) -> no daemon
78
- }
79
- if (sec === null) return null;
80
-
81
- const r = new DataReader(sec);
82
- r.readU16(); // format_version
83
- const hasDaemon = r.readU8() === 1;
84
- const n = r.readU16(); // n_scheduled
85
- // The 5-byte header must read cleanly; a short read here is a garbage section
86
- // (fail closed -> no daemon). A `hasDaemon` byte salvaged from a too-short
87
- // version field is not trustworthy.
88
- if (!r.ok) return null;
89
- const tasks: ScheduledTask[] = [];
90
- for (let i = 0; i < n && r.ok; i++) {
91
- const name = r.readString();
92
- const taskIndex = r.readU16();
93
- const kind = r.readU8(); // schedule_kind: 0 = interval, 1 = cron
94
- const intervalMs = r.readU64(); // bigint; 0 when kind = 1
95
- const minute = r.readU64(); // cron_minute_mask (bits 0..59)
96
- const hour = r.readU32(); // cron_hour_mask
97
- const dom = r.readU32(); // cron_dom_mask
98
- const month = r.readU16(); // cron_month_mask
99
- const dow = r.readU8(); // cron_dow_mask
100
- const overlapPolicy = r.readU8();
101
- const catchupPolicy = r.readU8();
102
- const gasHint = r.readU64();
103
- if (r.ok)
104
- tasks.push({
105
- name,
106
- taskIndex,
107
- schedule:
108
- kind === 1
109
- ? { kind: 'cron', masks: { minute, hour, dom, month, dow } }
110
- : { kind: 'interval', ms: Number(intervalMs) },
111
- overlapPolicy,
112
- catchupPolicy,
113
- gasHint,
114
- });
115
- }
116
- // A section that decoded nothing AND claims no daemon is indistinguishable
117
- // from garbage -> fail closed (no daemon).
118
- if (!r.ok && tasks.length === 0 && !hasDaemon) return null;
119
- return { hasDaemon, tasks };
120
- }
@@ -1,87 +0,0 @@
1
- /**
2
- * Cron BITMASK evaluation for the dev daemon scheduler. The catalog carries five
3
- * precomputed bitmasks (RECONCILIATION F6 / Part 5); this module does bit tests
4
- * against a wall-clock minute, NEVER a cron-string parse. The bit semantics match
5
- * the toilscript emitter (`expandCronField` in `dbcatalog.ts`):
6
- *
7
- * minute bits 0..59 hour bits 0..23 dom bits 1..31
8
- * month bits 1..12 dow bits 0..6 (0 = Sunday)
9
- *
10
- * Standard cron semantics for day-of-month vs day-of-week: when BOTH fields are
11
- * restricted (not all bits set) the match is the UNION (either matches); when one
12
- * is unrestricted it does not constrain. The dev evaluator walks forward
13
- * minute-by-minute from "now" to the next matching minute, exactly as the edge
14
- * evaluates the same masks.
15
- */
16
-
17
- import type { CronMasks } from './catalog.js';
18
-
19
- /** All 60 minute bits set (bits 0..59). A field the emitter left fully open. */
20
- const ALL_MINUTES = (1n << 60n) - 1n;
21
- /** All 24 hour bits set (bits 0..23). */
22
- const ALL_HOURS = (1 << 24) - 1;
23
- /** dom bits 1..31 all set (bit 0 unused). `1 << 32` overflows in JS, so spell it. */
24
- const ALL_DOM = 0xfffffffe;
25
- /** month bits 1..12 all set (bit 0 unused). */
26
- const ALL_MONTH = ((1 << 13) - 1) & ~1;
27
- /** dow bits 0..6 all set. */
28
- const ALL_DOW = (1 << 7) - 1;
29
-
30
- function minuteBit(masks: CronMasks, minute: number): boolean {
31
- return (masks.minute & (1n << BigInt(minute))) !== 0n;
32
- }
33
-
34
- /** True when every cron field's bit is set for `date`'s local-time components. */
35
- export function cronMatches(masks: CronMasks, date: Date): boolean {
36
- if (!minuteBit(masks, date.getMinutes())) return false;
37
- if ((masks.hour & (1 << date.getHours())) === 0) return false;
38
- if ((masks.month & (1 << date.getMonth() + 1)) === 0) return false;
39
-
40
- // dom/dow union rule (POSIX cron): if both are restricted, either may match.
41
- const domRestricted = (masks.dom & ALL_DOM) !== ALL_DOM;
42
- const dowRestricted = (masks.dow & ALL_DOW) !== ALL_DOW;
43
- const domHit = (masks.dom & (1 << date.getDate())) !== 0;
44
- const dowHit = (masks.dow & (1 << date.getDay())) !== 0;
45
- if (domRestricted && dowRestricted) {
46
- if (!domHit && !dowHit) return false;
47
- } else if (domRestricted) {
48
- if (!domHit) return false;
49
- } else if (dowRestricted) {
50
- if (!dowHit) return false;
51
- }
52
- return true;
53
- }
54
-
55
- /** True when a mask can never fire (all-zero) -> the schedule is rejected. */
56
- export function cronNeverFires(masks: CronMasks): boolean {
57
- return (
58
- (masks.minute & ALL_MINUTES) === 0n ||
59
- (masks.hour & ALL_HOURS) === 0 ||
60
- (masks.month & ALL_MONTH) === 0 ||
61
- // dom OR dow must be able to fire (union); both empty => never.
62
- ((masks.dom & ALL_DOM) === 0 && (masks.dow & ALL_DOW) === 0)
63
- );
64
- }
65
-
66
- /**
67
- * The epoch-ms of the next minute (strictly after `fromMs`) whose components all
68
- * pass the masks, walking forward minute-by-minute. Returns `null` when no match
69
- * is found within `horizonMinutes` (a safety bound; an all-zero mask is caught by
70
- * {@link cronNeverFires} before this is called). Fires land on the :00 second of
71
- * the matching minute.
72
- */
73
- export function nextCronFireMs(
74
- masks: CronMasks,
75
- fromMs: number,
76
- horizonMinutes = 366 * 24 * 60,
77
- ): number | null {
78
- // Start at the next whole minute boundary strictly after `fromMs`.
79
- const d = new Date(fromMs);
80
- d.setSeconds(0, 0);
81
- d.setMinutes(d.getMinutes() + 1);
82
- for (let i = 0; i < horizonMinutes; i++) {
83
- if (cronMatches(masks, d)) return d.getTime();
84
- d.setMinutes(d.getMinutes() + 1);
85
- }
86
- return null;
87
- }
@@ -1,134 +0,0 @@
1
- /**
2
- * Host-import surface for the dev daemon (cold) box, mirroring the production
3
- * edge's `daemon.*` imports. DAEMON path only (streams are Phase 4).
4
- *
5
- * Per RECONCILIATION:
6
- * - Part 4 `daemon.*`: is_leader / current_epoch / yield / sleep_ms / task_count
7
- * / next_fire_ms / http_call. In a single dev process the leader
8
- * stub is always true and the lease never expires (section 5.2). Fenced DB
9
- * writes are TRANSPARENT (no `daemon.db_write_fenced` import).
10
- * - Part 3 error bridge: a u16 subsystem code `c` is returned as `-(0x10000 + c)`;
11
- * the buffer sentinels `-1` (TOO_SMALL) / `-2` (ABSENT) are unchanged.
12
- *
13
- * These live in their OWN wasm module namespace, `daemon`, with BARE names, exactly
14
- * as the edge registers them (toil-backend `src/wasm/cold/imports.rs`, `NS_DAEMON`).
15
- * They are NOT dotted names under `env` like `data.*` / `crypto.*` are: a cold box
16
- * that declares them that way resolves here and then trap-stubs at the edge, which
17
- * is the one shape of drift a dev emulator must never hide.
18
- *
19
- * The cold box also imports the request-surface `env.*` MINUS the response/stream
20
- * functions it must not have (no `set_status`/`set_header`/`respond_file`/
21
- * `client_ip`/`ratelimit_check`); it keeps `@data`/crypto/env/`Date.now`/email so a
22
- * daemon can read+write the DB and send mail. The two allow-lists live in
23
- * `runtime/module.ts`.
24
- */
25
-
26
- import { buildDatabaseImports, type DbDevState, freshDbState } from '../db/index.js';
27
- import { buildCryptoImports, type CryptoState, freshCryptoState } from '../runtime/crypto.js';
28
- import { buildEnvImports, type MemoryRef } from '../runtime/host.js';
29
-
30
- /**
31
- * Resolved daemon (L4) config the dev scheduler reads. Structurally identical to
32
- * the compiler's `ResolvedDaemonConfig`; declared here so the devserver package has
33
- * no source dependency on the compiler package (its tsconfig is isolated to
34
- * `src/devserver`). The compiler's resolved config is passed in verbatim.
35
- */
36
- export interface ResolvedDaemonConfig {
37
- readonly region: string | null;
38
- readonly standbyRegion: string | null;
39
- readonly defaultIntervalMs: number;
40
- readonly tickBudgetMs: number;
41
- readonly gasTick: number;
42
- readonly maxTasks: number;
43
- }
44
-
45
- /** RECONCILIATION Part 3 u16 error registry (the subset the daemon imports use). */
46
- export const enum AbiError {
47
- DaemonNotLeader = 0x0401,
48
- DaemonLeaseLost = 0x0402,
49
- DaemonCallFailed = 0x0405,
50
- }
51
-
52
- /** The edge's `EPOCH_NONE` / "no such task" sentinel: a plain -1, not a bridged error. */
53
- const DAEMON_NONE = -1n;
54
-
55
- /** Encode a u16 subsystem error per the Part 3 negative-return bridge:
56
- * `code = (-v) - 0x10000`, so `v = -(0x10000 + code)`. */
57
- export function encodeAbiError(code: number): number {
58
- return -(0x10000 + code);
59
- }
60
-
61
- /** The minimal scheduler/leader view the `daemon.*` imports read. Implemented by
62
- * the resident `DaemonHost`. */
63
- export interface DaemonRuntime {
64
- isLeader(): boolean;
65
- /** Monotonic fencing token; bumps on each (re)start. */
66
- epoch(): bigint;
67
- /** Number of registered `@scheduled` tasks. */
68
- taskCount(): number;
69
- /** Next computed fire time (epoch ms) for `taskId`, or `null` if unknown. */
70
- nextFireMs(taskId: number): number | null;
71
- }
72
-
73
- /** A host-import map: each value is a function over i32 args (i64 params are typed
74
- * `number | bigint` individually, matching the existing db/crypto import maps). */
75
- type HostFnMap = Record<string, (...args: number[]) => number | bigint>;
76
-
77
- /** Build the `daemon` namespace, closing over the resident `DaemonRuntime`. The names
78
- * are BARE (`is_leader`, not `daemon.is_leader`): they are keyed under the `daemon`
79
- * wasm module, matching the edge. These imports do not read guest memory (they answer
80
- * from the resident scheduler state), so they take no `MemoryRef`. */
81
- export function buildDaemonNamespace(rt: DaemonRuntime): HostFnMap {
82
- return {
83
- is_leader: (): number => (rt.isLeader() ? 1 : 0),
84
- // The edge answers -1 (EPOCH_NONE) when this node does not hold the lease.
85
- current_epoch: (): bigint => (rt.isLeader() ? rt.epoch() : DAEMON_NONE),
86
- // The dev lease never expires, so yield/sleep never report LEASE_LOST.
87
- yield: (): number => 0,
88
- sleep_ms: (_ms: number | bigint): number => 0,
89
- task_count: (): number => rt.taskCount(),
90
- // An unknown / never-firing task is -1 at the edge, NOT a bridged error.
91
- next_fire_ms: (taskId: number): bigint => {
92
- const at = rt.nextFireMs(taskId);
93
- return at === null ? DAEMON_NONE : BigInt(at);
94
- },
95
- // Outbound HTTP call stub: dev returns a "call failed" sentinel rather than
96
- // performing real network I/O from a synchronous wasm import (section 5.4).
97
- http_call: (
98
- _reqPtr: number,
99
- _reqLen: number,
100
- _outPtr: number,
101
- _outCap: number,
102
- ): bigint => BigInt(encodeAbiError(AbiError.DaemonCallFailed)),
103
- };
104
- }
105
-
106
- /** Per-cold-box host state (DB + crypto scratch), analogous to `DispatchState`. */
107
- export interface DaemonState {
108
- crypto: CryptoState;
109
- db: DbDevState;
110
- }
111
-
112
- export function freshDaemonState(): DaemonState {
113
- return { crypto: freshCryptoState(), db: freshDbState() };
114
- }
115
-
116
- /**
117
- * The import object for the cold daemon box: the request-surface `env` MINUS the
118
- * response/stream functions (built by `buildEnvImports`), plus the separate
119
- * `daemon` module. The cold box has no `handle` entry and no response surface.
120
- */
121
- export function buildDaemonImports(
122
- ref: MemoryRef,
123
- state: DaemonState,
124
- rt: DaemonRuntime,
125
- ): WebAssembly.Imports {
126
- return {
127
- env: {
128
- ...buildEnvImports(ref, state),
129
- ...buildCryptoImports(ref, state.crypto),
130
- ...buildDatabaseImports(ref, state.db),
131
- },
132
- daemon: buildDaemonNamespace(rt),
133
- };
134
- }