@pattern-js/runtime-node 0.2.0
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/LICENSE +21 -0
- package/README.md +70 -0
- package/dist/bin/pattern.d.ts +20 -0
- package/dist/bin/pattern.d.ts.map +1 -0
- package/dist/bin/pattern.js +365 -0
- package/dist/bin/pattern.js.map +1 -0
- package/dist/cli.d.ts +21 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +75 -0
- package/dist/cli.js.map +1 -0
- package/dist/filesystem.d.ts +38 -0
- package/dist/filesystem.d.ts.map +1 -0
- package/dist/filesystem.js +59 -0
- package/dist/filesystem.js.map +1 -0
- package/dist/http.d.ts +94 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +799 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/load/generator.d.ts +19 -0
- package/dist/load/generator.d.ts.map +1 -0
- package/dist/load/generator.js +94 -0
- package/dist/load/generator.js.map +1 -0
- package/dist/load/index.d.ts +43 -0
- package/dist/load/index.d.ts.map +1 -0
- package/dist/load/index.js +87 -0
- package/dist/load/index.js.map +1 -0
- package/dist/load/recorder.d.ts +36 -0
- package/dist/load/recorder.d.ts.map +1 -0
- package/dist/load/recorder.js +106 -0
- package/dist/load/recorder.js.map +1 -0
- package/dist/load/report.d.ts +28 -0
- package/dist/load/report.d.ts.map +1 -0
- package/dist/load/report.js +84 -0
- package/dist/load/report.js.map +1 -0
- package/dist/load/types.d.ts +85 -0
- package/dist/load/types.d.ts.map +1 -0
- package/dist/load/types.js +51 -0
- package/dist/load/types.js.map +1 -0
- package/dist/mods.d.ts +23 -0
- package/dist/mods.d.ts.map +1 -0
- package/dist/mods.js +48 -0
- package/dist/mods.js.map +1 -0
- package/dist/project.d.ts +94 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +168 -0
- package/dist/project.js.map +1 -0
- package/dist/schedule.d.ts +33 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +132 -0
- package/dist/schedule.js.map +1 -0
- package/dist/trace/index.d.ts +32 -0
- package/dist/trace/index.d.ts.map +1 -0
- package/dist/trace/index.js +48 -0
- package/dist/trace/index.js.map +1 -0
- package/dist/trace/memory.d.ts +83 -0
- package/dist/trace/memory.d.ts.map +1 -0
- package/dist/trace/memory.js +260 -0
- package/dist/trace/memory.js.map +1 -0
- package/dist/trace/sqlite.d.ts +80 -0
- package/dist/trace/sqlite.d.ts.map +1 -0
- package/dist/trace/sqlite.js +344 -0
- package/dist/trace/sqlite.js.map +1 -0
- package/dist/worker/entry.d.ts +16 -0
- package/dist/worker/entry.d.ts.map +1 -0
- package/dist/worker/entry.js +135 -0
- package/dist/worker/entry.js.map +1 -0
- package/dist/worker-pool.d.ts +49 -0
- package/dist/worker-pool.d.ts.map +1 -0
- package/dist/worker-pool.js +203 -0
- package/dist/worker-pool.js.map +1 -0
- package/dist/ws-registry.d.ts +39 -0
- package/dist/ws-registry.d.ts.map +1 -0
- package/dist/ws-registry.js +99 -0
- package/dist/ws-registry.js.map +1 -0
- package/dist/ws.d.ts +72 -0
- package/dist/ws.d.ts.map +1 -0
- package/dist/ws.js +236 -0
- package/dist/ws.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pattern-js/runtime-node — worker-thread pool transport (§4, §8).
|
|
3
|
+
*
|
|
4
|
+
* A `RunTransport` that dispatches each run to a `node:worker_threads` worker,
|
|
5
|
+
* so a run executes off the host event loop (the workflow is the unit of
|
|
6
|
+
* isolation, §1). Streamed out-gate results are reconstructed on the host;
|
|
7
|
+
* cancellation crosses the seam. It is a drop-in for `InProcessTransport` — the
|
|
8
|
+
* scheduler never knows the difference — and the same interface later allows a
|
|
9
|
+
* queue + remote workers (distribution).
|
|
10
|
+
*
|
|
11
|
+
* Workers register the base op catalog plus any `mods` passed in options (each
|
|
12
|
+
* a module specifier the worker `import()`s and `engine.use()`s), so workflows
|
|
13
|
+
* using mod-contributed ops can run on the pool too. A crashed worker rejects
|
|
14
|
+
* its in-flight runs and is respawned in place.
|
|
15
|
+
*/
|
|
16
|
+
import type { RunHandle, RunRequest, RunTransport, TraceEvent } from "@pattern-js/core";
|
|
17
|
+
export interface WorkerPoolOptions {
|
|
18
|
+
/** Number of workers (default: available parallelism − 1, min 1). */
|
|
19
|
+
size?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Mod module specifiers each worker loads at startup (same resolution as
|
|
22
|
+
* `loadMods`: bare npm/workspace specifiers, or absolute paths/file URLs).
|
|
23
|
+
* Without this, workers have only the base op catalog and a workflow using a
|
|
24
|
+
* mod-contributed op fails with "unknown op".
|
|
25
|
+
*/
|
|
26
|
+
mods?: string[];
|
|
27
|
+
/**
|
|
28
|
+
* Receive each offloaded run's trace lifecycle (run start/spans/ready/end),
|
|
29
|
+
* tagged with the worker that ran it — wire it to the host `engine.ingestTrace`
|
|
30
|
+
* so offloaded runs appear in the Runs view like inline ones. Omit to drop
|
|
31
|
+
* worker traces (the runs still execute fine).
|
|
32
|
+
*/
|
|
33
|
+
onTrace?: (evt: TraceEvent) => void;
|
|
34
|
+
}
|
|
35
|
+
export declare class WorkerPoolTransport implements RunTransport {
|
|
36
|
+
private workers;
|
|
37
|
+
private rr;
|
|
38
|
+
private closed;
|
|
39
|
+
private readonly mods;
|
|
40
|
+
private readonly onTrace?;
|
|
41
|
+
constructor(opts?: WorkerPoolOptions);
|
|
42
|
+
private spawn;
|
|
43
|
+
/** Replace a crashed worker so the pool keeps its capacity. */
|
|
44
|
+
private respawn;
|
|
45
|
+
describe(): Record<string, unknown>;
|
|
46
|
+
dispatch(req: RunRequest): RunHandle;
|
|
47
|
+
close(): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=worker-pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-pool.d.ts","sourceRoot":"","sources":["../src/worker-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAa,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAyInG,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,CAAC;CACrC;AAED,qBAAa,mBAAoB,YAAW,YAAY;IACtD,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,EAAE,CAAK;IACf,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAW;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAA4B;gBAEzC,IAAI,GAAE,iBAAsB;IAOxC,OAAO,CAAC,KAAK;IAIb,+DAA+D;IAC/D,OAAO,CAAC,OAAO;IAOf,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IASnC,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,SAAS;IAsB9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAM7B"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pattern-js/runtime-node — worker-thread pool transport (§4, §8).
|
|
3
|
+
*
|
|
4
|
+
* A `RunTransport` that dispatches each run to a `node:worker_threads` worker,
|
|
5
|
+
* so a run executes off the host event loop (the workflow is the unit of
|
|
6
|
+
* isolation, §1). Streamed out-gate results are reconstructed on the host;
|
|
7
|
+
* cancellation crosses the seam. It is a drop-in for `InProcessTransport` — the
|
|
8
|
+
* scheduler never knows the difference — and the same interface later allows a
|
|
9
|
+
* queue + remote workers (distribution).
|
|
10
|
+
*
|
|
11
|
+
* Workers register the base op catalog plus any `mods` passed in options (each
|
|
12
|
+
* a module specifier the worker `import()`s and `engine.use()`s), so workflows
|
|
13
|
+
* using mod-contributed ops can run on the pool too. A crashed worker rejects
|
|
14
|
+
* its in-flight runs and is respawned in place.
|
|
15
|
+
*/
|
|
16
|
+
import { Worker } from "node:worker_threads";
|
|
17
|
+
import { availableParallelism } from "node:os";
|
|
18
|
+
const WORKER_URL = new URL("./worker/entry.js", import.meta.url);
|
|
19
|
+
class WorkerWrapper {
|
|
20
|
+
onTrace;
|
|
21
|
+
worker;
|
|
22
|
+
pending = new Map();
|
|
23
|
+
inflight = 0;
|
|
24
|
+
constructor(mods, onFatal, onTrace) {
|
|
25
|
+
this.onTrace = onTrace;
|
|
26
|
+
this.worker = new Worker(WORKER_URL, { workerData: { mods } });
|
|
27
|
+
this.worker.on("message", (msg) => this.onMessage(msg));
|
|
28
|
+
this.worker.on("error", (err) => {
|
|
29
|
+
// The thread is dead: fail every in-flight run and hand the slot back to
|
|
30
|
+
// the pool for a respawn. Resetting `inflight` matters — a leaked count
|
|
31
|
+
// would silently remove this slot from least-inflight selection forever.
|
|
32
|
+
for (const p of this.pending.values())
|
|
33
|
+
if (!p.settled)
|
|
34
|
+
p.reject(err);
|
|
35
|
+
this.pending.clear();
|
|
36
|
+
this.inflight = 0;
|
|
37
|
+
onFatal(this, err);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
onMessage(msg) {
|
|
41
|
+
// Trace events carry their own runId (inside the event) and aren't tied to a
|
|
42
|
+
// pending entry — handle them before the pending-lookup guard. Tag which
|
|
43
|
+
// worker ran it so the Runs view can show "ran on worker N".
|
|
44
|
+
if (msg.type === "trace") {
|
|
45
|
+
this.tagAndForward(msg.event);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const p = this.pending.get(msg.id ?? msg.runId);
|
|
49
|
+
if (!p)
|
|
50
|
+
return;
|
|
51
|
+
switch (msg.type) {
|
|
52
|
+
case "result": {
|
|
53
|
+
const outputs = {};
|
|
54
|
+
for (const [nodeId, payload] of Object.entries(msg.outputs)) {
|
|
55
|
+
const sp = {};
|
|
56
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
57
|
+
if (v && typeof v === "object" && "__patternStream" in v) {
|
|
58
|
+
const streamId = v.__patternStream;
|
|
59
|
+
sp[k] = new ReadableStream({
|
|
60
|
+
start: (controller) => p.controllers.set(streamId, controller),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
sp[k] = v;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
outputs[nodeId] = sp;
|
|
68
|
+
}
|
|
69
|
+
p.settled = true;
|
|
70
|
+
p.resolve({
|
|
71
|
+
runId: msg.id,
|
|
72
|
+
status: msg.status,
|
|
73
|
+
outputs,
|
|
74
|
+
error: msg.error ? Object.assign(new Error(msg.error.message), { name: msg.error.name }) : undefined,
|
|
75
|
+
});
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "chunk":
|
|
79
|
+
p.controllers.get(msg.streamId)?.enqueue(msg.value);
|
|
80
|
+
break;
|
|
81
|
+
case "streamEnd":
|
|
82
|
+
try {
|
|
83
|
+
p.controllers.get(msg.streamId)?.close();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
/* ignore */
|
|
87
|
+
}
|
|
88
|
+
p.controllers.delete(msg.streamId);
|
|
89
|
+
break;
|
|
90
|
+
case "streamError":
|
|
91
|
+
try {
|
|
92
|
+
p.controllers.get(msg.streamId)?.error(new Error(msg.error?.message ?? "stream error"));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
/* ignore */
|
|
96
|
+
}
|
|
97
|
+
p.controllers.delete(msg.streamId);
|
|
98
|
+
break;
|
|
99
|
+
case "done":
|
|
100
|
+
this.pending.delete(msg.id);
|
|
101
|
+
this.inflight = Math.max(0, this.inflight - 1);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Stamp the worker's identity onto a forwarded trace event, then hand it up. */
|
|
106
|
+
tagAndForward(evt) {
|
|
107
|
+
if (!this.onTrace)
|
|
108
|
+
return;
|
|
109
|
+
const executor = `worker:${this.worker.threadId}`;
|
|
110
|
+
if (evt.kind === "spanEnd") {
|
|
111
|
+
evt.span.attributes = { ...evt.span.attributes, executor };
|
|
112
|
+
}
|
|
113
|
+
else if (evt.kind === "runStart") {
|
|
114
|
+
evt.run.executor = executor;
|
|
115
|
+
}
|
|
116
|
+
this.onTrace(evt);
|
|
117
|
+
}
|
|
118
|
+
run(req, runId) {
|
|
119
|
+
this.inflight++;
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
this.pending.set(runId, { resolve, reject, controllers: new Map(), settled: false });
|
|
122
|
+
this.worker.postMessage({
|
|
123
|
+
type: "run",
|
|
124
|
+
id: runId,
|
|
125
|
+
workflow: req.workflow,
|
|
126
|
+
triggerNodeId: req.triggerNodeId,
|
|
127
|
+
input: req.input,
|
|
128
|
+
principal: req.principal,
|
|
129
|
+
params: req.params,
|
|
130
|
+
sampleIo: req.sampleIo,
|
|
131
|
+
hookDepth: req.hookDepth,
|
|
132
|
+
parent: req.parent,
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
abort(runId) {
|
|
137
|
+
this.worker.postMessage({ type: "abort", id: runId });
|
|
138
|
+
}
|
|
139
|
+
terminate() {
|
|
140
|
+
return this.worker.terminate();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
export class WorkerPoolTransport {
|
|
144
|
+
workers;
|
|
145
|
+
rr = 0;
|
|
146
|
+
closed = false;
|
|
147
|
+
mods;
|
|
148
|
+
onTrace;
|
|
149
|
+
constructor(opts = {}) {
|
|
150
|
+
const size = Math.max(1, opts.size ?? availableParallelism() - 1);
|
|
151
|
+
this.mods = opts.mods ?? [];
|
|
152
|
+
this.onTrace = opts.onTrace;
|
|
153
|
+
this.workers = Array.from({ length: size }, () => this.spawn());
|
|
154
|
+
}
|
|
155
|
+
spawn() {
|
|
156
|
+
return new WorkerWrapper(this.mods, (dead) => this.respawn(dead), this.onTrace);
|
|
157
|
+
}
|
|
158
|
+
/** Replace a crashed worker so the pool keeps its capacity. */
|
|
159
|
+
respawn(dead) {
|
|
160
|
+
void dead.terminate().catch(() => { });
|
|
161
|
+
if (this.closed)
|
|
162
|
+
return;
|
|
163
|
+
const i = this.workers.indexOf(dead);
|
|
164
|
+
if (i >= 0)
|
|
165
|
+
this.workers[i] = this.spawn();
|
|
166
|
+
}
|
|
167
|
+
describe() {
|
|
168
|
+
return {
|
|
169
|
+
kind: "worker-pool",
|
|
170
|
+
size: this.workers.length,
|
|
171
|
+
inflight: this.workers.map((w) => w.inflight),
|
|
172
|
+
threadIds: this.workers.map((w) => w.worker.threadId),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
dispatch(req) {
|
|
176
|
+
if (this.closed || this.workers.length === 0) {
|
|
177
|
+
throw new Error("WorkerPoolTransport is closed");
|
|
178
|
+
}
|
|
179
|
+
// Least-inflight selection; the rotating scan start breaks ties fairly.
|
|
180
|
+
const n = this.workers.length;
|
|
181
|
+
let worker = this.workers[this.rr % n];
|
|
182
|
+
for (let i = 1; i < n; i++) {
|
|
183
|
+
const w = this.workers[(this.rr + i) % n];
|
|
184
|
+
if (w.inflight < worker.inflight)
|
|
185
|
+
worker = w;
|
|
186
|
+
}
|
|
187
|
+
this.rr = (this.rr + 1) % n;
|
|
188
|
+
const runId = req.runId ?? crypto.randomUUID();
|
|
189
|
+
const result = worker.run(req, runId);
|
|
190
|
+
return {
|
|
191
|
+
runId,
|
|
192
|
+
result,
|
|
193
|
+
abort: () => worker.abort(runId),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
async close() {
|
|
197
|
+
this.closed = true;
|
|
198
|
+
const workers = this.workers;
|
|
199
|
+
this.workers = [];
|
|
200
|
+
await Promise.all(workers.map((w) => w.terminate()));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=worker-pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-pool.js","sourceRoot":"","sources":["../src/worker-pool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pattern-js/runtime-node — connection registry bound to live WebSocket sockets (§7).
|
|
3
|
+
*
|
|
4
|
+
* Implements core's `ConnectionRegistry` interface. Connections are host-local
|
|
5
|
+
* (like a socket), so this registry is the one place that knows about sockets;
|
|
6
|
+
* `core.ws.*` ops and the `boundary.ws.send` out-gate route through the interface
|
|
7
|
+
* and stay socket-agnostic — a pub/sub backplane could replace this for
|
|
8
|
+
* distribution without touching any workflow.
|
|
9
|
+
*/
|
|
10
|
+
import type { WebSocket } from "ws";
|
|
11
|
+
import type { ConnectionRef, ConnectionRegistry, Principal } from "@pattern-js/core";
|
|
12
|
+
export declare class NodeConnectionRegistry implements ConnectionRegistry {
|
|
13
|
+
private sockets;
|
|
14
|
+
private rooms;
|
|
15
|
+
private principals;
|
|
16
|
+
/** Register a freshly-opened socket; returns its connection ref. */
|
|
17
|
+
add(socket: WebSocket, id?: string, principal?: Principal): ConnectionRef;
|
|
18
|
+
remove(id: string): void;
|
|
19
|
+
/** The principal resolved at upgrade time, fixed for the connection's life (§9). */
|
|
20
|
+
principalOf(id: string): Principal | undefined;
|
|
21
|
+
get(id: string): WebSocket | undefined;
|
|
22
|
+
/** Ids of every live connection (observability; tests). */
|
|
23
|
+
ids(): string[];
|
|
24
|
+
send(connection: ConnectionRef | string, data: unknown): Promise<void>;
|
|
25
|
+
sendStream(connection: ConnectionRef | string, data: ReadableStream<unknown>): Promise<void>;
|
|
26
|
+
broadcast(room: string, data: unknown): Promise<void>;
|
|
27
|
+
join(connection: ConnectionRef | string, room: string): Promise<void>;
|
|
28
|
+
leave(connection: ConnectionRef | string, room: string): Promise<void>;
|
|
29
|
+
close(connection: ConnectionRef | string, code?: number, reason?: string): Promise<void>;
|
|
30
|
+
/** Members of a room (inspection/testing). */
|
|
31
|
+
members(room: string): string[];
|
|
32
|
+
/**
|
|
33
|
+
* Close every member of a room (§9: session revocation closes the session's
|
|
34
|
+
* sockets via the `session:{id}` room). Copies the member list first — close
|
|
35
|
+
* mutates room membership.
|
|
36
|
+
*/
|
|
37
|
+
closeRoom(room: string, code?: number, reason?: string): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=ws-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-registry.d.ts","sourceRoot":"","sources":["../src/ws-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAUrF,qBAAa,sBAAuB,YAAW,kBAAkB;IAC/D,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,UAAU,CAAgC;IAElD,oEAAoE;IACpE,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,GAAE,MAA4B,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,aAAa;IAM9F,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAMxB,oFAAoF;IACpF,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI9C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAItC,2DAA2D;IAC3D,GAAG,IAAI,MAAM,EAAE;IAIT,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,UAAU,CAAC,UAAU,EAAE,aAAa,GAAG,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAe5F,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrD,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMrE,KAAK,CAAC,UAAU,EAAE,aAAa,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,KAAK,CAAC,UAAU,EAAE,aAAa,GAAG,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9F,8CAA8C;IAC9C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE;IAI/B;;;;OAIG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG7E"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pattern-js/runtime-node — connection registry bound to live WebSocket sockets (§7).
|
|
3
|
+
*
|
|
4
|
+
* Implements core's `ConnectionRegistry` interface. Connections are host-local
|
|
5
|
+
* (like a socket), so this registry is the one place that knows about sockets;
|
|
6
|
+
* `core.ws.*` ops and the `boundary.ws.send` out-gate route through the interface
|
|
7
|
+
* and stay socket-agnostic — a pub/sub backplane could replace this for
|
|
8
|
+
* distribution without touching any workflow.
|
|
9
|
+
*/
|
|
10
|
+
const idOf = (c) => (typeof c === "string" ? c : c.id);
|
|
11
|
+
function encode(data) {
|
|
12
|
+
if (typeof data === "string")
|
|
13
|
+
return data;
|
|
14
|
+
if (data instanceof Uint8Array)
|
|
15
|
+
return data;
|
|
16
|
+
return JSON.stringify(data);
|
|
17
|
+
}
|
|
18
|
+
export class NodeConnectionRegistry {
|
|
19
|
+
sockets = new Map();
|
|
20
|
+
rooms = new Map();
|
|
21
|
+
principals = new Map();
|
|
22
|
+
/** Register a freshly-opened socket; returns its connection ref. */
|
|
23
|
+
add(socket, id = crypto.randomUUID(), principal) {
|
|
24
|
+
this.sockets.set(id, socket);
|
|
25
|
+
if (principal)
|
|
26
|
+
this.principals.set(id, principal);
|
|
27
|
+
return { id };
|
|
28
|
+
}
|
|
29
|
+
remove(id) {
|
|
30
|
+
this.sockets.delete(id);
|
|
31
|
+
this.principals.delete(id);
|
|
32
|
+
for (const set of this.rooms.values())
|
|
33
|
+
set.delete(id);
|
|
34
|
+
}
|
|
35
|
+
/** The principal resolved at upgrade time, fixed for the connection's life (§9). */
|
|
36
|
+
principalOf(id) {
|
|
37
|
+
return this.principals.get(id);
|
|
38
|
+
}
|
|
39
|
+
get(id) {
|
|
40
|
+
return this.sockets.get(id);
|
|
41
|
+
}
|
|
42
|
+
/** Ids of every live connection (observability; tests). */
|
|
43
|
+
ids() {
|
|
44
|
+
return [...this.sockets.keys()];
|
|
45
|
+
}
|
|
46
|
+
async send(connection, data) {
|
|
47
|
+
this.sockets.get(idOf(connection))?.send(encode(data));
|
|
48
|
+
}
|
|
49
|
+
async sendStream(connection, data) {
|
|
50
|
+
const socket = this.sockets.get(idOf(connection));
|
|
51
|
+
if (!socket)
|
|
52
|
+
return;
|
|
53
|
+
const reader = data.getReader();
|
|
54
|
+
try {
|
|
55
|
+
for (;;) {
|
|
56
|
+
const { done, value } = await reader.read();
|
|
57
|
+
if (done)
|
|
58
|
+
break;
|
|
59
|
+
socket.send(encode(value));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
reader.releaseLock();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async broadcast(room, data) {
|
|
67
|
+
const encoded = encode(data);
|
|
68
|
+
for (const id of this.rooms.get(room) ?? [])
|
|
69
|
+
this.sockets.get(id)?.send(encoded);
|
|
70
|
+
}
|
|
71
|
+
async join(connection, room) {
|
|
72
|
+
let set = this.rooms.get(room);
|
|
73
|
+
if (!set)
|
|
74
|
+
this.rooms.set(room, (set = new Set()));
|
|
75
|
+
set.add(idOf(connection));
|
|
76
|
+
}
|
|
77
|
+
async leave(connection, room) {
|
|
78
|
+
this.rooms.get(room)?.delete(idOf(connection));
|
|
79
|
+
}
|
|
80
|
+
async close(connection, code, reason) {
|
|
81
|
+
const id = idOf(connection);
|
|
82
|
+
this.sockets.get(id)?.close(code, reason);
|
|
83
|
+
this.remove(id);
|
|
84
|
+
}
|
|
85
|
+
/** Members of a room (inspection/testing). */
|
|
86
|
+
members(room) {
|
|
87
|
+
return [...(this.rooms.get(room) ?? [])];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Close every member of a room (§9: session revocation closes the session's
|
|
91
|
+
* sockets via the `session:{id}` room). Copies the member list first — close
|
|
92
|
+
* mutates room membership.
|
|
93
|
+
*/
|
|
94
|
+
async closeRoom(room, code, reason) {
|
|
95
|
+
for (const id of this.members(room))
|
|
96
|
+
await this.close(id, code, reason);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=ws-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-registry.js","sourceRoot":"","sources":["../src/ws-registry.ts"],"names":[],"mappings":"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"}
|
package/dist/ws.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pattern-js/runtime-node — WebSocket host (§7, §9).
|
|
3
|
+
*
|
|
4
|
+
* Node has no built-in WS *server*, so this host uses `ws` on the HTTP upgrade
|
|
5
|
+
* (an adapter-level dependency; core stays clean). It fires a run **per inbound
|
|
6
|
+
* message** through `boundary.ws.message`, plus optional `boundary.ws.open` /
|
|
7
|
+
* `boundary.ws.close` lifecycle workflows, and sends `boundary.ws.send` results
|
|
8
|
+
* back on the originating connection.
|
|
9
|
+
*
|
|
10
|
+
* Auth (§9) happens **at upgrade time**: the provider chain resolves a principal
|
|
11
|
+
* from the upgrade request's headers (same cookie the HTTP host sees), every
|
|
12
|
+
* bound trigger's `requireAuth` is enforced *before* the socket is accepted
|
|
13
|
+
* (raw 401 + destroy on failure), and the principal is fixed to the connection
|
|
14
|
+
* for its lifetime — open/message/close runs all execute as it and see it on
|
|
15
|
+
* their `user` port. Authenticated connections auto-join the `user:{id}` and
|
|
16
|
+
* `session:{sessionId}` rooms, so "message all of this user's devices" is a
|
|
17
|
+
* broadcast and session revocation is a `closeRoom`.
|
|
18
|
+
*/
|
|
19
|
+
import type { Server } from "node:http";
|
|
20
|
+
import { WebSocketServer } from "ws";
|
|
21
|
+
import { type Engine, type Workflow } from "@pattern-js/core";
|
|
22
|
+
import { NodeConnectionRegistry } from "./ws-registry.js";
|
|
23
|
+
export interface WsBinding {
|
|
24
|
+
workflow: Workflow | string;
|
|
25
|
+
trigger?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface WsHostOptions {
|
|
28
|
+
/** Run per inbound message (boundary.ws.message). */
|
|
29
|
+
onMessage?: WsBinding;
|
|
30
|
+
/** Run on connection open (boundary.ws.open). */
|
|
31
|
+
onOpen?: WsBinding;
|
|
32
|
+
/** Run on connection close (boundary.ws.close). */
|
|
33
|
+
onClose?: WsBinding;
|
|
34
|
+
/** Restrict to a path (e.g. "/ws"). */
|
|
35
|
+
path?: string;
|
|
36
|
+
}
|
|
37
|
+
export declare class WsHost {
|
|
38
|
+
private readonly engine;
|
|
39
|
+
private readonly opts;
|
|
40
|
+
readonly wss: WebSocketServer;
|
|
41
|
+
readonly connections: NodeConnectionRegistry;
|
|
42
|
+
constructor(engine: Engine, opts: WsHostOptions, connections?: NodeConnectionRegistry);
|
|
43
|
+
/** Attach to an HTTP server's upgrade event. */
|
|
44
|
+
attach(server: Server): this;
|
|
45
|
+
/** Resolve a principal from the upgrade request via the provider chain (§9). */
|
|
46
|
+
private authenticate;
|
|
47
|
+
/**
|
|
48
|
+
* Enforce every bound trigger's `requireAuth` (§9). One socket serves all
|
|
49
|
+
* three lifecycle triggers, so the connection must satisfy the union of
|
|
50
|
+
* their requirements — a protected message handler protects the upgrade.
|
|
51
|
+
*/
|
|
52
|
+
private deniedReason;
|
|
53
|
+
/** Explicit binding (when configured) or auto mode is on for this host. */
|
|
54
|
+
private get auto();
|
|
55
|
+
/**
|
|
56
|
+
* Bindings to enforce/fire for a trigger op. Explicit options win; auto mode
|
|
57
|
+
* resolves LIVE from the workflow registry (workflows deployed from the
|
|
58
|
+
* admin bind without a restart, mirroring HttpHost's declarative routing).
|
|
59
|
+
* Auth enforces ALL matching workflows' requirements; firing uses the first
|
|
60
|
+
* (one socket, no path dispatch — multi-handler WS is an explicit-options
|
|
61
|
+
* setup).
|
|
62
|
+
*/
|
|
63
|
+
private resolved;
|
|
64
|
+
private bindingFor;
|
|
65
|
+
private onConnection;
|
|
66
|
+
private resolve;
|
|
67
|
+
private fire;
|
|
68
|
+
private fireAndSend;
|
|
69
|
+
}
|
|
70
|
+
/** Create a WS host (attach it to an HTTP server with `.attach(server)`). */
|
|
71
|
+
export declare function createWsHost(engine: Engine, opts: WsHostOptions, connections?: NodeConnectionRegistry): WsHost;
|
|
72
|
+
//# sourceMappingURL=ws.d.ts.map
|
package/dist/ws.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../src/ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAAE,eAAe,EAAkB,MAAM,IAAI,CAAC;AACrD,OAAO,EAIL,KAAK,MAAM,EAGX,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,iDAAiD;IACjD,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,mDAAmD;IACnD,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAID,qBAAa,MAAM;IAKf,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IALvB,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,sBAAsB,CAAC;gBAG1B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,aAAa,EACpC,WAAW,CAAC,EAAE,sBAAsB;IAYtC,gDAAgD;IAChD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IA+B5B,gFAAgF;YAClE,YAAY;IAS1B;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAcpB,2EAA2E;IAC3E,OAAO,KAAK,IAAI,GAEf;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ;IAoBhB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,YAAY;IA0CpB,OAAO,CAAC,OAAO;YASD,IAAI;YAWJ,WAAW;CAoC1B;AAsBD,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,WAAW,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAE9G"}
|