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
@@ -0,0 +1,13 @@
1
+ import { DaemonHost } from './index.js';
2
+ import type { ResolvedDaemonConfig } from './host.js';
3
+ export interface DaemonRuntimeOptions {
4
+ readonly coldWasmFile?: string;
5
+ readonly nodeMode?: string;
6
+ readonly daemon?: ResolvedDaemonConfig;
7
+ readonly pollMs?: number;
8
+ }
9
+ export interface RunningDaemonRuntime {
10
+ readonly host: DaemonHost;
11
+ close(): void;
12
+ }
13
+ export declare function startDaemonRuntime(options: DaemonRuntimeOptions): RunningDaemonRuntime | null;
@@ -0,0 +1,29 @@
1
+ import pc from 'picocolors';
2
+ import { DaemonHost, daemonEmulationEnabled } from './index.js';
3
+ export function startDaemonRuntime(options) {
4
+ const nodeMode = options.nodeMode ?? 'all';
5
+ if (options.coldWasmFile === undefined ||
6
+ !daemonEmulationEnabled(nodeMode) ||
7
+ options.daemon === undefined) {
8
+ return null;
9
+ }
10
+ const host = new DaemonHost(options.coldWasmFile, options.daemon, nodeMode);
11
+ const pollDaemon = () => {
12
+ try {
13
+ host.refresh();
14
+ }
15
+ catch (e) {
16
+ process.stdout.write(pc.red(` x daemon reload failed: ${String(e)}`) + '\n');
17
+ }
18
+ };
19
+ pollDaemon();
20
+ const timer = setInterval(pollDaemon, options.pollMs ?? 500);
21
+ timer.unref?.();
22
+ return {
23
+ host,
24
+ close: () => {
25
+ clearInterval(timer);
26
+ host.close();
27
+ },
28
+ };
29
+ }
@@ -55,6 +55,7 @@ export declare class DevDatabase {
55
55
  viewGet(ref: MemoryRef, db: DbDevState, handle: number, keyPtr: number, keyLen: number): number;
56
56
  viewPublish(ref: MemoryRef, db: DbDevState, handle: number, keyPtr: number, keyLen: number, valPtr: number, valLen: number): number;
57
57
  counterGet(ref: MemoryRef, db: DbDevState, handle: number, keyPtr: number, keyLen: number): number;
58
+ private recordWrite;
58
59
  counterAdd(ref: MemoryRef, db: DbDevState, handle: number, keyPtr: number, keyLen: number, delta: number | bigint, idemPtr: number): number;
59
60
  append(ref: MemoryRef, db: DbDevState, handle: number, keyPtr: number, keyLen: number, evPtr: number, evLen: number, idemPtr: number): number;
60
61
  appendOnce(ref: MemoryRef, db: DbDevState, handle: number, keyPtr: number, keyLen: number, evidPtr: number, evidLen: number, evPtr: number, evLen: number): number;
@@ -509,6 +509,7 @@ export class DevDatabase {
509
509
  if (outcome.kind === 'unit') {
510
510
  this.store.set(sk, value);
511
511
  this.stampVersion(coll, sk);
512
+ this.recordWrite(db, coll);
512
513
  }
513
514
  this.recordIdemFinish(coll, key, 'C', idem, requestHash, outcome);
514
515
  return this.replayRecordOutcome(db, outcome);
@@ -533,6 +534,7 @@ export class DevDatabase {
533
534
  if (outcome.kind === 'value') {
534
535
  this.store.set(sk, v);
535
536
  this.stampVersion(coll, sk);
537
+ this.recordWrite(db, coll);
536
538
  }
537
539
  this.recordIdemFinish(coll, key, 'P', idem, requestHash, outcome);
538
540
  return this.replayRecordOutcome(db, outcome);
@@ -721,6 +723,11 @@ export class DevDatabase {
721
723
  db.lastResult = out;
722
724
  return out.length;
723
725
  }
726
+ recordWrite(db, coll) {
727
+ if (db.functionKind === DbFunctionKind.Derive)
728
+ return;
729
+ db.writtenCollections.add(coll.name);
730
+ }
724
731
  counterAdd(ref, db, handle, keyPtr, keyLen, delta, idemPtr) {
725
732
  const coll = collForOp(db, handle, DbOp.CounterAdd, CollectionFamily.Counter);
726
733
  if (typeof coll === 'number')
@@ -737,6 +744,7 @@ export class DevDatabase {
737
744
  }
738
745
  const sk = storeKey(coll.name, key);
739
746
  this.counters.set(sk, satI64((this.counters.get(sk) ?? 0n) + d));
747
+ this.recordWrite(db, coll);
740
748
  return 0;
741
749
  }
742
750
  append(ref, db, handle, keyPtr, keyLen, evPtr, evLen, idemPtr) {
@@ -770,6 +778,7 @@ export class DevDatabase {
770
778
  log.push(ev);
771
779
  (this.eventVersions.get(sk) ?? this.eventVersions.set(sk, []).get(sk)).push(sv);
772
780
  }
781
+ this.recordWrite(db, coll);
773
782
  return 0;
774
783
  }
775
784
  appendOnce(ref, db, handle, keyPtr, keyLen, evidPtr, evidLen, evPtr, evLen) {
@@ -799,6 +808,7 @@ export class DevDatabase {
799
808
  (this.eventVersions.get(sk) ?? this.eventVersions.set(sk, []).get(sk)).push(sv);
800
809
  }
801
810
  seen.add(evid);
811
+ this.recordWrite(db, coll);
802
812
  return 1;
803
813
  }
804
814
  enqueue(ref, db, handle, keyPtr, keyLen, valPtr, valLen, idemPtr) {
@@ -0,0 +1,7 @@
1
+ export interface DeriveEntry {
2
+ readonly deriveId: number;
3
+ readonly dbName: string;
4
+ readonly methodName: string;
5
+ }
6
+ export declare function parseDerives(wasm: Buffer): readonly DeriveEntry[];
7
+ export declare function derivesForWrites(derives: readonly DeriveEntry[], written: ReadonlySet<string>): readonly DeriveEntry[];
@@ -0,0 +1,94 @@
1
+ import { customSection } from '../wasm/sections.js';
2
+ const SECTION = 'toildb.derives';
3
+ const VERSION = 1;
4
+ const MAX_SECTION_BYTES = 128 * 1024;
5
+ const MAX_DERIVES = 1024;
6
+ const MAX_NAME_BYTES = 1024;
7
+ const UTF8_DECODER = new TextDecoder('utf-8', { fatal: true });
8
+ export function parseDerives(wasm) {
9
+ let section;
10
+ try {
11
+ section = customSection(wasm, SECTION);
12
+ }
13
+ catch {
14
+ return [];
15
+ }
16
+ if (section === null)
17
+ return [];
18
+ if (section.length > MAX_SECTION_BYTES)
19
+ return [];
20
+ const r = new Reader(section);
21
+ const version = r.u16();
22
+ if (!r.ok || version !== VERSION)
23
+ return [];
24
+ const count = r.u16();
25
+ if (!r.ok || count > MAX_DERIVES)
26
+ return [];
27
+ const derives = [];
28
+ for (let i = 0; i < count && r.ok; i++) {
29
+ const deriveId = r.u16();
30
+ const dbName = r.string();
31
+ const methodName = r.string();
32
+ if (!r.ok || dbName.length === 0)
33
+ return [];
34
+ derives.push({ deriveId, dbName, methodName });
35
+ }
36
+ if (!r.ok || r.remaining() !== 0)
37
+ return [];
38
+ return derives;
39
+ }
40
+ export function derivesForWrites(derives, written) {
41
+ if (derives.length === 0 || written.size === 0)
42
+ return [];
43
+ const dbs = new Set();
44
+ for (const key of written) {
45
+ const slash = key.indexOf('/');
46
+ dbs.add(slash >= 0 ? key.slice(0, slash) : key);
47
+ }
48
+ return derives.filter((d) => dbs.has(d.dbName));
49
+ }
50
+ class Reader {
51
+ bytes;
52
+ pos = 0;
53
+ ok = true;
54
+ constructor(bytes) {
55
+ this.bytes = bytes;
56
+ }
57
+ remaining() {
58
+ return this.bytes.length - this.pos;
59
+ }
60
+ u16() {
61
+ if (!this.ok || this.pos + 2 > this.bytes.length) {
62
+ this.ok = false;
63
+ return 0;
64
+ }
65
+ const out = this.bytes.readUInt16LE(this.pos);
66
+ this.pos += 2;
67
+ return out;
68
+ }
69
+ u32() {
70
+ if (!this.ok || this.pos + 4 > this.bytes.length) {
71
+ this.ok = false;
72
+ return 0;
73
+ }
74
+ const out = this.bytes.readUInt32LE(this.pos);
75
+ this.pos += 4;
76
+ return out;
77
+ }
78
+ string() {
79
+ const len = this.u32();
80
+ if (!this.ok || len > MAX_NAME_BYTES || this.pos + len > this.bytes.length) {
81
+ this.ok = false;
82
+ return '';
83
+ }
84
+ try {
85
+ const out = UTF8_DECODER.decode(this.bytes.subarray(this.pos, this.pos + len));
86
+ this.pos += len;
87
+ return out;
88
+ }
89
+ catch {
90
+ this.ok = false;
91
+ return '';
92
+ }
93
+ }
94
+ }
@@ -1,3 +1,4 @@
1
1
  export { __resetDbForTests, __setDbCatalogForTests, buildDatabaseImports, configureDbPersistence, DevDatabase, devDb, persistDb, setDbCatalog, } from './database.js';
2
2
  export { parseCatalog } from './catalog.js';
3
+ export { type DeriveEntry, derivesForWrites, parseDerives } from './derives.js';
3
4
  export { CollectionFamily, DbFunctionKind, type DbDevState, freshDbState } from './types.js';
@@ -1,3 +1,4 @@
1
1
  export { __resetDbForTests, __setDbCatalogForTests, buildDatabaseImports, configureDbPersistence, DevDatabase, devDb, persistDb, setDbCatalog, } from './database.js';
2
2
  export { parseCatalog } from './catalog.js';
3
+ export { derivesForWrites, parseDerives } from './derives.js';
3
4
  export { CollectionFamily, DbFunctionKind, freshDbState } from './types.js';
@@ -37,6 +37,7 @@ export interface DbDevState {
37
37
  lastResult: Buffer | null;
38
38
  lastResultVersion: number;
39
39
  functionKind: DbFunctionKind;
40
+ writtenCollections: Set<string>;
40
41
  }
41
42
  export declare function freshDbState(): DbDevState;
42
43
  export interface Reservation {
@@ -25,6 +25,7 @@ export function freshDbState() {
25
25
  lastResult: null,
26
26
  lastResultVersion: -1,
27
27
  functionKind: DbFunctionKind.Job,
28
+ writtenCollections: new Set(),
28
29
  };
29
30
  }
30
31
  export const MAX_RESERVATIONS = 4096;
@@ -1,7 +1,11 @@
1
- import { type Request, type Response, type Server } from '@dacely/hyper-express';
1
+ import { type Request, type Response, type Server, type Websocket } from '@dacely/hyper-express';
2
2
  export interface ViteTarget {
3
3
  readonly host: string;
4
4
  readonly port: number;
5
5
  }
6
6
  export declare function proxyToVite(request: Request, response: Response, target: ViteTarget): Promise<void>;
7
7
  export declare function wireWebsocketProxy(app: Server, target: ViteTarget): void;
8
+ export declare function pipeToVite(ws: Websocket, target: ViteTarget, ctx: {
9
+ url: string;
10
+ protocol: string;
11
+ }): void;
@@ -59,41 +59,44 @@ export function wireWebsocketProxy(app, target) {
59
59
  });
60
60
  });
61
61
  app.ws('/*', { message_type: 'Buffer', idle_timeout: 120, max_payload_length: 16 * 1024 * 1024 }, (ws) => {
62
- const { url, protocol } = ws.context;
63
- const upstream = new WebSocket(`ws://${target.host}:${String(target.port)}${url}`, protocol ? protocol.split(',').map((p) => p.trim()) : []);
64
- upstream.binaryType = 'arraybuffer';
65
- const pending = [];
66
- let open = false;
67
- upstream.onopen = () => {
68
- open = true;
69
- for (const m of pending)
70
- upstream.send(m);
71
- pending.length = 0;
72
- };
73
- upstream.onmessage = (event) => {
74
- if (typeof event.data === 'string')
75
- ws.send(event.data);
76
- else
77
- ws.send(Buffer.from(event.data), true);
78
- };
79
- upstream.onclose = (event) => {
80
- ws.close(event.code, event.reason);
81
- };
82
- upstream.onerror = () => {
83
- ws.close();
84
- };
85
- ws.on('message', (message, isBinary) => {
86
- const m = toUpstreamMessage(message, isBinary);
87
- if (open)
88
- upstream.send(m);
89
- else
90
- pending.push(m);
91
- });
92
- ws.on('close', () => {
93
- if (upstream.readyState === WebSocket.OPEN ||
94
- upstream.readyState === WebSocket.CONNECTING) {
95
- upstream.close();
96
- }
97
- });
62
+ pipeToVite(ws, target, ws.context);
63
+ });
64
+ }
65
+ export function pipeToVite(ws, target, ctx) {
66
+ const { url, protocol } = ctx;
67
+ const upstream = new WebSocket(`ws://${target.host}:${String(target.port)}${url}`, protocol ? protocol.split(',').map((p) => p.trim()) : []);
68
+ upstream.binaryType = 'arraybuffer';
69
+ const pending = [];
70
+ let open = false;
71
+ upstream.onopen = () => {
72
+ open = true;
73
+ for (const m of pending)
74
+ upstream.send(m);
75
+ pending.length = 0;
76
+ };
77
+ upstream.onmessage = (event) => {
78
+ if (typeof event.data === 'string')
79
+ ws.send(event.data);
80
+ else
81
+ ws.send(Buffer.from(event.data), true);
82
+ };
83
+ upstream.onclose = (event) => {
84
+ ws.close(event.code, event.reason);
85
+ };
86
+ upstream.onerror = () => {
87
+ ws.close();
88
+ };
89
+ ws.on('message', (message, isBinary) => {
90
+ const m = toUpstreamMessage(message, isBinary);
91
+ if (open)
92
+ upstream.send(m);
93
+ else
94
+ pending.push(m);
95
+ });
96
+ ws.on('close', () => {
97
+ if (upstream.readyState === WebSocket.OPEN ||
98
+ upstream.readyState === WebSocket.CONNECTING) {
99
+ upstream.close();
100
+ }
98
101
  });
99
102
  }
