@lumencast/server 0.1.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 +201 -0
- package/README.md +69 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/adapters/http-poll.d.ts +16 -0
- package/dist/adapters/http-poll.d.ts.map +1 -0
- package/dist/adapters/http-poll.js +46 -0
- package/dist/adapters/http-poll.js.map +1 -0
- package/dist/auth.d.ts +33 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +65 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +154 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/replay-buffer.d.ts +32 -0
- package/dist/replay-buffer.d.ts.map +1 -0
- package/dist/replay-buffer.js +60 -0
- package/dist/replay-buffer.js.map +1 -0
- package/dist/scene.d.ts +67 -0
- package/dist/scene.d.ts.map +1 -0
- package/dist/scene.js +109 -0
- package/dist/scene.js.map +1 -0
- package/dist/server.d.ts +38 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +279 -0
- package/dist/server.js.map +1 -0
- package/dist/store.d.ts +17 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +34 -0
- package/dist/store.js.map +1 -0
- package/dist/test-control.d.ts +22 -0
- package/dist/test-control.d.ts.map +1 -0
- package/dist/test-control.js +225 -0
- package/dist/test-control.js.map +1 -0
- package/package.json +50 -0
- package/src/adapters/http-poll.ts +59 -0
- package/src/auth.ts +81 -0
- package/src/cli.ts +166 -0
- package/src/index.ts +20 -0
- package/src/replay-buffer.ts +72 -0
- package/src/scene.ts +168 -0
- package/src/server.ts +386 -0
- package/src/store.ts +40 -0
- package/src/test-control.ts +279 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// HTTP + WebSocket server kit for LSDP/1.
|
|
2
|
+
//
|
|
3
|
+
// Single-scene at a time, single-process. The active scene is mutable so the
|
|
4
|
+
// interop test control plane can swap it via /test/setup without restarting
|
|
5
|
+
// the server.
|
|
6
|
+
//
|
|
7
|
+
// LSDP/1 spec compliance highlights:
|
|
8
|
+
// - WebSocket subprotocol negotiation: `lsdp.v1`
|
|
9
|
+
// - subscribe → snapshot(seq=1) → delta(seq=2,...)
|
|
10
|
+
// - input frames validated against `authenticate()` decision + canWritePath()
|
|
11
|
+
// - error frames carry codes from the closed taxonomy
|
|
12
|
+
// - heartbeat: ping → pong
|
|
13
|
+
import { createServer } from "node:http";
|
|
14
|
+
import { WebSocketServer } from "ws";
|
|
15
|
+
import { delta as deltaFrame, decodeClientFrame, encodeFrame, errorFrame, isProtocolErrorCode, LumencastError, pong, snapshot as snapshotFrame, WS_SUBPROTOCOL, WS_SUBPROTOCOL_V1_1, } from "@lumencast/protocol";
|
|
16
|
+
import { canWritePath, defaultAuthenticate } from "./auth.js";
|
|
17
|
+
export async function startServer(config) {
|
|
18
|
+
const host = config.host ?? "127.0.0.1";
|
|
19
|
+
const wsPath = config.wsPath ?? "/lsdp/v1";
|
|
20
|
+
const bundlePathPrefix = config.bundlePathPrefix ?? "/lsdp/v1/scenes";
|
|
21
|
+
const authenticate = config.authenticate ?? defaultAuthenticate;
|
|
22
|
+
// Mutable refs — swappable via setActiveScene / setBundleProvider.
|
|
23
|
+
let activeScene = config.scene;
|
|
24
|
+
let bundleProvider = config.bundleProvider;
|
|
25
|
+
const httpServer = createServer((req, res) => handleHttp(req, res));
|
|
26
|
+
await new Promise((resolve, reject) => {
|
|
27
|
+
httpServer.once("error", reject);
|
|
28
|
+
httpServer.listen(config.port ?? 0, host, () => {
|
|
29
|
+
httpServer.off("error", reject);
|
|
30
|
+
resolve();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
const address = httpServer.address();
|
|
34
|
+
if (!address || typeof address === "string") {
|
|
35
|
+
throw new Error("server: failed to bind HTTP");
|
|
36
|
+
}
|
|
37
|
+
const port = address.port;
|
|
38
|
+
const wss = new WebSocketServer({
|
|
39
|
+
server: httpServer,
|
|
40
|
+
path: wsPath,
|
|
41
|
+
handleProtocols: (offered) => {
|
|
42
|
+
// LSDP/1.1 preferred, 1.0 fallback. Anything else → reject upgrade.
|
|
43
|
+
if (offered.has(WS_SUBPROTOCOL_V1_1))
|
|
44
|
+
return WS_SUBPROTOCOL_V1_1;
|
|
45
|
+
if (offered.has(WS_SUBPROTOCOL))
|
|
46
|
+
return WS_SUBPROTOCOL;
|
|
47
|
+
return false;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const subscribers = new Set();
|
|
51
|
+
wss.on("connection", (ws) => {
|
|
52
|
+
const sub = { ws, decision: null, unsubscribePatches: null };
|
|
53
|
+
subscribers.add(sub);
|
|
54
|
+
ws.on("message", (data) => handleMessage(sub, String(data)).catch(() => undefined));
|
|
55
|
+
ws.on("close", () => {
|
|
56
|
+
sub.unsubscribePatches?.();
|
|
57
|
+
subscribers.delete(sub);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
async function handleMessage(sub, raw) {
|
|
61
|
+
let frame;
|
|
62
|
+
try {
|
|
63
|
+
frame = decodeClientFrame(raw);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
const message = err instanceof Error ? err.message : "decode error";
|
|
67
|
+
// LSDP/1 §13: envelope.v mismatch → close with code 1002 directly,
|
|
68
|
+
// no error frame (the v != 1 client wouldn't grok our v: 1 envelope).
|
|
69
|
+
if (message.includes("envelope.v")) {
|
|
70
|
+
sub.ws.close(1002, "VERSION_MISMATCH");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const code = err instanceof LumencastError ? err.code : "INVALID_VALUE";
|
|
74
|
+
const recoverable = err instanceof LumencastError ? err.recoverable : false;
|
|
75
|
+
const path = err instanceof LumencastError ? err.path : undefined;
|
|
76
|
+
sendError(sub, code, message, recoverable, path !== undefined ? { path } : undefined);
|
|
77
|
+
// Recoverable decode errors (e.g. forbidden object value in a single
|
|
78
|
+
// patch per §3.2.1) MUST keep the connection open ; only fatal
|
|
79
|
+
// structural errors close.
|
|
80
|
+
if (!recoverable) {
|
|
81
|
+
sub.ws.close(1002, code);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (frame === null)
|
|
86
|
+
return; // forward-compat ignore
|
|
87
|
+
switch (frame.type) {
|
|
88
|
+
case "subscribe": {
|
|
89
|
+
await handleSubscribe(sub, frame.token, frame.since_sequence);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
case "input": {
|
|
93
|
+
await handleInput(sub, frame.patches, frame.client_msg_id);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
case "ping": {
|
|
97
|
+
// LSDP/1.1 §3.5 — echo nonce verbatim in pong (omit if absent).
|
|
98
|
+
sendFrame(sub, pong(frame.nonce));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
case "unsubscribe": {
|
|
102
|
+
// LSDP/1.1 §4.4 — clean teardown. Close WS within 1s, no error frame.
|
|
103
|
+
sub.ws.close(1000, "unsubscribe");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function handleSubscribe(sub, token, sinceSequence) {
|
|
109
|
+
let decision;
|
|
110
|
+
try {
|
|
111
|
+
decision = await authenticate(token);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
const code = err instanceof LumencastError && isProtocolErrorCode(err.code) ? err.code : "AUTH_DENIED";
|
|
115
|
+
sendError(sub, code, err instanceof Error ? err.message : "auth error", false);
|
|
116
|
+
sub.ws.close(1008, code);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
sub.decision = decision;
|
|
120
|
+
// LSDP/1.1 §4.1, §18 — honour since_sequence when the replay buffer
|
|
121
|
+
// covers the gap. Otherwise fall back to a fresh snapshot at the
|
|
122
|
+
// current scene seq.
|
|
123
|
+
const curSeq = activeScene.currentSeq();
|
|
124
|
+
let useReplay = false;
|
|
125
|
+
if (sinceSequence !== undefined && sinceSequence > 0 && sinceSequence <= curSeq) {
|
|
126
|
+
const { records, covered } = activeScene.replaySince(sinceSequence);
|
|
127
|
+
if (covered) {
|
|
128
|
+
useReplay = true;
|
|
129
|
+
for (const r of records) {
|
|
130
|
+
sendFrame(sub, deltaFrame({ seq: r.seq, patches: r.patches, cause: r.cause }));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!useReplay) {
|
|
135
|
+
sendFrame(sub, snapshotFrame({
|
|
136
|
+
seq: curSeq,
|
|
137
|
+
scene_id: activeScene.sceneId,
|
|
138
|
+
scene_version: activeScene.sceneVersion,
|
|
139
|
+
state: activeScene.store.snapshot(),
|
|
140
|
+
ts: new Date().toISOString(),
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
// Wire up delta forwarding from the *current* active scene. If the active
|
|
144
|
+
// scene swaps, this subscriber stays bound to the old one and is detached
|
|
145
|
+
// by setActiveScene(). Per-scene seq (§18.1.1) — the listener receives
|
|
146
|
+
// the seq directly from the scene.
|
|
147
|
+
sub.unsubscribePatches = activeScene.onPatches((seq, patches, cause) => {
|
|
148
|
+
sendFrame(sub, deltaFrame({ seq, patches, cause }));
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
async function handleInput(sub, patches, clientMsgId) {
|
|
152
|
+
if (!sub.decision) {
|
|
153
|
+
sendError(sub, "AUTH_DENIED", "input before subscribe", false);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// 1. Role-based authorization (LSDP/1 §9).
|
|
157
|
+
for (const p of patches) {
|
|
158
|
+
if (!canWritePath(sub.decision, p.path)) {
|
|
159
|
+
sendError(sub, "WRITE_FORBIDDEN", `role ${sub.decision.role} cannot write ${p.path}`, true, { path: p.path });
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// 2. Schema validation against operator_inputs (LSDP/1 §4.2 + LSML §8).
|
|
164
|
+
// Atomic: reject the whole frame on the first violation, no patches applied.
|
|
165
|
+
const validationError = activeScene.validateInput(patches);
|
|
166
|
+
if (validationError) {
|
|
167
|
+
sendError(sub, validationError.code, validationError.message, true, {
|
|
168
|
+
path: validationError.path,
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// LSDP/1.1 §3.2.3 — derive provenance metadata for the resulting delta.
|
|
173
|
+
// The `source` is conventionally `<role>:<subject>` ; when client_msg_id
|
|
174
|
+
// is provided, echo it verbatim into `input_id` for optimistic-UI
|
|
175
|
+
// correlation (§4.2). Cause is omitted entirely if neither yields data,
|
|
176
|
+
// so 1.0 wire shape is preserved when no client opts into 1.1.
|
|
177
|
+
const subject = sub.decision.subject ?? sub.decision.role;
|
|
178
|
+
const cause = clientMsgId
|
|
179
|
+
? { source: `${sub.decision.role}:${subject}`, input_id: clientMsgId }
|
|
180
|
+
: undefined;
|
|
181
|
+
activeScene.update(patches, cause);
|
|
182
|
+
}
|
|
183
|
+
function handleHttp(req, res) {
|
|
184
|
+
const url = req.url ?? "/";
|
|
185
|
+
res.setHeader("access-control-allow-origin", "*");
|
|
186
|
+
res.setHeader("access-control-allow-methods", "GET, OPTIONS");
|
|
187
|
+
res.setHeader("access-control-allow-headers", "*");
|
|
188
|
+
if (req.method === "OPTIONS") {
|
|
189
|
+
res.statusCode = 204;
|
|
190
|
+
res.end();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (req.method === "GET" && url === `${wsPath}/health`) {
|
|
194
|
+
writeJson(res, 200, { status: "ok" });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// GET /lsdp/v1/scenes/{id}/bundle?v=sha256:...
|
|
198
|
+
const escaped = bundlePathPrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
199
|
+
const re = new RegExp(`^${escaped}/([^/?]+)/bundle(?:\\?v=([^&]+))?$`);
|
|
200
|
+
const match = url.match(re);
|
|
201
|
+
if (req.method === "GET" && match) {
|
|
202
|
+
const versionParam = match[2] ? decodeURIComponent(match[2]) : activeScene.sceneVersion;
|
|
203
|
+
Promise.resolve(bundleProvider(versionParam))
|
|
204
|
+
.then((bundle) => {
|
|
205
|
+
if (!bundle) {
|
|
206
|
+
writeJson(res, 404, { error: "bundle_not_found" });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
res.statusCode = 200;
|
|
210
|
+
res.setHeader("content-type", "application/json");
|
|
211
|
+
res.setHeader("cache-control", "public, max-age=31536000, immutable");
|
|
212
|
+
res.end(JSON.stringify(bundle));
|
|
213
|
+
})
|
|
214
|
+
.catch((err) => writeJson(res, 500, { error: String(err) }));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
writeJson(res, 404, { error: "not_found" });
|
|
218
|
+
}
|
|
219
|
+
function sendFrame(sub, frame) {
|
|
220
|
+
if (sub.ws.readyState !== sub.ws.OPEN)
|
|
221
|
+
return;
|
|
222
|
+
sub.ws.send(encodeFrame(frame));
|
|
223
|
+
}
|
|
224
|
+
function sendError(sub, code, message, recoverable, extras) {
|
|
225
|
+
// Errors are connection-scoped, not scene-scoped events
|
|
226
|
+
// (LSDP/1.1 §18.1.1) — they ride the current scene seq without
|
|
227
|
+
// advancing it.
|
|
228
|
+
sendFrame(sub, errorFrame({
|
|
229
|
+
seq: activeScene.currentSeq(),
|
|
230
|
+
code,
|
|
231
|
+
message,
|
|
232
|
+
recoverable,
|
|
233
|
+
...(extras?.path !== undefined ? { path: extras.path } : {}),
|
|
234
|
+
...(extras?.retry_after_ms !== undefined ? { retry_after_ms: extras.retry_after_ms } : {}),
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
function detachAllSubscribers(closeCode = 1012, reason = "service restart") {
|
|
238
|
+
for (const sub of subscribers) {
|
|
239
|
+
sub.unsubscribePatches?.();
|
|
240
|
+
try {
|
|
241
|
+
sub.ws.close(closeCode, reason);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// ignore
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
subscribers.clear();
|
|
248
|
+
}
|
|
249
|
+
function setActiveScene(scene) {
|
|
250
|
+
detachAllSubscribers();
|
|
251
|
+
activeScene = scene;
|
|
252
|
+
}
|
|
253
|
+
function setBundleProvider(fn) {
|
|
254
|
+
bundleProvider = fn;
|
|
255
|
+
}
|
|
256
|
+
function reset() {
|
|
257
|
+
detachAllSubscribers();
|
|
258
|
+
}
|
|
259
|
+
async function close() {
|
|
260
|
+
detachAllSubscribers(1000, "shutdown");
|
|
261
|
+
await new Promise((resolve) => wss.close(() => resolve()));
|
|
262
|
+
await new Promise((resolve) => httpServer.close(() => resolve()));
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
wsUrl: `ws://${host}:${port}${wsPath}`,
|
|
266
|
+
httpUrl: `http://${host}:${port}`,
|
|
267
|
+
setActiveScene,
|
|
268
|
+
setBundleProvider,
|
|
269
|
+
activeScene: () => activeScene,
|
|
270
|
+
reset,
|
|
271
|
+
close,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function writeJson(res, status, body) {
|
|
275
|
+
res.statusCode = status;
|
|
276
|
+
res.setHeader("content-type", "application/json");
|
|
277
|
+
res.end(JSON.stringify(body));
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,6EAA6E;AAC7E,4EAA4E;AAC5E,cAAc;AACd,EAAE;AACF,qCAAqC;AACrC,mDAAmD;AACnD,qDAAqD;AACrD,gFAAgF;AAChF,wDAAwD;AACxD,6BAA6B;AAE7B,OAAO,EAAE,YAAY,EAA0D,MAAM,WAAW,CAAC;AACjG,OAAO,EAAE,eAAe,EAAkB,MAAM,IAAI,CAAC;AACrD,OAAO,EACL,KAAK,IAAI,UAAU,EACnB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,mBAAmB,EACnB,cAAc,EACd,IAAI,EACJ,QAAQ,IAAI,aAAa,EACzB,cAAc,EACd,mBAAmB,GAKpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAwC,MAAM,WAAW,CAAC;AA8CpG,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAoB;IACpD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,WAAW,CAAC;IACxC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,UAAU,CAAC;IAC3C,MAAM,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,IAAI,iBAAiB,CAAC;IACtE,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,mBAAmB,CAAC;IAEhE,mEAAmE;IACnE,IAAI,WAAW,GAAU,MAAM,CAAC,KAAK,CAAC;IACtC,IAAI,cAAc,GAAG,MAAM,CAAC,cAAc,CAAC;IAE3C,MAAM,UAAU,GAAW,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IAE5E,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACjC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE;YAC7C,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAChC,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC;IACrC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE1B,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC;QAC9B,MAAM,EAAE,UAAU;QAClB,IAAI,EAAE,MAAM;QACZ,eAAe,EAAE,CAAC,OAAoB,EAAE,EAAE;YACxC,oEAAoE;YACpE,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;gBAAE,OAAO,mBAAmB,CAAC;YACjE,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;gBAAE,OAAO,cAAc,CAAC;YACvD,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,IAAI,GAAG,EAAc,CAAC;IAE1C,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,EAAE;QAC1B,MAAM,GAAG,GAAe,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC;QACzE,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAErB,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;QACpF,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAClB,GAAG,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC3B,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,KAAK,UAAU,aAAa,CAAC,GAAe,EAAE,GAAW;QACvD,IAAI,KAAK,CAAC;QACV,IAAI,CAAC;YACH,KAAK,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC;YACpE,mEAAmE;YACnE,sEAAsE;YACtE,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;gBACnC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;gBACvC,OAAO;YACT,CAAC;YACD,MAAM,IAAI,GAAG,GAAG,YAAY,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC;YACxE,MAAM,WAAW,GAAG,GAAG,YAAY,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC;YAC5E,MAAM,IAAI,GAAG,GAAG,YAAY,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;YAClE,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACtF,qEAAqE;YACrE,+DAA+D;YAC/D,2BAA2B;YAC3B,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC3B,CAAC;YACD,OAAO;QACT,CAAC;QACD,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,CAAC,wBAAwB;QAEpD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,WAAW,CAAC,CAAC,CAAC;gBACjB,MAAM,eAAe,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;gBAC9D,OAAO;YACT,CAAC;YACD,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,MAAM,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;gBAC3D,OAAO;YACT,CAAC;YACD,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,gEAAgE;gBAChE,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;gBAClC,OAAO;YACT,CAAC;YACD,KAAK,aAAa,CAAC,CAAC,CAAC;gBACnB,sEAAsE;gBACtE,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;gBAClC,OAAO;YACT,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,UAAU,eAAe,CAC5B,GAAe,EACf,KAAa,EACb,aAAsB;QAEtB,IAAI,QAAsB,CAAC;QAC3B,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,GACR,GAAG,YAAY,cAAc,IAAI,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC;YAC5F,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;YAC/E,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACzB,OAAO;QACT,CAAC;QACD,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAExB,oEAAoE;QACpE,iEAAiE;QACjE,qBAAqB;QACrB,MAAM,MAAM,GAAG,WAAW,CAAC,UAAU,EAAE,CAAC;QACxC,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,IAAI,aAAa,KAAK,SAAS,IAAI,aAAa,GAAG,CAAC,IAAI,aAAa,IAAI,MAAM,EAAE,CAAC;YAChF,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;YACpE,IAAI,OAAO,EAAE,CAAC;gBACZ,SAAS,GAAG,IAAI,CAAC;gBACjB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;oBACxB,SAAS,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;gBACjF,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,SAAS,CACP,GAAG,EACH,aAAa,CAAC;gBACZ,GAAG,EAAE,MAAM;gBACX,QAAQ,EAAE,WAAW,CAAC,OAAO;gBAC7B,aAAa,EAAE,WAAW,CAAC,YAAY;gBACvC,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,QAAQ,EAAE;gBACnC,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aAC7B,CAAC,CACH,CAAC;QACJ,CAAC;QAED,0EAA0E;QAC1E,0EAA0E;QAC1E,uEAAuE;QACvE,mCAAmC;QACnC,GAAG,CAAC,kBAAkB,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACrE,SAAS,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,UAAU,WAAW,CACxB,GAAe,EACf,OAAgB,EAChB,WAAoB;QAEpB,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;YAClB,SAAS,CAAC,GAAG,EAAE,aAAa,EAAE,wBAAwB,EAAE,KAAK,CAAC,CAAC;YAC/D,OAAO;QACT,CAAC;QACD,2CAA2C;QAC3C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxC,SAAS,CACP,GAAG,EACH,iBAAiB,EACjB,QAAQ,GAAG,CAAC,QAAQ,CAAC,IAAI,iBAAiB,CAAC,CAAC,IAAI,EAAE,EAClD,IAAI,EACJ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CACjB,CAAC;gBACF,OAAO;YACT,CAAC;QACH,CAAC;QACD,wEAAwE;QACxE,gFAAgF;QAChF,MAAM,eAAe,GAAG,WAAW,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC3D,IAAI,eAAe,EAAE,CAAC;YACpB,SAAS,CAAC,GAAG,EAAE,eAAe,CAAC,IAAI,EAAE,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE;gBAClE,IAAI,EAAE,eAAe,CAAC,IAAI;aAC3B,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,wEAAwE;QACxE,yEAAyE;QACzE,kEAAkE;QAClE,wEAAwE;QACxE,+DAA+D;QAC/D,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;QAC1D,MAAM,KAAK,GAAsB,WAAW;YAC1C,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,OAAO,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE;YACtE,CAAC,CAAC,SAAS,CAAC;QACd,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,SAAS,UAAU,CAAC,GAAoB,EAAE,GAAmB;QAC3D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC;QAC3B,GAAG,CAAC,SAAS,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;QAClD,GAAG,CAAC,SAAS,CAAC,8BAA8B,EAAE,cAAc,CAAC,CAAC;QAC9D,GAAG,CAAC,SAAS,CAAC,8BAA8B,EAAE,GAAG,CAAC,CAAC;QACnD,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC7B,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;YACrB,GAAG,CAAC,GAAG,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;YACvD,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,OAAO;QACT,CAAC;QAED,+CAA+C;QAC/C,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;QACxE,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,IAAI,OAAO,oCAAoC,CAAC,CAAC;QACvE,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,KAAK,EAAE,CAAC;YAClC,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC;YACxF,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;iBAC1C,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;gBACf,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;oBACnD,OAAO;gBACT,CAAC;gBACD,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;gBACrB,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;gBAClD,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,qCAAqC,CAAC,CAAC;gBACtE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;YAClC,CAAC,CAAC;iBACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/D,OAAO;QACT,CAAC;QAED,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,SAAS,SAAS,CAAC,GAAe,EAAE,KAAa;QAC/C,IAAI,GAAG,CAAC,EAAE,CAAC,UAAU,KAAK,GAAG,CAAC,EAAE,CAAC,IAAI;YAAE,OAAO;QAC9C,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,KAA0C,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,SAAS,SAAS,CAChB,GAAe,EACf,IAAe,EACf,OAAe,EACf,WAAoB,EACpB,MAAmD;QAEnD,wDAAwD;QACxD,+DAA+D;QAC/D,gBAAgB;QAChB,SAAS,CACP,GAAG,EACH,UAAU,CAAC;YACT,GAAG,EAAE,WAAW,CAAC,UAAU,EAAE;YAC7B,IAAI;YACJ,OAAO;YACP,WAAW;YACX,GAAG,CAAC,MAAM,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,GAAG,CAAC,MAAM,EAAE,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC3F,CAAC,CACH,CAAC;IACJ,CAAC;IAED,SAAS,oBAAoB,CAAC,SAAS,GAAG,IAAI,EAAE,MAAM,GAAG,iBAAiB;QACxE,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC9B,GAAG,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YAClC,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC;QACD,WAAW,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAED,SAAS,cAAc,CAAC,KAAY;QAClC,oBAAoB,EAAE,CAAC;QACvB,WAAW,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,SAAS,iBAAiB,CAAC,EAAyD;QAClF,cAAc,GAAG,EAAE,CAAC;IACtB,CAAC;IAED,SAAS,KAAK;QACZ,oBAAoB,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,UAAU,KAAK;QAClB,oBAAoB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACvC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACjE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC1E,CAAC;IAED,OAAO;QACL,KAAK,EAAE,QAAQ,IAAI,IAAI,IAAI,GAAG,MAAM,EAAE;QACtC,OAAO,EAAE,UAAU,IAAI,IAAI,IAAI,EAAE;QACjC,cAAc;QACd,iBAAiB;QACjB,WAAW,EAAE,GAAG,EAAE,CAAC,WAAW;QAC9B,KAAK;QACL,KAAK;KACN,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAa;IACnE,GAAG,CAAC,UAAU,GAAG,MAAM,CAAC;IACxB,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;IAClD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AAChC,CAAC"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Cause, LeafPath, LeafValue, Patch } from "@lumencast/protocol";
|
|
2
|
+
export type LeafStoreListener = (patches: Patch[], cause?: Cause) => void;
|
|
3
|
+
export declare class LeafStore {
|
|
4
|
+
private state;
|
|
5
|
+
private listeners;
|
|
6
|
+
constructor(initial?: Record<LeafPath, LeafValue>);
|
|
7
|
+
/** Snapshot of the full state (shallow copy). */
|
|
8
|
+
snapshot(): Record<LeafPath, LeafValue>;
|
|
9
|
+
/** Read a single leaf. */
|
|
10
|
+
get(path: LeafPath): LeafValue | undefined;
|
|
11
|
+
/** Apply patches and notify listeners. Returns the patches actually applied.
|
|
12
|
+
* Optional `cause` is forwarded to listeners so a downstream delta can carry
|
|
13
|
+
* provenance (LSDP/1.1 §3.2.3). */
|
|
14
|
+
apply(patches: Patch[], cause?: Cause): Patch[];
|
|
15
|
+
onPatches(listener: LeafStoreListener): () => void;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAE7E,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;AAE1E,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,SAAS,CAAgC;gBAErC,OAAO,GAAE,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAM;IAIrD,iDAAiD;IACjD,QAAQ,IAAI,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC;IAIvC,0BAA0B;IAC1B,GAAG,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS;IAI1C;;uCAEmC;IACnC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,KAAK,EAAE;IAO/C,SAAS,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,IAAI;CAInD"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// LeafStore — a leaf-grain key/value map with an `onPatches` event.
|
|
2
|
+
// Adapters write into the store; the Scene listens and rebroadcasts as deltas.
|
|
3
|
+
export class LeafStore {
|
|
4
|
+
state = {};
|
|
5
|
+
listeners = new Set();
|
|
6
|
+
constructor(initial = {}) {
|
|
7
|
+
this.state = { ...initial };
|
|
8
|
+
}
|
|
9
|
+
/** Snapshot of the full state (shallow copy). */
|
|
10
|
+
snapshot() {
|
|
11
|
+
return { ...this.state };
|
|
12
|
+
}
|
|
13
|
+
/** Read a single leaf. */
|
|
14
|
+
get(path) {
|
|
15
|
+
return this.state[path];
|
|
16
|
+
}
|
|
17
|
+
/** Apply patches and notify listeners. Returns the patches actually applied.
|
|
18
|
+
* Optional `cause` is forwarded to listeners so a downstream delta can carry
|
|
19
|
+
* provenance (LSDP/1.1 §3.2.3). */
|
|
20
|
+
apply(patches, cause) {
|
|
21
|
+
if (patches.length === 0)
|
|
22
|
+
return [];
|
|
23
|
+
for (const p of patches)
|
|
24
|
+
this.state[p.path] = p.value;
|
|
25
|
+
for (const l of this.listeners)
|
|
26
|
+
l(patches, cause);
|
|
27
|
+
return patches;
|
|
28
|
+
}
|
|
29
|
+
onPatches(listener) {
|
|
30
|
+
this.listeners.add(listener);
|
|
31
|
+
return () => this.listeners.delete(listener);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,+EAA+E;AAM/E,MAAM,OAAO,SAAS;IACZ,KAAK,GAAgC,EAAE,CAAC;IACxC,SAAS,GAAG,IAAI,GAAG,EAAqB,CAAC;IAEjD,YAAY,UAAuC,EAAE;QACnD,IAAI,CAAC,KAAK,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;IAC9B,CAAC;IAED,iDAAiD;IACjD,QAAQ;QACN,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;IAED,0BAA0B;IAC1B,GAAG,CAAC,IAAc;QAChB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED;;uCAEmC;IACnC,KAAK,CAAC,OAAgB,EAAE,KAAa;QACnC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACpC,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;QACtD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS;YAAE,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAClD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,SAAS,CAAC,QAA2B;QACnC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type AuthDecision, type StaticTokens } from "./auth.js";
|
|
2
|
+
import type { ServerHandle } from "./server.js";
|
|
3
|
+
export interface TestControlOptions {
|
|
4
|
+
/** TCP port. 0 = random. */
|
|
5
|
+
port?: number;
|
|
6
|
+
host?: string;
|
|
7
|
+
/** The lumencast server the plane drives. */
|
|
8
|
+
server: ServerHandle;
|
|
9
|
+
/** The static-token authenticator the plane mutates on /test/setup. */
|
|
10
|
+
auth: StaticTokens;
|
|
11
|
+
}
|
|
12
|
+
export interface TestControlHandle {
|
|
13
|
+
readonly url: string;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export declare function startTestControl(options: TestControlOptions): Promise<TestControlHandle>;
|
|
17
|
+
/** Replace the StaticTokens contents with one entry per recognised
|
|
18
|
+
* placeholder. $TOKEN_INVALID is intentionally NOT installed — the LSDP/1
|
|
19
|
+
* conformance suite expects auth to reject it. */
|
|
20
|
+
export declare function installTokens(auth: StaticTokens, tokens: Record<string, string>): void;
|
|
21
|
+
export type { AuthDecision };
|
|
22
|
+
//# sourceMappingURL=test-control.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-control.d.ts","sourceRoot":"","sources":["../src/test-control.ts"],"names":[],"mappings":"AASA,OAAO,EAAgB,KAAK,YAAY,EAAa,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE1F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,kBAAkB;IACjC,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,MAAM,EAAE,YAAY,CAAC;IACrB,uEAAuE;IACvE,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA8BD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAiK9F;AAED;;mDAEmD;AACnD,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAWtF;AA6CD,YAAY,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// LSDP/1 interop test control plane.
|
|
2
|
+
//
|
|
3
|
+
// HTTP API spec: lumencast-protocol/interop/CONTROL.md
|
|
4
|
+
// Reference impl: lumencast-go/interop/control/control.go (mirrored here)
|
|
5
|
+
//
|
|
6
|
+
// This module is for test infrastructure only. NEVER mount on a public-facing
|
|
7
|
+
// port. The serve-scenario CLI is the only sanctioned consumer.
|
|
8
|
+
import { createServer } from "node:http";
|
|
9
|
+
import { canWritePath } from "./auth.js";
|
|
10
|
+
import { createScene } from "./scene.js";
|
|
11
|
+
const SERVER_NAME = "lumencast-js";
|
|
12
|
+
const CONTROL_PLANE_VERSION = 1;
|
|
13
|
+
const ROUTES = {
|
|
14
|
+
"/test/setup": { method: "POST" },
|
|
15
|
+
"/test/reset": { method: "POST" },
|
|
16
|
+
"/test/state": { method: "GET" },
|
|
17
|
+
"/test/emit": { method: "POST" },
|
|
18
|
+
"/test/health": { method: "GET" },
|
|
19
|
+
};
|
|
20
|
+
export async function startTestControl(options) {
|
|
21
|
+
const host = options.host ?? "127.0.0.1";
|
|
22
|
+
const httpServer = createServer((req, res) => handle(req, res));
|
|
23
|
+
await new Promise((resolve, reject) => {
|
|
24
|
+
httpServer.once("error", reject);
|
|
25
|
+
httpServer.listen(options.port ?? 0, host, () => {
|
|
26
|
+
httpServer.off("error", reject);
|
|
27
|
+
resolve();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
const address = httpServer.address();
|
|
31
|
+
if (!address || typeof address === "string") {
|
|
32
|
+
throw new Error("test-control: failed to bind HTTP");
|
|
33
|
+
}
|
|
34
|
+
const port = address.port;
|
|
35
|
+
function handle(req, res) {
|
|
36
|
+
const url = req.url ?? "/";
|
|
37
|
+
const method = req.method ?? "";
|
|
38
|
+
const route = ROUTES[url];
|
|
39
|
+
if (!route) {
|
|
40
|
+
writeProblem(res, 404, "not-found", `unknown path ${url}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (method !== route.method) {
|
|
44
|
+
writeProblem(res, 405, "method-not-allowed", `${method} not allowed on ${url}`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
switch (url) {
|
|
48
|
+
case "/test/setup":
|
|
49
|
+
return void onSetup(req, res);
|
|
50
|
+
case "/test/reset":
|
|
51
|
+
return void onReset(req, res);
|
|
52
|
+
case "/test/state":
|
|
53
|
+
return void onState(req, res);
|
|
54
|
+
case "/test/emit":
|
|
55
|
+
return void onEmit(req, res);
|
|
56
|
+
case "/test/health":
|
|
57
|
+
return void onHealth(req, res);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function onSetup(req, res) {
|
|
61
|
+
let body;
|
|
62
|
+
try {
|
|
63
|
+
body = (await readJson(req));
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
writeProblem(res, 400, "bad-body", `invalid JSON: ${err.message}`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!body || !Array.isArray(body.bundles) || body.bundles.length === 0) {
|
|
70
|
+
writeProblem(res, 400, "missing-bundle", "at least one bundle required");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
options.server.reset();
|
|
74
|
+
installTokens(options.auth, body.tokens ?? {});
|
|
75
|
+
const primary = body.bundles[0];
|
|
76
|
+
if (!primary || !primary.id) {
|
|
77
|
+
writeProblem(res, 400, "missing-bundle-id", "bundles[0].id required");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const initialState = (body.initial_state ?? {});
|
|
81
|
+
// Prefer the inline LSML's `scene_id` over the bundle id (which is just
|
|
82
|
+
// the $BUNDLE.<id>.hash placeholder identifier). Fall back to the bundle id.
|
|
83
|
+
const inline = primary.inline && typeof primary.inline === "object" && primary.inline !== null
|
|
84
|
+
? primary.inline
|
|
85
|
+
: undefined;
|
|
86
|
+
const inlineSceneId = inline?.scene_id;
|
|
87
|
+
const sceneId = typeof inlineSceneId === "string" ? inlineSceneId : primary.id;
|
|
88
|
+
const sceneVersion = primary.hash ?? "";
|
|
89
|
+
const operatorInputs = Array.isArray(inline?.operator_inputs)
|
|
90
|
+
? inline.operator_inputs
|
|
91
|
+
: undefined;
|
|
92
|
+
const scene = createScene({
|
|
93
|
+
sceneId,
|
|
94
|
+
sceneVersion,
|
|
95
|
+
initialState,
|
|
96
|
+
...(operatorInputs ? { operatorInputs } : {}),
|
|
97
|
+
});
|
|
98
|
+
options.server.setActiveScene(scene);
|
|
99
|
+
// The bundle provider serves the inline body of any registered bundle.
|
|
100
|
+
const bundleMap = new Map();
|
|
101
|
+
for (const b of body.bundles) {
|
|
102
|
+
if (b.hash && b.inline !== undefined)
|
|
103
|
+
bundleMap.set(b.hash, b.inline);
|
|
104
|
+
}
|
|
105
|
+
options.server.setBundleProvider((version) => bundleMap.get(version));
|
|
106
|
+
writeJson(res, 200, {
|
|
107
|
+
ws_url: options.server.wsUrl,
|
|
108
|
+
scene_id: sceneId,
|
|
109
|
+
scene_version: sceneVersion,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function onReset(_req, res) {
|
|
113
|
+
options.server.reset();
|
|
114
|
+
options.auth.reset();
|
|
115
|
+
res.statusCode = 204;
|
|
116
|
+
res.end();
|
|
117
|
+
}
|
|
118
|
+
function onState(_req, res) {
|
|
119
|
+
const scene = options.server.activeScene();
|
|
120
|
+
// The contract returns 409 if /test/setup wasn't called; we approximate
|
|
121
|
+
// that condition by checking whether the scene has been swapped from a
|
|
122
|
+
// synthetic initial scene. Since the kit always has an active scene, we
|
|
123
|
+
// expose whatever is current. Callers that need the 409 semantics call
|
|
124
|
+
// /test/reset first to drop everything.
|
|
125
|
+
writeJson(res, 200, {
|
|
126
|
+
scene_id: scene.sceneId,
|
|
127
|
+
scene_version: scene.sceneVersion,
|
|
128
|
+
state: scene.store.snapshot(),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async function onEmit(req, res) {
|
|
132
|
+
let body;
|
|
133
|
+
try {
|
|
134
|
+
body = (await readJson(req));
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
writeProblem(res, 400, "bad-body", `invalid JSON: ${err.message}`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (!body || !Array.isArray(body.patches) || body.patches.length === 0) {
|
|
141
|
+
writeProblem(res, 400, "empty-patches", "at least one patch required");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const scene = options.server.activeScene();
|
|
145
|
+
try {
|
|
146
|
+
scene.update(body.patches);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
writeProblem(res, 400, "invalid", err.message);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
res.statusCode = 204;
|
|
153
|
+
res.end();
|
|
154
|
+
}
|
|
155
|
+
function onHealth(_req, res) {
|
|
156
|
+
writeJson(res, 200, {
|
|
157
|
+
status: "ok",
|
|
158
|
+
control_plane_version: CONTROL_PLANE_VERSION,
|
|
159
|
+
server: SERVER_NAME,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
async function close() {
|
|
163
|
+
await new Promise((resolve) => httpServer.close(() => resolve()));
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
url: `http://${host}:${port}`,
|
|
167
|
+
close,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** Replace the StaticTokens contents with one entry per recognised
|
|
171
|
+
* placeholder. $TOKEN_INVALID is intentionally NOT installed — the LSDP/1
|
|
172
|
+
* conformance suite expects auth to reject it. */
|
|
173
|
+
export function installTokens(auth, tokens) {
|
|
174
|
+
auth.reset();
|
|
175
|
+
for (const [placeholder, value] of Object.entries(tokens)) {
|
|
176
|
+
if (placeholder === "$TOKEN_INVALID" || !value)
|
|
177
|
+
continue;
|
|
178
|
+
const role = placeholderRole(placeholder);
|
|
179
|
+
if (!role)
|
|
180
|
+
continue;
|
|
181
|
+
auth.set(value, { role, subject: placeholder });
|
|
182
|
+
}
|
|
183
|
+
// canWritePath is referenced indirectly via the role we install — keep the
|
|
184
|
+
// import alive so the bundler doesn't drop it.
|
|
185
|
+
void canWritePath;
|
|
186
|
+
}
|
|
187
|
+
function placeholderRole(placeholder) {
|
|
188
|
+
switch (placeholder) {
|
|
189
|
+
case "$TOKEN_OPERATOR":
|
|
190
|
+
return "operator";
|
|
191
|
+
case "$TOKEN_VIEWER":
|
|
192
|
+
return "viewer";
|
|
193
|
+
case "$TOKEN_SERVICE":
|
|
194
|
+
return "service";
|
|
195
|
+
case "$TOKEN_TEST":
|
|
196
|
+
return "test";
|
|
197
|
+
default:
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function writeJson(res, status, body) {
|
|
202
|
+
res.statusCode = status;
|
|
203
|
+
res.setHeader("content-type", "application/json");
|
|
204
|
+
res.end(JSON.stringify(body));
|
|
205
|
+
}
|
|
206
|
+
function writeProblem(res, status, code, detail) {
|
|
207
|
+
res.statusCode = status;
|
|
208
|
+
res.setHeader("content-type", "application/problem+json");
|
|
209
|
+
res.end(JSON.stringify({
|
|
210
|
+
type: "about:blank",
|
|
211
|
+
title: `control: ${code}`,
|
|
212
|
+
status,
|
|
213
|
+
detail,
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
async function readJson(req) {
|
|
217
|
+
const chunks = [];
|
|
218
|
+
for await (const chunk of req)
|
|
219
|
+
chunks.push(chunk);
|
|
220
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
221
|
+
if (body.length === 0)
|
|
222
|
+
return {};
|
|
223
|
+
return JSON.parse(body);
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=test-control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-control.js","sourceRoot":"","sources":["../src/test-control.ts"],"names":[],"mappings":"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"}
|