@lumencast/protocol 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 +55 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +162 -0
- package/dist/cli.js.map +1 -0
- package/dist/codec.d.ts +15 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +328 -0
- package/dist/codec.js.map +1 -0
- package/dist/conformance/bundle-hash.d.ts +4 -0
- package/dist/conformance/bundle-hash.d.ts.map +1 -0
- package/dist/conformance/bundle-hash.js +49 -0
- package/dist/conformance/bundle-hash.js.map +1 -0
- package/dist/conformance/control-client.d.ts +42 -0
- package/dist/conformance/control-client.d.ts.map +1 -0
- package/dist/conformance/control-client.js +63 -0
- package/dist/conformance/control-client.js.map +1 -0
- package/dist/conformance/harness.d.ts +41 -0
- package/dist/conformance/harness.d.ts.map +1 -0
- package/dist/conformance/harness.js +441 -0
- package/dist/conformance/harness.js.map +1 -0
- package/dist/conformance/index.d.ts +8 -0
- package/dist/conformance/index.d.ts.map +1 -0
- package/dist/conformance/index.js +12 -0
- package/dist/conformance/index.js.map +1 -0
- package/dist/conformance/loader.d.ts +9 -0
- package/dist/conformance/loader.d.ts.map +1 -0
- package/dist/conformance/loader.js +27 -0
- package/dist/conformance/loader.js.map +1 -0
- package/dist/conformance/match.d.ts +7 -0
- package/dist/conformance/match.d.ts.map +1 -0
- package/dist/conformance/match.js +82 -0
- package/dist/conformance/match.js.map +1 -0
- package/dist/conformance/placeholders.d.ts +2 -0
- package/dist/conformance/placeholders.d.ts.map +1 -0
- package/dist/conformance/placeholders.js +40 -0
- package/dist/conformance/placeholders.js.map +1 -0
- package/dist/conformance/scenario.d.ts +33 -0
- package/dist/conformance/scenario.d.ts.map +1 -0
- package/dist/conformance/scenario.js +26 -0
- package/dist/conformance/scenario.js.map +1 -0
- package/dist/envelope.d.ts +66 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +111 -0
- package/dist/envelope.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +38 -0
- package/dist/errors.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/leaf-path.d.ts +21 -0
- package/dist/leaf-path.d.ts.map +1 -0
- package/dist/leaf-path.js +51 -0
- package/dist/leaf-path.js.map +1 -0
- package/dist/sequence.d.ts +25 -0
- package/dist/sequence.d.ts.map +1 -0
- package/dist/sequence.js +51 -0
- package/dist/sequence.js.map +1 -0
- package/dist/types.d.ts +159 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
- package/src/cli.ts +176 -0
- package/src/codec.ts +374 -0
- package/src/conformance/bundle-hash.ts +54 -0
- package/src/conformance/control-client.ts +93 -0
- package/src/conformance/harness.ts +492 -0
- package/src/conformance/index.ts +34 -0
- package/src/conformance/loader.ts +39 -0
- package/src/conformance/match.ts +92 -0
- package/src/conformance/placeholders.ts +45 -0
- package/src/conformance/scenario.ts +71 -0
- package/src/envelope.ts +180 -0
- package/src/errors.ts +55 -0
- package/src/index.ts +63 -0
- package/src/leaf-path.ts +58 -0
- package/src/sequence.ts +60 -0
- package/src/types.ts +201 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Placeholder substitution for scenarios.
|
|
2
|
+
//
|
|
3
|
+
// Two placeholder families :
|
|
4
|
+
// - $TOKEN_OPERATOR / $TOKEN_VIEWER / $TOKEN_SERVICE / $TOKEN_TEST / $TOKEN_INVALID
|
|
5
|
+
// → live token strings supplied by the harness via /test/setup
|
|
6
|
+
// - $BUNDLE.<id>.hash
|
|
7
|
+
// → sha256:<hex> of the inline bundle declared in scenario.bundles
|
|
8
|
+
//
|
|
9
|
+
// Unknown placeholders pass through verbatim. Some scenarios rely on this
|
|
10
|
+
// (auth-denied uses $TOKEN_INVALID literally because the server should reject
|
|
11
|
+
// any value the harness substitutes — cleaner to send the literal string).
|
|
12
|
+
const TOKEN_PREFIX = "$TOKEN_";
|
|
13
|
+
const BUNDLE_PREFIX = "$BUNDLE.";
|
|
14
|
+
const BUNDLE_SUFFIX = ".hash";
|
|
15
|
+
export function substitute(value, tokens, bundleHashes) {
|
|
16
|
+
if (typeof value === "string") {
|
|
17
|
+
if (value.startsWith(TOKEN_PREFIX)) {
|
|
18
|
+
const replacement = tokens[value];
|
|
19
|
+
return replacement ?? value;
|
|
20
|
+
}
|
|
21
|
+
if (value.startsWith(BUNDLE_PREFIX) && value.endsWith(BUNDLE_SUFFIX)) {
|
|
22
|
+
const id = value.slice(BUNDLE_PREFIX.length, value.length - BUNDLE_SUFFIX.length);
|
|
23
|
+
const hash = bundleHashes[id];
|
|
24
|
+
return hash ?? value;
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
return value.map((v) => substitute(v, tokens, bundleHashes));
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === "object" && value !== null) {
|
|
32
|
+
const out = {};
|
|
33
|
+
for (const [k, v] of Object.entries(value)) {
|
|
34
|
+
out[k] = substitute(v, tokens, bundleHashes);
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=placeholders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placeholders.js","sourceRoot":"","sources":["../../src/conformance/placeholders.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,6BAA6B;AAC7B,sFAAsF;AACtF,mEAAmE;AACnE,wBAAwB;AACxB,uEAAuE;AACvE,EAAE;AACF,0EAA0E;AAC1E,8EAA8E;AAC9E,2EAA2E;AAE3E,MAAM,YAAY,GAAG,SAAS,CAAC;AAC/B,MAAM,aAAa,GAAG,UAAU,CAAC;AACjC,MAAM,aAAa,GAAG,OAAO,CAAC;AAE9B,MAAM,UAAU,UAAU,CACxB,KAAc,EACd,MAA8B,EAC9B,YAAoC;IAEpC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YACnC,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;YAClC,OAAO,WAAW,IAAI,KAAK,CAAC;QAC9B,CAAC;QACD,IAAI,KAAK,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACrE,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;YAClF,MAAM,IAAI,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;YAC9B,OAAO,IAAI,IAAI,KAAK,CAAC;QACvB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;YACtE,GAAG,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type Tag = "required" | "recommended" | "extended";
|
|
2
|
+
export type Target = "any" | "server" | "runtime";
|
|
3
|
+
export type StepKind = "client-sends" | "server-sends" | "server-emits" | "expect-runtime-state" | "expect-server-state" | "expect-no-frame-for" | "expect-client-action";
|
|
4
|
+
export type ClientAction = "close-with-reason" | "reconnect";
|
|
5
|
+
export interface Step {
|
|
6
|
+
kind: StepKind;
|
|
7
|
+
/** client-sends, server-sends */
|
|
8
|
+
frame?: Record<string, unknown>;
|
|
9
|
+
/** expect-runtime-state, expect-server-state */
|
|
10
|
+
state?: Record<string, unknown>;
|
|
11
|
+
/** expect-no-frame-for */
|
|
12
|
+
duration_ms?: number;
|
|
13
|
+
/** expect-client-action */
|
|
14
|
+
action?: ClientAction;
|
|
15
|
+
reason?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface BundleDecl {
|
|
18
|
+
id: string;
|
|
19
|
+
inline: Record<string, unknown>;
|
|
20
|
+
/** sha256:<hex> — populated lazily by computeBundleHashes(). */
|
|
21
|
+
hash?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface Scenario {
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
tag: Tag;
|
|
27
|
+
target: Target;
|
|
28
|
+
spec_refs?: string[];
|
|
29
|
+
bundles?: BundleDecl[];
|
|
30
|
+
steps: Step[];
|
|
31
|
+
}
|
|
32
|
+
export declare function parseScenario(raw: string): Scenario;
|
|
33
|
+
//# sourceMappingURL=scenario.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario.d.ts","sourceRoot":"","sources":["../../src/conformance/scenario.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,GAAG,GAAG,UAAU,GAAG,aAAa,GAAG,UAAU,CAAC;AAC1D,MAAM,MAAM,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;AAElD,MAAM,MAAM,QAAQ,GAChB,cAAc,GACd,cAAc,GACd,cAAc,GACd,sBAAsB,GACtB,qBAAqB,GACrB,qBAAqB,GACrB,sBAAsB,CAAC;AAE3B,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,WAAW,CAAC;AAE7D,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,QAAQ,CAAC;IACf,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,GAAG,CAAC;IACT,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAoBnD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Scenario YAML loader. Mirrors the Go reference at
|
|
2
|
+
// lumencast-go/conformance/scenario.go and the contract in
|
|
3
|
+
// lumencast-protocol/conformance/v1/SCENARIO-FORMAT.md.
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
5
|
+
export function parseScenario(raw) {
|
|
6
|
+
const obj = parseYaml(raw);
|
|
7
|
+
if (!obj || typeof obj !== "object") {
|
|
8
|
+
throw new Error("scenario: not a YAML mapping");
|
|
9
|
+
}
|
|
10
|
+
if (!obj.name)
|
|
11
|
+
throw new Error("scenario: missing name");
|
|
12
|
+
if (!Array.isArray(obj.steps))
|
|
13
|
+
throw new Error("scenario: missing steps[]");
|
|
14
|
+
const tag = obj.tag ?? "required";
|
|
15
|
+
const target = obj.target ?? "any";
|
|
16
|
+
return {
|
|
17
|
+
name: obj.name,
|
|
18
|
+
description: obj.description ?? "",
|
|
19
|
+
tag,
|
|
20
|
+
target,
|
|
21
|
+
...(obj.spec_refs ? { spec_refs: obj.spec_refs } : {}),
|
|
22
|
+
...(obj.bundles ? { bundles: obj.bundles } : {}),
|
|
23
|
+
steps: obj.steps,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=scenario.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario.js","sourceRoot":"","sources":["../../src/conformance/scenario.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,2DAA2D;AAC3D,wDAAwD;AAExD,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AA8C1C,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CAAkC,CAAC;IAC5D,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IACzD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAE5E,MAAM,GAAG,GAAS,GAAG,CAAC,GAAuB,IAAI,UAAU,CAAC;IAC5D,MAAM,MAAM,GAAY,GAAG,CAAC,MAA6B,IAAI,KAAK,CAAC;IAEnE,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,EAAE;QAClC,GAAG;QACH,MAAM;QACN,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAuB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,KAAK,EAAE,GAAG,CAAC,KAAe;KAC3B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type Cause, type DeltaFrame, type ErrorCode, type ErrorFrame, type InputFrame, type LeafPath, type LeafValue, type Patch, type PingFrame, type PongFrame, type SceneChangedFrame, type SceneId, type SceneTransition, type SceneVersion, type SessionId, type SnapshotFrame, type SubscribeFrame, type UnsubscribeFrame } from "./types.js";
|
|
2
|
+
export interface SnapshotInit {
|
|
3
|
+
seq: number;
|
|
4
|
+
scene_id: SceneId;
|
|
5
|
+
scene_version: SceneVersion;
|
|
6
|
+
state: Record<LeafPath, LeafValue>;
|
|
7
|
+
ts?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function snapshot(init: SnapshotInit): SnapshotFrame;
|
|
10
|
+
export interface DeltaInit {
|
|
11
|
+
seq: number;
|
|
12
|
+
patches: Patch[];
|
|
13
|
+
ts?: string;
|
|
14
|
+
/** LSDP/1.1 §3.2.3 — optional provenance metadata. */
|
|
15
|
+
cause?: Cause;
|
|
16
|
+
}
|
|
17
|
+
export declare function delta(init: DeltaInit): DeltaFrame;
|
|
18
|
+
export interface SceneChangedInit {
|
|
19
|
+
seq: number;
|
|
20
|
+
scene_id: SceneId;
|
|
21
|
+
scene_version: SceneVersion;
|
|
22
|
+
ts?: string;
|
|
23
|
+
/** LSDP/1.1 §3.3.1 — previously active scene id. */
|
|
24
|
+
from_scene_id?: SceneId;
|
|
25
|
+
/** LSDP/1.1 §3.3.1 — show-level scene transition. */
|
|
26
|
+
transition?: SceneTransition;
|
|
27
|
+
}
|
|
28
|
+
export declare function sceneChanged(init: SceneChangedInit): SceneChangedFrame;
|
|
29
|
+
export interface ErrorInit {
|
|
30
|
+
seq: number;
|
|
31
|
+
code: ErrorCode;
|
|
32
|
+
message: string;
|
|
33
|
+
recoverable: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* REQUIRED for path-scoped codes (`WRITE_FORBIDDEN`, `UNKNOWN_PATH`,
|
|
36
|
+
* `INVALID_VALUE`) per LSDP/1.0.1 §3.4.1.
|
|
37
|
+
*/
|
|
38
|
+
path?: LeafPath;
|
|
39
|
+
retry_after_ms?: number;
|
|
40
|
+
requested_version?: string;
|
|
41
|
+
supported_version?: string;
|
|
42
|
+
session?: string;
|
|
43
|
+
ts?: string;
|
|
44
|
+
}
|
|
45
|
+
export declare function errorFrame(init: ErrorInit): ErrorFrame;
|
|
46
|
+
export declare function pong(nonce?: string): PongFrame;
|
|
47
|
+
export interface SubscribeInit {
|
|
48
|
+
token: string;
|
|
49
|
+
scene?: SceneId;
|
|
50
|
+
session?: SessionId;
|
|
51
|
+
/** LSDP/1.1 §4.1 — incremental resume from a known last-seen seq. */
|
|
52
|
+
since_sequence?: number;
|
|
53
|
+
}
|
|
54
|
+
export declare function subscribe(init: SubscribeInit): SubscribeFrame;
|
|
55
|
+
export interface InputInit {
|
|
56
|
+
patches: Patch[];
|
|
57
|
+
/** LSDP/1.1 §4.2 — optimistic-UI correlation tag. */
|
|
58
|
+
client_msg_id?: string;
|
|
59
|
+
}
|
|
60
|
+
export declare function input(patches: Patch[], init?: {
|
|
61
|
+
client_msg_id?: string;
|
|
62
|
+
}): InputFrame;
|
|
63
|
+
export declare function ping(nonce?: string): PingFrame;
|
|
64
|
+
/** LSDP/1.1 §4.4 — clean teardown signal. */
|
|
65
|
+
export declare function unsubscribe(): UnsubscribeFrame;
|
|
66
|
+
//# sourceMappingURL=envelope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"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"}
|
package/dist/envelope.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Envelope helpers — convenience constructors that stamp `v` automatically.
|
|
2
|
+
// Useful when emitting frames from a server or test fixture.
|
|
3
|
+
import { PROTOCOL_VERSION, } from "./types.js";
|
|
4
|
+
export function snapshot(init) {
|
|
5
|
+
const frame = {
|
|
6
|
+
v: PROTOCOL_VERSION,
|
|
7
|
+
type: "snapshot",
|
|
8
|
+
seq: init.seq,
|
|
9
|
+
scene_id: init.scene_id,
|
|
10
|
+
scene_version: init.scene_version,
|
|
11
|
+
state: init.state,
|
|
12
|
+
};
|
|
13
|
+
if (init.ts !== undefined)
|
|
14
|
+
frame.ts = init.ts;
|
|
15
|
+
return frame;
|
|
16
|
+
}
|
|
17
|
+
export function delta(init) {
|
|
18
|
+
if (init.patches.length === 0) {
|
|
19
|
+
throw new Error("delta.patches must be non-empty");
|
|
20
|
+
}
|
|
21
|
+
const frame = {
|
|
22
|
+
v: PROTOCOL_VERSION,
|
|
23
|
+
type: "delta",
|
|
24
|
+
seq: init.seq,
|
|
25
|
+
patches: init.patches,
|
|
26
|
+
};
|
|
27
|
+
if (init.ts !== undefined)
|
|
28
|
+
frame.ts = init.ts;
|
|
29
|
+
if (init.cause !== undefined)
|
|
30
|
+
frame.cause = init.cause;
|
|
31
|
+
return frame;
|
|
32
|
+
}
|
|
33
|
+
export function sceneChanged(init) {
|
|
34
|
+
const frame = {
|
|
35
|
+
v: PROTOCOL_VERSION,
|
|
36
|
+
type: "scene_changed",
|
|
37
|
+
seq: init.seq,
|
|
38
|
+
scene_id: init.scene_id,
|
|
39
|
+
scene_version: init.scene_version,
|
|
40
|
+
};
|
|
41
|
+
if (init.ts !== undefined)
|
|
42
|
+
frame.ts = init.ts;
|
|
43
|
+
if (init.from_scene_id !== undefined)
|
|
44
|
+
frame.from_scene_id = init.from_scene_id;
|
|
45
|
+
if (init.transition !== undefined)
|
|
46
|
+
frame.transition = init.transition;
|
|
47
|
+
return frame;
|
|
48
|
+
}
|
|
49
|
+
export function errorFrame(init) {
|
|
50
|
+
const frame = {
|
|
51
|
+
v: PROTOCOL_VERSION,
|
|
52
|
+
type: "error",
|
|
53
|
+
seq: init.seq,
|
|
54
|
+
code: init.code,
|
|
55
|
+
message: init.message,
|
|
56
|
+
recoverable: init.recoverable,
|
|
57
|
+
};
|
|
58
|
+
if (init.path !== undefined)
|
|
59
|
+
frame.path = init.path;
|
|
60
|
+
if (init.retry_after_ms !== undefined)
|
|
61
|
+
frame.retry_after_ms = init.retry_after_ms;
|
|
62
|
+
if (init.requested_version !== undefined)
|
|
63
|
+
frame.requested_version = init.requested_version;
|
|
64
|
+
if (init.supported_version !== undefined)
|
|
65
|
+
frame.supported_version = init.supported_version;
|
|
66
|
+
if (init.session !== undefined)
|
|
67
|
+
frame.session = init.session;
|
|
68
|
+
if (init.ts !== undefined)
|
|
69
|
+
frame.ts = init.ts;
|
|
70
|
+
return frame;
|
|
71
|
+
}
|
|
72
|
+
export function pong(nonce) {
|
|
73
|
+
const frame = { v: PROTOCOL_VERSION, type: "pong" };
|
|
74
|
+
if (nonce !== undefined)
|
|
75
|
+
frame.nonce = nonce;
|
|
76
|
+
return frame;
|
|
77
|
+
}
|
|
78
|
+
export function subscribe(init) {
|
|
79
|
+
const frame = {
|
|
80
|
+
v: PROTOCOL_VERSION,
|
|
81
|
+
type: "subscribe",
|
|
82
|
+
token: init.token,
|
|
83
|
+
};
|
|
84
|
+
if (init.scene !== undefined)
|
|
85
|
+
frame.scene = init.scene;
|
|
86
|
+
if (init.session !== undefined)
|
|
87
|
+
frame.session = init.session;
|
|
88
|
+
if (init.since_sequence !== undefined)
|
|
89
|
+
frame.since_sequence = init.since_sequence;
|
|
90
|
+
return frame;
|
|
91
|
+
}
|
|
92
|
+
export function input(patches, init) {
|
|
93
|
+
if (patches.length === 0) {
|
|
94
|
+
throw new Error("input.patches must be non-empty");
|
|
95
|
+
}
|
|
96
|
+
const frame = { v: PROTOCOL_VERSION, type: "input", patches };
|
|
97
|
+
if (init?.client_msg_id !== undefined)
|
|
98
|
+
frame.client_msg_id = init.client_msg_id;
|
|
99
|
+
return frame;
|
|
100
|
+
}
|
|
101
|
+
export function ping(nonce) {
|
|
102
|
+
const frame = { v: PROTOCOL_VERSION, type: "ping" };
|
|
103
|
+
if (nonce !== undefined)
|
|
104
|
+
frame.nonce = nonce;
|
|
105
|
+
return frame;
|
|
106
|
+
}
|
|
107
|
+
/** LSDP/1.1 §4.4 — clean teardown signal. */
|
|
108
|
+
export function unsubscribe() {
|
|
109
|
+
return { v: PROTOCOL_VERSION, type: "unsubscribe" };
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ErrorCode } from "./types.js";
|
|
2
|
+
export declare function isProtocolErrorCode(value: unknown): value is ErrorCode;
|
|
3
|
+
export interface LumencastErrorInit {
|
|
4
|
+
code: ErrorCode;
|
|
5
|
+
message: string;
|
|
6
|
+
recoverable: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* REQUIRED for path-scoped codes (`WRITE_FORBIDDEN`, `UNKNOWN_PATH`,
|
|
9
|
+
* `INVALID_VALUE`) per LSDP/1.0.1 §3.4.1.
|
|
10
|
+
*/
|
|
11
|
+
path?: string;
|
|
12
|
+
retry_after_ms?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Typed runtime exception. Mirrors the shape of an LSDP `error` frame so that
|
|
16
|
+
* runtime hosts can match `error.code` against the closed taxonomy.
|
|
17
|
+
*/
|
|
18
|
+
export declare class LumencastError extends Error {
|
|
19
|
+
readonly code: ErrorCode;
|
|
20
|
+
readonly recoverable: boolean;
|
|
21
|
+
readonly path: string | undefined;
|
|
22
|
+
readonly retry_after_ms: number | undefined;
|
|
23
|
+
constructor(init: LumencastErrorInit);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAiB5C,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAEtE;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEhC,IAAI,EAAE,kBAAkB;CAQrC"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Error helpers — typed exception + code-set membership check.
|
|
2
|
+
// Code semantics live in lumencast-protocol/spec/ERROR-CODES.md.
|
|
3
|
+
const ERROR_CODES = new Set([
|
|
4
|
+
"AUTH_DENIED",
|
|
5
|
+
"WRITE_FORBIDDEN",
|
|
6
|
+
"SCENE_NOT_FOUND",
|
|
7
|
+
"BUNDLE_FETCH_FAILED",
|
|
8
|
+
"BUNDLE_INCOMPATIBLE",
|
|
9
|
+
"VERSION_GAP",
|
|
10
|
+
"VERSION_MISMATCH",
|
|
11
|
+
"UNKNOWN_PATH",
|
|
12
|
+
"INVALID_VALUE",
|
|
13
|
+
"RATE_LIMIT",
|
|
14
|
+
"TEST_SESSION_EXPIRED",
|
|
15
|
+
"INTERNAL",
|
|
16
|
+
]);
|
|
17
|
+
export function isProtocolErrorCode(value) {
|
|
18
|
+
return typeof value === "string" && ERROR_CODES.has(value);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Typed runtime exception. Mirrors the shape of an LSDP `error` frame so that
|
|
22
|
+
* runtime hosts can match `error.code` against the closed taxonomy.
|
|
23
|
+
*/
|
|
24
|
+
export class LumencastError extends Error {
|
|
25
|
+
code;
|
|
26
|
+
recoverable;
|
|
27
|
+
path;
|
|
28
|
+
retry_after_ms;
|
|
29
|
+
constructor(init) {
|
|
30
|
+
super(init.message);
|
|
31
|
+
this.name = "LumencastError";
|
|
32
|
+
this.code = init.code;
|
|
33
|
+
this.recoverable = init.recoverable;
|
|
34
|
+
this.path = init.path;
|
|
35
|
+
this.retry_after_ms = init.retry_after_ms;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,iEAAiE;AAIjE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAY;IACrC,aAAa;IACb,iBAAiB;IACjB,iBAAiB;IACjB,qBAAqB;IACrB,qBAAqB;IACrB,aAAa;IACb,kBAAkB;IAClB,cAAc;IACd,eAAe;IACf,YAAY;IACZ,sBAAsB;IACtB,UAAU;CACX,CAAC,CAAC;AAEH,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,WAAW,CAAC,GAAG,CAAC,KAAkB,CAAC,CAAC;AAC1E,CAAC;AAcD;;;GAGG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,IAAI,CAAY;IAChB,WAAW,CAAU;IACrB,IAAI,CAAqB;IACzB,cAAc,CAAqB;IAE5C,YAAY,IAAwB;QAClC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC;IAC5C,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { PROTOCOL_VERSION, WS_SUBPROTOCOL, WS_SUBPROTOCOL_V1_1, WS_SUBPROTOCOLS, type Cause, type ClientFrame, type DeltaFrame, type ErrorCode, type ErrorFrame, type InputFrame, type LeafPath, type LeafValue, type Patch, type PingFrame, type PongFrame, type SceneChangedFrame, type SceneId, type SceneTransition, type SceneVersion, type ServerFrame, type SessionId, type SnapshotFrame, type SubscribeFrame, type TransitionSpec, type UnsubscribeFrame, } from "./types.js";
|
|
2
|
+
export { LumencastError, isProtocolErrorCode, type LumencastErrorInit } from "./errors.js";
|
|
3
|
+
export { encodeFrame, decodeServerFrame, decodeClientFrame } from "./codec.js";
|
|
4
|
+
export { SequenceTracker, type SequenceObservation } from "./sequence.js";
|
|
5
|
+
export { RESERVED_NAMESPACES, type ReservedNamespace, parseLeafPath, formatLeafPath, isReservedPath, isUnknownReservedPath, substituteScope, } from "./leaf-path.js";
|
|
6
|
+
export { snapshot, delta, sceneChanged, errorFrame, pong, subscribe, input, ping, unsubscribe, type SnapshotInit, type DeltaInit, type SceneChangedInit, type ErrorInit, type SubscribeInit, type InputInit, } from "./envelope.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,EACL,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,OAAO,EACZ,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,gBAAgB,GACtB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE3F,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAE1E,OAAO,EACL,mBAAmB,EACnB,KAAK,iBAAiB,EACtB,aAAa,EACb,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,QAAQ,EACR,KAAK,EACL,YAAY,EACZ,UAAU,EACV,IAAI,EACJ,SAAS,EACT,KAAK,EACL,IAAI,EACJ,WAAW,EACX,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,SAAS,GACf,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Public surface of @lumencast/protocol.
|
|
2
|
+
export { PROTOCOL_VERSION, WS_SUBPROTOCOL, WS_SUBPROTOCOL_V1_1, WS_SUBPROTOCOLS, } from "./types.js";
|
|
3
|
+
export { LumencastError, isProtocolErrorCode } from "./errors.js";
|
|
4
|
+
export { encodeFrame, decodeServerFrame, decodeClientFrame } from "./codec.js";
|
|
5
|
+
export { SequenceTracker } from "./sequence.js";
|
|
6
|
+
export { RESERVED_NAMESPACES, parseLeafPath, formatLeafPath, isReservedPath, isUnknownReservedPath, substituteScope, } from "./leaf-path.js";
|
|
7
|
+
export { snapshot, delta, sceneChanged, errorFrame, pong, subscribe, input, ping, unsubscribe, } from "./envelope.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yCAAyC;AAEzC,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,eAAe,GAsBhB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAA2B,MAAM,aAAa,CAAC;AAE3F,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,eAAe,EAA4B,MAAM,eAAe,CAAC;AAE1E,OAAO,EACL,mBAAmB,EAEnB,aAAa,EACb,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,QAAQ,EACR,KAAK,EACL,YAAY,EACZ,UAAU,EACV,IAAI,EACJ,SAAS,EACT,KAAK,EACL,IAAI,EACJ,WAAW,GAOZ,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LeafPath } from "./types.js";
|
|
2
|
+
/** Reserved top-level namespaces per LSDP/1 §10. */
|
|
3
|
+
export declare const RESERVED_NAMESPACES: readonly ["__inputs", "__system", "__test", "__schema"];
|
|
4
|
+
export type ReservedNamespace = (typeof RESERVED_NAMESPACES)[number];
|
|
5
|
+
/** Parse a `LeafPath` string into its segments. Throws on malformed input. */
|
|
6
|
+
export declare function parseLeafPath(path: LeafPath): string[];
|
|
7
|
+
/** Format an array of segments back to a `LeafPath`. */
|
|
8
|
+
export declare function formatLeafPath(segments: string[]): LeafPath;
|
|
9
|
+
/** True if the path begins with a reserved namespace (`__inputs`, `__system`, ...). */
|
|
10
|
+
export declare function isReservedPath(path: LeafPath): boolean;
|
|
11
|
+
/** True if the path begins with `__` but is NOT one of the four declared namespaces. */
|
|
12
|
+
export declare function isUnknownReservedPath(path: LeafPath): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Substitute `{scope}` placeholders inside a path template.
|
|
15
|
+
* Used by `repeat` primitives to bind per-item paths.
|
|
16
|
+
*
|
|
17
|
+
* Example: `substituteScope("{player}.score", { player: "players.0" })`
|
|
18
|
+
* → "players.0.score"
|
|
19
|
+
*/
|
|
20
|
+
export declare function substituteScope(template: LeafPath, scopes: Record<string, string>): LeafPath;
|
|
21
|
+
//# sourceMappingURL=leaf-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaf-path.d.ts","sourceRoot":"","sources":["../src/leaf-path.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAK3C,oDAAoD;AACpD,eAAO,MAAM,mBAAmB,yDAA0D,CAAC;AAC3F,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAErE,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,EAAE,CAStD;AAED,wDAAwD;AACxD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,QAAQ,CAE3D;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAGtD;AAED,wFAAwF;AACxF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAI7D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,QAAQ,CAQ5F"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// LeafPath canonical form + scope substitution.
|
|
2
|
+
// Reference: LSDP/1 §10 (reserved namespaces) and LSML 1.0 §7 (repeat scope).
|
|
3
|
+
/** A path segment is alphanumeric + underscore. Numeric indices are decimal. */
|
|
4
|
+
const SEGMENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$|^[0-9]+$/;
|
|
5
|
+
/** Reserved top-level namespaces per LSDP/1 §10. */
|
|
6
|
+
export const RESERVED_NAMESPACES = ["__inputs", "__system", "__test", "__schema"];
|
|
7
|
+
/** Parse a `LeafPath` string into its segments. Throws on malformed input. */
|
|
8
|
+
export function parseLeafPath(path) {
|
|
9
|
+
if (path.length === 0)
|
|
10
|
+
throw new Error("leaf-path is empty");
|
|
11
|
+
const segs = path.split(".");
|
|
12
|
+
for (const s of segs) {
|
|
13
|
+
if (!SEGMENT_RE.test(s)) {
|
|
14
|
+
throw new Error(`leaf-path segment is not valid: ${s}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return segs;
|
|
18
|
+
}
|
|
19
|
+
/** Format an array of segments back to a `LeafPath`. */
|
|
20
|
+
export function formatLeafPath(segments) {
|
|
21
|
+
return segments.join(".");
|
|
22
|
+
}
|
|
23
|
+
/** True if the path begins with a reserved namespace (`__inputs`, `__system`, ...). */
|
|
24
|
+
export function isReservedPath(path) {
|
|
25
|
+
const head = path.split(".", 1)[0];
|
|
26
|
+
return RESERVED_NAMESPACES.includes(head ?? "");
|
|
27
|
+
}
|
|
28
|
+
/** True if the path begins with `__` but is NOT one of the four declared namespaces. */
|
|
29
|
+
export function isUnknownReservedPath(path) {
|
|
30
|
+
if (!path.startsWith("__"))
|
|
31
|
+
return false;
|
|
32
|
+
const head = path.split(".", 1)[0] ?? "";
|
|
33
|
+
return !RESERVED_NAMESPACES.includes(head);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Substitute `{scope}` placeholders inside a path template.
|
|
37
|
+
* Used by `repeat` primitives to bind per-item paths.
|
|
38
|
+
*
|
|
39
|
+
* Example: `substituteScope("{player}.score", { player: "players.0" })`
|
|
40
|
+
* → "players.0.score"
|
|
41
|
+
*/
|
|
42
|
+
export function substituteScope(template, scopes) {
|
|
43
|
+
return template.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => {
|
|
44
|
+
const replacement = scopes[name];
|
|
45
|
+
if (replacement === undefined) {
|
|
46
|
+
throw new Error(`unknown scope identifier in path template: ${name}`);
|
|
47
|
+
}
|
|
48
|
+
return replacement;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=leaf-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaf-path.js","sourceRoot":"","sources":["../src/leaf-path.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,8EAA8E;AAI9E,gFAAgF;AAChF,MAAM,UAAU,GAAG,mCAAmC,CAAC;AAEvD,oDAAoD;AACpD,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAU,CAAC;AAG3F,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAAC,IAAc;IAC1C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,cAAc,CAAC,QAAkB;IAC/C,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,cAAc,CAAC,IAAc;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,OAAQ,mBAAyC,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,qBAAqB,CAAC,IAAc;IAClD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,OAAO,CAAE,mBAAyC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACpE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,QAAkB,EAAE,MAA8B;IAChF,OAAO,QAAQ,CAAC,OAAO,CAAC,+BAA+B,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE;QAC3E,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,8CAA8C,IAAI,EAAE,CAAC,CAAC;QACxE,CAAC;QACD,OAAO,WAAW,CAAC;IACrB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type SequenceObservation = {
|
|
2
|
+
kind: "in-order";
|
|
3
|
+
seq: number;
|
|
4
|
+
} | {
|
|
5
|
+
kind: "duplicate";
|
|
6
|
+
seq: number;
|
|
7
|
+
lastSeq: number;
|
|
8
|
+
} | {
|
|
9
|
+
kind: "gap";
|
|
10
|
+
seq: number;
|
|
11
|
+
lastSeq: number;
|
|
12
|
+
};
|
|
13
|
+
export declare class SequenceTracker {
|
|
14
|
+
private lastSeq;
|
|
15
|
+
/** Observe an incoming server seq. Returns the classification. */
|
|
16
|
+
observe(seq: number): SequenceObservation;
|
|
17
|
+
/** Rebase the tracker to a snapshot's seq. Called after scene_changed
|
|
18
|
+
* or back-pressure recovery — the tracker takes the snapshot value as
|
|
19
|
+
* the new baseline regardless of previous state. */
|
|
20
|
+
observeSnapshot(seq: number): void;
|
|
21
|
+
/** Reset the tracker. Called on reconnect with no resume. */
|
|
22
|
+
reset(): void;
|
|
23
|
+
get last(): number;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=sequence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sequence.d.ts","sourceRoot":"","sources":["../src/sequence.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAAK;IAEpB,kEAAkE;IAClE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB;IAqBzC;;wDAEoD;IACpD,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAMlC,6DAA6D;IAC7D,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
package/dist/sequence.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Monotonic sequence tracker with gap detection.
|
|
2
|
+
// LSDP/1.1 §18.1.1: seq is per-scene, NOT per-subscription. The first
|
|
3
|
+
// frame of a fresh subscription can carry any seq >= 1 (late-joining
|
|
4
|
+
// subscribers see the current scene seq). The tracker rebases to the
|
|
5
|
+
// snapshot value after scene_changed via observeSnapshot.
|
|
6
|
+
//
|
|
7
|
+
// Receiver rules:
|
|
8
|
+
// - first frame on a fresh tracker → establish baseline (any seq >= 1)
|
|
9
|
+
// - seq > last + 1 → gap, runtime MUST close + reconnect
|
|
10
|
+
// - seq <= last → replay, runtime MUST drop silently
|
|
11
|
+
// - seq == last + 1 → in-order, accept
|
|
12
|
+
export class SequenceTracker {
|
|
13
|
+
lastSeq = 0;
|
|
14
|
+
/** Observe an incoming server seq. Returns the classification. */
|
|
15
|
+
observe(seq) {
|
|
16
|
+
if (!Number.isInteger(seq) || seq < 1) {
|
|
17
|
+
// Treat malformed seq as a gap so the runtime reconnects.
|
|
18
|
+
return { kind: "gap", seq, lastSeq: this.lastSeq };
|
|
19
|
+
}
|
|
20
|
+
if (this.lastSeq === 0) {
|
|
21
|
+
// Fresh tracker — any seq >= 1 establishes the baseline
|
|
22
|
+
// (LSDP/1.1 §18.1.1).
|
|
23
|
+
this.lastSeq = seq;
|
|
24
|
+
return { kind: "in-order", seq };
|
|
25
|
+
}
|
|
26
|
+
if (seq <= this.lastSeq) {
|
|
27
|
+
return { kind: "duplicate", seq, lastSeq: this.lastSeq };
|
|
28
|
+
}
|
|
29
|
+
if (seq > this.lastSeq + 1) {
|
|
30
|
+
return { kind: "gap", seq, lastSeq: this.lastSeq };
|
|
31
|
+
}
|
|
32
|
+
this.lastSeq = seq;
|
|
33
|
+
return { kind: "in-order", seq };
|
|
34
|
+
}
|
|
35
|
+
/** Rebase the tracker to a snapshot's seq. Called after scene_changed
|
|
36
|
+
* or back-pressure recovery — the tracker takes the snapshot value as
|
|
37
|
+
* the new baseline regardless of previous state. */
|
|
38
|
+
observeSnapshot(seq) {
|
|
39
|
+
if (Number.isInteger(seq) && seq >= 1) {
|
|
40
|
+
this.lastSeq = seq;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Reset the tracker. Called on reconnect with no resume. */
|
|
44
|
+
reset() {
|
|
45
|
+
this.lastSeq = 0;
|
|
46
|
+
}
|
|
47
|
+
get last() {
|
|
48
|
+
return this.lastSeq;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=sequence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sequence.js","sourceRoot":"","sources":["../src/sequence.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,sEAAsE;AACtE,qEAAqE;AACrE,qEAAqE;AACrE,0DAA0D;AAC1D,EAAE;AACF,kBAAkB;AAClB,yEAAyE;AACzE,2EAA2E;AAC3E,0EAA0E;AAC1E,wDAAwD;AAOxD,MAAM,OAAO,eAAe;IAClB,OAAO,GAAG,CAAC,CAAC;IAEpB,kEAAkE;IAClE,OAAO,CAAC,GAAW;QACjB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YACtC,0DAA0D;YAC1D,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QACrD,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;YACvB,wDAAwD;YACxD,sBAAsB;YACtB,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC;YACnB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;QACnC,CAAC;QACD,IAAI,GAAG,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACxB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QAC3D,CAAC;QACD,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QACrD,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC;QACnB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;IACnC,CAAC;IAED;;wDAEoD;IACpD,eAAe,CAAC,GAAW;QACzB,IAAI,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC;QACrB,CAAC;IACH,CAAC;IAED,6DAA6D;IAC7D,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;IACnB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;CACF"}
|