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,208 +0,0 @@
1
- /**
2
- * Dev-server edge SSR: splice the guest `render` values into a route's
3
- * template-with-holes and serve real server-rendered HTML, mirroring the
4
- * production edge (`toil-backend/src/host/template/assemble.rs`).
5
- *
6
- * The templates are extracted once at dev startup against the LIVE (Vite-
7
- * transformed) dev shell (see `compiler/template-build.ts extractDevSsrTemplates`)
8
- * so the served markup boots the dev client and hydrates in place. At request
9
- * time the dev server runs the real `render` export (`WasmServerModule.
10
- * dispatchRender`), this module decodes the values envelope and splices each
11
- * value at its manifest-fixed offset. The hash-coherence guard the prod edge
12
- * enforces is skipped in dev: the guest and the template are built together
13
- * here, so there is no deploy skew to catch — only fail-safe envelopes (status
14
- * >= 500 or no slots) fall back to client rendering.
15
- */
16
-
17
- /** One SSR route's spliceable template + its slot insertion points. */
18
- export interface DevSsrTemplate {
19
- pattern: string;
20
- name: string;
21
- tmpl: Uint8Array;
22
- entries: { id: number; offset: number }[];
23
- /** Optional deployed template hash. Present for built/self-host SSR, omitted in dev. */
24
- hash?: Uint8Array;
25
- }
26
-
27
- /** A matchable SSR route. */
28
- export interface SsrRoute {
29
- /** Matches a request pathname (no query) to this route's template. */
30
- test: (pathname: string) => boolean;
31
- tmpl: Uint8Array;
32
- entries: { id: number; offset: number }[];
33
- /** Optional deployed template hash. When present, guest values must match it. */
34
- hash?: Uint8Array;
35
- }
36
-
37
- /** The pathname of a request URL (strip the query string). */
38
- export function pathnameOf(url: string): string {
39
- const q = url.indexOf('?');
40
- return q < 0 ? url : url.slice(0, q);
41
- }
42
-
43
- /** Compile a route pattern (`/hello`, `/u/:name`, `/blog/[id]`, `/files/[...path]`,
44
- * `/*`) to a pathname matcher. Dynamic segments match one path segment; catch-all
45
- * (`[...x]` / `*`) matches the rest. Trailing slashes are ignored. */
46
- function patternToTest(pattern: string): (pathname: string) => boolean {
47
- const norm = (p: string): string => (p.length > 1 && p.endsWith('/') ? p.slice(0, -1) : p);
48
- let re = '';
49
- // Tokenise into literal runs and dynamic/catch-all holes.
50
- let i = 0;
51
- while (i < pattern.length) {
52
- const ch = pattern[i];
53
- if (ch === ':') {
54
- // `:name` — one segment.
55
- i++;
56
- while (i < pattern.length && /[A-Za-z0-9_]/.test(pattern[i])) i++;
57
- re += '[^/]+';
58
- } else if (ch === '*') {
59
- re += '.*';
60
- i++;
61
- } else if (ch === '[') {
62
- const end = pattern.indexOf(']', i);
63
- const inner = end < 0 ? '' : pattern.slice(i + 1, end);
64
- re += inner.startsWith('...') ? '.*' : '[^/]+';
65
- i = end < 0 ? pattern.length : end + 1;
66
- } else {
67
- // Literal char, regex-escaped.
68
- re += ch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
69
- i++;
70
- }
71
- }
72
- const compiled = new RegExp(`^${re}$`);
73
- return (pathname: string): boolean => compiled.test(norm(pathname));
74
- }
75
-
76
- /** Build matchable SSR routes from the extracted dev templates. */
77
- export function buildSsrRoutes(templates: readonly DevSsrTemplate[]): SsrRoute[] {
78
- return templates.map((t) => ({
79
- test: patternToTest(t.pattern),
80
- tmpl: t.tmpl,
81
- entries: t.entries,
82
- hash: t.hash,
83
- }));
84
- }
85
-
86
- /** A decoded guest values envelope. */
87
- interface DecodedValues {
88
- status: number;
89
- hash: Uint8Array;
90
- headers: [string, string][];
91
- /** Slot value bytes keyed by numeric slot id. */
92
- values: Map<number, Uint8Array>;
93
- }
94
-
95
- /** Decode the guest values envelope (mirrors the prod host `decode_values`). All
96
- * fields little-endian, no padding. Returns null on a malformed/short buffer. */
97
- function decodeValues(buf: Uint8Array): DecodedValues | null {
98
- const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
99
- let o = 0;
100
- const need = (n: number): boolean => o + n <= buf.byteLength;
101
- try {
102
- if (!need(2 + 32 + 2)) return null;
103
- const status = dv.getUint16(o, true);
104
- o += 2;
105
- const hash = buf.subarray(o, o + 32);
106
- o += 32;
107
- const nHeaders = dv.getUint16(o, true);
108
- o += 2;
109
- const headers: [string, string][] = [];
110
- const dec = new TextDecoder();
111
- for (let i = 0; i < nHeaders; i++) {
112
- if (!need(4)) return null;
113
- const nameLen = dv.getUint16(o, true);
114
- const valLen = dv.getUint16(o + 2, true);
115
- o += 4;
116
- if (!need(nameLen + valLen)) return null;
117
- const name = dec.decode(buf.subarray(o, o + nameLen));
118
- o += nameLen;
119
- const val = dec.decode(buf.subarray(o, o + valLen));
120
- o += valLen;
121
- headers.push([name, val]);
122
- }
123
- if (!need(2)) return null;
124
- const nSlots = dv.getUint16(o, true);
125
- o += 2;
126
- const values = new Map<number, Uint8Array>();
127
- for (let i = 0; i < nSlots; i++) {
128
- if (!need(2 + 1 + 4)) return null;
129
- const id = dv.getUint16(o, true);
130
- o += 2;
131
- o += 1; // kind (the splice is kind-agnostic; the guest pre-escaped/stamped)
132
- const len = dv.getUint32(o, true);
133
- o += 4;
134
- if (!need(len)) return null;
135
- values.set(id, buf.subarray(o, o + len));
136
- o += len;
137
- }
138
- return { status, hash, headers, values };
139
- } catch {
140
- return null;
141
- }
142
- }
143
-
144
- function sameBytes(a: Uint8Array, b: Uint8Array): boolean {
145
- if (a.byteLength !== b.byteLength) return false;
146
- for (let i = 0; i < a.byteLength; i++) {
147
- if (a[i] !== b[i]) return false;
148
- }
149
- return true;
150
- }
151
-
152
- /** Splice ascending-offset inserts into the template (mirrors the host `assemble`). */
153
- function splice(tmpl: Uint8Array, inserts: { offset: number; value: Uint8Array }[]): Uint8Array {
154
- const parts: Uint8Array[] = [];
155
- let prev = 0;
156
- for (const ins of inserts) {
157
- if (ins.offset > prev) parts.push(tmpl.subarray(prev, ins.offset));
158
- if (ins.value.length > 0) parts.push(ins.value);
159
- prev = ins.offset;
160
- }
161
- if (tmpl.length > prev) parts.push(tmpl.subarray(prev));
162
- return Buffer.concat(parts.map((p) => Buffer.from(p.buffer, p.byteOffset, p.byteLength)));
163
- }
164
-
165
- /** A spliced SSR response. */
166
- export interface SsrResult {
167
- status: number;
168
- headers: [string, string][];
169
- html: Uint8Array;
170
- }
171
-
172
- /** The internal header a guest's `SlotValues.setTitle` rides in: the host splices its value into the
173
- * document `<title>` and strips it, so a per-request title never reaches the client as a real header. */
174
- const SSR_TITLE_HEADER = 'x-toil-ssr-title';
175
-
176
- /** Replace the document `<title>` content (the guest set a per-request title; the value is already
177
- * React-escaped). Mirrors the host `assemble` so dev and prod agree. */
178
- function replaceTitle(html: Uint8Array, title: string): Uint8Array {
179
- const out = new TextDecoder()
180
- .decode(html)
181
- .replace(/<title>[\s\S]*?<\/title>/i, `<title>${title}</title>`);
182
- return new TextEncoder().encode(out);
183
- }
184
-
185
- /**
186
- * Decode the guest envelope and splice it into `route`'s template. Returns null
187
- * to fall back to client rendering: a fail-safe envelope (status >= 500, e.g. no
188
- * renderer matched), no slots, or a decode error.
189
- */
190
- export function assembleSsr(route: SsrRoute, envelope: Uint8Array): SsrResult | null {
191
- const decoded = decodeValues(envelope);
192
- if (decoded === null) return null;
193
- if (decoded.status >= 500 || decoded.values.size === 0) return null;
194
- if (route.hash !== undefined && !sameBytes(decoded.hash, route.hash)) return null;
195
- const inserts = route.entries
196
- .map((e) => ({ offset: e.offset, value: decoded.values.get(e.id) ?? new Uint8Array(0) }))
197
- .sort((a, b) => a.offset - b.offset);
198
- let html = splice(route.tmpl, inserts);
199
- // A guest-set per-request <title> (SlotValues.setTitle) rides in an internal header: splice it into
200
- // the <title> and strip the header so it never reaches the client.
201
- let headers = decoded.headers;
202
- const ti = headers.findIndex(([k]) => k.toLowerCase() === SSR_TITLE_HEADER);
203
- if (ti >= 0) {
204
- html = replaceTitle(html, headers[ti][1]);
205
- headers = headers.filter((_, i) => i !== ti);
206
- }
207
- return { status: decoded.status, headers, html };
208
- }
@@ -1,106 +0,0 @@
1
- /**
2
- * Parse a compiled HOT server wasm's `toilstream.catalog` custom section into the dev stream router's
3
- * route table (doc 08 section 3.1; emitted by the toilscript hot pass per RECONCILIATION Part 5 when
4
- * any `@stream` class exists). The dev router keys on `route` (the `@stream` class's fixed mount path)
5
- * to intercept matching WebSocket upgrades (section 4.1/4.2) and ignore the rest (Vite HMR).
6
- *
7
- * Byte layout (Part 5, all little-endian; mirrors the toilscript emitter + the edge decoder):
8
- *
9
- * u16 format_version (= 1)
10
- * u16 n_streams
11
- * per stream:
12
- * str name (the @stream class name)
13
- * str route (the mount path, e.g. "/echo")
14
- * u8 hook_presence_bitmask (bit0 connect, bit1 message, bit2 close, bit3 disconnect)
15
- * u8 declared_scope (0 = L2 regional, 1 = L3 continental)
16
- * u8 message_mode (0 = raw bytes default, 1 = @data-typed)
17
- * u32 max_frame_bytes
18
- * u32 ingress_ring_bytes
19
- * u32 message_value_data_id (0 when message_mode = 0)
20
- * u32 message_schema_version (0 when message_mode = 0)
21
- * u16 stream_index
22
- *
23
- * Fails closed via `DataReader.ok`: a short read mid-record stops the loop and yields the cleanly
24
- * decoded prefix; an absent/unparseable section yields an empty map (the dev router serves no stream).
25
- */
26
-
27
- import { DataReader } from 'toiljs/io';
28
-
29
- import { customSection } from '../wasm/sections.js';
30
-
31
- /** One `@stream` class's catalog entry (doc 08 section 3.1). */
32
- export interface StreamDef {
33
- readonly name: string;
34
- readonly route: string;
35
- readonly hooks: {
36
- readonly connect: boolean;
37
- readonly message: boolean;
38
- readonly close: boolean;
39
- readonly disconnect: boolean;
40
- };
41
- readonly scope: 'regional' | 'continental';
42
- readonly messageMode: 'raw' | 'data';
43
- readonly maxFrameBytes: number;
44
- readonly ingressRingBytes: number;
45
- readonly messageValueDataId: number;
46
- readonly messageSchemaVersion: number;
47
- readonly streamIndex: number;
48
- }
49
-
50
- /** The dev router's route table, keyed by the `@stream` mount path. */
51
- export type StreamCatalog = Map<string, StreamDef>;
52
-
53
- /** Parse `toilstream.catalog` into a route table. Absent/unparseable -> empty (serve no stream). */
54
- export function parseStreamCatalog(wasm: Buffer): StreamCatalog {
55
- const out: StreamCatalog = new Map();
56
- let sec: Buffer | null;
57
- try {
58
- sec = customSection(wasm, 'toilstream.catalog');
59
- } catch {
60
- return out;
61
- }
62
- if (sec === null) return out;
63
- const r = new DataReader(sec);
64
- r.readU16(); // format_version
65
- const n = r.readU16();
66
- for (let i = 0; i < n && r.ok; i++) {
67
- const name = r.readString();
68
- const route = r.readString();
69
- const bits = r.readU8();
70
- const scope = r.readU8() === 1 ? 'continental' : 'regional';
71
- const messageMode = r.readU8() === 1 ? 'data' : 'raw';
72
- const maxFrameBytes = r.readU32();
73
- const ingressRingBytes = r.readU32();
74
- const messageValueDataId = r.readU32();
75
- const messageSchemaVersion = r.readU32();
76
- const streamIndex = r.readU16();
77
- if (!r.ok) break;
78
- out.set(route, {
79
- name,
80
- route,
81
- hooks: {
82
- connect: (bits & 1) !== 0,
83
- message: (bits & 2) !== 0,
84
- close: (bits & 4) !== 0,
85
- disconnect: (bits & 8) !== 0,
86
- },
87
- scope,
88
- messageMode,
89
- maxFrameBytes,
90
- ingressRingBytes,
91
- messageValueDataId,
92
- messageSchemaVersion,
93
- streamIndex,
94
- });
95
- }
96
- return out;
97
- }
98
-
99
- /** Exact-path route match (doc 08 section 4.2): strip the query string, then look the path up in the
100
- * catalog. Returns the matched `@stream` def, or `null` when the path is not a stream route (the dev
101
- * router then proxies the upgrade to Vite). */
102
- export function matchStreamRoute(catalog: StreamCatalog, path: string): StreamDef | null {
103
- const q = path.indexOf('?');
104
- const exact = q >= 0 ? path.slice(0, q) : path;
105
- return catalog.get(exact) ?? null;
106
- }
@@ -1,42 +0,0 @@
1
- /**
2
- * Host-import surface for the dev STREAM (hot, L2/L3) box, mirroring the production edge's stream box
3
- * (`toil-backend` `src/wasm/stream`). A `@stream` box is a HOT artifact: it imports the same request
4
- * `env.*` runtime surface (so the ToilScript runtime + `@data`/crypto/env/`Date.now` code runs
5
- * unchanged) and drives its lifecycle through the `stream_dispatch` export + the ingress/egress ring
6
- * bridge ({@link ../index.js}), NOT through `handle`.
7
- *
8
- * The `stream.*` namespace (`stream.send` / `@channel`) is DEFERRED: the raw `@message` ring bridge -
9
- * the only stream feature wired so far - needs NO host imports (the guest reads/writes the rings in
10
- * its own linear memory). A box that does not use `stream.*` instantiates against `env.*` alone.
11
- */
12
-
13
- import { buildDatabaseImports, type DbDevState, freshDbState } from '../db/index.js';
14
- import { buildCryptoImports, type CryptoState, freshCryptoState } from '../runtime/crypto.js';
15
- import { buildEnvImports, type MemoryRef } from '../runtime/host.js';
16
-
17
- /** Per-stream-box host scratch (DB + crypto), analogous to the daemon's `DaemonState`. Each resident
18
- * connection box carries its own, so two connections never share `@data` transaction scratch. */
19
- export interface StreamBoxState {
20
- crypto: CryptoState;
21
- db: DbDevState;
22
- }
23
-
24
- export function freshStreamBoxState(): StreamBoxState {
25
- return { crypto: freshCryptoState(), db: freshDbState() };
26
- }
27
-
28
- /**
29
- * The full `env` import object for a dev stream box: the request-surface `env.*` runtime (built by
30
- * {@link buildEnvImports}) plus the crypto and `@data` namespaces. The response/stream `env.*`
31
- * functions a box must not have (`set_status`/`respond_file`/...) are excluded by `buildEnvImports`
32
- * itself, exactly as for the daemon cold box.
33
- */
34
- export function buildStreamImports(ref: MemoryRef, state: StreamBoxState): WebAssembly.Imports {
35
- return {
36
- env: {
37
- ...buildEnvImports(ref, state),
38
- ...buildCryptoImports(ref, state.crypto),
39
- ...buildDatabaseImports(ref, state.db),
40
- },
41
- };
42
- }
@@ -1,301 +0,0 @@
1
- /**
2
- * Dev STREAM (L2/L3) emulation: load a `release-stream.wasm` hot artifact into a resident per-connection
3
- * box and drive its lifecycle (`@connect` / `@message` / `@close` / `@disconnect`) through the
4
- * `stream_dispatch` export + the ingress/egress RING BRIDGE - the dev-side port of the production
5
- * `StreamBox` (`toil-backend` `src/wasm/stream/runtime.rs`). The ring wire format (RingControl 32B +
6
- * RingFrame 12B + drained-reset + the packed-i64 reject bridge) AND the `@connect` info-block ABI are
7
- * replicated BYTE-FOR-BYTE so a `@stream` app behaves identically under `npm run dev` and at the edge.
8
- *
9
- * RESIDENT: one box per connection, NEVER reset between events (linear memory persists across
10
- * connect -> message -> close); a fresh `WebAssembly.Instance` per connection IS that residency.
11
- *
12
- * DEV LIMITATION vs the edge: Node's `WebAssembly` has NO gas-metering middleware, so a runaway guest
13
- * loop is NOT gas-killed in dev (it hangs) - only a genuine wasm TRAP (unreachable / OOB / abort)
14
- * surfaces, which the caller turns into a `STREAM_HOOK_TRAPPED` close. Packet gas, chunk reassembly,
15
- * `@channel`, and the `stream.*` host imports are deferred exactly as on the edge.
16
- */
17
-
18
- import { parseSurface } from '../wasm/surface.js';
19
- import { buildStreamImports, freshStreamBoxState, type StreamBoxState } from './host.js';
20
-
21
- // --- the stream_dispatch ABI (toil-backend src/wasm/stream/section.rs) ---
22
- const EVENT_CONNECT = 1;
23
- const EVENT_MESSAGE = 2;
24
- const EVENT_CLOSE = 3;
25
- const EVENT_DISCONNECT = 4;
26
-
27
- // --- ring wire format (05 sections 5-6; toil-backend src/wasm/stream/runtime.rs) ---
28
- const RING_CTRL_BYTES = 32; // RingControl header
29
- const RING_FRAME_HEADER = 12; // RingFrame header
30
- const RING_MAGIC = 0x3147_4e52; // "RNG1" little-endian
31
- const RING_VERSION = 1;
32
- const RC_WRITE = 12; // write_cursor offset in RingControl
33
- const RC_READ = 16; // read_cursor offset
34
- const FRAME_TYPE_DATA_RELIABLE = 1;
35
- const MAX_STREAM_FRAME_LEN = 65536; // 64 KiB per egress frame (05 6.1)
36
-
37
- // --- @connect info-block ABI (05 section 2.2; toil-backend runtime.rs write_connect_info) ---
38
- // Layout: [u64 stream_id][u8 transport][u8 _][u16 auth_len][u16 path_len][u16 _] then auth + path UTF8.
39
- const SI_TRANSPORT = 8;
40
- const SI_AUTH_LEN = 10;
41
- const SI_PATH_LEN = 12;
42
- const SI_RESERVED2 = 14;
43
- const SI_BODY = 16;
44
- const SI_TRANSPORT_WEBTRANSPORT = 1; // the v1 transport (the dev emulates it)
45
-
46
- /**
47
- * Decode a guest's NEGATIVE `stream_dispatch` return into a `0x02xx` reject/close code (the Part-3
48
- * bridge `-(0x10000 + code)`), clamped to the stream range `0x0200..=0x02FF`; an out-of-range value
49
- * normalizes to `0x0208 STREAM_REJECTED`, mirroring `toil-backend` `decode_reject_code`.
50
- */
51
- export function decodeRejectCode(rc: bigint): number {
52
- const raw = Number((-rc - 0x10000n) & 0xffffn);
53
- return raw >= 0x0200 && raw <= 0x02ff ? raw : 0x0208;
54
- }
55
-
56
- interface StreamExports {
57
- readonly memory: WebAssembly.Memory;
58
- readonly stream_dispatch: (eventKind: number, lo: number, hi: number) => bigint;
59
- readonly stream_ring_offset?: () => number;
60
- readonly stream_ring_capacity?: () => number;
61
- readonly stream_egress_offset?: () => number;
62
- readonly stream_egress_capacity?: () => number;
63
- readonly stream_info_offset?: () => number;
64
- readonly stream_info_capacity?: () => number;
65
- }
66
-
67
- interface Rings {
68
- readonly ingressOff: number;
69
- readonly ingressCap: number;
70
- readonly egressOff: number;
71
- readonly egressCap: number;
72
- }
73
-
74
- interface StreamInfo {
75
- readonly offset: number;
76
- readonly cap: number;
77
- }
78
-
79
- /** A `@message` dispatch outcome: the drained egress reply frames, or a guest reject code. */
80
- export type StreamMessageOutcome =
81
- | { readonly kind: 'reply'; readonly frames: Buffer[] }
82
- | { readonly kind: 'reject'; readonly code: number };
83
-
84
- /** A `@connect` outcome: accepted/rejected plus any immediate frames staged during the hook. */
85
- export type StreamConnectOutcome =
86
- | { readonly kind: 'accept'; readonly initialEgress: Buffer[] }
87
- | { readonly kind: 'reject'; readonly code: number; readonly initialEgress: Buffer[] };
88
-
89
- export class DevStreamBox {
90
- private constructor(
91
- private readonly exports: StreamExports,
92
- private readonly _state: StreamBoxState,
93
- private readonly rings: Rings | null,
94
- private readonly streamInfo: StreamInfo | null,
95
- ) {}
96
-
97
- /** Compile + instantiate a resident stream box from a HOT `release-stream.wasm`. Fails closed: a
98
- * cold artifact, a missing `stream_dispatch`/`memory`, or a bad module throws. */
99
- static load(wasm: Buffer): DevStreamBox {
100
- const surface = parseSurface(wasm);
101
- if (surface === 'invalid' || surface.targetMode !== 'hot' || !surface.flags.stream) {
102
- throw new Error('stream box requires a hot stream artifact with a valid toil.surface');
103
- }
104
- const ref: { memory: WebAssembly.Memory | null } = { memory: null };
105
- const state = freshStreamBoxState();
106
- const module = new WebAssembly.Module(new Uint8Array(wasm));
107
- const instance = new WebAssembly.Instance(module, buildStreamImports(ref, state));
108
- const exports = instance.exports as unknown as StreamExports;
109
- if (
110
- typeof exports.stream_dispatch !== 'function' ||
111
- !(exports.memory instanceof WebAssembly.Memory)
112
- ) {
113
- throw new Error("stream artifact must export 'stream_dispatch' + 'memory'");
114
- }
115
- ref.memory = exports.memory;
116
- const rings = DevStreamBox.resolveRings(exports);
117
- const streamInfo = DevStreamBox.resolveStreamInfo(exports);
118
- const box = new DevStreamBox(exports, state, rings, streamInfo);
119
- if (rings) box.stampRings();
120
- return box;
121
- }
122
-
123
- /** Whether the box carries the ring runtime (a pre-bridge fixture omits the exports). */
124
- get hasRings(): boolean {
125
- return this.rings !== null;
126
- }
127
-
128
- /** Whether the box carries the `@connect` info-block bridge (a `@connect(StreamInbound)` hook). */
129
- get hasConnectBridge(): boolean {
130
- return this.streamInfo !== null;
131
- }
132
-
133
- /**
134
- * Fire `@connect`: write the connect-info block (stream id + transport + authority + path) into the
135
- * guest's info region, dispatch `EVENT_CONNECT`, decode the `StreamOutbound` accept/reject, and
136
- * drain any staged egress as initial frames. A box without the bridge runs `@connect` context-free
137
- * (the write is a no-op) and always accepts unless it returns a negative code.
138
- */
139
- onConnect(streamId: bigint, authority: string, path: string): StreamConnectOutcome {
140
- this.writeConnectInfo(streamId, authority, path);
141
- const rc = this.dispatch(EVENT_CONNECT, streamId);
142
- const initialEgress = this.egressDrain();
143
- if (rc < 0n) return { kind: 'reject', code: decodeRejectCode(rc), initialEgress };
144
- return { kind: 'accept', initialEgress };
145
- }
146
-
147
- /** Fire `@close` (graceful close). */
148
- onClose(streamId: bigint): bigint {
149
- return this.dispatch(EVENT_CLOSE, streamId);
150
- }
151
-
152
- /** Fire `@disconnect` (abrupt teardown). */
153
- onDisconnect(streamId: bigint): bigint {
154
- return this.dispatch(EVENT_DISCONNECT, streamId);
155
- }
156
-
157
- /** Drive the raw-bytes `@message` bridge: write `inbound` as one DATA_RELIABLE frame into the
158
- * ingress ring, fire `@message`, then drain the egress ring (reply) or surface the reject code.
159
- * A genuine wasm trap propagates (the caller maps it to a STREAM_HOOK_TRAPPED close). */
160
- onMessage(streamId: bigint, inbound: Buffer): StreamMessageOutcome {
161
- if (!this.rings) {
162
- throw new Error('stream box has no ring runtime; the message bridge is unavailable');
163
- }
164
- this.ingressWrite(inbound);
165
- const ret = this.dispatch(EVENT_MESSAGE, streamId);
166
- if (ret < 0n) return { kind: 'reject', code: decodeRejectCode(ret) };
167
- return { kind: 'reply', frames: this.egressDrain() };
168
- }
169
-
170
- private dispatch(eventKind: number, streamId: bigint): bigint {
171
- const lo = Number(streamId & 0xffff_ffffn) | 0;
172
- const hi = Number((streamId >> 32n) & 0xffff_ffffn) | 0;
173
- return this.exports.stream_dispatch(eventKind, lo, hi);
174
- }
175
-
176
- private static resolveRings(e: StreamExports): Rings | null {
177
- if (
178
- typeof e.stream_ring_offset !== 'function' ||
179
- typeof e.stream_ring_capacity !== 'function' ||
180
- typeof e.stream_egress_offset !== 'function' ||
181
- typeof e.stream_egress_capacity !== 'function'
182
- ) {
183
- return null;
184
- }
185
- return {
186
- ingressOff: e.stream_ring_offset() >>> 0,
187
- ingressCap: e.stream_ring_capacity() >>> 0,
188
- egressOff: e.stream_egress_offset() >>> 0,
189
- egressCap: e.stream_egress_capacity() >>> 0,
190
- };
191
- }
192
-
193
- private static resolveStreamInfo(e: StreamExports): StreamInfo | null {
194
- if (
195
- typeof e.stream_info_offset !== 'function' ||
196
- typeof e.stream_info_capacity !== 'function'
197
- ) {
198
- return null;
199
- }
200
- return { offset: e.stream_info_offset() >>> 0, cap: e.stream_info_capacity() >>> 0 };
201
- }
202
-
203
- private stampRings(): void {
204
- const rings = this.rings;
205
- if (!rings) return;
206
- this.stampOne(rings.ingressOff, rings.ingressCap);
207
- this.stampOne(rings.egressOff, rings.egressCap);
208
- }
209
-
210
- private stampOne(base: number, cap: number): void {
211
- const dv = new DataView(this.exports.memory.buffer);
212
- dv.setUint32(base + 0, RING_MAGIC, true);
213
- dv.setUint16(base + 4, RING_VERSION, true);
214
- dv.setUint16(base + 6, 0, true); // flags
215
- dv.setUint32(base + 8, cap, true); // capacity
216
- dv.setUint32(base + RC_WRITE, 0, true);
217
- dv.setUint32(base + RC_READ, 0, true);
218
- }
219
-
220
- /** Write the `@connect` info block into the guest's info region (no-op if the box carries none).
221
- * Authority + path are bounded into `[SI_BODY, cap)` - truncated to fit, never an OOB write. */
222
- private writeConnectInfo(streamId: bigint, authority: string, path: string): void {
223
- const info = this.streamInfo;
224
- if (!info) return;
225
- const base = info.offset;
226
- const body = Math.max(0, info.cap - SI_BODY);
227
- const authBytes = Buffer.from(authority, 'utf8');
228
- const authLen = Math.min(authBytes.length, 0xffff, body);
229
- const pathBytes = Buffer.from(path, 'utf8');
230
- const pathLen = Math.min(pathBytes.length, 0xffff, body - authLen);
231
- const dv = new DataView(this.exports.memory.buffer);
232
- dv.setBigUint64(base + 0, streamId, true);
233
- dv.setUint8(base + SI_TRANSPORT, SI_TRANSPORT_WEBTRANSPORT);
234
- dv.setUint8(base + SI_TRANSPORT + 1, 0); // reserved
235
- dv.setUint16(base + SI_AUTH_LEN, authLen, true);
236
- dv.setUint16(base + SI_PATH_LEN, pathLen, true);
237
- dv.setUint16(base + SI_RESERVED2, 0, true); // reserved
238
- const memU8 = new Uint8Array(this.exports.memory.buffer);
239
- if (authLen > 0) memU8.set(authBytes.subarray(0, authLen), base + SI_BODY);
240
- if (pathLen > 0) memU8.set(pathBytes.subarray(0, pathLen), base + SI_BODY + authLen);
241
- }
242
-
243
- /** Host (producer) writes ONE inbound RingFrame into the ingress ring with the drained-reset
244
- * (host owns write_cursor; reset read_cursor only when the guest has drained). */
245
- private ingressWrite(inbound: Buffer): void {
246
- const rings = this.rings;
247
- if (!rings) throw new Error('ingressWrite: no ring runtime');
248
- const { ingressOff: base, ingressCap: cap } = rings;
249
- const dv = new DataView(this.exports.memory.buffer);
250
- const n = inbound.length;
251
- const frameLen = RING_FRAME_HEADER + n;
252
- if (frameLen > cap) {
253
- throw new Error(`inbound frame (${String(frameLen)} B) exceeds ingress capacity`);
254
- }
255
- const w0 = dv.getUint32(base + RC_WRITE, true);
256
- const r0 = dv.getUint32(base + RC_READ, true);
257
- let w: number;
258
- if (r0 === w0) {
259
- dv.setUint32(base + RC_WRITE, 0, true);
260
- dv.setUint32(base + RC_READ, 0, true);
261
- w = 0;
262
- } else {
263
- w = w0;
264
- }
265
- if (w + frameLen > cap) throw new Error('ingress frame would not fit (v1 is no-wrap)');
266
- const f = base + RING_CTRL_BYTES + w;
267
- dv.setUint8(f + 0, RING_VERSION);
268
- dv.setUint8(f + 1, FRAME_TYPE_DATA_RELIABLE);
269
- dv.setUint16(f + 2, 0, true); // flags
270
- dv.setUint32(f + 4, n, true); // length
271
- dv.setUint32(f + 8, 0, true); // msg_seq
272
- if (n > 0)
273
- new Uint8Array(this.exports.memory.buffer, f + RING_FRAME_HEADER, n).set(inbound);
274
- dv.setUint32(base + RC_WRITE, w + frameLen, true);
275
- }
276
-
277
- /** Host (consumer) drains every egress RingFrame the guest staged, copying each payload out and
278
- * advancing the host-owned read_cursor. The guest does the drained-reset on its next write. */
279
- private egressDrain(): Buffer[] {
280
- const rings = this.rings;
281
- if (!rings) return [];
282
- const { egressOff: base, egressCap: cap } = rings;
283
- const dv = new DataView(this.exports.memory.buffer);
284
- const w = dv.getUint32(base + RC_WRITE, true);
285
- let r = dv.getUint32(base + RC_READ, true);
286
- const frames: Buffer[] = [];
287
- while (r < w) {
288
- const f = base + RING_CTRL_BYTES + r;
289
- if (r + RING_FRAME_HEADER > cap) break; // header must fit the frame region
290
- const len = dv.getUint32(f + 4, true);
291
- if (len > MAX_STREAM_FRAME_LEN) break; // over-cap frame (contained, not over-read)
292
- const span = RING_FRAME_HEADER + len;
293
- if (r + span > cap) break; // payload must fit
294
- const payloadOff = f + RING_FRAME_HEADER;
295
- frames.push(Buffer.from(new Uint8Array(this.exports.memory.buffer, payloadOff, len)));
296
- r += span;
297
- }
298
- dv.setUint32(base + RC_READ, r, true);
299
- return frames;
300
- }
301
- }