@@ -0,0 +1,62 @@
1
+ import { type Request, type Response, type Server } from '@dacely/hyper-express';
2
+ import { type EnvelopeRequest } from './envelope.js';
3
+ import { type CacheableResult } from './cache.js';
4
+ import { WasmServerModule } from '../runtime/module.js';
5
+ import { type SsrResult, type SsrRoute } from '../ssr.js';
6
+ export declare const DEFAULT_MAX_BODY_LENGTH: number;
7
+ export declare const MAX_BODY_BUFFER: number;
8
+ export declare const HTTP_IDLE_TIMEOUT = 60;
9
+ export declare const HTTP_RESPONSE_TIMEOUT = 120;
10
+ export declare const MIME: Readonly<Record<string, string>>;
11
+ export interface RuntimeServerOptions {
12
+ readonly maxBodyLength?: number;
13
+ }
14
+ export declare function runtimeServerOptions(options: RuntimeServerOptions): {
15
+ max_body_length: number;
16
+ max_body_buffer: number;
17
+ fast_abort: true;
18
+ idle_timeout: number;
19
+ response_timeout: number;
20
+ };
21
+ export declare function installRuntimeErrorHandler(app: Server): void;
22
+ export declare function isDispatchableMethod(method: string): boolean;
23
+ export declare function resolveFileInside(root: string, file: string): string | null;
24
+ export declare function resolveStaticFile(root: string, requestPath: string): string | null;
25
+ export interface PreparedHttpResponse {
26
+ readonly status: number;
27
+ readonly headers: readonly (readonly [string, string])[];
28
+ readonly body: Uint8Array;
29
+ readonly sendfile: string | null;
30
+ }
31
+ export declare function toEnvelopeRequest(request: Request): Promise<EnvelopeRequest>;
32
+ export declare function prepareWasmResponse(root: string, result: Pick<CacheableResult, 'status' | 'headers' | 'body' | 'sendfile'>, serverHeader: string): PreparedHttpResponse;
33
+ export declare function sendPreparedResponse(response: Response, out: PreparedHttpResponse): void;
34
+ export declare function sendWasmResponse(response: Response, root: string, result: Pick<CacheableResult, 'status' | 'headers' | 'body' | 'sendfile'>, serverHeader: string): void;
35
+ export declare function prepareSsrResponse(out: SsrResult, headOnly: boolean, serverHeader: string): PreparedHttpResponse;
36
+ export declare function sendSsr(response: Response, out: SsrResult, headOnly: boolean, serverHeader: string): void;
37
+ export interface WasmDispatchOutcome {
38
+ readonly envelopeReq: EnvelopeRequest;
39
+ readonly handled: boolean;
40
+ }
41
+ export interface EnvelopeDispatchOutcome {
42
+ readonly result: CacheableResult | null;
43
+ readonly handled: boolean;
44
+ }
45
+ export declare function dispatchEnvelopeRequest(options: {
46
+ readonly module: WasmServerModule;
47
+ readonly envelopeReq: EnvelopeRequest;
48
+ readonly method: string;
49
+ readonly url: string;
50
+ readonly cacheHost: string;
51
+ readonly hasAuth: boolean;
52
+ }): EnvelopeDispatchOutcome;
53
+ export declare function dispatchWasmRequest(options: {
54
+ readonly module: WasmServerModule;
55
+ readonly request: Request;
56
+ readonly response: Response;
57
+ readonly root: string;
58
+ readonly cacheHost: string;
59
+ readonly serverHeader: string;
60
+ readonly errorPrefix: string;
61
+ }): Promise<WasmDispatchOutcome>;
62
+ export declare function assembleRouteSsr(route: SsrRoute, module: WasmServerModule | null, envelopeReq: EnvelopeRequest): SsrResult | null;
@@ -0,0 +1,194 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import pc from 'picocolors';
4
+ import { METHOD_CODES } from './envelope.js';
5
+ import { applyCacheRule, lookupCache } from './cache.js';
6
+ import { assembleSsr } from '../ssr.js';
7
+ export const DEFAULT_MAX_BODY_LENGTH = 1024 * 1024 * 8;
8
+ export const MAX_BODY_BUFFER = 1024 * 32;
9
+ export const HTTP_IDLE_TIMEOUT = 60;
10
+ export const HTTP_RESPONSE_TIMEOUT = 120;
11
+ export const MIME = {
12
+ '.html': 'text/html; charset=utf-8',
13
+ '.js': 'text/javascript; charset=utf-8',
14
+ '.mjs': 'text/javascript; charset=utf-8',
15
+ '.css': 'text/css; charset=utf-8',
16
+ '.json': 'application/json; charset=utf-8',
17
+ '.txt': 'text/plain; charset=utf-8',
18
+ '.svg': 'image/svg+xml',
19
+ '.png': 'image/png',
20
+ '.jpg': 'image/jpeg',
21
+ '.jpeg': 'image/jpeg',
22
+ '.webp': 'image/webp',
23
+ '.avif': 'image/avif',
24
+ '.gif': 'image/gif',
25
+ '.ico': 'image/x-icon',
26
+ '.wasm': 'application/wasm',
27
+ '.woff2': 'font/woff2',
28
+ };
29
+ export function runtimeServerOptions(options) {
30
+ return {
31
+ max_body_length: options.maxBodyLength ?? DEFAULT_MAX_BODY_LENGTH,
32
+ max_body_buffer: MAX_BODY_BUFFER,
33
+ fast_abort: true,
34
+ idle_timeout: HTTP_IDLE_TIMEOUT,
35
+ response_timeout: HTTP_RESPONSE_TIMEOUT,
36
+ };
37
+ }
38
+ export function installRuntimeErrorHandler(app) {
39
+ app.set_error_handler((_request, response, error) => {
40
+ if (response.completed)
41
+ return;
42
+ response.atomic(() => {
43
+ response.status(500).send(`internal error: ${error.message}\n`);
44
+ });
45
+ });
46
+ }
47
+ export function isDispatchableMethod(method) {
48
+ return METHOD_CODES[method] !== undefined;
49
+ }
50
+ export function resolveFileInside(root, file) {
51
+ const resolved = path.resolve(root, file);
52
+ if (resolved !== root && !resolved.startsWith(root + path.sep))
53
+ return null;
54
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile())
55
+ return null;
56
+ return resolved;
57
+ }
58
+ export function resolveStaticFile(root, requestPath) {
59
+ let decoded;
60
+ try {
61
+ decoded = decodeURIComponent(requestPath);
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ if (decoded === '/' || decoded === '')
67
+ return null;
68
+ return resolveFileInside(root, decoded.replace(/^\/+/, ''));
69
+ }
70
+ export async function toEnvelopeRequest(request) {
71
+ const hasBody = request.method !== 'GET' && request.method !== 'HEAD';
72
+ const body = hasBody ? new Uint8Array(await request.buffer()) : new Uint8Array(0);
73
+ const xff = request.headers['x-forwarded-for'];
74
+ const clientIp = typeof xff === 'string' && xff.length > 0 ? xff.split(',')[0].trim() : '127.0.0.1';
75
+ return {
76
+ method: request.method,
77
+ path: request.url,
78
+ headers: Object.entries(request.headers),
79
+ body,
80
+ clientIp,
81
+ };
82
+ }
83
+ export function prepareWasmResponse(root, result, serverHeader) {
84
+ const headers = [];
85
+ let hasContentType = false;
86
+ for (const [name, value] of result.headers) {
87
+ if (name.toLowerCase() === 'content-type')
88
+ hasContentType = true;
89
+ headers.push([name, value]);
90
+ }
91
+ headers.push(['server', serverHeader]);
92
+ if (result.sendfile !== null) {
93
+ const file = resolveFileInside(root, result.sendfile);
94
+ if (file === null) {
95
+ return {
96
+ status: 404,
97
+ headers: [
98
+ ['server', serverHeader],
99
+ ['content-type', 'text/plain; charset=utf-8'],
100
+ ],
101
+ body: new TextEncoder().encode('not found\n'),
102
+ sendfile: null,
103
+ };
104
+ }
105
+ if (!hasContentType) {
106
+ headers.push([
107
+ 'content-type',
108
+ MIME[path.extname(file).toLowerCase()] ?? 'application/octet-stream',
109
+ ]);
110
+ }
111
+ return { status: result.status, headers, body: new Uint8Array(0), sendfile: file };
112
+ }
113
+ if (!hasContentType)
114
+ headers.push(['content-type', 'text/plain; charset=utf-8']);
115
+ return { status: result.status, headers, body: result.body, sendfile: null };
116
+ }
117
+ export function sendPreparedResponse(response, out) {
118
+ response.status(out.status);
119
+ for (const [name, value] of out.headers)
120
+ response.header(name, value);
121
+ if (out.sendfile !== null) {
122
+ response.sendFile(out.sendfile);
123
+ return;
124
+ }
125
+ response.send(Buffer.from(out.body.buffer, out.body.byteOffset, out.body.length));
126
+ }
127
+ export function sendWasmResponse(response, root, result, serverHeader) {
128
+ sendPreparedResponse(response, prepareWasmResponse(root, result, serverHeader));
129
+ }
130
+ export function prepareSsrResponse(out, headOnly, serverHeader) {
131
+ const headers = [];
132
+ let hasContentType = false;
133
+ for (const [name, value] of out.headers) {
134
+ if (name.toLowerCase() === 'content-type')
135
+ hasContentType = true;
136
+ headers.push([name, value]);
137
+ }
138
+ if (!hasContentType)
139
+ headers.push(['content-type', 'text/html; charset=utf-8']);
140
+ headers.push(['server', serverHeader]);
141
+ return {
142
+ status: out.status,
143
+ headers,
144
+ body: headOnly ? new Uint8Array(0) : out.html,
145
+ sendfile: null,
146
+ };
147
+ }
148
+ export function sendSsr(response, out, headOnly, serverHeader) {
149
+ sendPreparedResponse(response, prepareSsrResponse(out, headOnly, serverHeader));
150
+ }
151
+ export function dispatchEnvelopeRequest(options) {
152
+ const cached = lookupCache(options.cacheHost, options.method, options.url, options.envelopeReq.body);
153
+ if (cached !== null)
154
+ return { result: cached, handled: true };
155
+ const result = options.module.dispatch(options.envelopeReq);
156
+ if (result.unhandled)
157
+ return { result: null, handled: false };
158
+ return {
159
+ result: applyCacheRule(options.cacheHost, options.method, options.url, options.envelopeReq.body, options.hasAuth, result),
160
+ handled: true,
161
+ };
162
+ }
163
+ export async function dispatchWasmRequest(options) {
164
+ const envelopeReq = await toEnvelopeRequest(options.request);
165
+ const hasAuth = options.request.headers.cookie !== undefined ||
166
+ options.request.headers.authorization !== undefined;
167
+ try {
168
+ const dispatch = dispatchEnvelopeRequest({
169
+ module: options.module,
170
+ envelopeReq,
171
+ method: options.request.method,
172
+ url: options.request.url,
173
+ cacheHost: options.cacheHost,
174
+ hasAuth,
175
+ });
176
+ if (dispatch.result !== null) {
177
+ sendWasmResponse(options.response, options.root, dispatch.result, options.serverHeader);
178
+ return { envelopeReq, handled: true };
179
+ }
180
+ }
181
+ catch (e) {
182
+ process.stdout.write(pc.red(` ${options.errorPrefix} ${options.request.method} ${options.request.path} server error: ${String(e)}`) + '\n');
183
+ options.response.status(500).send('internal error\n');
184
+ return { envelopeReq, handled: true };
185
+ }
186
+ return { envelopeReq, handled: false };
187
+ }
188
+ export function assembleRouteSsr(route, module, envelopeReq) {
189
+ if (route.entries.length === 0)
190
+ return { status: 200, headers: [], html: route.tmpl };
191
+ if (module === null || !module.available)
192
+ return null;
193
+ return assembleSsr(route, module.dispatchRender(envelopeReq));
194
+ }
@@ -1,5 +1,7 @@
1
1
  export { startDevServer } from './server.js';
2
2
  export type { DevServerOptions, RunningDevServer } from './server.js';
3
+ export { loadBuiltSsrTemplates, startBuiltServer } from './production.js';
4
+ export type { BuiltServerOptions, RunningBuiltServer } from './production.js';
3
5
  export { METHOD_CODES, encodeRequestEnvelope, decodeResponseEnvelope, unpackHandleResult, } from './http/envelope.js';
4
6
  export type { EnvelopeRequest, EnvelopeResponse } from './http/envelope.js';
5
7
  export { WasmServerModule, WasmAbortError, UNHANDLED_HEADER } from './runtime/module.js';
@@ -1,4 +1,5 @@
1
1
  export { startDevServer } from './server.js';
2
+ export { loadBuiltSsrTemplates, startBuiltServer } from './production.js';
2
3
  export { METHOD_CODES, encodeRequestEnvelope, decodeResponseEnvelope, unpackHandleResult, } from './http/envelope.js';
3
4
  export { WasmServerModule, WasmAbortError, UNHANDLED_HEADER } from './runtime/module.js';
4
5
  export { buildHostImports, freshDispatchState } from './runtime/host.js';
@@ -0,0 +1,50 @@
1
+ export interface ThreadedRequest {
2
+ readonly id: number;
3
+ readonly method: string;
4
+ readonly url: string;
5
+ readonly path: string;
6
+ readonly headers: readonly (readonly [string, string])[];
7
+ readonly body: string;
8
+ readonly clientIp: string;
9
+ }
10
+ export interface ThreadedHttpResponse {
11
+ readonly kind: 'response';
12
+ readonly status: number;
13
+ readonly headers: readonly (readonly [string, string])[];
14
+ readonly body: string;
15
+ readonly sendfile: string | null;
16
+ }
17
+ export interface ThreadedFallbackResponse {
18
+ readonly kind: 'fallback';
19
+ }
20
+ export type ThreadedReply = ThreadedHttpResponse | ThreadedFallbackResponse;
21
+ export type WorkerToPrimaryMessage = {
22
+ readonly toil: 'ready';
23
+ readonly port: number;
24
+ readonly workerId: number;
25
+ } | {
26
+ readonly toil: 'clientCount';
27
+ readonly count: number;
28
+ readonly workerId: number;
29
+ } | {
30
+ readonly toil: 'request';
31
+ readonly request: ThreadedRequest;
32
+ };
33
+ export type PrimaryToWorkerMessage = {
34
+ readonly toil: 'start';
35
+ readonly workerId: number;
36
+ readonly options: unknown;
37
+ } | {
38
+ readonly toil: 'reply';
39
+ readonly id: number;
40
+ readonly reply: ThreadedReply;
41
+ } | {
42
+ readonly toil: 'broadcast';
43
+ readonly message: string;
44
+ } | {
45
+ readonly toil: 'shutdown';
46
+ };
47
+ export declare function encodeBody(body: Uint8Array): string;
48
+ export declare function decodeBody(body: string): Uint8Array;
49
+ export declare function isWorkerToPrimaryMessage(value: unknown): value is WorkerToPrimaryMessage;
50
+ export declare function isPrimaryToWorkerMessage(value: unknown): value is PrimaryToWorkerMessage;