toiljs 0.0.67 → 0.0.68

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 (99) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +63 -61
  3. package/build/backend/.tsbuildinfo +1 -1
  4. package/build/cli/.tsbuildinfo +1 -1
  5. package/build/cli/index.js +13 -1
  6. package/build/client/.tsbuildinfo +1 -1
  7. package/build/client/index.d.ts +2 -0
  8. package/build/client/index.js +1 -0
  9. package/build/client/rpc.js +21 -1
  10. package/build/client/stream/client.d.ts +11 -0
  11. package/build/client/stream/client.js +59 -0
  12. package/build/compiler/.tsbuildinfo +1 -1
  13. package/build/compiler/config.d.ts +2 -0
  14. package/build/compiler/config.js +9 -7
  15. package/build/compiler/index.d.ts +1 -0
  16. package/build/compiler/index.js +16 -2
  17. package/build/compiler/toil-docs.generated.js +2 -2
  18. package/build/devserver/.tsbuildinfo +1 -1
  19. package/build/devserver/daemon/runtime.d.ts +13 -0
  20. package/build/devserver/daemon/runtime.js +29 -0
  21. package/build/devserver/db/database.d.ts +1 -0
  22. package/build/devserver/db/database.js +10 -0
  23. package/build/devserver/db/derives.d.ts +7 -0
  24. package/build/devserver/db/derives.js +94 -0
  25. package/build/devserver/db/index.d.ts +1 -0
  26. package/build/devserver/db/index.js +1 -0
  27. package/build/devserver/db/types.d.ts +1 -0
  28. package/build/devserver/db/types.js +1 -0
  29. package/build/devserver/http/proxy.d.ts +5 -1
  30. package/build/devserver/http/proxy.js +39 -36
  31. package/build/devserver/http/runtime.d.ts +62 -0
  32. package/build/devserver/http/runtime.js +194 -0
  33. package/build/devserver/index.d.ts +2 -0
  34. package/build/devserver/index.js +1 -0
  35. package/build/devserver/production-ipc.d.ts +50 -0
  36. package/build/devserver/production-ipc.js +21 -0
  37. package/build/devserver/production-worker.d.ts +1 -0
  38. package/build/devserver/production-worker.js +73 -0
  39. package/build/devserver/production.d.ts +35 -0
  40. package/build/devserver/production.js +502 -0
  41. package/build/devserver/runtime/module.d.ts +5 -0
  42. package/build/devserver/runtime/module.js +47 -1
  43. package/build/devserver/server.d.ts +1 -0
  44. package/build/devserver/server.js +32 -145
  45. package/build/devserver/ssr.d.ts +2 -0
  46. package/build/devserver/ssr.js +19 -2
  47. package/build/devserver/stream/catalog.d.ts +20 -0
  48. package/build/devserver/stream/catalog.js +54 -0
  49. package/build/devserver/stream/host.d.ts +9 -0
  50. package/build/devserver/stream/host.js +15 -0
  51. package/build/devserver/stream/index.d.ts +37 -0
  52. package/build/devserver/stream/index.js +220 -0
  53. package/build/devserver/stream/manager.d.ts +34 -0
  54. package/build/devserver/stream/manager.js +103 -0
  55. package/build/devserver/stream/router.d.ts +25 -0
  56. package/build/devserver/stream/router.js +64 -0
  57. package/build/devserver/stream/wire.d.ts +5 -0
  58. package/build/devserver/stream/wire.js +33 -0
  59. package/build/devserver/stream/ws.d.ts +18 -0
  60. package/build/devserver/stream/ws.js +46 -0
  61. package/docs/cli.md +3 -1
  62. package/docs/getting-started.md +7 -7
  63. package/examples/basic/server/routes/Guestbook.ts +38 -13
  64. package/package.json +2 -2
  65. package/src/cli/index.ts +14 -1
  66. package/src/client/index.ts +2 -0
  67. package/src/client/rpc.ts +25 -1
  68. package/src/client/stream/client.ts +107 -0
  69. package/src/compiler/config.ts +15 -7
  70. package/src/compiler/index.ts +24 -5
  71. package/src/compiler/toil-docs.generated.ts +2 -2
  72. package/src/devserver/daemon/runtime.ts +48 -0
  73. package/src/devserver/db/database.ts +14 -0
  74. package/src/devserver/db/derives.ts +121 -0
  75. package/src/devserver/db/index.ts +1 -0
  76. package/src/devserver/db/types.ts +6 -0
  77. package/src/devserver/http/proxy.ts +53 -39
  78. package/src/devserver/http/runtime.ts +287 -0
  79. package/src/devserver/index.ts +2 -0
  80. package/src/devserver/production-ipc.ts +63 -0
  81. package/src/devserver/production-worker.ts +83 -0
  82. package/src/devserver/production.ts +706 -0
  83. package/src/devserver/runtime/module.ts +95 -1
  84. package/src/devserver/server.ts +52 -201
  85. package/src/devserver/ssr.ts +23 -3
  86. package/src/devserver/stream/catalog.ts +106 -0
  87. package/src/devserver/stream/host.ts +42 -0
  88. package/src/devserver/stream/index.ts +308 -0
  89. package/src/devserver/stream/manager.ts +163 -0
  90. package/src/devserver/stream/router.ts +101 -0
  91. package/src/devserver/stream/wire.ts +58 -0
  92. package/src/devserver/stream/ws.ts +76 -0
  93. package/test/built-ssr.test.ts +98 -0
  94. package/test/devserver.test.ts +20 -4
  95. package/test/example-guestbook.test.ts +8 -5
  96. package/test/fixtures/stream-echo.ts +26 -0
  97. package/test/fixtures/stream-gate.ts +24 -0
  98. package/test/fixtures/stream-trap.ts +18 -0
  99. package/test/stream-emulation.test.ts +394 -0
