toiljs 0.0.66 → 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.
- package/CHANGELOG.md +10 -0
- package/README.md +63 -61
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +13 -1
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/index.d.ts +2 -0
- package/build/client/index.js +1 -0
- package/build/client/rpc.js +21 -1
- package/build/client/stream/client.d.ts +11 -0
- package/build/client/stream/client.js +59 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/config.d.ts +2 -0
- package/build/compiler/config.js +9 -7
- package/build/compiler/index.d.ts +1 -0
- package/build/compiler/index.js +22 -6
- package/build/compiler/toil-docs.generated.js +3 -3
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/daemon/index.js +4 -3
- package/build/devserver/daemon/runtime.d.ts +13 -0
- package/build/devserver/daemon/runtime.js +29 -0
- package/build/devserver/db/catalog.js +8 -12
- package/build/devserver/db/database.d.ts +1 -0
- package/build/devserver/db/database.js +10 -0
- package/build/devserver/db/derives.d.ts +7 -0
- package/build/devserver/db/derives.js +94 -0
- package/build/devserver/db/index.d.ts +1 -0
- package/build/devserver/db/index.js +1 -0
- package/build/devserver/db/types.d.ts +1 -0
- package/build/devserver/db/types.js +1 -0
- package/build/devserver/http/proxy.d.ts +5 -1
- package/build/devserver/http/proxy.js +39 -36
- package/build/devserver/http/runtime.d.ts +62 -0
- package/build/devserver/http/runtime.js +194 -0
- package/build/devserver/index.d.ts +2 -0
- package/build/devserver/index.js +1 -0
- package/build/devserver/production-ipc.d.ts +50 -0
- package/build/devserver/production-ipc.js +21 -0
- package/build/devserver/production-worker.d.ts +1 -0
- package/build/devserver/production-worker.js +73 -0
- package/build/devserver/production.d.ts +35 -0
- package/build/devserver/production.js +502 -0
- package/build/devserver/runtime/module.d.ts +5 -0
- package/build/devserver/runtime/module.js +47 -1
- package/build/devserver/server.d.ts +1 -0
- package/build/devserver/server.js +32 -145
- package/build/devserver/ssr.d.ts +2 -0
- package/build/devserver/ssr.js +19 -2
- package/build/devserver/stream/catalog.d.ts +20 -0
- package/build/devserver/stream/catalog.js +54 -0
- package/build/devserver/stream/host.d.ts +9 -0
- package/build/devserver/stream/host.js +15 -0
- package/build/devserver/stream/index.d.ts +37 -0
- package/build/devserver/stream/index.js +220 -0
- package/build/devserver/stream/manager.d.ts +34 -0
- package/build/devserver/stream/manager.js +103 -0
- package/build/devserver/stream/router.d.ts +25 -0
- package/build/devserver/stream/router.js +64 -0
- package/build/devserver/stream/wire.d.ts +5 -0
- package/build/devserver/stream/wire.js +33 -0
- package/build/devserver/stream/ws.d.ts +18 -0
- package/build/devserver/stream/ws.js +46 -0
- package/build/devserver/wasm/surface.d.ts +1 -1
- package/build/devserver/wasm/surface.js +1 -1
- package/docs/cli.md +3 -1
- package/docs/getting-started.md +7 -7
- package/docs/tiers.md +15 -9
- package/examples/basic/server/routes/Guestbook.ts +38 -13
- package/package.json +2 -2
- package/src/cli/index.ts +14 -1
- package/src/client/index.ts +2 -0
- package/src/client/rpc.ts +25 -1
- package/src/client/stream/client.ts +107 -0
- package/src/compiler/config.ts +15 -7
- package/src/compiler/index.ts +43 -18
- package/src/compiler/toil-docs.generated.ts +3 -3
- package/src/devserver/daemon/index.ts +7 -7
- package/src/devserver/daemon/runtime.ts +48 -0
- package/src/devserver/db/catalog.ts +9 -13
- package/src/devserver/db/database.ts +14 -0
- package/src/devserver/db/derives.ts +121 -0
- package/src/devserver/db/index.ts +1 -0
- package/src/devserver/db/types.ts +6 -0
- package/src/devserver/http/proxy.ts +53 -39
- package/src/devserver/http/runtime.ts +287 -0
- package/src/devserver/index.ts +2 -0
- package/src/devserver/production-ipc.ts +63 -0
- package/src/devserver/production-worker.ts +83 -0
- package/src/devserver/production.ts +706 -0
- package/src/devserver/runtime/module.ts +95 -1
- package/src/devserver/server.ts +52 -201
- package/src/devserver/ssr.ts +23 -3
- package/src/devserver/stream/catalog.ts +106 -0
- package/src/devserver/stream/host.ts +42 -0
- package/src/devserver/stream/index.ts +308 -0
- package/src/devserver/stream/manager.ts +163 -0
- package/src/devserver/stream/router.ts +101 -0
- package/src/devserver/stream/wire.ts +58 -0
- package/src/devserver/stream/ws.ts +76 -0
- package/src/devserver/wasm/surface.ts +5 -7
- package/test/built-ssr.test.ts +98 -0
- package/test/daemon-build.test.ts +15 -7
- package/test/daemon-catalog.test.ts +17 -8
- package/test/devserver-database.test.ts +8 -8
- package/test/devserver.test.ts +20 -4
- package/test/example-guestbook.test.ts +8 -5
- package/test/fixtures/stream-echo.ts +26 -0
- package/test/fixtures/stream-gate.ts +24 -0
- package/test/fixtures/stream-trap.ts +18 -0
- package/test/rpc-bignum-wire.test.ts +8 -8
- package/test/stream-emulation.test.ts +394 -0
|
@@ -139,20 +139,21 @@ export class DaemonHost implements DaemonRuntime {
|
|
|
139
139
|
// else the emulator stays off (fail-closed; section 3.3 / 5.1).
|
|
140
140
|
const surface = parseSurface(bytes);
|
|
141
141
|
if (surface === 'invalid') {
|
|
142
|
-
this.log(
|
|
142
|
+
this.log(
|
|
143
|
+
pc.red(' ✗ cold artifact toil.surface is corrupt; daemon not started') + '\n',
|
|
144
|
+
);
|
|
143
145
|
if (this.running) this.stop();
|
|
144
146
|
this.loadedMtimeMs = mtimeMs;
|
|
145
147
|
return false;
|
|
146
148
|
}
|
|
147
|
-
if (surface
|
|
149
|
+
if (surface.targetMode !== 'cold')
|
|
148
150
|
this.log(
|
|
149
151
|
pc.yellow(' ! ') +
|
|
150
152
|
pc.dim('cold slot holds a hot-mode artifact; ignoring daemon emulator') +
|
|
151
153
|
'\n',
|
|
152
154
|
);
|
|
153
155
|
const catalog = parseDaemonCatalog(bytes);
|
|
154
|
-
const declaresDaemon =
|
|
155
|
-
(surface === 'absent' ? false : surface.flags.daemon) || (catalog?.hasDaemon ?? false);
|
|
156
|
+
const declaresDaemon = surface.flags.daemon || (catalog?.hasDaemon ?? false);
|
|
156
157
|
|
|
157
158
|
// A restart: stop the old box (timers + instance), bump epoch, start fresh.
|
|
158
159
|
if (this.running) this.stop();
|
|
@@ -318,9 +319,8 @@ export class DaemonHost implements DaemonRuntime {
|
|
|
318
319
|
const ret = this.exports.scheduled_tick(task.taskIndex); // packed-i64
|
|
319
320
|
if (ret < 0n)
|
|
320
321
|
this.log(
|
|
321
|
-
pc.yellow(
|
|
322
|
-
|
|
323
|
-
) + '\n',
|
|
322
|
+
pc.yellow(` ⏱ @scheduled ${task.name} returned error ${decodeAbiError(ret)}`) +
|
|
323
|
+
'\n',
|
|
324
324
|
);
|
|
325
325
|
} catch (e) {
|
|
326
326
|
// A trapped tick does NOT tear down the long-lived daemon box (unlike a
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
|
|
3
|
+
import { DaemonHost, daemonEmulationEnabled } from './index.js';
|
|
4
|
+
import type { ResolvedDaemonConfig } from './host.js';
|
|
5
|
+
|
|
6
|
+
export interface DaemonRuntimeOptions {
|
|
7
|
+
readonly coldWasmFile?: string;
|
|
8
|
+
readonly nodeMode?: string;
|
|
9
|
+
readonly daemon?: ResolvedDaemonConfig;
|
|
10
|
+
readonly pollMs?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RunningDaemonRuntime {
|
|
14
|
+
readonly host: DaemonHost;
|
|
15
|
+
close(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Starts the shared cold-artifact daemon runtime used by both `dev` and `start`. */
|
|
19
|
+
export function startDaemonRuntime(options: DaemonRuntimeOptions): RunningDaemonRuntime | null {
|
|
20
|
+
const nodeMode = options.nodeMode ?? 'all';
|
|
21
|
+
if (
|
|
22
|
+
options.coldWasmFile === undefined ||
|
|
23
|
+
!daemonEmulationEnabled(nodeMode) ||
|
|
24
|
+
options.daemon === undefined
|
|
25
|
+
) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const host = new DaemonHost(options.coldWasmFile, options.daemon, nodeMode);
|
|
30
|
+
const pollDaemon = (): void => {
|
|
31
|
+
try {
|
|
32
|
+
host.refresh();
|
|
33
|
+
} catch (e) {
|
|
34
|
+
process.stdout.write(pc.red(` x daemon reload failed: ${String(e)}`) + '\n');
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
pollDaemon();
|
|
38
|
+
const timer = setInterval(pollDaemon, options.pollMs ?? 500);
|
|
39
|
+
timer.unref?.();
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
host,
|
|
43
|
+
close: (): void => {
|
|
44
|
+
clearInterval(timer);
|
|
45
|
+
host.close();
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -48,7 +48,7 @@ export function parseCatalog(wasm: Buffer): DbCatalogState {
|
|
|
48
48
|
|
|
49
49
|
const r = new DataReader(sec);
|
|
50
50
|
const version = r.readU16();
|
|
51
|
-
if (!r.ok ||
|
|
51
|
+
if (!r.ok || version !== 1) return { kind: 'malformed' };
|
|
52
52
|
const ndb = r.readU16();
|
|
53
53
|
for (let d = 0; d < ndb && r.ok; d++) {
|
|
54
54
|
const db = r.readString();
|
|
@@ -63,18 +63,14 @@ export function parseCatalog(wasm: Buffer): DbCatalogState {
|
|
|
63
63
|
r.readU32(); // generation
|
|
64
64
|
const replication = r.readU8(); // emitter order: replication then placement
|
|
65
65
|
const placement = r.readU8();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
fillMaxWaitMs
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
)
|
|
75
|
-
return { kind: 'malformed' };
|
|
76
|
-
fillAllowStale = fillAllowStaleByte === 1;
|
|
77
|
-
}
|
|
66
|
+
const fillMaxWaitMs = r.readU32();
|
|
67
|
+
const fillAllowStaleByte = r.readU8();
|
|
68
|
+
if (
|
|
69
|
+
fillMaxWaitMs > MAX_FILL_WAIT_MS ||
|
|
70
|
+
(fillAllowStaleByte !== 0 && fillAllowStaleByte !== 1)
|
|
71
|
+
)
|
|
72
|
+
return { kind: 'malformed' };
|
|
73
|
+
const fillAllowStale = fillAllowStaleByte === 1;
|
|
78
74
|
const nFields = r.readU16();
|
|
79
75
|
for (let f = 0; f < nFields; f++) {
|
|
80
76
|
r.readString(); // field name
|
|
@@ -700,6 +700,7 @@ export class DevDatabase {
|
|
|
700
700
|
if (outcome.kind === 'unit') {
|
|
701
701
|
this.store.set(sk, value);
|
|
702
702
|
this.stampVersion(coll, sk); // stamp the value type's current schema version
|
|
703
|
+
this.recordWrite(db, coll);
|
|
703
704
|
}
|
|
704
705
|
this.recordIdemFinish(coll, key, 'C', idem, requestHash, outcome);
|
|
705
706
|
return this.replayRecordOutcome(db, outcome);
|
|
@@ -732,6 +733,7 @@ export class DevDatabase {
|
|
|
732
733
|
if (outcome.kind === 'value') {
|
|
733
734
|
this.store.set(sk, v);
|
|
734
735
|
this.stampVersion(coll, sk); // a patch rewrites the row at the current version
|
|
736
|
+
this.recordWrite(db, coll);
|
|
735
737
|
}
|
|
736
738
|
this.recordIdemFinish(coll, key, 'P', idem, requestHash, outcome);
|
|
737
739
|
return this.replayRecordOutcome(db, outcome);
|
|
@@ -1023,6 +1025,15 @@ export class DevDatabase {
|
|
|
1023
1025
|
|
|
1024
1026
|
// `delta` is the wasm i64 (a BigInt across the boundary); `BigInt()`
|
|
1025
1027
|
// normalizes the test's plain-number form too. Saturates like the edge.
|
|
1028
|
+
/** Note a successful write to a SOURCE collection, so the runtime can re-run
|
|
1029
|
+
* the `@derive` materializers that depend on it after this dispatch. A
|
|
1030
|
+
* derive's OWN writes run under FunctionKind=Derive and must never
|
|
1031
|
+
* re-trigger a derive (which would loop), so they are never recorded. */
|
|
1032
|
+
private recordWrite(db: DbDevState, coll: DevCollectionHandle): void {
|
|
1033
|
+
if (db.functionKind === DbFunctionKind.Derive) return;
|
|
1034
|
+
db.writtenCollections.add(coll.name);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1026
1037
|
counterAdd(
|
|
1027
1038
|
ref: MemoryRef,
|
|
1028
1039
|
db: DbDevState,
|
|
@@ -1045,6 +1056,7 @@ export class DevDatabase {
|
|
|
1045
1056
|
}
|
|
1046
1057
|
const sk = storeKey(coll.name, key);
|
|
1047
1058
|
this.counters.set(sk, satI64((this.counters.get(sk) ?? 0n) + d));
|
|
1059
|
+
this.recordWrite(db, coll);
|
|
1048
1060
|
return 0;
|
|
1049
1061
|
}
|
|
1050
1062
|
|
|
@@ -1086,6 +1098,7 @@ export class DevDatabase {
|
|
|
1086
1098
|
log.push(ev);
|
|
1087
1099
|
(this.eventVersions.get(sk) ?? this.eventVersions.set(sk, []).get(sk)!).push(sv);
|
|
1088
1100
|
}
|
|
1101
|
+
this.recordWrite(db, coll);
|
|
1089
1102
|
return 0;
|
|
1090
1103
|
}
|
|
1091
1104
|
|
|
@@ -1124,6 +1137,7 @@ export class DevDatabase {
|
|
|
1124
1137
|
(this.eventVersions.get(sk) ?? this.eventVersions.set(sk, []).get(sk)!).push(sv);
|
|
1125
1138
|
}
|
|
1126
1139
|
seen.add(evid);
|
|
1140
|
+
this.recordWrite(db, coll);
|
|
1127
1141
|
return 1;
|
|
1128
1142
|
}
|
|
1129
1143
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { customSection } from '../wasm/sections.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parses the `toildb.derives` wiring section emitted by toilscript for a
|
|
5
|
+
* `@database` class with `@derive` materializer methods (see the compiler's
|
|
6
|
+
* `buildToilDbDerives`). It maps each derive to its owning `@database` class, so
|
|
7
|
+
* the dev runtime (`runtime/module.ts`) can, after a dispatch writes a source
|
|
8
|
+
* collection, re-run that database's derives under FunctionKind=Derive. Fails
|
|
9
|
+
* closed: any malformed byte yields `[]` (no derive runs) rather than throwing.
|
|
10
|
+
*
|
|
11
|
+
* Section layout (LE), mirroring `buildToilDbDerives`:
|
|
12
|
+
* u16 format_version = 1
|
|
13
|
+
* u16 n_derives
|
|
14
|
+
* per derive: u16 derive_id, str db_name, str method_name (str = u32 len + bytes)
|
|
15
|
+
*/
|
|
16
|
+
export interface DeriveEntry {
|
|
17
|
+
readonly deriveId: number;
|
|
18
|
+
readonly dbName: string;
|
|
19
|
+
readonly methodName: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const SECTION = 'toildb.derives';
|
|
23
|
+
const VERSION = 1;
|
|
24
|
+
const MAX_SECTION_BYTES = 128 * 1024;
|
|
25
|
+
const MAX_DERIVES = 1024;
|
|
26
|
+
const MAX_NAME_BYTES = 1024;
|
|
27
|
+
const UTF8_DECODER = new TextDecoder('utf-8', { fatal: true });
|
|
28
|
+
|
|
29
|
+
export function parseDerives(wasm: Buffer): readonly DeriveEntry[] {
|
|
30
|
+
let section: Buffer | null;
|
|
31
|
+
try {
|
|
32
|
+
section = customSection(wasm, SECTION);
|
|
33
|
+
} catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
if (section === null) return [];
|
|
37
|
+
if (section.length > MAX_SECTION_BYTES) return [];
|
|
38
|
+
|
|
39
|
+
const r = new Reader(section);
|
|
40
|
+
const version = r.u16();
|
|
41
|
+
if (!r.ok || version !== VERSION) return [];
|
|
42
|
+
const count = r.u16();
|
|
43
|
+
if (!r.ok || count > MAX_DERIVES) return [];
|
|
44
|
+
|
|
45
|
+
const derives: DeriveEntry[] = [];
|
|
46
|
+
for (let i = 0; i < count && r.ok; i++) {
|
|
47
|
+
const deriveId = r.u16();
|
|
48
|
+
const dbName = r.string();
|
|
49
|
+
const methodName = r.string();
|
|
50
|
+
if (!r.ok || dbName.length === 0) return [];
|
|
51
|
+
derives.push({ deriveId, dbName, methodName });
|
|
52
|
+
}
|
|
53
|
+
if (!r.ok || r.remaining() !== 0) return [];
|
|
54
|
+
return derives;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The derives whose owning `@database` had at least one source collection
|
|
59
|
+
* written during this dispatch. `written` holds "Db/coll" store keys; the
|
|
60
|
+
* database is the prefix before the first `/`. Each affected derive appears at
|
|
61
|
+
* most once (coalescing: many writes to one database run its derives once).
|
|
62
|
+
*/
|
|
63
|
+
export function derivesForWrites(
|
|
64
|
+
derives: readonly DeriveEntry[],
|
|
65
|
+
written: ReadonlySet<string>,
|
|
66
|
+
): readonly DeriveEntry[] {
|
|
67
|
+
if (derives.length === 0 || written.size === 0) return [];
|
|
68
|
+
const dbs = new Set<string>();
|
|
69
|
+
for (const key of written) {
|
|
70
|
+
const slash = key.indexOf('/');
|
|
71
|
+
dbs.add(slash >= 0 ? key.slice(0, slash) : key);
|
|
72
|
+
}
|
|
73
|
+
return derives.filter((d) => dbs.has(d.dbName));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class Reader {
|
|
77
|
+
private pos = 0;
|
|
78
|
+
ok = true;
|
|
79
|
+
|
|
80
|
+
constructor(private readonly bytes: Buffer) {}
|
|
81
|
+
|
|
82
|
+
remaining(): number {
|
|
83
|
+
return this.bytes.length - this.pos;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
u16(): number {
|
|
87
|
+
if (!this.ok || this.pos + 2 > this.bytes.length) {
|
|
88
|
+
this.ok = false;
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
const out = this.bytes.readUInt16LE(this.pos);
|
|
92
|
+
this.pos += 2;
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
u32(): number {
|
|
97
|
+
if (!this.ok || this.pos + 4 > this.bytes.length) {
|
|
98
|
+
this.ok = false;
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
const out = this.bytes.readUInt32LE(this.pos);
|
|
102
|
+
this.pos += 4;
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
string(): string {
|
|
107
|
+
const len = this.u32();
|
|
108
|
+
if (!this.ok || len > MAX_NAME_BYTES || this.pos + len > this.bytes.length) {
|
|
109
|
+
this.ok = false;
|
|
110
|
+
return '';
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const out = UTF8_DECODER.decode(this.bytes.subarray(this.pos, this.pos + len));
|
|
114
|
+
this.pos += len;
|
|
115
|
+
return out;
|
|
116
|
+
} catch {
|
|
117
|
+
this.ok = false;
|
|
118
|
+
return '';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -15,4 +15,5 @@ export {
|
|
|
15
15
|
setDbCatalog,
|
|
16
16
|
} from './database.js';
|
|
17
17
|
export { parseCatalog } from './catalog.js';
|
|
18
|
+
export { type DeriveEntry, derivesForWrites, parseDerives } from './derives.js';
|
|
18
19
|
export { CollectionFamily, DbFunctionKind, type DbDevState, freshDbState } from './types.js';
|
|
@@ -50,6 +50,11 @@ export interface DbDevState {
|
|
|
50
50
|
lastResult: Buffer | null;
|
|
51
51
|
lastResultVersion: number;
|
|
52
52
|
functionKind: DbFunctionKind;
|
|
53
|
+
/** Names ("Db/coll") of source collections written during this dispatch, so
|
|
54
|
+
* the runtime can re-run the affected `@derive` materializers afterward.
|
|
55
|
+
* Only populated for non-Derive dispatches (a derive's own writes must not
|
|
56
|
+
* re-trigger it - see `database.ts` `recordWrite`). */
|
|
57
|
+
writtenCollections: Set<string>;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
export function freshDbState(): DbDevState {
|
|
@@ -58,6 +63,7 @@ export function freshDbState(): DbDevState {
|
|
|
58
63
|
lastResult: null,
|
|
59
64
|
lastResultVersion: -1,
|
|
60
65
|
functionKind: DbFunctionKind.Job,
|
|
66
|
+
writtenCollections: new Set<string>(),
|
|
61
67
|
};
|
|
62
68
|
}
|
|
63
69
|
|
|
@@ -114,45 +114,59 @@ export function wireWebsocketProxy(app: Server, target: ViteTarget): void {
|
|
|
114
114
|
'/*',
|
|
115
115
|
{ message_type: 'Buffer', idle_timeout: 120, max_payload_length: 16 * 1024 * 1024 },
|
|
116
116
|
(ws: Websocket) => {
|
|
117
|
-
|
|
118
|
-
const upstream = new WebSocket(
|
|
119
|
-
`ws://${target.host}:${String(target.port)}${url}`,
|
|
120
|
-
protocol ? protocol.split(',').map((p) => p.trim()) : [],
|
|
121
|
-
);
|
|
122
|
-
upstream.binaryType = 'arraybuffer';
|
|
123
|
-
|
|
124
|
-
const pending: (string | Uint8Array<ArrayBuffer>)[] = [];
|
|
125
|
-
let open = false;
|
|
126
|
-
|
|
127
|
-
upstream.onopen = (): void => {
|
|
128
|
-
open = true;
|
|
129
|
-
for (const m of pending) upstream.send(m);
|
|
130
|
-
pending.length = 0;
|
|
131
|
-
};
|
|
132
|
-
upstream.onmessage = (event: MessageEvent): void => {
|
|
133
|
-
if (typeof event.data === 'string') ws.send(event.data);
|
|
134
|
-
else ws.send(Buffer.from(event.data as ArrayBuffer), true);
|
|
135
|
-
};
|
|
136
|
-
upstream.onclose = (event: CloseEvent): void => {
|
|
137
|
-
ws.close(event.code, event.reason);
|
|
138
|
-
};
|
|
139
|
-
upstream.onerror = (): void => {
|
|
140
|
-
ws.close();
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
ws.on('message', (message: Buffer, isBinary: boolean) => {
|
|
144
|
-
const m = toUpstreamMessage(message, isBinary);
|
|
145
|
-
if (open) upstream.send(m);
|
|
146
|
-
else pending.push(m);
|
|
147
|
-
});
|
|
148
|
-
ws.on('close', () => {
|
|
149
|
-
if (
|
|
150
|
-
upstream.readyState === WebSocket.OPEN ||
|
|
151
|
-
upstream.readyState === WebSocket.CONNECTING
|
|
152
|
-
) {
|
|
153
|
-
upstream.close();
|
|
154
|
-
}
|
|
155
|
-
});
|
|
117
|
+
pipeToVite(ws, target, ws.context as { url: string; protocol: string });
|
|
156
118
|
},
|
|
157
119
|
);
|
|
158
120
|
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Pipe ONE upgraded websocket to the internal Vite HMR server. The verbatim body extracted from
|
|
124
|
+
* {@link wireWebsocketProxy} so the dev STREAM router (doc 08 4.1 `wireStreams`) can reuse it for every
|
|
125
|
+
* NON-stream upgrade while it handles `@stream`-route upgrades itself - HMR stays byte-for-byte
|
|
126
|
+
* unchanged. `ctx` is the upgrade context (`{ url, protocol }`) the upgrade handler stamped.
|
|
127
|
+
*/
|
|
128
|
+
export function pipeToVite(
|
|
129
|
+
ws: Websocket,
|
|
130
|
+
target: ViteTarget,
|
|
131
|
+
ctx: { url: string; protocol: string },
|
|
132
|
+
): void {
|
|
133
|
+
const { url, protocol } = ctx;
|
|
134
|
+
const upstream = new WebSocket(
|
|
135
|
+
`ws://${target.host}:${String(target.port)}${url}`,
|
|
136
|
+
protocol ? protocol.split(',').map((p) => p.trim()) : [],
|
|
137
|
+
);
|
|
138
|
+
upstream.binaryType = 'arraybuffer';
|
|
139
|
+
|
|
140
|
+
const pending: (string | Uint8Array<ArrayBuffer>)[] = [];
|
|
141
|
+
let open = false;
|
|
142
|
+
|
|
143
|
+
upstream.onopen = (): void => {
|
|
144
|
+
open = true;
|
|
145
|
+
for (const m of pending) upstream.send(m);
|
|
146
|
+
pending.length = 0;
|
|
147
|
+
};
|
|
148
|
+
upstream.onmessage = (event: MessageEvent): void => {
|
|
149
|
+
if (typeof event.data === 'string') ws.send(event.data);
|
|
150
|
+
else ws.send(Buffer.from(event.data as ArrayBuffer), true);
|
|
151
|
+
};
|
|
152
|
+
upstream.onclose = (event: CloseEvent): void => {
|
|
153
|
+
ws.close(event.code, event.reason);
|
|
154
|
+
};
|
|
155
|
+
upstream.onerror = (): void => {
|
|
156
|
+
ws.close();
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
ws.on('message', (message: Buffer, isBinary: boolean) => {
|
|
160
|
+
const m = toUpstreamMessage(message, isBinary);
|
|
161
|
+
if (open) upstream.send(m);
|
|
162
|
+
else pending.push(m);
|
|
163
|
+
});
|
|
164
|
+
ws.on('close', () => {
|
|
165
|
+
if (
|
|
166
|
+
upstream.readyState === WebSocket.OPEN ||
|
|
167
|
+
upstream.readyState === WebSocket.CONNECTING
|
|
168
|
+
) {
|
|
169
|
+
upstream.close();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|