@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/cli.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// lumencast-js server CLI.
|
|
3
|
+
//
|
|
4
|
+
// Subcommands:
|
|
5
|
+
// serve-scenario --ws-port N --test-control-port M [--host H]
|
|
6
|
+
// Boots the LSDP/1 server with the interop test control plane on a
|
|
7
|
+
// separate port. Prints exactly one JSON discovery line on stdout :
|
|
8
|
+
// {"control_url":"http://...","ws_url":"ws://.../lsdp/v1"}
|
|
9
|
+
// before any other output, then stays silent on stdout (logs go to stderr).
|
|
10
|
+
import { parseArgs } from "node:util";
|
|
11
|
+
import { createScene } from "./scene.js";
|
|
12
|
+
import { startServer } from "./server.js";
|
|
13
|
+
import { StaticTokens } from "./auth.js";
|
|
14
|
+
import { startTestControl } from "./test-control.js";
|
|
15
|
+
async function main(argv) {
|
|
16
|
+
const sub = argv[0];
|
|
17
|
+
if (!sub || sub === "--help" || sub === "-h") {
|
|
18
|
+
printUsage();
|
|
19
|
+
return sub ? 0 : 2;
|
|
20
|
+
}
|
|
21
|
+
if (sub === "serve-scenario")
|
|
22
|
+
return cmdServeScenario(argv.slice(1));
|
|
23
|
+
process.stderr.write(`lumencast-js: unknown subcommand "${sub}"\n`);
|
|
24
|
+
printUsage();
|
|
25
|
+
return 2;
|
|
26
|
+
}
|
|
27
|
+
function printUsage() {
|
|
28
|
+
process.stderr.write([
|
|
29
|
+
"usage: lumencast-js <subcommand> [flags]",
|
|
30
|
+
"",
|
|
31
|
+
"subcommands:",
|
|
32
|
+
" serve-scenario --ws-port N --test-control-port M [--host H]",
|
|
33
|
+
" Boot LSDP/1 server + interop test control plane (separate ports).",
|
|
34
|
+
"",
|
|
35
|
+
].join("\n"));
|
|
36
|
+
}
|
|
37
|
+
async function cmdServeScenario(args) {
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = parseArgs({
|
|
41
|
+
args,
|
|
42
|
+
options: {
|
|
43
|
+
"ws-port": { type: "string" },
|
|
44
|
+
"test-control-port": { type: "string" },
|
|
45
|
+
host: { type: "string", default: "127.0.0.1" },
|
|
46
|
+
help: { type: "boolean", short: "h" },
|
|
47
|
+
},
|
|
48
|
+
strict: true,
|
|
49
|
+
allowPositionals: false,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
process.stderr.write(`serve-scenario: ${err.message}\n`);
|
|
54
|
+
return 2;
|
|
55
|
+
}
|
|
56
|
+
if (parsed.values["help"]) {
|
|
57
|
+
process.stderr.write("serve-scenario --ws-port N --test-control-port M [--host H]\n");
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
const wsPort = parseIntFlag(parsed.values["ws-port"], "--ws-port");
|
|
61
|
+
const controlPort = parseIntFlag(parsed.values["test-control-port"], "--test-control-port");
|
|
62
|
+
if (wsPort === null || controlPort === null)
|
|
63
|
+
return 2;
|
|
64
|
+
const host = parsed.values["host"] ?? "127.0.0.1";
|
|
65
|
+
// Bootstrap a synthetic scene + empty bundle provider. Both are immediately
|
|
66
|
+
// overridden by the first POST /test/setup.
|
|
67
|
+
const initialScene = createScene({
|
|
68
|
+
sceneId: "__interop_initial__",
|
|
69
|
+
sceneVersion: "sha256:" + "0".repeat(64),
|
|
70
|
+
initialState: {},
|
|
71
|
+
});
|
|
72
|
+
const auth = new StaticTokens();
|
|
73
|
+
let server;
|
|
74
|
+
let control;
|
|
75
|
+
try {
|
|
76
|
+
server = await startServer({
|
|
77
|
+
port: wsPort,
|
|
78
|
+
host,
|
|
79
|
+
scene: initialScene,
|
|
80
|
+
bundleProvider: () => undefined,
|
|
81
|
+
authenticate: auth.authenticate,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
process.stderr.write(`serve-scenario: bind ws: ${err.message}\n`);
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
control = await startTestControl({
|
|
90
|
+
port: controlPort,
|
|
91
|
+
host,
|
|
92
|
+
server,
|
|
93
|
+
auth,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
await server.close();
|
|
98
|
+
process.stderr.write(`serve-scenario: bind control: ${err.message}\n`);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
// Discovery line — written before any other stdout output. The matrix
|
|
102
|
+
// driver greps for "control_url" to know we're ready.
|
|
103
|
+
process.stdout.write(JSON.stringify({
|
|
104
|
+
control_url: control.url,
|
|
105
|
+
ws_url: server.wsUrl,
|
|
106
|
+
}) + "\n");
|
|
107
|
+
let shutdown = async () => {
|
|
108
|
+
shutdown = null;
|
|
109
|
+
process.stderr.write("[lumencast-js] shutting down\n");
|
|
110
|
+
try {
|
|
111
|
+
await control.close();
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// ignore
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
await server.close();
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// ignore
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
process.on("SIGINT", () => void shutdown?.());
|
|
124
|
+
process.on("SIGTERM", () => void shutdown?.());
|
|
125
|
+
await new Promise((resolve) => {
|
|
126
|
+
const tick = () => {
|
|
127
|
+
if (shutdown === null)
|
|
128
|
+
resolve();
|
|
129
|
+
else
|
|
130
|
+
setTimeout(tick, 100);
|
|
131
|
+
};
|
|
132
|
+
tick();
|
|
133
|
+
});
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
function parseIntFlag(raw, name) {
|
|
137
|
+
if (typeof raw !== "string") {
|
|
138
|
+
process.stderr.write(`${name} required\n`);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const n = Number.parseInt(raw, 10);
|
|
142
|
+
if (!Number.isFinite(n) || n < 0 || n > 65535) {
|
|
143
|
+
process.stderr.write(`${name}: invalid port\n`);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
return n;
|
|
147
|
+
}
|
|
148
|
+
main(process.argv.slice(2)).then((code) => {
|
|
149
|
+
process.exitCode = code;
|
|
150
|
+
}, (err) => {
|
|
151
|
+
process.stderr.write(`lumencast-js: ${err.stack ?? String(err)}\n`);
|
|
152
|
+
process.exitCode = 1;
|
|
153
|
+
});
|
|
154
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { startServer, type ServerConfig, type ServerHandle } from "./server.js";
|
|
2
|
+
export { createScene, type Scene, type SceneInit } from "./scene.js";
|
|
3
|
+
export { LeafStore, type LeafStoreListener } from "./store.js";
|
|
4
|
+
export { defaultAuthenticate, canWritePath, StaticTokens, type Authenticate, type AuthDecision, type Role, } from "./auth.js";
|
|
5
|
+
export { startHttpPoll, type HttpPollOptions } from "./adapters/http-poll.js";
|
|
6
|
+
export { startTestControl, installTokens, type TestControlOptions, type TestControlHandle, } from "./test-control.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,YAAY,EACZ,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,IAAI,GACV,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,GACvB,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Public surface of @lumencast/server.
|
|
2
|
+
export { startServer } from "./server.js";
|
|
3
|
+
export { createScene } from "./scene.js";
|
|
4
|
+
export { LeafStore } from "./store.js";
|
|
5
|
+
export { defaultAuthenticate, canWritePath, StaticTokens, } from "./auth.js";
|
|
6
|
+
export { startHttpPoll } from "./adapters/http-poll.js";
|
|
7
|
+
export { startTestControl, installTokens, } from "./test-control.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AAEvC,OAAO,EAAE,WAAW,EAAwC,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,WAAW,EAA8B,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,SAAS,EAA0B,MAAM,YAAY,CAAC;AAC/D,OAAO,EACL,mBAAmB,EACnB,YAAY,EACZ,YAAY,GAIb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAwB,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EACL,gBAAgB,EAChB,aAAa,GAGd,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Cause, Patch } from "@lumencast/protocol";
|
|
2
|
+
export interface ReplayRecord {
|
|
3
|
+
readonly seq: number;
|
|
4
|
+
readonly patches: Patch[];
|
|
5
|
+
readonly cause?: Cause;
|
|
6
|
+
}
|
|
7
|
+
/** Default capacity (LSDP/1.1 §18.1 SHOULD ≥ 256). */
|
|
8
|
+
export declare const DEFAULT_REPLAY_BUFFER_SIZE = 256;
|
|
9
|
+
export declare class ReplayBuffer {
|
|
10
|
+
private readonly cap;
|
|
11
|
+
private readonly records;
|
|
12
|
+
private head;
|
|
13
|
+
private size;
|
|
14
|
+
constructor(capacity?: number);
|
|
15
|
+
/** Record one emission. Caller is responsible for monotonic seq. */
|
|
16
|
+
push(record: ReplayRecord): void;
|
|
17
|
+
/**
|
|
18
|
+
* Return every record with seq > sinceSeq, in monotonic order.
|
|
19
|
+
*
|
|
20
|
+
* The boolean is `false` when sinceSeq is older than the buffer's
|
|
21
|
+
* earliest retained entry — the caller MUST then fall back to a
|
|
22
|
+
* fresh snapshot per LSDP/1.1 §18.1.
|
|
23
|
+
*/
|
|
24
|
+
since(sinceSeq: number): {
|
|
25
|
+
records: ReplayRecord[];
|
|
26
|
+
covered: boolean;
|
|
27
|
+
};
|
|
28
|
+
/** Clear the buffer. Used on scene_changed. */
|
|
29
|
+
reset(): void;
|
|
30
|
+
get length(): number;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=replay-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-buffer.d.ts","sourceRoot":"","sources":["../src/replay-buffer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACxB;AAED,sDAAsD;AACtD,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,IAAI,CAAK;gBAEL,QAAQ,SAA6B;IAKjD,oEAAoE;IACpE,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAMhC;;;;;;OAMG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,YAAY,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE;IAoBtE,+CAA+C;IAC/C,KAAK,IAAI,IAAI;IAMb,IAAI,MAAM,IAAI,MAAM,CAEnB;CACF"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Per-scene replay buffer (LSDP/1.1 §18.1).
|
|
2
|
+
// Bounded ring of recent (seq, patches, cause) emissions so a 1.1
|
|
3
|
+
// client reconnecting with `since_sequence` can resume without a fresh
|
|
4
|
+
// snapshot.
|
|
5
|
+
/** Default capacity (LSDP/1.1 §18.1 SHOULD ≥ 256). */
|
|
6
|
+
export const DEFAULT_REPLAY_BUFFER_SIZE = 256;
|
|
7
|
+
export class ReplayBuffer {
|
|
8
|
+
cap;
|
|
9
|
+
records;
|
|
10
|
+
head = 0;
|
|
11
|
+
size = 0;
|
|
12
|
+
constructor(capacity = DEFAULT_REPLAY_BUFFER_SIZE) {
|
|
13
|
+
this.cap = capacity > 0 ? capacity : DEFAULT_REPLAY_BUFFER_SIZE;
|
|
14
|
+
this.records = new Array(this.cap);
|
|
15
|
+
}
|
|
16
|
+
/** Record one emission. Caller is responsible for monotonic seq. */
|
|
17
|
+
push(record) {
|
|
18
|
+
this.records[this.head] = record;
|
|
19
|
+
this.head = (this.head + 1) % this.cap;
|
|
20
|
+
if (this.size < this.cap)
|
|
21
|
+
this.size++;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Return every record with seq > sinceSeq, in monotonic order.
|
|
25
|
+
*
|
|
26
|
+
* The boolean is `false` when sinceSeq is older than the buffer's
|
|
27
|
+
* earliest retained entry — the caller MUST then fall back to a
|
|
28
|
+
* fresh snapshot per LSDP/1.1 §18.1.
|
|
29
|
+
*/
|
|
30
|
+
since(sinceSeq) {
|
|
31
|
+
if (this.size === 0) {
|
|
32
|
+
// Empty buffer — caller decides whether sinceSeq matches the
|
|
33
|
+
// current scene seq (caught up) or warrants a snapshot.
|
|
34
|
+
return { records: [], covered: true };
|
|
35
|
+
}
|
|
36
|
+
const tail = (this.head - this.size + this.cap) % this.cap;
|
|
37
|
+
const earliest = this.records[tail].seq;
|
|
38
|
+
if (sinceSeq + 1 < earliest) {
|
|
39
|
+
return { records: [], covered: false };
|
|
40
|
+
}
|
|
41
|
+
const out = [];
|
|
42
|
+
for (let i = 0; i < this.size; i++) {
|
|
43
|
+
const idx = (tail + i) % this.cap;
|
|
44
|
+
const r = this.records[idx];
|
|
45
|
+
if (r.seq > sinceSeq)
|
|
46
|
+
out.push(r);
|
|
47
|
+
}
|
|
48
|
+
return { records: out, covered: true };
|
|
49
|
+
}
|
|
50
|
+
/** Clear the buffer. Used on scene_changed. */
|
|
51
|
+
reset() {
|
|
52
|
+
this.head = 0;
|
|
53
|
+
this.size = 0;
|
|
54
|
+
this.records.fill(undefined);
|
|
55
|
+
}
|
|
56
|
+
get length() {
|
|
57
|
+
return this.size;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=replay-buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-buffer.js","sourceRoot":"","sources":["../src/replay-buffer.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,kEAAkE;AAClE,uEAAuE;AACvE,YAAY;AAUZ,sDAAsD;AACtD,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAE9C,MAAM,OAAO,YAAY;IACN,GAAG,CAAS;IACZ,OAAO,CAA+B;IAC/C,IAAI,GAAG,CAAC,CAAC;IACT,IAAI,GAAG,CAAC,CAAC;IAEjB,YAAY,QAAQ,GAAG,0BAA0B;QAC/C,IAAI,CAAC,GAAG,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,0BAA0B,CAAC;QAChE,IAAI,CAAC,OAAO,GAAG,IAAI,KAAK,CAA2B,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/D,CAAC;IAED,oEAAoE;IACpE,IAAI,CAAC,MAAoB;QACvB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;QACvC,IAAI,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG;YAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IACxC,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,QAAgB;QACpB,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACpB,6DAA6D;YAC7D,wDAAwD;YACxD,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACxC,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;QAC3D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAE,CAAC,GAAG,CAAC;QACzC,IAAI,QAAQ,GAAG,CAAC,GAAG,QAAQ,EAAE,CAAC;YAC5B,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACzC,CAAC;QACD,MAAM,GAAG,GAAmB,EAAE,CAAC;QAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;YAClC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAE,CAAC;YAC7B,IAAI,CAAC,CAAC,GAAG,GAAG,QAAQ;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpC,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACzC,CAAC;IAED,+CAA+C;IAC/C,KAAK;QACH,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QACd,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QACd,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;CACF"}
|
package/dist/scene.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Cause, LeafPath, LeafValue, Patch, SceneId, SceneVersion } from "@lumencast/protocol";
|
|
2
|
+
import { LeafStore } from "./store.js";
|
|
3
|
+
import { type ReplayRecord } from "./replay-buffer.js";
|
|
4
|
+
export interface OperatorInputDecl {
|
|
5
|
+
path: LeafPath;
|
|
6
|
+
/** "string" | "number" | "boolean" | "enum" | ... — see LSML 1.0 §8. */
|
|
7
|
+
type: string;
|
|
8
|
+
/** Type-specific constraints — currently maxLength, minLength, min, max, pattern. */
|
|
9
|
+
constraints?: {
|
|
10
|
+
maxLength?: number;
|
|
11
|
+
minLength?: number;
|
|
12
|
+
min?: number;
|
|
13
|
+
max?: number;
|
|
14
|
+
pattern?: string;
|
|
15
|
+
};
|
|
16
|
+
values?: unknown[];
|
|
17
|
+
writable_by?: string[];
|
|
18
|
+
[extra: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
export interface SceneInit {
|
|
21
|
+
sceneId: SceneId;
|
|
22
|
+
sceneVersion: SceneVersion;
|
|
23
|
+
initialState?: Record<LeafPath, LeafValue>;
|
|
24
|
+
/** Declared operator inputs. When set, `validateInput` enforces the schema. */
|
|
25
|
+
operatorInputs?: OperatorInputDecl[];
|
|
26
|
+
}
|
|
27
|
+
export type ValidationError = {
|
|
28
|
+
code: "UNKNOWN_PATH";
|
|
29
|
+
path: LeafPath;
|
|
30
|
+
message: string;
|
|
31
|
+
} | {
|
|
32
|
+
code: "INVALID_VALUE";
|
|
33
|
+
path: LeafPath;
|
|
34
|
+
message: string;
|
|
35
|
+
};
|
|
36
|
+
export interface Scene {
|
|
37
|
+
readonly sceneId: SceneId;
|
|
38
|
+
readonly sceneVersion: SceneVersion;
|
|
39
|
+
readonly store: LeafStore;
|
|
40
|
+
readonly operatorInputs: OperatorInputDecl[];
|
|
41
|
+
/** Update one or more leaves. Atomic per call. Optional `cause` propagates
|
|
42
|
+
* to subscribers as the resulting Delta.cause (LSDP/1.1 §3.2.3). */
|
|
43
|
+
update(patches: Patch[] | Record<LeafPath, LeafValue>, cause?: Cause): void;
|
|
44
|
+
/** Subscribe to all patches emitted by this scene. The listener receives
|
|
45
|
+
* the per-scene `seq` (LSDP/1.1 §18.1.1) — the same value across all
|
|
46
|
+
* concurrent listeners on a given delta. */
|
|
47
|
+
onPatches(listener: (seq: number, patches: Patch[], cause?: Cause) => void): () => void;
|
|
48
|
+
/** Current per-scene seq counter (LSDP/1.1 §18.1.1). 1 on a fresh
|
|
49
|
+
* scene ; increments on every emit. Late-joining subscribers ship
|
|
50
|
+
* snapshot at this value. */
|
|
51
|
+
currentSeq(): number;
|
|
52
|
+
/** Replay records strictly after `sinceSeq` for §18.1 resume.
|
|
53
|
+
* Returns `covered: false` when sinceSeq is older than the buffer's
|
|
54
|
+
* earliest entry — the caller MUST fall back to a fresh snapshot. */
|
|
55
|
+
replaySince(sinceSeq: number): {
|
|
56
|
+
records: ReplayRecord[];
|
|
57
|
+
covered: boolean;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Validate input patches against the declared operator_inputs schema.
|
|
61
|
+
* Returns the first error or null. The reserved `__test.*` namespace is
|
|
62
|
+
* always permitted (test sessions own that namespace per LSDP/1 §10).
|
|
63
|
+
*/
|
|
64
|
+
validateInput(patches: Patch[]): ValidationError | null;
|
|
65
|
+
}
|
|
66
|
+
export declare function createScene(init: SceneInit): Scene;
|
|
67
|
+
//# sourceMappingURL=scene.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene.d.ts","sourceRoot":"","sources":["../src/scene.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAA4C,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEjG,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,CAAC;IACf,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,WAAW,CAAC,EAAE;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,YAAY,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC3C,+EAA+E;IAC/E,cAAc,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACtC;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,iBAAiB,EAAE,CAAC;IAC7C;wEACoE;IACpE,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IAC5E;;gDAE4C;IAC5C,SAAS,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACxF;;iCAE6B;IAC7B,UAAU,IAAI,MAAM,CAAC;IACrB;;yEAEqE;IACrE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,YAAY,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7E;;;;OAIG;IACH,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,eAAe,GAAG,IAAI,CAAC;CACzD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,KAAK,CAiElD"}
|
package/dist/scene.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Scene — the server's authoritative view of one Lumencast scene.
|
|
2
|
+
// Wraps a LeafStore + identity (sceneId, sceneVersion) and an optional
|
|
3
|
+
// operator_inputs schema used by the server to validate `input` frames.
|
|
4
|
+
import { LeafStore } from "./store.js";
|
|
5
|
+
import { DEFAULT_REPLAY_BUFFER_SIZE, ReplayBuffer } from "./replay-buffer.js";
|
|
6
|
+
export function createScene(init) {
|
|
7
|
+
const store = new LeafStore(init.initialState ?? {});
|
|
8
|
+
const operatorInputs = init.operatorInputs ?? [];
|
|
9
|
+
const inputsByPath = new Map();
|
|
10
|
+
for (const oi of operatorInputs)
|
|
11
|
+
inputsByPath.set(oi.path, oi);
|
|
12
|
+
// LSDP/1.1 §18.1.1 — per-scene monotonic seq. Pre-seeded to 1 so the
|
|
13
|
+
// very first subscriber's snapshot ships at seq=1 (matches every
|
|
14
|
+
// existing 1.0 conformance scenario). Subsequent emits increment.
|
|
15
|
+
let seq = 1;
|
|
16
|
+
const replay = new ReplayBuffer(DEFAULT_REPLAY_BUFFER_SIZE);
|
|
17
|
+
const sceneListeners = new Set();
|
|
18
|
+
// Bridge LeafStore.onPatches → scene listeners. Single store listener
|
|
19
|
+
// increments `seq`, records to the buffer, fans out to scene listeners.
|
|
20
|
+
store.onPatches((patches, cause) => {
|
|
21
|
+
seq += 1;
|
|
22
|
+
replay.push({ seq, patches: patches.slice(), cause });
|
|
23
|
+
for (const l of sceneListeners)
|
|
24
|
+
l(seq, patches, cause);
|
|
25
|
+
});
|
|
26
|
+
function update(input, cause) {
|
|
27
|
+
const patches = Array.isArray(input)
|
|
28
|
+
? input
|
|
29
|
+
: Object.entries(input).map(([path, value]) => ({ path, value }));
|
|
30
|
+
store.apply(patches, cause);
|
|
31
|
+
}
|
|
32
|
+
function validateInput(patches) {
|
|
33
|
+
// No declared schema → server accepts any path (legacy / dev-mode behavior).
|
|
34
|
+
if (operatorInputs.length === 0)
|
|
35
|
+
return null;
|
|
36
|
+
for (const p of patches) {
|
|
37
|
+
if (p.path.startsWith("__test."))
|
|
38
|
+
continue; // test namespace bypass
|
|
39
|
+
const decl = inputsByPath.get(p.path);
|
|
40
|
+
if (!decl) {
|
|
41
|
+
return {
|
|
42
|
+
code: "UNKNOWN_PATH",
|
|
43
|
+
path: p.path,
|
|
44
|
+
message: `path ${p.path} not declared in operator_inputs`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const err = checkConstraint(decl, p.value);
|
|
48
|
+
if (err)
|
|
49
|
+
return { code: "INVALID_VALUE", path: p.path, message: err };
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
sceneId: init.sceneId,
|
|
55
|
+
sceneVersion: init.sceneVersion,
|
|
56
|
+
store,
|
|
57
|
+
operatorInputs,
|
|
58
|
+
update,
|
|
59
|
+
onPatches: (listener) => {
|
|
60
|
+
sceneListeners.add(listener);
|
|
61
|
+
return () => {
|
|
62
|
+
sceneListeners.delete(listener);
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
currentSeq: () => seq,
|
|
66
|
+
replaySince: (sinceSeq) => replay.since(sinceSeq),
|
|
67
|
+
validateInput,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function checkConstraint(decl, value) {
|
|
71
|
+
switch (decl.type) {
|
|
72
|
+
case "string":
|
|
73
|
+
case "text":
|
|
74
|
+
if (typeof value !== "string")
|
|
75
|
+
return `expected string, got ${typeof value}`;
|
|
76
|
+
if (decl.constraints?.maxLength !== undefined && value.length > decl.constraints.maxLength) {
|
|
77
|
+
return `exceeds maxLength ${decl.constraints.maxLength}`;
|
|
78
|
+
}
|
|
79
|
+
if (decl.constraints?.minLength !== undefined && value.length < decl.constraints.minLength) {
|
|
80
|
+
return `below minLength ${decl.constraints.minLength}`;
|
|
81
|
+
}
|
|
82
|
+
if (decl.constraints?.pattern && !new RegExp(decl.constraints.pattern).test(value)) {
|
|
83
|
+
return `does not match pattern`;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
case "number":
|
|
87
|
+
if (typeof value !== "number")
|
|
88
|
+
return `expected number, got ${typeof value}`;
|
|
89
|
+
if (decl.constraints?.min !== undefined && value < decl.constraints.min) {
|
|
90
|
+
return `below min ${decl.constraints.min}`;
|
|
91
|
+
}
|
|
92
|
+
if (decl.constraints?.max !== undefined && value > decl.constraints.max) {
|
|
93
|
+
return `above max ${decl.constraints.max}`;
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
case "boolean":
|
|
97
|
+
if (typeof value !== "boolean")
|
|
98
|
+
return `expected boolean, got ${typeof value}`;
|
|
99
|
+
return null;
|
|
100
|
+
case "enum":
|
|
101
|
+
if (decl.values && !decl.values.includes(value)) {
|
|
102
|
+
return `not in enum`;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
default:
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=scene.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene.js","sourceRoot":"","sources":["../src/scene.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type SceneVersion } from "@lumencast/protocol";
|
|
2
|
+
import { type Authenticate } from "./auth.js";
|
|
3
|
+
import type { Scene } from "./scene.js";
|
|
4
|
+
export interface ServerConfig {
|
|
5
|
+
/** TCP port to listen on. 0 = random. */
|
|
6
|
+
port?: number;
|
|
7
|
+
/** Hostname to bind. Defaults to 127.0.0.1. */
|
|
8
|
+
host?: string;
|
|
9
|
+
/** The initial scene this server serves. May be swapped via setActiveScene(). */
|
|
10
|
+
scene: Scene;
|
|
11
|
+
/** Resolves a SceneVersion to its LSML bundle JSON. May be swapped. */
|
|
12
|
+
bundleProvider: (version: SceneVersion) => Promise<unknown> | unknown;
|
|
13
|
+
/** Authenticate every subscribe; default accepts everything as `viewer`. */
|
|
14
|
+
authenticate?: Authenticate;
|
|
15
|
+
/** Optional ws path. Defaults to `/lsdp/v1`. */
|
|
16
|
+
wsPath?: string;
|
|
17
|
+
/** Optional bundle path prefix. Defaults to `/lsdp/v1/scenes`. */
|
|
18
|
+
bundlePathPrefix?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ServerHandle {
|
|
21
|
+
readonly wsUrl: string;
|
|
22
|
+
readonly httpUrl: string;
|
|
23
|
+
/**
|
|
24
|
+
* Replace the active scene. Detaches every subscriber (they receive a 1012
|
|
25
|
+
* "service restart" close so well-behaved clients reconnect cleanly).
|
|
26
|
+
* Test/interop only — production code should never call this.
|
|
27
|
+
*/
|
|
28
|
+
setActiveScene(scene: Scene): void;
|
|
29
|
+
/** Replace the bundle provider. Test/interop only. */
|
|
30
|
+
setBundleProvider(fn: (version: SceneVersion) => Promise<unknown> | unknown): void;
|
|
31
|
+
/** Read the current active scene. */
|
|
32
|
+
activeScene(): Scene;
|
|
33
|
+
/** Drop all subscribers without replacing the scene. Test/interop only. */
|
|
34
|
+
reset(): void;
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export declare function startServer(config: ServerConfig): Promise<ServerHandle>;
|
|
38
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAeA,OAAO,EAcL,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAqC,KAAK,YAAY,EAAqB,MAAM,WAAW,CAAC;AACpG,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,KAAK,EAAE,KAAK,CAAC;IACb,uEAAuE;IACvE,cAAc,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACtE,4EAA4E;IAC5E,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACnC,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC;IACnF,qCAAqC;IACrC,WAAW,IAAI,KAAK,CAAC;IACrB,2EAA2E;IAC3E,KAAK,IAAI,IAAI,CAAC;IACd,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAUD,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CA8S7E"}
|