@@ -1,5 +1,5 @@
1
1
  import fs from 'node:fs';
2
- import { DbFunctionKind, persistDb, setDbCatalog } from '../db/index.js';
2
+ import { DbFunctionKind, derivesForWrites, parseDerives, persistDb, setDbCatalog, } from '../db/index.js';
3
3
  import { parseRouteKinds, routeKindForRequest } from '../db/routeKinds.js';
4
4
  import { decodeResponseEnvelope, encodeRequestEnvelope, unpackHandleResult, } from '../http/envelope.js';
5
5
  import { buildHostImports, freshDispatchState } from './host.js';
@@ -87,6 +87,8 @@ export class WasmServerModule {
87
87
  module = null;
88
88
  loadedMtimeMs = -1;
89
89
  routeKinds = [];
90
+ derives = [];
91
+ derivesDirty = false;
90
92
  constructor(wasmPath) {
91
93
  this.wasmPath = wasmPath;
92
94
  }
@@ -101,6 +103,8 @@ export class WasmServerModule {
101
103
  catch {
102
104
  this.module = null;
103
105
  this.routeKinds = [];
106
+ this.derives = [];
107
+ this.derivesDirty = false;
104
108
  this.loadedMtimeMs = -1;
105
109
  return false;
106
110
  }
@@ -112,13 +116,16 @@ export class WasmServerModule {
112
116
  this.assertExportSurface(module);
113
117
  setDbCatalog(bytes);
114
118
  this.routeKinds = parseRouteKinds(bytes);
119
+ this.derives = parseDerives(bytes);
115
120
  this.module = module;
116
121
  this.loadedMtimeMs = mtimeMs;
122
+ this.derivesDirty = this.derives.length > 0;
117
123
  return true;
118
124
  }
119
125
  dispatch(req) {
120
126
  if (this.module === null)
121
127
  throw new Error(`server wasm not loaded (${this.wasmPath})`);
128
+ this.rebuildDerivedViewsIfStale();
122
129
  const envelope = encodeRequestEnvelope(req);
123
130
  const ref = { memory: null };
124
131
  const state = freshDispatchState();
@@ -139,6 +146,7 @@ export class WasmServerModule {
139
146
  const headers = [...resp.headers, ...state.headers];
140
147
  const status = state.status ?? resp.status;
141
148
  const unhandled = headers.some(([n]) => n.toLowerCase() === UNHANDLED_HEADER);
149
+ this.runAffectedDerives(state.db.writtenCollections);
142
150
  persistDb();
143
151
  return {
144
152
  status,
@@ -171,6 +179,44 @@ export class WasmServerModule {
171
179
  throw new Error(`guest returned out-of-bounds values: ptr=${String(ptr)} len=${String(len)}`);
172
180
  return new Uint8Array(exports.memory.buffer, ptr, len).slice();
173
181
  }
182
+ runAffectedDerives(written) {
183
+ if (this.module === null)
184
+ return;
185
+ for (const derive of derivesForWrites(this.derives, written)) {
186
+ try {
187
+ this.runDerive(derive.deriveId);
188
+ }
189
+ catch (err) {
190
+ console.error(`[toil] derive ${derive.dbName}#${derive.methodName} failed:`, err);
191
+ }
192
+ }
193
+ }
194
+ rebuildDerivedViewsIfStale() {
195
+ if (!this.derivesDirty)
196
+ return;
197
+ this.derivesDirty = false;
198
+ for (const derive of this.derives) {
199
+ try {
200
+ this.runDerive(derive.deriveId);
201
+ }
202
+ catch (err) {
203
+ console.error(`[toil] derive ${derive.dbName}#${derive.methodName} failed on load:`, err);
204
+ }
205
+ }
206
+ }
207
+ runDerive(deriveId) {
208
+ if (this.module === null)
209
+ return;
210
+ const ref = { memory: null };
211
+ const state = freshDispatchState();
212
+ state.db.functionKind = DbFunctionKind.Derive;
213
+ const instance = new WebAssembly.Instance(this.module, buildHostImports(ref, state));
214
+ const exports = instance.exports;
215
+ ref.memory = exports.memory;
216
+ if (typeof exports.derive_run !== 'function')
217
+ return;
218
+ exports.derive_run(deriveId);
219
+ }
174
220
  assertImportSurface(module) {
175
221
  const missing = WebAssembly.Module.imports(module)
176
222
  .filter((i) => i.kind === 'function' && (i.module !== 'env' || !PROVIDED_IMPORTS.has(i.name)))
@@ -8,6 +8,7 @@ export interface DevServerOptions {
8
8
  readonly host?: string;
9
9
  readonly wasmFile: string;
10
10
  readonly coldWasmFile?: string;
11
+ readonly streamWasmFile?: string;
11
12
  readonly nodeMode?: string;
12
13
  readonly daemon?: ResolvedDaemonConfig;
13
14
  readonly vite: ViteTarget;
@@ -1,101 +1,19 @@
1
- import fs from 'node:fs';
2
1
  import path from 'node:path';
3
2
  import { Server } from '@dacely/hyper-express';
4
3
  import pc from 'picocolors';
5
- import { DaemonHost, daemonEmulationEnabled } from './daemon/index.js';
4
+ import { startDaemonRuntime } from './daemon/runtime.js';
6
5
  import { configureDbPersistence } from './db/index.js';
7
6
  import { initEmailService } from './email/index.js';
8
- import { applyCacheRule, lookupCache } from './http/cache.js';
9
- import { METHOD_CODES } from './http/envelope.js';
10
7
  import { proxyToVite, wireWebsocketProxy } from './http/proxy.js';
8
+ import { assembleRouteSsr, dispatchWasmRequest, installRuntimeErrorHandler, isDispatchableMethod, runtimeServerOptions, sendSsr, } from './http/runtime.js';
11
9
  import { WasmServerModule } from './runtime/module.js';
12
- import { assembleSsr, buildSsrRoutes, pathnameOf, } from './ssr.js';
13
- const DEFAULT_MAX_BODY_LENGTH = 1024 * 1024 * 8;
10
+ import { StreamRouter } from './stream/router.js';
11
+ import { streamEmulationEnabled, wireStreams } from './stream/wire.js';
12
+ import { buildSsrRoutes, pathnameOf } from './ssr.js';
14
13
  const VITE_PREFIXES = ['/@', '/node_modules/', '/__toil/'];
15
- const MIME = {
16
- '.html': 'text/html; charset=utf-8',
17
- '.js': 'text/javascript; charset=utf-8',
18
- '.mjs': 'text/javascript; charset=utf-8',
19
- '.css': 'text/css; charset=utf-8',
20
- '.json': 'application/json; charset=utf-8',
21
- '.txt': 'text/plain; charset=utf-8',
22
- '.svg': 'image/svg+xml',
23
- '.png': 'image/png',
24
- '.jpg': 'image/jpeg',
25
- '.jpeg': 'image/jpeg',
26
- '.webp': 'image/webp',
27
- '.avif': 'image/avif',
28
- '.gif': 'image/gif',
29
- '.ico': 'image/x-icon',
30
- '.wasm': 'application/wasm',
31
- '.woff2': 'font/woff2',
32
- };
33
14
  function isViteInternal(url) {
34
15
  return VITE_PREFIXES.some((p) => url.startsWith(p));
35
16
  }
36
- function resolveSendfile(root, file) {
37
- const resolved = path.resolve(root, file);
38
- if (resolved !== root && !resolved.startsWith(root + path.sep))
39
- return null;
40
- if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile())
41
- return null;
42
- return resolved;
43
- }
44
- async function toEnvelopeRequest(request) {
45
- const hasBody = request.method !== 'GET' && request.method !== 'HEAD';
46
- const body = hasBody ? new Uint8Array(await request.buffer()) : new Uint8Array(0);
47
- const xff = request.headers['x-forwarded-for'];
48
- const clientIp = typeof xff === 'string' && xff.length > 0 ? xff.split(',')[0].trim() : '127.0.0.1';
49
- return {
50
- method: request.method,
51
- path: request.url,
52
- headers: Object.entries(request.headers),
53
- body,
54
- clientIp,
55
- };
56
- }
57
- function sendWasmResponse(response, root, result) {
58
- response.status(result.status);
59
- let hasContentType = false;
60
- for (const [name, value] of result.headers) {
61
- if (name.toLowerCase() === 'content-type')
62
- hasContentType = true;
63
- response.header(name, value);
64
- }
65
- response.header('server', 'toil-dev');
66
- if (result.sendfile !== null) {
67
- const file = resolveSendfile(root, result.sendfile);
68
- if (file === null) {
69
- response.status(404).send('not found\n');
70
- return;
71
- }
72
- if (!hasContentType) {
73
- response.header('content-type', MIME[path.extname(file).toLowerCase()] ?? 'application/octet-stream');
74
- }
75
- response.sendFile(file);
76
- return;
77
- }
78
- if (!hasContentType)
79
- response.header('content-type', 'text/plain; charset=utf-8');
80
- response.send(Buffer.from(result.body.buffer, result.body.byteOffset, result.body.length));
81
- }
82
- function sendSsr(response, out, headOnly) {
83
- response.status(out.status);
84
- let hasContentType = false;
85
- for (const [name, value] of out.headers) {
86
- if (name.toLowerCase() === 'content-type')
87
- hasContentType = true;
88
- response.header(name, value);
89
- }
90
- if (!hasContentType)
91
- response.header('content-type', 'text/html; charset=utf-8');
92
- response.header('server', 'toil-dev');
93
- if (headOnly) {
94
- response.send('');
95
- return;
96
- }
97
- response.send(Buffer.from(out.html.buffer, out.html.byteOffset, out.html.length));
98
- }
99
17
  export async function startDevServer(options) {
100
18
  const host = options.host ?? '127.0.0.1';
101
19
  const root = path.resolve(options.root);
@@ -132,74 +50,45 @@ export async function startDevServer(options) {
132
50
  }
133
51
  };
134
52
  refresh();
135
- const app = new Server({
136
- max_body_length: options.maxBodyLength ?? DEFAULT_MAX_BODY_LENGTH,
137
- max_body_buffer: 1024 * 32,
138
- fast_abort: true,
139
- });
140
- app.set_error_handler((_request, response, error) => {
141
- if (response.completed)
142
- return;
143
- response.atomic(() => {
144
- response.status(500).send(`internal error: ${error.message}\n`);
145
- });
146
- });
147
- wireWebsocketProxy(app, options.vite);
53
+ const app = new Server(runtimeServerOptions(options));
54
+ installRuntimeErrorHandler(app);
148
55
  const nodeMode = options.nodeMode ?? 'all';
149
- let daemonHost = null;
150
- let daemonTimer = null;
151
- if (options.coldWasmFile !== undefined && daemonEmulationEnabled(nodeMode) && options.daemon) {
152
- daemonHost = new DaemonHost(options.coldWasmFile, options.daemon, nodeMode);
153
- const pollDaemon = () => {
154
- try {
155
- daemonHost?.refresh();
156
- }
157
- catch (e) {
158
- process.stdout.write(pc.red(` ✗ daemon reload failed: ${String(e)}`) + '\n');
159
- }
160
- };
161
- pollDaemon();
162
- daemonTimer = setInterval(pollDaemon, 500);
163
- daemonTimer.unref?.();
56
+ if (options.streamWasmFile !== undefined && streamEmulationEnabled(nodeMode)) {
57
+ wireStreams(app, options.vite, new StreamRouter(options.streamWasmFile));
58
+ }
59
+ else {
60
+ wireWebsocketProxy(app, options.vite);
164
61
  }
62
+ const daemon = startDaemonRuntime({
63
+ coldWasmFile: options.coldWasmFile,
64
+ nodeMode,
65
+ daemon: options.daemon,
66
+ });
165
67
  app.any('/*', async (request, response) => {
166
68
  response.removeHeader('uWebSockets');
167
- const dispatchable = !isViteInternal(request.url) && METHOD_CODES[request.method] !== undefined;
69
+ const dispatchable = !isViteInternal(request.url) && isDispatchableMethod(request.method);
168
70
  if (dispatchable)
169
71
  refresh();
170
72
  if (dispatchable && module.available) {
171
- const envelopeReq = await toEnvelopeRequest(request);
172
- const cacheHost = request.headers.host ?? 'dev';
173
- const hasAuth = request.headers.cookie !== undefined || request.headers.authorization !== undefined;
174
- const cached = lookupCache(cacheHost, request.method, request.url, envelopeReq.body);
175
- if (cached !== null) {
176
- sendWasmResponse(response, root, cached);
177
- return;
178
- }
179
- try {
180
- const result = module.dispatch(envelopeReq);
181
- if (!result.unhandled) {
182
- const finalized = applyCacheRule(cacheHost, request.method, request.url, envelopeReq.body, hasAuth, result);
183
- sendWasmResponse(response, root, finalized);
184
- return;
185
- }
186
- }
187
- catch (e) {
188
- process.stdout.write(pc.red(` ✗ ${request.method} ${request.path} server error: ${String(e)}`) +
189
- '\n');
190
- response.status(500).send('internal error\n');
73
+ const dispatch = await dispatchWasmRequest({
74
+ module,
75
+ request,
76
+ response,
77
+ root,
78
+ cacheHost: request.headers.host ?? 'dev',
79
+ serverHeader: 'toil-dev',
80
+ errorPrefix: '✗',
81
+ });
82
+ if (dispatch.handled) {
191
83
  return;
192
84
  }
193
- if ((request.method === 'GET' || request.method === 'HEAD') &&
194
- ssrRoutes.length > 0) {
85
+ if ((request.method === 'GET' || request.method === 'HEAD') && ssrRoutes.length > 0) {
195
86
  const route = ssrRoutes.find((r) => r.test(pathnameOf(request.url)));
196
87
  if (route) {
197
88
  try {
198
- const out = route.entries.length === 0
199
- ? { status: 200, headers: [], html: route.tmpl }
200
- : assembleSsr(route, module.dispatchRender(envelopeReq));
89
+ const out = assembleRouteSsr(route, module, dispatch.envelopeReq);
201
90
  if (out !== null) {
202
- sendSsr(response, out, request.method === 'HEAD');
91
+ sendSsr(response, out, request.method === 'HEAD', 'toil-dev');
203
92
  return;
204
93
  }
205
94
  }
@@ -216,9 +105,7 @@ export async function startDevServer(options) {
216
105
  port: options.port,
217
106
  host,
218
107
  close: async () => {
219
- if (daemonTimer !== null)
220
- clearInterval(daemonTimer);
221
- daemonHost?.close();
108
+ daemon?.close();
222
109
  await app.shutdown();
223
110
  },
224
111
  };
@@ -6,6 +6,7 @@ export interface DevSsrTemplate {
6
6
  id: number;
7
7
  offset: number;
8
8
  }[];
9
+ hash?: Uint8Array;
9
10
  }
10
11
  export interface SsrRoute {
11
12
  test: (pathname: string) => boolean;
@@ -14,6 +15,7 @@ export interface SsrRoute {
14
15
  id: number;
15
16
  offset: number;
16
17
  }[];
18
+ hash?: Uint8Array;
17
19
  }
18
20
  export declare function pathnameOf(url: string): string;
19
21
  export declare function buildSsrRoutes(templates: readonly DevSsrTemplate[]): SsrRoute[];
@@ -33,7 +33,12 @@ function patternToTest(pattern) {
33
33
  return (pathname) => compiled.test(norm(pathname));
34
34
  }
35
35
  export function buildSsrRoutes(templates) {
36
- return templates.map((t) => ({ test: patternToTest(t.pattern), tmpl: t.tmpl, entries: t.entries }));
36
+ return templates.map((t) => ({
37
+ test: patternToTest(t.pattern),
38
+ tmpl: t.tmpl,
39
+ entries: t.entries,
40
+ hash: t.hash,
41
+ }));
37
42
  }
38
43
  function decodeValues(buf) {
39
44
  const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
@@ -44,6 +49,7 @@ function decodeValues(buf) {
44
49
  return null;
45
50
  const status = dv.getUint16(o, true);
46
51
  o += 2;
52
+ const hash = buf.subarray(o, o + 32);
47
53
  o += 32;
48
54
  const nHeaders = dv.getUint16(o, true);
49
55
  o += 2;
@@ -81,12 +87,21 @@ function decodeValues(buf) {
81
87
  values.set(id, buf.subarray(o, o + len));
82
88
  o += len;
83
89
  }
84
- return { status, headers, values };
90
+ return { status, hash, headers, values };
85
91
  }
86
92
  catch {
87
93
  return null;
88
94
  }
89
95
  }
96
+ function sameBytes(a, b) {
97
+ if (a.byteLength !== b.byteLength)
98
+ return false;
99
+ for (let i = 0; i < a.byteLength; i++) {
100
+ if (a[i] !== b[i])
101
+ return false;
102
+ }
103
+ return true;
104
+ }
90
105
  function splice(tmpl, inserts) {
91
106
  const parts = [];
92
107
  let prev = 0;
@@ -107,6 +122,8 @@ export function assembleSsr(route, envelope) {
107
122
  return null;
108
123
  if (decoded.status >= 500 || decoded.values.size === 0)
109
124
  return null;
125
+ if (route.hash !== undefined && !sameBytes(decoded.hash, route.hash))
126
+ return null;
110
127
  const inserts = route.entries
111
128
  .map((e) => ({ offset: e.offset, value: decoded.values.get(e.id) ?? new Uint8Array(0) }))
112
129
  .sort((a, b) => a.offset - b.offset);
@@ -0,0 +1,20 @@
1
+ export interface StreamDef {
2
+ readonly name: string;
3
+ readonly route: string;
4
+ readonly hooks: {
5
+ readonly connect: boolean;
6
+ readonly message: boolean;
7
+ readonly close: boolean;
8
+ readonly disconnect: boolean;
9
+ };
10
+ readonly scope: 'regional' | 'continental';
11
+ readonly messageMode: 'raw' | 'data';
12
+ readonly maxFrameBytes: number;
13
+ readonly ingressRingBytes: number;
14
+ readonly messageValueDataId: number;
15
+ readonly messageSchemaVersion: number;
16
+ readonly streamIndex: number;
17
+ }
18
+ export type StreamCatalog = Map<string, StreamDef>;
19
+ export declare function parseStreamCatalog(wasm: Buffer): StreamCatalog;
20
+ export declare function matchStreamRoute(catalog: StreamCatalog, path: string): StreamDef | null;
@@ -0,0 +1,54 @@
1
+ import { DataReader } from 'toiljs/io';
2
+ import { customSection } from '../wasm/sections.js';
3
+ export function parseStreamCatalog(wasm) {
4
+ const out = new Map();
5
+ let sec;
6
+ try {
7
+ sec = customSection(wasm, 'toilstream.catalog');
8
+ }
9
+ catch {
10
+ return out;
11
+ }
12
+ if (sec === null)
13
+ return out;
14
+ const r = new DataReader(sec);
15
+ r.readU16();
16
+ const n = r.readU16();
17
+ for (let i = 0; i < n && r.ok; i++) {
18
+ const name = r.readString();
19
+ const route = r.readString();
20
+ const bits = r.readU8();
21
+ const scope = r.readU8() === 1 ? 'continental' : 'regional';
22
+ const messageMode = r.readU8() === 1 ? 'data' : 'raw';
23
+ const maxFrameBytes = r.readU32();
24
+ const ingressRingBytes = r.readU32();
25
+ const messageValueDataId = r.readU32();
26
+ const messageSchemaVersion = r.readU32();
27
+ const streamIndex = r.readU16();
28
+ if (!r.ok)
29
+ break;
30
+ out.set(route, {
31
+ name,
32
+ route,
33
+ hooks: {
34
+ connect: (bits & 1) !== 0,
35
+ message: (bits & 2) !== 0,
36
+ close: (bits & 4) !== 0,
37
+ disconnect: (bits & 8) !== 0,
38
+ },
39
+ scope,
40
+ messageMode,
41
+ maxFrameBytes,
42
+ ingressRingBytes,
43
+ messageValueDataId,
44
+ messageSchemaVersion,
45
+ streamIndex,
46
+ });
47
+ }
48
+ return out;
49
+ }
50
+ export function matchStreamRoute(catalog, path) {
51
+ const q = path.indexOf('?');
52
+ const exact = q >= 0 ? path.slice(0, q) : path;
53
+ return catalog.get(exact) ?? null;
54
+ }
@@ -0,0 +1,9 @@
1
+ import { type DbDevState } from '../db/index.js';
2
+ import { type CryptoState } from '../runtime/crypto.js';
3
+ import { type MemoryRef } from '../runtime/host.js';
4
+ export interface StreamBoxState {
5
+ crypto: CryptoState;
6
+ db: DbDevState;
7
+ }
8
+ export declare function freshStreamBoxState(): StreamBoxState;
9
+ export declare function buildStreamImports(ref: MemoryRef, state: StreamBoxState): WebAssembly.Imports;
@@ -0,0 +1,15 @@
1
+ import { buildDatabaseImports, freshDbState } from '../db/index.js';
2
+ import { buildCryptoImports, freshCryptoState } from '../runtime/crypto.js';
3
+ import { buildEnvImports } from '../runtime/host.js';
4
+ export function freshStreamBoxState() {
5
+ return { crypto: freshCryptoState(), db: freshDbState() };
6
+ }
7
+ export function buildStreamImports(ref, state) {
8
+ return {
9
+ env: {
10
+ ...buildEnvImports(ref, state),
11
+ ...buildCryptoImports(ref, state.crypto),
12
+ ...buildDatabaseImports(ref, state.db),
13
+ },
14
+ };
15
+ }
@@ -0,0 +1,37 @@
1
+ export declare function decodeRejectCode(rc: bigint): number;
2
+ export type StreamMessageOutcome = {
3
+ readonly kind: 'reply';
4
+ readonly frames: Buffer[];
5
+ } | {
6
+ readonly kind: 'reject';
7
+ readonly code: number;
8
+ };
9
+ export type StreamConnectOutcome = {
10
+ readonly kind: 'accept';
11
+ } | {
12
+ readonly kind: 'reject';
13
+ readonly code: number;
14
+ };
15
+ export declare class DevStreamBox {
16
+ private readonly exports;
17
+ private readonly _state;
18
+ private readonly rings;
19
+ private readonly streamInfo;
20
+ private constructor();
21
+ static load(wasm: Buffer): DevStreamBox;
22
+ get hasRings(): boolean;
23
+ get hasConnectBridge(): boolean;
24
+ onConnect(streamId: bigint, authority: string, path: string): StreamConnectOutcome;
25
+ onClose(streamId: bigint): bigint;
26
+ onDisconnect(streamId: bigint): bigint;
27
+ onMessage(streamId: bigint, inbound: Buffer): StreamMessageOutcome;
28
+ private dispatch;
29
+ private static resolveRings;
30
+ private static resolveStreamInfo;
31
+ private stampRings;
32
+ private stampOne;
33
+ private writeConnectInfo;
34
+ private resetEgressRing;
35
+ private ingressWrite;
36
+ private egressDrain;
37
+ }