@velajs/live-protocol 1.1.0 → 1.22.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/CHANGELOG.md +19 -0
- package/README.md +3 -2
- package/dist/index.d.ts +46 -28
- package/dist/index.js +208 -139
- package/dist/index.js.map +1 -1
- package/package.json +16 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @velajs/live-protocol
|
|
2
2
|
|
|
3
|
+
## 1.22.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Continue the coordinated framework release on the 1.x line with the current checked provider, endpoint, identity, CRUD, live, and Studio APIs. Breaking API changes are accepted during this development phase; maintained applications use the current contracts.
|
|
8
|
+
- Publish from the pnpm packages workspace with TypeScript 7 and GitHub OIDC. Obsolete standalone examples have been removed; runnable applications live in apps/.
|
|
9
|
+
|
|
10
|
+
## 2.0.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Publish from the unified packages workspace with corrected repository paths, shared native tooling, TypeScript 7 checks, and npm OIDC releases. Runnable examples now live in apps/.
|
|
15
|
+
|
|
16
|
+
## 2.0.0
|
|
17
|
+
|
|
18
|
+
Shared runtime argument/result parsers and validated live frame contracts used by the server and every client.
|
|
19
|
+
|
|
20
|
+
Requires the coordinated Vela 2.0 package set. See the workspace migration guide.
|
|
21
|
+
|
|
3
22
|
## 1.1.0
|
|
4
23
|
|
|
5
24
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
The normative wire protocol for **Vela live queries** — the single source of truth both the server (`@velajs/vela/live`) and the client (`@velajs/client`) implement against, so the two sides cannot drift.
|
|
4
4
|
|
|
5
|
-
Zero runtime dependencies. Ships
|
|
5
|
+
Zero runtime dependencies. Ships four things:
|
|
6
6
|
|
|
7
7
|
1. **The frame catalog** — live frames ride Vela's WebSocket envelope under the reserved event `$live`, discriminated on `t`:
|
|
8
8
|
- client → server: `sub`, `unsub`, `presence`
|
|
9
9
|
- server → client: `ack`, `data`, `delta`, `settled`, `resume`, `error`
|
|
10
10
|
- plus the `Vela-Commit-Cursor` / `Vela-Commit-Epoch` HTTP header names used to gate optimistic-update drops.
|
|
11
|
-
2. **The shared keyed-delta codec** — `encodeListDelta(previous, next)` / `applyListDelta(current, ops)` with identical bail
|
|
11
|
+
2. **The shared keyed-delta codec** — `encodeListDelta(previous, next)` / `applyListDelta(current, ops)` with identical correctness bail rules on both sides, and an exact-reconstruction guarantee: whenever the encoder does not bail, applying the ops reproduces `next` byte-for-byte, ordering included. The codec does not guess whether a valid delta is cheaper from its operation count; delivery code compares the completed canonical delta and snapshot wire encodings.
|
|
12
12
|
3. **Golden conformance fixtures** — `runProtocolConformance(codec)` runs byte-exact frame fixtures, pinned delta fixtures, and a seeded randomized sweep. The server and client test suites both call it; a wire change that forgets to update the fixtures fails a test instead of shipping an incompatibility.
|
|
13
|
+
4. **Portable query definitions** — `defineLiveQuery({ args, result })` infers argument and result types from two runtime parsers. Share the same definition with the server's `@LiveQuery` decorator and the client's query map; any schema exposing `parse(unknown)` fits without adding a protocol runtime dependency.
|
|
13
14
|
|
|
14
15
|
## Versioning
|
|
15
16
|
|
package/dist/index.d.ts
CHANGED
|
@@ -9,31 +9,44 @@
|
|
|
9
9
|
* frame; a server that cannot serve that version replies
|
|
10
10
|
* `{ t: 'error', code: 'unsupported_protocol', fatal: true }`.
|
|
11
11
|
*/
|
|
12
|
-
declare const LIVE_PROTOCOL = 2;
|
|
12
|
+
export declare const LIVE_PROTOCOL = 2;
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/query.d.ts
|
|
15
|
+
/** A portable runtime schema: Zod and other parsers can implement this directly. */
|
|
16
|
+
interface LiveQueryDefinition<Args, Result> {
|
|
17
|
+
readonly args: {
|
|
18
|
+
parse(value: unknown): Args;
|
|
19
|
+
};
|
|
20
|
+
readonly result: {
|
|
21
|
+
parse(value: unknown): Result;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Share one args/result contract between a resolver and its typed client. */
|
|
25
|
+
export declare function defineLiveQuery<Args, Result>(definition: LiveQueryDefinition<Args, Result>): LiveQueryDefinition<Args, Result>;
|
|
13
26
|
//#endregion
|
|
14
27
|
//#region src/frames.d.ts
|
|
15
28
|
/** The reserved envelope event every live frame rides under. */
|
|
16
|
-
declare const LIVE_EVENT = "$live";
|
|
29
|
+
export declare const LIVE_EVENT = "$live";
|
|
17
30
|
/**
|
|
18
31
|
* The reserved event-name prefix. The WS dispatcher rejects app gateways that
|
|
19
32
|
* register a `$…` event at bootstrap so live (and future framework) frames can
|
|
20
33
|
* never collide with app events.
|
|
21
34
|
*/
|
|
22
|
-
declare const RESERVED_EVENT_PREFIX = "$";
|
|
35
|
+
export declare const RESERVED_EVENT_PREFIX = "$";
|
|
23
36
|
/**
|
|
24
37
|
* HTTP response headers carrying the commit cursor/epoch of the log scope a
|
|
25
38
|
* mutation's invalidations landed in. The client gates optimistic-layer drops
|
|
26
39
|
* on a subscription frame whose `cursor` passes this value (and whose `epoch`
|
|
27
40
|
* matches) — never on HTTP response timing, which races the broadcast.
|
|
28
41
|
*/
|
|
29
|
-
declare const COMMIT_CURSOR_HEADER = "Vela-Commit-Cursor";
|
|
30
|
-
declare const COMMIT_EPOCH_HEADER = "Vela-Commit-Epoch";
|
|
42
|
+
export declare const COMMIT_CURSOR_HEADER = "Vela-Commit-Cursor";
|
|
43
|
+
export declare const COMMIT_EPOCH_HEADER = "Vela-Commit-Epoch";
|
|
31
44
|
/** Default hard limits shared by every live-protocol endpoint. */
|
|
32
|
-
declare const MAX_LIVE_FRAME_BYTES: number;
|
|
33
|
-
declare const MAX_PRESENCE_METADATA_BYTES: number;
|
|
34
|
-
declare const MAX_DELTA_OPS = 1000;
|
|
45
|
+
export declare const MAX_LIVE_FRAME_BYTES: number;
|
|
46
|
+
export declare const MAX_PRESENCE_METADATA_BYTES: number;
|
|
47
|
+
export declare const MAX_DELTA_OPS = 1000;
|
|
35
48
|
/** Well-known `error` frame codes. The code space is open — receivers must tolerate unknown codes. */
|
|
36
|
-
declare const LIVE_ERROR_CODES: {
|
|
49
|
+
export declare const LIVE_ERROR_CODES: {
|
|
37
50
|
readonly UNSUPPORTED_PROTOCOL: 'unsupported_protocol';
|
|
38
51
|
readonly DUPLICATE_SUB: 'duplicate_sub';
|
|
39
52
|
readonly UNKNOWN_QUERY: 'unknown_query';
|
|
@@ -126,23 +139,23 @@ type ServerLiveFrame = {
|
|
|
126
139
|
};
|
|
127
140
|
type LiveFrame = ClientLiveFrame | ServerLiveFrame;
|
|
128
141
|
/** Structural guard for a single {@link RowOp}. Unknown extra fields are tolerated. */
|
|
129
|
-
declare const isRowOp: (value: unknown) => value is RowOp;
|
|
130
|
-
declare const isRowOps: (value: unknown) => value is RowOp[];
|
|
142
|
+
export declare const isRowOp: (value: unknown) => value is RowOp;
|
|
143
|
+
export declare const isRowOps: (value: unknown) => value is RowOp[];
|
|
131
144
|
/**
|
|
132
145
|
* Structural guard for a client frame. Frames with an unknown `t` return
|
|
133
146
|
* false — per the forward-compat rule the receiver then ignores the frame.
|
|
134
147
|
*/
|
|
135
|
-
declare const isClientLiveFrame: (value: unknown) => value is ClientLiveFrame;
|
|
148
|
+
export declare const isClientLiveFrame: (value: unknown) => value is ClientLiveFrame;
|
|
136
149
|
/** Structural guard for a server frame. Unknown `t` → false (receiver ignores). */
|
|
137
|
-
declare const isServerLiveFrame: (value: unknown) => value is ServerLiveFrame;
|
|
150
|
+
export declare const isServerLiveFrame: (value: unknown) => value is ServerLiveFrame;
|
|
138
151
|
/**
|
|
139
152
|
* Extract the live frame from a parsed WS envelope, or `undefined` when the
|
|
140
153
|
* envelope is not a live envelope. Does NOT validate the frame — pair with
|
|
141
154
|
* {@link isClientLiveFrame} / {@link isServerLiveFrame} on the receiving side.
|
|
142
155
|
*/
|
|
143
|
-
declare const readLiveEnvelope: (envelope: unknown) => unknown;
|
|
156
|
+
export declare const readLiveEnvelope: (envelope: unknown) => unknown;
|
|
144
157
|
/** Wrap a frame in the `$live` envelope object. */
|
|
145
|
-
declare const liveEnvelope: (frame: LiveFrame) => {
|
|
158
|
+
export declare const liveEnvelope: (frame: LiveFrame) => {
|
|
146
159
|
event: typeof LIVE_EVENT;
|
|
147
160
|
data: LiveFrame;
|
|
148
161
|
};
|
|
@@ -151,23 +164,28 @@ declare const liveEnvelope: (frame: LiveFrame) => {
|
|
|
151
164
|
* `JSON.stringify` of the result is the frame's canonical wire form — the one
|
|
152
165
|
* the golden fixtures pin byte-for-byte.
|
|
153
166
|
*/
|
|
154
|
-
declare const canonicalLiveFrame: (frame: LiveFrame) =>
|
|
167
|
+
export declare const canonicalLiveFrame: (frame: LiveFrame) => LiveFrame;
|
|
155
168
|
/** Canonical JSON encoding of a bare frame (no envelope). */
|
|
156
|
-
declare const encodeLiveFrame: (frame: LiveFrame) => string;
|
|
157
|
-
/**
|
|
158
|
-
|
|
169
|
+
export declare const encodeLiveFrame: (frame: LiveFrame) => string;
|
|
170
|
+
/**
|
|
171
|
+
* Canonical JSON encoding of the full `$live` envelope — what actually goes
|
|
172
|
+
* on the socket. The shared 64 KiB limit applies to this complete wire value,
|
|
173
|
+
* matching {@link readLiveEnvelope} and receiver-side raw-frame checks.
|
|
174
|
+
*/
|
|
175
|
+
export declare const encodeLiveEnvelope: (frame: LiveFrame) => string;
|
|
159
176
|
//#endregion
|
|
160
177
|
//#region src/delta.d.ts
|
|
161
178
|
/** Default row-identity field. Per-query override rides the `sub` frame's `key`. */
|
|
162
|
-
declare const DEFAULT_KEY_FIELD = "id";
|
|
179
|
+
export declare const DEFAULT_KEY_FIELD = "id";
|
|
163
180
|
/**
|
|
164
|
-
* Diff `previous` vs `next` into row ops, or `undefined` when any
|
|
165
|
-
* holds and the caller must send a full snapshot instead.
|
|
181
|
+
* Diff `previous` vs `next` into row ops, or `undefined` when any correctness
|
|
182
|
+
* bail rule holds and the caller must send a full snapshot instead. Whether a
|
|
183
|
+
* valid delta is cheaper than that snapshot is a delivery-layer decision.
|
|
166
184
|
*
|
|
167
185
|
* An empty array is a valid result (no row-level change — typically the server
|
|
168
186
|
* catches byte-identical results earlier and sends `settled` instead).
|
|
169
187
|
*/
|
|
170
|
-
declare const encodeListDelta: (previous: unknown, next: unknown, keyField?: string) => RowOp[] | undefined;
|
|
188
|
+
export declare const encodeListDelta: (previous: unknown, next: unknown, keyField?: string) => RowOp[] | undefined;
|
|
171
189
|
/**
|
|
172
190
|
* Merge row ops into a cached array result, returning a NEW array (the input
|
|
173
191
|
* is never mutated), or `undefined` when the ops cannot be applied cleanly —
|
|
@@ -177,7 +195,7 @@ declare const encodeListDelta: (previous: unknown, next: unknown, keyField?: str
|
|
|
177
195
|
* Idempotent by construction: replaying an op after a snapshot already
|
|
178
196
|
* delivered its effect changes nothing.
|
|
179
197
|
*/
|
|
180
|
-
declare const applyListDelta: (current: unknown, ops: readonly RowOp[], keyField?: string) => unknown[] | undefined;
|
|
198
|
+
export declare const applyListDelta: (current: unknown, ops: readonly RowOp[], keyField?: string) => unknown[] | undefined;
|
|
181
199
|
//#endregion
|
|
182
200
|
//#region src/conformance.d.ts
|
|
183
201
|
/** The two halves a wire endpoint must implement compatibly. */
|
|
@@ -194,7 +212,7 @@ interface ConformanceReport {
|
|
|
194
212
|
* Run the full conformance suite against a codec (defaults to the reference
|
|
195
213
|
* codec in this package — the package's own tests run exactly this).
|
|
196
214
|
*/
|
|
197
|
-
declare const runProtocolConformance: (codec?: DeltaCodec) => ConformanceReport;
|
|
215
|
+
export declare const runProtocolConformance: (codec?: DeltaCodec) => ConformanceReport;
|
|
198
216
|
//#endregion
|
|
199
217
|
//#region src/fixtures.d.ts
|
|
200
218
|
interface FrameFixture {
|
|
@@ -203,7 +221,7 @@ interface FrameFixture {
|
|
|
203
221
|
/** Exact canonical envelope bytes: `encodeLiveEnvelope(frame)` must equal this. */
|
|
204
222
|
wire: string;
|
|
205
223
|
}
|
|
206
|
-
declare const FRAME_FIXTURES: FrameFixture[];
|
|
224
|
+
export declare const FRAME_FIXTURES: FrameFixture[];
|
|
207
225
|
interface DeltaFixture {
|
|
208
226
|
name: string;
|
|
209
227
|
previous: unknown;
|
|
@@ -212,7 +230,7 @@ interface DeltaFixture {
|
|
|
212
230
|
/** Expected ops, or `null` when the encoder MUST bail to snapshot. */
|
|
213
231
|
expected: RowOp[] | null;
|
|
214
232
|
}
|
|
215
|
-
declare const DELTA_FIXTURES: DeltaFixture[];
|
|
233
|
+
export declare const DELTA_FIXTURES: DeltaFixture[];
|
|
216
234
|
//#endregion
|
|
217
|
-
export {
|
|
235
|
+
export type { ClientLiveFrame, ConformanceReport, DeltaCodec, DeltaFixture, FrameFixture, LiveErrorCode, LiveFrame, LiveQueryDefinition, RowOp, ServerLiveFrame };
|
|
218
236
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -11,13 +11,19 @@
|
|
|
11
11
|
*/
|
|
12
12
|
const LIVE_PROTOCOL = 2;
|
|
13
13
|
//#endregion
|
|
14
|
+
//#region src/query.ts
|
|
15
|
+
/** Share one args/result contract between a resolver and its typed client. */
|
|
16
|
+
function defineLiveQuery(definition) {
|
|
17
|
+
return definition;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
14
20
|
//#region src/frames.ts
|
|
15
21
|
/**
|
|
16
22
|
* The normative frame catalog for Vela live queries.
|
|
17
23
|
*
|
|
18
24
|
* Live frames ride Vela's existing WebSocket envelope `{ event, data }` under
|
|
19
25
|
* the single reserved event name `$live`; the frame itself is the envelope's
|
|
20
|
-
* `data`, discriminated on `t`. Classic gateway events,
|
|
26
|
+
* `data`, discriminated on `t`. Classic gateway events, `$ping`→`$pong`
|
|
21
27
|
* keepalive, and live frames coexist on one socket. The `$` prefix is reserved
|
|
22
28
|
* for the framework: app gateways must never register a `$…` event.
|
|
23
29
|
*
|
|
@@ -44,8 +50,8 @@ const RESERVED_EVENT_PREFIX = "$";
|
|
|
44
50
|
const COMMIT_CURSOR_HEADER = "Vela-Commit-Cursor";
|
|
45
51
|
const COMMIT_EPOCH_HEADER = "Vela-Commit-Epoch";
|
|
46
52
|
/** Default hard limits shared by every live-protocol endpoint. */
|
|
47
|
-
const MAX_LIVE_FRAME_BYTES =
|
|
48
|
-
const MAX_PRESENCE_METADATA_BYTES =
|
|
53
|
+
const MAX_LIVE_FRAME_BYTES = 65536;
|
|
54
|
+
const MAX_PRESENCE_METADATA_BYTES = 4096;
|
|
49
55
|
const MAX_DELTA_OPS = 1e3;
|
|
50
56
|
/** Well-known `error` frame codes. The code space is open — receivers must tolerate unknown codes. */
|
|
51
57
|
const LIVE_ERROR_CODES = {
|
|
@@ -57,7 +63,7 @@ const LIVE_ERROR_CODES = {
|
|
|
57
63
|
LIMIT_EXCEEDED: "limit_exceeded",
|
|
58
64
|
INTERNAL: "internal"
|
|
59
65
|
};
|
|
60
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
66
|
+
const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
61
67
|
const isCursor = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
62
68
|
const isBoundedString = (value, max, allowEmpty = false) => typeof value === "string" && (allowEmpty || value.length > 0) && value.length <= max;
|
|
63
69
|
const isOptionalBoundedString = (value, max) => value === void 0 || isBoundedString(value, max);
|
|
@@ -65,26 +71,26 @@ const hasOwn = (value, key) => Object.hasOwn(value, key);
|
|
|
65
71
|
const hasCursorPair = (value, cursor, epoch) => value[cursor] === void 0 && value[epoch] === void 0 || isCursor(value[cursor]) && isBoundedString(value[epoch], 256);
|
|
66
72
|
/** Structural guard for a single {@link RowOp}. Unknown extra fields are tolerated. */
|
|
67
73
|
const isRowOp = (value) => {
|
|
68
|
-
if (!isRecord(value) || !isBoundedString(value["key"], 512)) return false;
|
|
74
|
+
if (!isRecord$1(value) || !isJsonWithin(value, 65536) || !isBoundedString(value["key"], 512)) return false;
|
|
69
75
|
const op = value["op"];
|
|
70
76
|
if (op === "delete") return true;
|
|
71
77
|
if (op !== "insert" && op !== "update") return false;
|
|
72
|
-
if (!isRecord(value["row"])
|
|
78
|
+
if (!isRecord$1(value["row"])) return false;
|
|
73
79
|
if (op === "insert") {
|
|
74
80
|
const before = value["before"];
|
|
75
81
|
return before === null || isBoundedString(before, 512);
|
|
76
82
|
}
|
|
77
83
|
return true;
|
|
78
84
|
};
|
|
79
|
-
const isRowOps = (value) => Array.isArray(value) && value.length <= 1e3 && value.every(isRowOp);
|
|
85
|
+
const isRowOps = (value) => Array.isArray(value) && value.length <= 1e3 && isJsonWithin(value, 65536) && value.every(isRowOp);
|
|
80
86
|
/**
|
|
81
87
|
* Structural guard for a client frame. Frames with an unknown `t` return
|
|
82
88
|
* false — per the forward-compat rule the receiver then ignores the frame.
|
|
83
89
|
*/
|
|
84
90
|
const isClientLiveFrame = (value) => {
|
|
85
|
-
if (!isRecord(value) || !isJsonWithin(value, 65536)) return false;
|
|
91
|
+
if (!isRecord$1(value) || !isJsonWithin(value, 65536)) return false;
|
|
86
92
|
switch (value["t"]) {
|
|
87
|
-
case "sub": return isBoundedString(value["sub"], 256) && isBoundedString(value["query"], 256) && hasCursorPair(value, "sinceCursor", "sinceEpoch") && isOptionalBoundedString(value["key"], 128) && value["v"] === 2 && (!hasOwn(value, "args") || isJsonWithin(value["args"],
|
|
93
|
+
case "sub": return isBoundedString(value["sub"], 256) && isBoundedString(value["query"], 256) && hasCursorPair(value, "sinceCursor", "sinceEpoch") && isOptionalBoundedString(value["key"], 128) && value["v"] === 2 && (!hasOwn(value, "args") || isJsonWithin(value["args"], 32768));
|
|
88
94
|
case "unsub": return isBoundedString(value["sub"], 256);
|
|
89
95
|
case "presence": return isBoundedString(value["room"], 512) && (!hasOwn(value, "meta") || isJsonWithin(value["meta"], 4096));
|
|
90
96
|
default: return false;
|
|
@@ -92,7 +98,7 @@ const isClientLiveFrame = (value) => {
|
|
|
92
98
|
};
|
|
93
99
|
/** Structural guard for a server frame. Unknown `t` → false (receiver ignores). */
|
|
94
100
|
const isServerLiveFrame = (value) => {
|
|
95
|
-
if (!isRecord(value) || !isJsonWithin(value, 65536)) return false;
|
|
101
|
+
if (!isRecord$1(value) || !isJsonWithin(value, 65536)) return false;
|
|
96
102
|
switch (value["t"]) {
|
|
97
103
|
case "ack": return isBoundedString(value["sub"], 256);
|
|
98
104
|
case "data": return isBoundedString(value["sub"], 256) && hasOwn(value, "snapshot") && hasCursorPair(value, "cursor", "epoch");
|
|
@@ -109,7 +115,7 @@ const isServerLiveFrame = (value) => {
|
|
|
109
115
|
* {@link isClientLiveFrame} / {@link isServerLiveFrame} on the receiving side.
|
|
110
116
|
*/
|
|
111
117
|
const readLiveEnvelope = (envelope) => {
|
|
112
|
-
if (!isRecord(envelope) || envelope["event"] !== "$live" || !hasOwn(envelope, "data") || !isJsonWithin(envelope, 65536)) return;
|
|
118
|
+
if (!isRecord$1(envelope) || envelope["event"] !== "$live" || !hasOwn(envelope, "data") || !isJsonWithin(envelope, 65536)) return;
|
|
113
119
|
return envelope["data"];
|
|
114
120
|
};
|
|
115
121
|
const DANGEROUS_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -117,11 +123,12 @@ const DANGEROUS_KEYS = /* @__PURE__ */ new Set([
|
|
|
117
123
|
"constructor",
|
|
118
124
|
"prototype"
|
|
119
125
|
]);
|
|
126
|
+
const UTF8_ENCODER = new TextEncoder();
|
|
120
127
|
const isJsonWithin = (value, maxBytes) => {
|
|
121
|
-
if (!isJsonValue(value, /* @__PURE__ */ new WeakSet(), { nodes: 0 }, 0)) return false;
|
|
122
128
|
try {
|
|
129
|
+
if (!isJsonValue(value, /* @__PURE__ */ new WeakSet(), { nodes: 0 }, 0)) return false;
|
|
123
130
|
const serialized = JSON.stringify(value);
|
|
124
|
-
return serialized !== void 0 &&
|
|
131
|
+
return serialized !== void 0 && UTF8_ENCODER.encode(serialized).byteLength <= maxBytes;
|
|
125
132
|
} catch {
|
|
126
133
|
return false;
|
|
127
134
|
}
|
|
@@ -134,15 +141,30 @@ const isJsonValue = (value, seen, budget, depth) => {
|
|
|
134
141
|
if (typeof value !== "object" || seen.has(value)) return false;
|
|
135
142
|
seen.add(value);
|
|
136
143
|
const values = [];
|
|
137
|
-
if (Array.isArray(value))
|
|
138
|
-
|
|
144
|
+
if (Array.isArray(value)) {
|
|
145
|
+
const prototype = Object.getPrototypeOf(value);
|
|
146
|
+
if (prototype !== Array.prototype && prototype !== null) return false;
|
|
147
|
+
if (value.length > 1e4 - budget.nodes) return false;
|
|
148
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
149
|
+
if (key === "length") continue;
|
|
150
|
+
if (typeof key !== "string" || !/^(0|[1-9]\d*)$/.test(key)) return false;
|
|
151
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
152
|
+
if (descriptor === void 0 || !descriptor.enumerable || !("value" in descriptor)) return false;
|
|
153
|
+
const child = descriptor.value;
|
|
154
|
+
values.push(child);
|
|
155
|
+
}
|
|
156
|
+
if (values.length !== value.length) return false;
|
|
157
|
+
} else if (isRecord$1(value)) {
|
|
139
158
|
const prototype = Object.getPrototypeOf(value);
|
|
140
159
|
if (prototype !== Object.prototype && prototype !== null) return false;
|
|
141
|
-
for (const key of
|
|
142
|
-
if (DANGEROUS_KEYS.has(key)) return false;
|
|
143
|
-
|
|
160
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
161
|
+
if (typeof key !== "string" || DANGEROUS_KEYS.has(key)) return false;
|
|
162
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
163
|
+
if (descriptor === void 0 || !descriptor.enumerable || !("value" in descriptor)) return false;
|
|
164
|
+
const child = descriptor.value;
|
|
165
|
+
values.push(child);
|
|
144
166
|
}
|
|
145
|
-
}
|
|
167
|
+
} else return false;
|
|
146
168
|
for (const child of values) if (!isJsonValue(child, seen, budget, depth + 1)) return false;
|
|
147
169
|
seen.delete(value);
|
|
148
170
|
return true;
|
|
@@ -152,69 +174,28 @@ const liveEnvelope = (frame) => ({
|
|
|
152
174
|
event: LIVE_EVENT,
|
|
153
175
|
data: frame
|
|
154
176
|
});
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
"t",
|
|
158
|
-
"sub",
|
|
159
|
-
"query",
|
|
160
|
-
"args",
|
|
161
|
-
"sinceCursor",
|
|
162
|
-
"sinceEpoch",
|
|
163
|
-
"key",
|
|
164
|
-
"v"
|
|
165
|
-
],
|
|
166
|
-
unsub: ["t", "sub"],
|
|
167
|
-
presence: [
|
|
168
|
-
"t",
|
|
169
|
-
"room",
|
|
170
|
-
"meta"
|
|
171
|
-
],
|
|
172
|
-
ack: ["t", "sub"],
|
|
173
|
-
data: [
|
|
174
|
-
"t",
|
|
175
|
-
"sub",
|
|
176
|
-
"snapshot",
|
|
177
|
-
"cursor",
|
|
178
|
-
"epoch"
|
|
179
|
-
],
|
|
180
|
-
delta: [
|
|
181
|
-
"t",
|
|
182
|
-
"sub",
|
|
183
|
-
"ops",
|
|
184
|
-
"cursor",
|
|
185
|
-
"epoch"
|
|
186
|
-
],
|
|
187
|
-
settled: [
|
|
188
|
-
"t",
|
|
189
|
-
"sub",
|
|
190
|
-
"cursor",
|
|
191
|
-
"epoch"
|
|
192
|
-
],
|
|
193
|
-
resume: [
|
|
194
|
-
"t",
|
|
195
|
-
"sub",
|
|
196
|
-
"cursor",
|
|
197
|
-
"epoch"
|
|
198
|
-
],
|
|
199
|
-
error: [
|
|
200
|
-
"t",
|
|
201
|
-
"sub",
|
|
202
|
-
"code",
|
|
203
|
-
"message",
|
|
204
|
-
"fatal"
|
|
205
|
-
]
|
|
177
|
+
const unreachableVariant = (value) => {
|
|
178
|
+
throw new TypeError(`Unknown live protocol variant: ${JSON.stringify(value)}`);
|
|
206
179
|
};
|
|
207
|
-
const ROW_OP_KEYS = [
|
|
208
|
-
"op",
|
|
209
|
-
"key",
|
|
210
|
-
"row",
|
|
211
|
-
"before"
|
|
212
|
-
];
|
|
213
180
|
const canonicalRowOp = (op) => {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
181
|
+
switch (op.op) {
|
|
182
|
+
case "insert": return {
|
|
183
|
+
op: op.op,
|
|
184
|
+
key: op.key,
|
|
185
|
+
row: op.row,
|
|
186
|
+
before: op.before
|
|
187
|
+
};
|
|
188
|
+
case "update": return {
|
|
189
|
+
op: op.op,
|
|
190
|
+
key: op.key,
|
|
191
|
+
row: op.row
|
|
192
|
+
};
|
|
193
|
+
case "delete": return {
|
|
194
|
+
op: op.op,
|
|
195
|
+
key: op.key
|
|
196
|
+
};
|
|
197
|
+
default: return unreachableVariant(op);
|
|
198
|
+
}
|
|
218
199
|
};
|
|
219
200
|
/**
|
|
220
201
|
* Rebuild a frame with the canonical key order, dropping absent optionals.
|
|
@@ -222,31 +203,84 @@ const canonicalRowOp = (op) => {
|
|
|
222
203
|
* the golden fixtures pin byte-for-byte.
|
|
223
204
|
*/
|
|
224
205
|
const canonicalLiveFrame = (frame) => {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
206
|
+
switch (frame.t) {
|
|
207
|
+
case "sub": return {
|
|
208
|
+
t: frame.t,
|
|
209
|
+
sub: frame.sub,
|
|
210
|
+
query: frame.query,
|
|
211
|
+
...frame.args === void 0 ? {} : { args: frame.args },
|
|
212
|
+
...frame.sinceCursor === void 0 ? {} : { sinceCursor: frame.sinceCursor },
|
|
213
|
+
...frame.sinceEpoch === void 0 ? {} : { sinceEpoch: frame.sinceEpoch },
|
|
214
|
+
...frame.key === void 0 ? {} : { key: frame.key },
|
|
215
|
+
v: frame.v
|
|
216
|
+
};
|
|
217
|
+
case "unsub":
|
|
218
|
+
case "ack": return {
|
|
219
|
+
t: frame.t,
|
|
220
|
+
sub: frame.sub
|
|
221
|
+
};
|
|
222
|
+
case "presence": return {
|
|
223
|
+
t: frame.t,
|
|
224
|
+
room: frame.room,
|
|
225
|
+
...frame.meta === void 0 ? {} : { meta: frame.meta }
|
|
226
|
+
};
|
|
227
|
+
case "data": return {
|
|
228
|
+
t: frame.t,
|
|
229
|
+
sub: frame.sub,
|
|
230
|
+
snapshot: frame.snapshot,
|
|
231
|
+
...frame.cursor === void 0 ? {} : { cursor: frame.cursor },
|
|
232
|
+
...frame.epoch === void 0 ? {} : { epoch: frame.epoch }
|
|
233
|
+
};
|
|
234
|
+
case "delta": return {
|
|
235
|
+
t: frame.t,
|
|
236
|
+
sub: frame.sub,
|
|
237
|
+
ops: frame.ops.map(canonicalRowOp),
|
|
238
|
+
...frame.cursor === void 0 ? {} : { cursor: frame.cursor },
|
|
239
|
+
...frame.epoch === void 0 ? {} : { epoch: frame.epoch }
|
|
240
|
+
};
|
|
241
|
+
case "settled": return {
|
|
242
|
+
t: frame.t,
|
|
243
|
+
sub: frame.sub,
|
|
244
|
+
...frame.cursor === void 0 ? {} : { cursor: frame.cursor },
|
|
245
|
+
...frame.epoch === void 0 ? {} : { epoch: frame.epoch }
|
|
246
|
+
};
|
|
247
|
+
case "resume": return {
|
|
248
|
+
t: frame.t,
|
|
249
|
+
sub: frame.sub,
|
|
250
|
+
cursor: frame.cursor,
|
|
251
|
+
epoch: frame.epoch
|
|
252
|
+
};
|
|
253
|
+
case "error": return {
|
|
254
|
+
t: frame.t,
|
|
255
|
+
...frame.sub === void 0 ? {} : { sub: frame.sub },
|
|
256
|
+
code: frame.code,
|
|
257
|
+
message: frame.message,
|
|
258
|
+
fatal: frame.fatal
|
|
259
|
+
};
|
|
260
|
+
default: return unreachableVariant(frame);
|
|
233
261
|
}
|
|
234
|
-
return out;
|
|
235
262
|
};
|
|
236
263
|
/** Canonical JSON encoding of a bare frame (no envelope). */
|
|
237
264
|
const encodeLiveFrame = (frame) => {
|
|
238
265
|
if (!isClientLiveFrame(frame) && !isServerLiveFrame(frame)) throw new TypeError("Cannot encode an invalid or oversized live frame.");
|
|
239
266
|
return JSON.stringify(canonicalLiveFrame(frame));
|
|
240
267
|
};
|
|
241
|
-
/**
|
|
242
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Canonical JSON encoding of the full `$live` envelope — what actually goes
|
|
270
|
+
* on the socket. The shared 64 KiB limit applies to this complete wire value,
|
|
271
|
+
* matching {@link readLiveEnvelope} and receiver-side raw-frame checks.
|
|
272
|
+
*/
|
|
273
|
+
const encodeLiveEnvelope = (frame) => {
|
|
274
|
+
const encoded = `{"event":${JSON.stringify(LIVE_EVENT)},"data":${encodeLiveFrame(frame)}}`;
|
|
275
|
+
if (UTF8_ENCODER.encode(encoded).byteLength > 65536) throw new TypeError("Cannot encode an oversized live envelope.");
|
|
276
|
+
return encoded;
|
|
277
|
+
};
|
|
243
278
|
//#endregion
|
|
244
279
|
//#region src/delta.ts
|
|
245
280
|
/** Default row-identity field. Per-query override rides the `sub` frame's `key`. */
|
|
246
281
|
const DEFAULT_KEY_FIELD = "id";
|
|
247
282
|
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
248
283
|
const readRowKey = (row, keyField) => {
|
|
249
|
-
if (!isPlainObject(row)) return void 0;
|
|
250
284
|
const key = row[keyField];
|
|
251
285
|
return typeof key === "string" ? key : void 0;
|
|
252
286
|
};
|
|
@@ -257,17 +291,13 @@ const readRowKey = (row, keyField) => {
|
|
|
257
291
|
*/
|
|
258
292
|
const indexRows = (rows, keyField) => {
|
|
259
293
|
const byKey = /* @__PURE__ */ new Map();
|
|
260
|
-
const order = [];
|
|
261
294
|
for (const row of rows) {
|
|
295
|
+
if (!isPlainObject(row)) return void 0;
|
|
262
296
|
const key = readRowKey(row, keyField);
|
|
263
297
|
if (key === void 0 || byKey.has(key)) return void 0;
|
|
264
298
|
byKey.set(key, row);
|
|
265
|
-
order.push(key);
|
|
266
299
|
}
|
|
267
|
-
return
|
|
268
|
-
byKey,
|
|
269
|
-
order
|
|
270
|
-
};
|
|
300
|
+
return byKey;
|
|
271
301
|
};
|
|
272
302
|
/**
|
|
273
303
|
* True when rows present in BOTH lists keep the same relative order (bail
|
|
@@ -275,14 +305,15 @@ const indexRows = (rows, keyField) => {
|
|
|
275
305
|
* survivor that moved cannot be expressed as deltas.
|
|
276
306
|
*/
|
|
277
307
|
const survivorsKeepOrder = (previous, next) => {
|
|
278
|
-
const survivingPrevious = previous.
|
|
279
|
-
const survivingNext = next.
|
|
308
|
+
const survivingPrevious = [...previous.keys()].filter((key) => next.has(key));
|
|
309
|
+
const survivingNext = [...next.keys()].filter((key) => previous.has(key));
|
|
280
310
|
if (survivingPrevious.length !== survivingNext.length) return false;
|
|
281
311
|
return survivingPrevious.every((key, index) => survivingNext[index] === key);
|
|
282
312
|
};
|
|
283
313
|
/**
|
|
284
|
-
* Diff `previous` vs `next` into row ops, or `undefined` when any
|
|
285
|
-
* holds and the caller must send a full snapshot instead.
|
|
314
|
+
* Diff `previous` vs `next` into row ops, or `undefined` when any correctness
|
|
315
|
+
* bail rule holds and the caller must send a full snapshot instead. Whether a
|
|
316
|
+
* valid delta is cheaper than that snapshot is a delivery-layer decision.
|
|
286
317
|
*
|
|
287
318
|
* An empty array is a valid result (no row-level change — typically the server
|
|
288
319
|
* catches byte-identical results earlier and sends `settled` instead).
|
|
@@ -295,27 +326,25 @@ const encodeListDelta = (previous, next, keyField = "id") => {
|
|
|
295
326
|
if (previousIndex === void 0 || nextIndex === void 0) return void 0;
|
|
296
327
|
if (!survivorsKeepOrder(previousIndex, nextIndex)) return void 0;
|
|
297
328
|
const ops = [];
|
|
298
|
-
for (const key of previousIndex.
|
|
329
|
+
for (const key of previousIndex.keys()) if (!nextIndex.has(key)) ops.push({
|
|
299
330
|
op: "delete",
|
|
300
331
|
key
|
|
301
332
|
});
|
|
302
|
-
const followingSurvivor = new
|
|
333
|
+
const followingSurvivor = /* @__PURE__ */ new Map();
|
|
303
334
|
let anchor = null;
|
|
304
|
-
for (
|
|
305
|
-
followingSurvivor
|
|
306
|
-
|
|
307
|
-
if (previousIndex.byKey.has(key)) anchor = key;
|
|
335
|
+
for (const key of [...nextIndex.keys()].toReversed()) {
|
|
336
|
+
followingSurvivor.set(key, anchor);
|
|
337
|
+
if (previousIndex.has(key)) anchor = key;
|
|
308
338
|
}
|
|
309
|
-
for (const [
|
|
310
|
-
const
|
|
311
|
-
const previousRow = previousIndex.byKey.get(key);
|
|
339
|
+
for (const [key, nextRow] of nextIndex) {
|
|
340
|
+
const previousRow = previousIndex.get(key);
|
|
312
341
|
const nextFingerprint = JSON.stringify(nextRow);
|
|
313
342
|
if (previousRow === void 0) {
|
|
314
343
|
ops.push({
|
|
315
344
|
op: "insert",
|
|
316
345
|
key,
|
|
317
346
|
row: nextRow,
|
|
318
|
-
before: followingSurvivor
|
|
347
|
+
before: followingSurvivor.get(key) ?? null
|
|
319
348
|
});
|
|
320
349
|
continue;
|
|
321
350
|
}
|
|
@@ -325,7 +354,6 @@ const encodeListDelta = (previous, next, keyField = "id") => {
|
|
|
325
354
|
row: nextRow
|
|
326
355
|
});
|
|
327
356
|
}
|
|
328
|
-
if (ops.length > next.length) return void 0;
|
|
329
357
|
return ops;
|
|
330
358
|
} catch {
|
|
331
359
|
return;
|
|
@@ -345,30 +373,36 @@ const applyListDelta = (current, ops, keyField = "id") => {
|
|
|
345
373
|
const rows = [];
|
|
346
374
|
const seen = /* @__PURE__ */ new Set();
|
|
347
375
|
for (const element of current) {
|
|
376
|
+
if (!isPlainObject(element)) return void 0;
|
|
348
377
|
const key = readRowKey(element, keyField);
|
|
349
378
|
if (key === void 0 || seen.has(key)) return void 0;
|
|
350
379
|
seen.add(key);
|
|
351
380
|
rows.push(element);
|
|
352
381
|
}
|
|
353
|
-
|
|
382
|
+
const next = [...rows];
|
|
354
383
|
for (const op of ops) {
|
|
355
384
|
const existingIndex = next.findIndex((row) => row[keyField] === op.key);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
if (existingIndex !== -1) {
|
|
361
|
-
next[existingIndex] = op.row;
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
if (op.op === "insert" && op.before !== null) {
|
|
365
|
-
const anchorIndex = next.findIndex((row) => row[keyField] === op.before);
|
|
366
|
-
if (anchorIndex !== -1) {
|
|
367
|
-
next.splice(anchorIndex, 0, op.row);
|
|
385
|
+
switch (op.op) {
|
|
386
|
+
case "delete":
|
|
387
|
+
if (existingIndex !== -1) next.splice(existingIndex, 1);
|
|
368
388
|
continue;
|
|
369
|
-
|
|
389
|
+
case "insert":
|
|
390
|
+
case "update":
|
|
391
|
+
if (existingIndex !== -1) {
|
|
392
|
+
next[existingIndex] = op.row;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (op.op === "insert" && op.before !== null) {
|
|
396
|
+
const anchorIndex = next.findIndex((row) => row[keyField] === op.before);
|
|
397
|
+
if (anchorIndex !== -1) {
|
|
398
|
+
next.splice(anchorIndex, 0, op.row);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
next.push(op.row);
|
|
403
|
+
continue;
|
|
404
|
+
default: throw new TypeError(`Unknown live row operation: ${JSON.stringify(op)}`);
|
|
370
405
|
}
|
|
371
|
-
next = [...next, op.row];
|
|
372
406
|
}
|
|
373
407
|
return next;
|
|
374
408
|
};
|
|
@@ -703,20 +737,53 @@ const DELTA_FIXTURES = [
|
|
|
703
737
|
}]
|
|
704
738
|
},
|
|
705
739
|
{
|
|
706
|
-
name: "
|
|
740
|
+
name: "clear list remains expressible regardless of op count",
|
|
707
741
|
previous: [{ id: "a" }, { id: "b" }],
|
|
708
742
|
next: [],
|
|
709
|
-
expected:
|
|
743
|
+
expected: [{
|
|
744
|
+
op: "delete",
|
|
745
|
+
key: "a"
|
|
746
|
+
}, {
|
|
747
|
+
op: "delete",
|
|
748
|
+
key: "b"
|
|
749
|
+
}]
|
|
710
750
|
},
|
|
711
751
|
{
|
|
712
|
-
name: "
|
|
752
|
+
name: "near-total change remains expressible regardless of op count",
|
|
713
753
|
previous: [{ id: "a" }, { id: "b" }],
|
|
714
754
|
next: [
|
|
715
755
|
{ id: "c" },
|
|
716
756
|
{ id: "d" },
|
|
717
757
|
{ id: "e" }
|
|
718
758
|
],
|
|
719
|
-
expected:
|
|
759
|
+
expected: [
|
|
760
|
+
{
|
|
761
|
+
op: "delete",
|
|
762
|
+
key: "a"
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
op: "delete",
|
|
766
|
+
key: "b"
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
op: "insert",
|
|
770
|
+
key: "c",
|
|
771
|
+
row: { id: "c" },
|
|
772
|
+
before: null
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
op: "insert",
|
|
776
|
+
key: "d",
|
|
777
|
+
row: { id: "d" },
|
|
778
|
+
before: null
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
op: "insert",
|
|
782
|
+
key: "e",
|
|
783
|
+
row: { id: "e" },
|
|
784
|
+
before: null
|
|
785
|
+
}
|
|
786
|
+
]
|
|
720
787
|
},
|
|
721
788
|
{
|
|
722
789
|
name: "bail: previous not array (rule 1)",
|
|
@@ -790,17 +857,18 @@ const REFERENCE_CODEC = {
|
|
|
790
857
|
encodeListDelta,
|
|
791
858
|
applyListDelta
|
|
792
859
|
};
|
|
860
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
793
861
|
const deepEqual = (a, b) => {
|
|
794
862
|
if (a === b) return true;
|
|
795
863
|
if (Array.isArray(a) || Array.isArray(b)) {
|
|
796
864
|
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
797
865
|
return a.every((value, index) => deepEqual(value, b[index]));
|
|
798
866
|
}
|
|
799
|
-
if (
|
|
867
|
+
if (isRecord(a) && isRecord(b)) {
|
|
800
868
|
const aKeys = Object.keys(a);
|
|
801
869
|
const bKeys = Object.keys(b);
|
|
802
870
|
if (aKeys.length !== bKeys.length) return false;
|
|
803
|
-
return aKeys.every((key) => key
|
|
871
|
+
return aKeys.every((key) => Object.hasOwn(b, key) && deepEqual(a[key], b[key]));
|
|
804
872
|
}
|
|
805
873
|
return false;
|
|
806
874
|
};
|
|
@@ -815,8 +883,8 @@ const makeRandom = (seed) => {
|
|
|
815
883
|
/**
|
|
816
884
|
* Generate a mergeable previous/next pair: start from a random keyed list,
|
|
817
885
|
* then delete a random subset, update random payloads, and insert fresh keys
|
|
818
|
-
* at random positions
|
|
819
|
-
*
|
|
886
|
+
* at random positions. Survivor order is preserved by construction, so every
|
|
887
|
+
* generated case is expressible as a delta.
|
|
820
888
|
*/
|
|
821
889
|
const generateCase = (random, caseIndex) => {
|
|
822
890
|
const previousLength = Math.floor(random() * 8);
|
|
@@ -860,7 +928,8 @@ const runProtocolConformance = (codec = REFERENCE_CODEC) => {
|
|
|
860
928
|
failures.push(`frame "${fixture.name}": encoded wire differs\n expected ${fixture.wire}\n actual ${encoded}`);
|
|
861
929
|
continue;
|
|
862
930
|
}
|
|
863
|
-
const
|
|
931
|
+
const envelope = JSON.parse(fixture.wire);
|
|
932
|
+
const frame = readLiveEnvelope(envelope);
|
|
864
933
|
if (frame === void 0) {
|
|
865
934
|
failures.push(`frame "${fixture.name}": readLiveEnvelope did not recognize the envelope`);
|
|
866
935
|
continue;
|
|
@@ -897,7 +966,7 @@ const runProtocolConformance = (codec = REFERENCE_CODEC) => {
|
|
|
897
966
|
const { previous, next } = generateCase(random, caseIndex);
|
|
898
967
|
const ops = codec.encodeListDelta(previous, next);
|
|
899
968
|
if (ops === void 0) {
|
|
900
|
-
|
|
969
|
+
failures.push(`random #${caseIndex}: codec bailed on an expressible list change`);
|
|
901
970
|
continue;
|
|
902
971
|
}
|
|
903
972
|
const merged = codec.applyListDelta(previous, ops);
|
|
@@ -909,6 +978,6 @@ const runProtocolConformance = (codec = REFERENCE_CODEC) => {
|
|
|
909
978
|
};
|
|
910
979
|
};
|
|
911
980
|
//#endregion
|
|
912
|
-
export { COMMIT_CURSOR_HEADER, COMMIT_EPOCH_HEADER, DEFAULT_KEY_FIELD, DELTA_FIXTURES, FRAME_FIXTURES, LIVE_ERROR_CODES, LIVE_EVENT, LIVE_PROTOCOL, MAX_DELTA_OPS, MAX_LIVE_FRAME_BYTES, MAX_PRESENCE_METADATA_BYTES, RESERVED_EVENT_PREFIX, applyListDelta, canonicalLiveFrame, encodeListDelta, encodeLiveEnvelope, encodeLiveFrame, isClientLiveFrame, isRowOp, isRowOps, isServerLiveFrame, liveEnvelope, readLiveEnvelope, runProtocolConformance };
|
|
981
|
+
export { COMMIT_CURSOR_HEADER, COMMIT_EPOCH_HEADER, DEFAULT_KEY_FIELD, DELTA_FIXTURES, FRAME_FIXTURES, LIVE_ERROR_CODES, LIVE_EVENT, LIVE_PROTOCOL, MAX_DELTA_OPS, MAX_LIVE_FRAME_BYTES, MAX_PRESENCE_METADATA_BYTES, RESERVED_EVENT_PREFIX, applyListDelta, canonicalLiveFrame, defineLiveQuery, encodeListDelta, encodeLiveEnvelope, encodeLiveFrame, isClientLiveFrame, isRowOp, isRowOps, isServerLiveFrame, liveEnvelope, readLiveEnvelope, runProtocolConformance };
|
|
913
982
|
|
|
914
983
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/version.ts","../src/frames.ts","../src/delta.ts","../src/fixtures.ts","../src/conformance.ts"],"sourcesContent":["/**\n * Live-protocol wire version. Bumped ONLY on a breaking wire change (renaming\n * or removing a field, changing a delivery guarantee). Additive changes — new\n * optional fields, new frame types — do NOT bump it: receivers MUST ignore\n * unknown frame `t` values and unknown object fields.\n *\n * A client advertises the version it speaks via the `v` field on its `sub`\n * frame; a server that cannot serve that version replies\n * `{ t: 'error', code: 'unsupported_protocol', fatal: true }`.\n */\nexport const LIVE_PROTOCOL = 2;\n","/**\n * The normative frame catalog for Vela live queries.\n *\n * Live frames ride Vela's existing WebSocket envelope `{ event, data }` under\n * the single reserved event name `$live`; the frame itself is the envelope's\n * `data`, discriminated on `t`. Classic gateway events, `ping`→`pong`\n * keepalive, and live frames coexist on one socket. The `$` prefix is reserved\n * for the framework: app gateways must never register a `$…` event.\n *\n * Byte-identical encoding matters: golden fixtures pin the exact wire string\n * for every frame shape, and both the server and the client encode through\n * {@link encodeLiveFrame} / {@link encodeLiveEnvelope} so the two sides cannot\n * drift. Canonical key order is the declaration order of each type below;\n * absent optionals are omitted entirely.\n */\nimport { LIVE_PROTOCOL } from './version';\n\n/** The reserved envelope event every live frame rides under. */\nexport const LIVE_EVENT = '$live';\n\n/**\n * The reserved event-name prefix. The WS dispatcher rejects app gateways that\n * register a `$…` event at bootstrap so live (and future framework) frames can\n * never collide with app events.\n */\nexport const RESERVED_EVENT_PREFIX = '$';\n\n/**\n * HTTP response headers carrying the commit cursor/epoch of the log scope a\n * mutation's invalidations landed in. The client gates optimistic-layer drops\n * on a subscription frame whose `cursor` passes this value (and whose `epoch`\n * matches) — never on HTTP response timing, which races the broadcast.\n */\nexport const COMMIT_CURSOR_HEADER = 'Vela-Commit-Cursor';\nexport const COMMIT_EPOCH_HEADER = 'Vela-Commit-Epoch';\n\n/** Default hard limits shared by every live-protocol endpoint. */\nexport const MAX_LIVE_FRAME_BYTES = 64 * 1024;\nexport const MAX_PRESENCE_METADATA_BYTES = 4 * 1024;\nexport const MAX_DELTA_OPS = 1000;\n\n/** Well-known `error` frame codes. The code space is open — receivers must tolerate unknown codes. */\nexport const LIVE_ERROR_CODES = {\n UNSUPPORTED_PROTOCOL: 'unsupported_protocol',\n DUPLICATE_SUB: 'duplicate_sub',\n UNKNOWN_QUERY: 'unknown_query',\n FORBIDDEN: 'forbidden',\n BAD_ARGS: 'bad_args',\n LIMIT_EXCEEDED: 'limit_exceeded',\n INTERNAL: 'internal',\n} as const;\n\nexport type LiveErrorCode =\n | (typeof LIVE_ERROR_CODES)[keyof typeof LIVE_ERROR_CODES]\n | (string & {});\n\n/**\n * One row change inside a `delta` frame. Ops are keyed by the query's key\n * field (default `'id'`); `insert`/`update` carry the full new row, `delete`\n * omits it. An `insert` carries `before` — the key of the row it precedes in\n * the authoritative result (`null` = append) — so the client reconstructs the\n * server's ordering exactly. Application is idempotent: `insert` on an\n * existing key replaces in place, `delete` of an absent key is a no-op.\n */\nexport type RowOp =\n | { op: 'insert'; key: string; row: Record<string, unknown>; before: string | null }\n | { op: 'update'; key: string; row: Record<string, unknown> }\n | { op: 'delete'; key: string };\n\n/** Client → server frames (the `data` of a `{ event: '$live' }` envelope). */\nexport type ClientLiveFrame =\n | {\n t: 'sub';\n /** Client-chosen subscription id, unique per socket. */\n sub: string;\n /** The live-query identifier declared by `@LiveQuery(name)`. */\n query: string;\n args?: unknown;\n /** Resume watermark: last observed cursor/epoch. Omitted = cold subscribe. */\n sinceCursor?: number;\n sinceEpoch?: string;\n /** Key-field override for list deltas (default `'id'`). */\n key?: string;\n /** Protocol version the client speaks (see LIVE_PROTOCOL). */\n v: number;\n }\n | { t: 'unsub'; sub: string }\n | { t: 'presence'; room: string; meta?: unknown };\n\n/** Server → client frames. `ack` precedes any `data`/`resume` for a sub. */\nexport type ServerLiveFrame =\n | { t: 'ack'; sub: string }\n | { t: 'data'; sub: string; snapshot: unknown; cursor?: number; epoch?: string }\n | { t: 'delta'; sub: string; ops: RowOp[]; cursor?: number; epoch?: string }\n /** Re-run result was byte-identical — no payload, but the cursor still advances (drops optimistic layers). */\n | { t: 'settled'; sub: string; cursor?: number; epoch?: string }\n /** Resume verdict: nothing relevant changed while away — keep the cached value, advance the cursor. */\n | { t: 'resume'; sub: string; cursor: number; epoch: string }\n | { t: 'error'; sub?: string; code: LiveErrorCode; message: string; fatal: boolean };\n\nexport type LiveFrame = ClientLiveFrame | ServerLiveFrame;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isCursor = (value: unknown): value is number =>\n typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;\n\nconst isOptionalCursor = (value: unknown): value is number | undefined =>\n value === undefined || isCursor(value);\n\nconst isBoundedString = (value: unknown, max: number, allowEmpty = false): value is string =>\n typeof value === 'string' && (allowEmpty || value.length > 0) && value.length <= max;\n\nconst isOptionalBoundedString = (value: unknown, max: number): value is string | undefined =>\n value === undefined || isBoundedString(value, max);\n\nconst hasOwn = (value: Record<string, unknown>, key: string): boolean => Object.hasOwn(value, key);\n\nconst hasCursorPair = (value: Record<string, unknown>, cursor: string, epoch: string): boolean =>\n (value[cursor] === undefined && value[epoch] === undefined) ||\n (isCursor(value[cursor]) && isBoundedString(value[epoch], 256));\n\n/** Structural guard for a single {@link RowOp}. Unknown extra fields are tolerated. */\nexport const isRowOp = (value: unknown): value is RowOp => {\n if (!isRecord(value) || !isBoundedString(value['key'], 512)) return false;\n const op = value['op'];\n if (op === 'delete') return true;\n if (op !== 'insert' && op !== 'update') return false;\n if (!isRecord(value['row']) || !isJsonWithin(value['row'], MAX_LIVE_FRAME_BYTES)) return false;\n if (op === 'insert') {\n const before = value['before'];\n return before === null || isBoundedString(before, 512);\n }\n return true;\n};\n\nexport const isRowOps = (value: unknown): value is RowOp[] =>\n Array.isArray(value) && value.length <= MAX_DELTA_OPS && value.every(isRowOp);\n\n/**\n * Structural guard for a client frame. Frames with an unknown `t` return\n * false — per the forward-compat rule the receiver then ignores the frame.\n */\nexport const isClientLiveFrame = (value: unknown): value is ClientLiveFrame => {\n if (!isRecord(value) || !isJsonWithin(value, MAX_LIVE_FRAME_BYTES)) return false;\n switch (value['t']) {\n case 'sub':\n return (\n isBoundedString(value['sub'], 256) &&\n isBoundedString(value['query'], 256) &&\n hasCursorPair(value, 'sinceCursor', 'sinceEpoch') &&\n isOptionalBoundedString(value['key'], 128) &&\n value['v'] === LIVE_PROTOCOL &&\n (!hasOwn(value, 'args') || isJsonWithin(value['args'], 32 * 1024))\n );\n case 'unsub':\n return isBoundedString(value['sub'], 256);\n case 'presence':\n return (\n isBoundedString(value['room'], 512) &&\n (!hasOwn(value, 'meta') || isJsonWithin(value['meta'], MAX_PRESENCE_METADATA_BYTES))\n );\n default:\n return false;\n }\n};\n\n/** Structural guard for a server frame. Unknown `t` → false (receiver ignores). */\nexport const isServerLiveFrame = (value: unknown): value is ServerLiveFrame => {\n if (!isRecord(value) || !isJsonWithin(value, MAX_LIVE_FRAME_BYTES)) return false;\n switch (value['t']) {\n case 'ack':\n return isBoundedString(value['sub'], 256);\n case 'data':\n return (\n isBoundedString(value['sub'], 256) &&\n hasOwn(value, 'snapshot') &&\n hasCursorPair(value, 'cursor', 'epoch')\n );\n case 'delta':\n return (\n isBoundedString(value['sub'], 256) &&\n isRowOps(value['ops']) &&\n hasCursorPair(value, 'cursor', 'epoch')\n );\n case 'settled':\n return isBoundedString(value['sub'], 256) && hasCursorPair(value, 'cursor', 'epoch');\n case 'resume':\n return (\n isBoundedString(value['sub'], 256) &&\n isCursor(value['cursor']) &&\n isBoundedString(value['epoch'], 256)\n );\n case 'error':\n return (\n isOptionalBoundedString(value['sub'], 256) &&\n isBoundedString(value['code'], 128) &&\n isBoundedString(value['message'], 2048, true) &&\n typeof value['fatal'] === 'boolean'\n );\n default:\n return false;\n }\n};\n\n/**\n * Extract the live frame from a parsed WS envelope, or `undefined` when the\n * envelope is not a live envelope. Does NOT validate the frame — pair with\n * {@link isClientLiveFrame} / {@link isServerLiveFrame} on the receiving side.\n */\nexport const readLiveEnvelope = (envelope: unknown): unknown => {\n if (\n !isRecord(envelope) ||\n envelope['event'] !== LIVE_EVENT ||\n !hasOwn(envelope, 'data') ||\n !isJsonWithin(envelope, MAX_LIVE_FRAME_BYTES)\n ) {\n return undefined;\n }\n return envelope['data'];\n};\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nconst isJsonWithin = (value: unknown, maxBytes: number): boolean => {\n if (!isJsonValue(value, new WeakSet(), { nodes: 0 }, 0)) return false;\n try {\n const serialized = JSON.stringify(value);\n return serialized !== undefined && new TextEncoder().encode(serialized).byteLength <= maxBytes;\n } catch {\n return false;\n }\n};\n\nconst isJsonValue = (\n value: unknown,\n seen: WeakSet<object>,\n budget: { nodes: number },\n depth: number,\n): boolean => {\n budget.nodes += 1;\n if (budget.nodes > 10_000 || depth > 32) return false;\n if (value === null || typeof value === 'string' || typeof value === 'boolean') return true;\n if (typeof value === 'number') return Number.isFinite(value);\n if (typeof value !== 'object' || seen.has(value)) return false;\n seen.add(value);\n\n const values: unknown[] = [];\n if (Array.isArray(value)) {\n values.push(...value);\n } else {\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) return false;\n for (const key of Object.keys(value)) {\n if (DANGEROUS_KEYS.has(key)) return false;\n values.push((value as Record<string, unknown>)[key]);\n }\n }\n\n for (const child of values) {\n if (!isJsonValue(child, seen, budget, depth + 1)) return false;\n }\n seen.delete(value);\n return true;\n};\n\n/** Wrap a frame in the `$live` envelope object. */\nexport const liveEnvelope = (frame: LiveFrame): { event: typeof LIVE_EVENT; data: LiveFrame } => ({\n event: LIVE_EVENT,\n data: frame,\n});\n\nconst CANONICAL_KEYS: Record<string, readonly string[]> = {\n sub: ['t', 'sub', 'query', 'args', 'sinceCursor', 'sinceEpoch', 'key', 'v'],\n unsub: ['t', 'sub'],\n presence: ['t', 'room', 'meta'],\n ack: ['t', 'sub'],\n data: ['t', 'sub', 'snapshot', 'cursor', 'epoch'],\n delta: ['t', 'sub', 'ops', 'cursor', 'epoch'],\n settled: ['t', 'sub', 'cursor', 'epoch'],\n resume: ['t', 'sub', 'cursor', 'epoch'],\n error: ['t', 'sub', 'code', 'message', 'fatal'],\n};\n\nconst ROW_OP_KEYS = ['op', 'key', 'row', 'before'] as const;\n\nconst canonicalRowOp = (op: RowOp): Record<string, unknown> => {\n const source = op as unknown as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const key of ROW_OP_KEYS) {\n if (source[key] !== undefined) out[key] = source[key];\n }\n return out;\n};\n\n/**\n * Rebuild a frame with the canonical key order, dropping absent optionals.\n * `JSON.stringify` of the result is the frame's canonical wire form — the one\n * the golden fixtures pin byte-for-byte.\n */\nexport const canonicalLiveFrame = (frame: LiveFrame): Record<string, unknown> => {\n const source = frame as unknown as Record<string, unknown>;\n const keys = CANONICAL_KEYS[frame.t];\n if (keys === undefined) {\n throw new Error(`Unknown live frame type: ${String(frame.t)}`);\n }\n const out: Record<string, unknown> = {};\n for (const key of keys) {\n const value = source[key];\n if (value === undefined) continue;\n out[key] =\n frame.t === 'delta' && key === 'ops' ? (value as RowOp[]).map(canonicalRowOp) : value;\n }\n return out;\n};\n\n/** Canonical JSON encoding of a bare frame (no envelope). */\nexport const encodeLiveFrame = (frame: LiveFrame): string => {\n if (!isClientLiveFrame(frame) && !isServerLiveFrame(frame)) {\n throw new TypeError('Cannot encode an invalid or oversized live frame.');\n }\n return JSON.stringify(canonicalLiveFrame(frame));\n};\n\n/** Canonical JSON encoding of the full `$live` envelope — what actually goes on the socket. */\nexport const encodeLiveEnvelope = (frame: LiveFrame): string =>\n `{\"event\":${JSON.stringify(LIVE_EVENT)},\"data\":${encodeLiveFrame(frame)}}`;\n","/**\n * The shared keyed list-delta codec — BOTH sides of the wire implement the\n * delta contract through this one module: the server encodes a previous-vs-next\n * query result into `RowOp`s ({@link encodeListDelta}), the client merges them\n * into its cached value ({@link applyListDelta}).\n *\n * Ported from lunora's `subscription-delivery.ts` (encoder) and\n * `delta-merge.ts` (merge), with two deliberate changes:\n *\n * 1. The key field defaults to `'id'` (Vela/CRUD convention, not lunora's\n * `_id`) and is configurable per query.\n * 2. `insert` ops carry an explicit `before` anchor (the key of the row they\n * precede in the authoritative result; `null` = append) instead of\n * approximating position via a `_creationTime` heuristic. Because encoder\n * and merge live in the same package, this buys the exact-reconstruction\n * property the conformance suite enforces: whenever the encoder does not\n * bail, `applyListDelta(previous, encodeListDelta(previous, next))` is\n * deep-equal to `next`, ordering included.\n *\n * Bail-to-snapshot contract (identical on both sides — the server MUST send a\n * full `data` snapshot and the client MUST fall back to full replacement when\n * any of these hold):\n *\n * 1. previous or next is not an array;\n * 2. any row is not a plain object carrying a string key (or the value cannot\n * be JSON-serialized);\n * 3. a duplicate key appears in either array;\n * 4. rows present in BOTH arrays changed relative order (the merge replaces\n * survivors in place and never reorders them);\n * 5. the op count exceeds the next array's length (a near-total change is\n * cheaper as a snapshot).\n *\n * Op ordering inside a delta: deletes first (previous order), then\n * inserts/updates (next order) — the merge never sees a transient over-length\n * list. Merging is idempotent (`insert` on an existing key replaces in place,\n * `delete` of an absent key is a no-op) so at-least-once replay after a\n * reconnect is harmless.\n */\n\nimport type { RowOp } from './frames';\n\n/** Default row-identity field. Per-query override rides the `sub` frame's `key`. */\nexport const DEFAULT_KEY_FIELD = 'id';\n\ntype Row = Record<string, unknown>;\n\ninterface RowIndex {\n byKey: Map<string, Row>;\n order: string[];\n}\n\nconst isPlainObject = (value: unknown): value is Row =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst readRowKey = (row: unknown, keyField: string): string | undefined => {\n if (!isPlainObject(row)) return undefined;\n const key = row[keyField];\n return typeof key === 'string' ? key : undefined;\n};\n\n/**\n * Index rows by key preserving order; `undefined` the moment any row is\n * unkeyable or a key repeats (bail rules 2 and 3 — a duplicated key cannot be\n * expressed as keyed deltas without silently collapsing rows).\n */\nconst indexRows = (rows: unknown[], keyField: string): RowIndex | undefined => {\n const byKey = new Map<string, Row>();\n const order: string[] = [];\n for (const row of rows) {\n const key = readRowKey(row, keyField);\n if (key === undefined || byKey.has(key)) return undefined;\n byKey.set(key, row as Row);\n order.push(key);\n }\n return { byKey, order };\n};\n\n/**\n * True when rows present in BOTH lists keep the same relative order (bail\n * rule 4): the merge updates survivors in place and never reorders them, so a\n * survivor that moved cannot be expressed as deltas.\n */\nconst survivorsKeepOrder = (previous: RowIndex, next: RowIndex): boolean => {\n const survivingPrevious = previous.order.filter((key) => next.byKey.has(key));\n const survivingNext = next.order.filter((key) => previous.byKey.has(key));\n if (survivingPrevious.length !== survivingNext.length) return false;\n return survivingPrevious.every((key, index) => survivingNext[index] === key);\n};\n\n/**\n * Diff `previous` vs `next` into row ops, or `undefined` when any bail rule\n * holds and the caller must send a full snapshot instead.\n *\n * An empty array is a valid result (no row-level change — typically the server\n * catches byte-identical results earlier and sends `settled` instead).\n */\nexport const encodeListDelta = (\n previous: unknown,\n next: unknown,\n keyField: string = DEFAULT_KEY_FIELD,\n): RowOp[] | undefined => {\n try {\n if (!Array.isArray(previous) || !Array.isArray(next)) return undefined;\n\n const previousIndex = indexRows(previous, keyField);\n const nextIndex = indexRows(next, keyField);\n if (previousIndex === undefined || nextIndex === undefined) return undefined;\n if (!survivorsKeepOrder(previousIndex, nextIndex)) return undefined;\n\n const ops: RowOp[] = [];\n\n // Deletes first, in previous order.\n for (const key of previousIndex.order) {\n if (!nextIndex.byKey.has(key)) ops.push({ op: 'delete', key });\n }\n\n // The `before` anchor for an insert at position i is the nearest FOLLOWING\n // survivor in next order (null = append). At merge time, when the insert\n // applies, the list holds exactly the survivors (in order, updates replace\n // in place) plus earlier inserts; splicing sequentially before the anchor\n // therefore reproduces next's ordering exactly — inserts sharing an anchor\n // stack in emission order, trailing inserts append in emission order.\n const followingSurvivor: (string | null)[] = new Array(nextIndex.order.length);\n let anchor: string | null = null;\n for (let index = nextIndex.order.length - 1; index >= 0; index -= 1) {\n followingSurvivor[index] = anchor;\n const key = nextIndex.order[index] as string;\n if (previousIndex.byKey.has(key)) anchor = key;\n }\n\n // Inserts/updates in next order. Each row is fingerprinted with a single\n // JSON.stringify reused for the changed-row compare; an unserializable row\n // throws and the whole encode bails to snapshot (rule 2).\n for (const [index, key] of nextIndex.order.entries()) {\n const nextRow = nextIndex.byKey.get(key) as Row;\n const previousRow = previousIndex.byKey.get(key);\n const nextFingerprint = JSON.stringify(nextRow);\n if (previousRow === undefined) {\n ops.push({ op: 'insert', key, row: nextRow, before: followingSurvivor[index] ?? null });\n continue;\n }\n if (JSON.stringify(previousRow) !== nextFingerprint) {\n ops.push({ op: 'update', key, row: nextRow });\n }\n }\n\n // Bail rule 5: a near-total change is better sent as one snapshot.\n if (ops.length > next.length) return undefined;\n\n return ops;\n } catch {\n return undefined;\n }\n};\n\n/**\n * Merge row ops into a cached array result, returning a NEW array (the input\n * is never mutated), or `undefined` when the ops cannot be applied cleanly —\n * the caller then falls back to full replacement and lets the next snapshot\n * reconcile.\n *\n * Idempotent by construction: replaying an op after a snapshot already\n * delivered its effect changes nothing.\n */\nexport const applyListDelta = (\n current: unknown,\n ops: readonly RowOp[],\n keyField: string = DEFAULT_KEY_FIELD,\n): unknown[] | undefined => {\n if (!Array.isArray(current)) return undefined;\n\n const rows: Row[] = [];\n const seen = new Set<string>();\n for (const element of current) {\n const key = readRowKey(element, keyField);\n if (key === undefined || seen.has(key)) return undefined;\n seen.add(key);\n rows.push(element as Row);\n }\n\n let next = [...rows];\n for (const op of ops) {\n const existingIndex = next.findIndex((row) => row[keyField] === op.key);\n\n if (op.op === 'delete') {\n if (existingIndex !== -1) next.splice(existingIndex, 1);\n continue;\n }\n\n if (existingIndex !== -1) {\n // Present → replace in place. Covers `update`, and an `insert` whose row\n // a snapshot already delivered (replay idempotency).\n next[existingIndex] = op.row;\n continue;\n }\n\n if (op.op === 'insert' && op.before !== null) {\n const anchorIndex = next.findIndex((row) => row[keyField] === op.before);\n if (anchorIndex !== -1) {\n next.splice(anchorIndex, 0, op.row);\n continue;\n }\n }\n\n // `insert` with a null/missing anchor, or an `update` for a row this page\n // never held (degraded replay) → append.\n next = [...next, op.row];\n }\n\n return next;\n};\n","/**\n * Golden wire fixtures — the drift tripwire. The server suite (@velajs/vela)\n * and the client suite (@velajs/client) both run these through\n * `runProtocolConformance`, so an encoding change on either side fails a test\n * instead of surfacing as a production incompatibility.\n *\n * `wire` strings are byte-exact: they pin the canonical key order of\n * `encodeLiveEnvelope`. Do not reformat them.\n */\n\nimport type { LiveFrame, RowOp } from './frames';\n\nexport interface FrameFixture {\n name: string;\n frame: LiveFrame;\n /** Exact canonical envelope bytes: `encodeLiveEnvelope(frame)` must equal this. */\n wire: string;\n}\n\nexport const FRAME_FIXTURES: FrameFixture[] = [\n {\n name: 'sub (full)',\n frame: {\n t: 'sub',\n sub: 's1',\n query: 'todos.list',\n args: { listId: 'l1' },\n sinceCursor: 42,\n sinceEpoch: 'e-1',\n key: 'id',\n v: 2,\n },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"sub\",\"sub\":\"s1\",\"query\":\"todos.list\",\"args\":{\"listId\":\"l1\"},\"sinceCursor\":42,\"sinceEpoch\":\"e-1\",\"key\":\"id\",\"v\":2}}',\n },\n {\n name: 'sub (minimal)',\n frame: { t: 'sub', sub: 's2', query: 'todos.all', v: 2 },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"sub\",\"sub\":\"s2\",\"query\":\"todos.all\",\"v\":2}}',\n },\n {\n name: 'unsub',\n frame: { t: 'unsub', sub: 's1' },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"unsub\",\"sub\":\"s1\"}}',\n },\n {\n name: 'presence',\n frame: { t: 'presence', room: 'r1', meta: { name: 'kauan' } },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"presence\",\"room\":\"r1\",\"meta\":{\"name\":\"kauan\"}}}',\n },\n {\n name: 'ack',\n frame: { t: 'ack', sub: 's1' },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"ack\",\"sub\":\"s1\"}}',\n },\n {\n name: 'data',\n frame: { t: 'data', sub: 's1', snapshot: [{ id: 'a', text: 'hi' }], cursor: 7, epoch: 'e-1' },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"data\",\"sub\":\"s1\",\"snapshot\":[{\"id\":\"a\",\"text\":\"hi\"}],\"cursor\":7,\"epoch\":\"e-1\"}}',\n },\n {\n name: 'data (cold, no cursor)',\n frame: { t: 'data', sub: 's1', snapshot: null },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"data\",\"sub\":\"s1\",\"snapshot\":null}}',\n },\n {\n name: 'delta',\n frame: {\n t: 'delta',\n sub: 's1',\n ops: [\n { op: 'delete', key: 'a' },\n { op: 'insert', key: 'b', row: { id: 'b' }, before: null },\n { op: 'update', key: 'c', row: { id: 'c', n: 2 } },\n ],\n cursor: 8,\n epoch: 'e-1',\n },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"delta\",\"sub\":\"s1\",\"ops\":[{\"op\":\"delete\",\"key\":\"a\"},{\"op\":\"insert\",\"key\":\"b\",\"row\":{\"id\":\"b\"},\"before\":null},{\"op\":\"update\",\"key\":\"c\",\"row\":{\"id\":\"c\",\"n\":2}}],\"cursor\":8,\"epoch\":\"e-1\"}}',\n },\n {\n name: 'settled',\n frame: { t: 'settled', sub: 's1', cursor: 9, epoch: 'e-1' },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"settled\",\"sub\":\"s1\",\"cursor\":9,\"epoch\":\"e-1\"}}',\n },\n {\n name: 'resume',\n frame: { t: 'resume', sub: 's1', cursor: 42, epoch: 'e-1' },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"resume\",\"sub\":\"s1\",\"cursor\":42,\"epoch\":\"e-1\"}}',\n },\n {\n name: 'error (subscription)',\n frame: { t: 'error', sub: 's1', code: 'forbidden', message: 'nope', fatal: true },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"error\",\"sub\":\"s1\",\"code\":\"forbidden\",\"message\":\"nope\",\"fatal\":true}}',\n },\n {\n name: 'error (connection)',\n frame: { t: 'error', code: 'unsupported_protocol', message: 'v2 required', fatal: true },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"error\",\"code\":\"unsupported_protocol\",\"message\":\"v2 required\",\"fatal\":true}}',\n },\n];\n\nexport interface DeltaFixture {\n name: string;\n previous: unknown;\n next: unknown;\n keyField?: string;\n /** Expected ops, or `null` when the encoder MUST bail to snapshot. */\n expected: RowOp[] | null;\n}\n\nexport const DELTA_FIXTURES: DeltaFixture[] = [\n { name: 'noop', previous: [], next: [], expected: [] },\n {\n name: 'insert into empty',\n previous: [],\n next: [{ id: 'a', n: 1 }],\n expected: [{ op: 'insert', key: 'a', row: { id: 'a', n: 1 }, before: null }],\n },\n {\n name: 'insert head',\n previous: [{ id: 'b' }],\n next: [{ id: 'a' }, { id: 'b' }],\n expected: [{ op: 'insert', key: 'a', row: { id: 'a' }, before: 'b' }],\n },\n {\n name: 'insert middle',\n previous: [{ id: 'a' }, { id: 'c' }],\n next: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],\n expected: [{ op: 'insert', key: 'b', row: { id: 'b' }, before: 'c' }],\n },\n {\n name: 'insert tail',\n previous: [{ id: 'a' }],\n next: [{ id: 'a' }, { id: 'b' }],\n expected: [{ op: 'insert', key: 'b', row: { id: 'b' }, before: null }],\n },\n {\n name: 'update in place',\n previous: [{ id: 'a', n: 1 }],\n next: [{ id: 'a', n: 2 }],\n expected: [{ op: 'update', key: 'a', row: { id: 'a', n: 2 } }],\n },\n {\n name: 'delete one',\n previous: [{ id: 'a' }, { id: 'b' }],\n next: [{ id: 'a' }],\n expected: [{ op: 'delete', key: 'b' }],\n },\n {\n name: 'mixed delete+update+insert',\n previous: [\n { id: 'a', n: 1 },\n { id: 'b', n: 1 },\n { id: 'c', n: 1 },\n ],\n next: [\n { id: 'b', n: 2 },\n { id: 'd', n: 1 },\n { id: 'c', n: 1 },\n ],\n expected: [\n { op: 'delete', key: 'a' },\n { op: 'update', key: 'b', row: { id: 'b', n: 2 } },\n { op: 'insert', key: 'd', row: { id: 'd', n: 1 }, before: 'c' },\n ],\n },\n {\n name: 'stacked inserts share an anchor in next order',\n previous: [{ id: 'z' }],\n next: [{ id: 'x' }, { id: 'y' }, { id: 'z' }],\n expected: [\n { op: 'insert', key: 'x', row: { id: 'x' }, before: 'z' },\n { op: 'insert', key: 'y', row: { id: 'y' }, before: 'z' },\n ],\n },\n {\n name: 'custom key field',\n previous: [{ _key: 'a', n: 1 }],\n next: [{ _key: 'a', n: 2 }],\n keyField: '_key',\n expected: [{ op: 'update', key: 'a', row: { _key: 'a', n: 2 } }],\n },\n // ---- bail cases (expected: null → full snapshot) ----\n {\n name: 'bail: clear list (rule 5)',\n previous: [{ id: 'a' }, { id: 'b' }],\n next: [],\n expected: null,\n },\n {\n name: 'bail: near-total change (rule 5)',\n previous: [{ id: 'a' }, { id: 'b' }],\n next: [{ id: 'c' }, { id: 'd' }, { id: 'e' }],\n expected: null,\n },\n {\n name: 'bail: previous not array (rule 1)',\n previous: { id: 'a' },\n next: [{ id: 'a' }],\n expected: null,\n },\n {\n name: 'bail: next not array (rule 1)',\n previous: [{ id: 'a' }],\n next: { id: 'a' },\n expected: null,\n },\n {\n name: 'bail: row missing key (rule 2)',\n previous: [{ id: 'a' }],\n next: [{ text: 'no key' }],\n expected: null,\n },\n {\n name: 'bail: non-string key (rule 2)',\n previous: [{ id: 'a' }],\n next: [{ id: 5 }],\n expected: null,\n },\n { name: 'bail: scalar row (rule 2)', previous: [{ id: 'a' }], next: ['a'], expected: null },\n {\n name: 'bail: duplicate key in previous (rule 3)',\n previous: [{ id: 'a' }, { id: 'a' }],\n next: [{ id: 'a' }],\n expected: null,\n },\n {\n name: 'bail: duplicate key in next (rule 3)',\n previous: [{ id: 'a' }],\n next: [{ id: 'a' }, { id: 'a' }],\n expected: null,\n },\n {\n name: 'bail: survivors reordered (rule 4)',\n previous: [{ id: 'a' }, { id: 'b' }],\n next: [{ id: 'b' }, { id: 'a' }],\n expected: null,\n },\n];\n","/**\n * The protocol conformance runner. Both wire endpoints run this in their own\n * test suites (see `@velajs/testing`'s live harness) so a codec that drifts\n * from the golden fixtures — or from the shared delta semantics — fails a test\n * on the offending side.\n *\n * Checks, in order:\n * 1. Frame encoding: `encodeLiveEnvelope(fixture.frame)` is byte-identical to\n * the pinned wire string, the wire parses back to a guard-recognized frame,\n * and `readLiveEnvelope` extracts it.\n * 2. Delta fixtures: the codec's `encodeListDelta` produces exactly the pinned\n * ops (or bails where the fixture says it must), and for every mergeable\n * fixture `applyListDelta` reconstructs `next` exactly — then reapplying\n * the same ops changes nothing (at-least-once replay idempotency).\n * 3. A seeded randomized sweep of generated list pairs asserting the\n * exact-reconstruction property on cases the fixtures don't enumerate.\n */\n\nimport { DEFAULT_KEY_FIELD, applyListDelta, encodeListDelta } from './delta';\nimport {\n encodeLiveEnvelope,\n isClientLiveFrame,\n isServerLiveFrame,\n readLiveEnvelope,\n} from './frames';\nimport type { RowOp } from './frames';\nimport { DELTA_FIXTURES, FRAME_FIXTURES } from './fixtures';\n\n/** The two halves a wire endpoint must implement compatibly. */\nexport interface DeltaCodec {\n encodeListDelta: (previous: unknown, next: unknown, keyField?: string) => RowOp[] | undefined;\n applyListDelta: (\n current: unknown,\n ops: readonly RowOp[],\n keyField?: string,\n ) => unknown[] | undefined;\n}\n\nconst REFERENCE_CODEC: DeltaCodec = { encodeListDelta, applyListDelta };\n\nconst deepEqual = (a: unknown, b: unknown): boolean => {\n if (a === b) return true;\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;\n return a.every((value, index) => deepEqual(value, b[index]));\n }\n if (typeof a === 'object' && typeof b === 'object' && a !== null && b !== null) {\n const aKeys = Object.keys(a as Record<string, unknown>);\n const bKeys = Object.keys(b as Record<string, unknown>);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every(\n (key) =>\n key in (b as Record<string, unknown>) &&\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n }\n return false;\n};\n\n/** Deterministic LCG so the randomized sweep is reproducible (no Math.random). */\nconst makeRandom = (seed: number): (() => number) => {\n let state = seed >>> 0;\n return () => {\n state = (state * 1664525 + 1013904223) >>> 0;\n return state / 0x1_0000_0000;\n };\n};\n\ninterface GeneratedCase {\n previous: Record<string, unknown>[];\n next: Record<string, unknown>[];\n}\n\n/**\n * Generate a mergeable previous/next pair: start from a random keyed list,\n * then delete a random subset, update random payloads, and insert fresh keys\n * at random positions — survivor order is preserved by construction, so the\n * encoder may only bail via the op-count cap (rule 5).\n */\nconst generateCase = (random: () => number, caseIndex: number): GeneratedCase => {\n const previousLength = Math.floor(random() * 8);\n const previous: Record<string, unknown>[] = [];\n for (let index = 0; index < previousLength; index += 1) {\n previous.push({ id: `k${caseIndex}-${index}`, n: Math.floor(random() * 100) });\n }\n\n const next: Record<string, unknown>[] = [];\n for (const row of previous) {\n if (random() < 0.25) continue; // delete\n next.push(random() < 0.4 ? { ...row, n: Math.floor(random() * 100) } : row);\n }\n const insertions = Math.floor(random() * 4);\n for (let index = 0; index < insertions; index += 1) {\n const position = Math.floor(random() * (next.length + 1));\n next.splice(position, 0, { id: `f${caseIndex}-${index}`, n: Math.floor(random() * 100) });\n }\n\n return { previous, next };\n};\n\nexport interface ConformanceReport {\n /** Human-readable failure descriptions; empty = conformant. */\n failures: string[];\n checks: number;\n}\n\n/**\n * Run the full conformance suite against a codec (defaults to the reference\n * codec in this package — the package's own tests run exactly this).\n */\nexport const runProtocolConformance = (codec: DeltaCodec = REFERENCE_CODEC): ConformanceReport => {\n const failures: string[] = [];\n let checks = 0;\n\n for (const fixture of FRAME_FIXTURES) {\n checks += 1;\n const encoded = encodeLiveEnvelope(fixture.frame);\n if (encoded !== fixture.wire) {\n failures.push(\n `frame \"${fixture.name}\": encoded wire differs\\n expected ${fixture.wire}\\n actual ${encoded}`,\n );\n continue;\n }\n const frame = readLiveEnvelope(JSON.parse(fixture.wire));\n if (frame === undefined) {\n failures.push(`frame \"${fixture.name}\": readLiveEnvelope did not recognize the envelope`);\n continue;\n }\n if (!isClientLiveFrame(frame) && !isServerLiveFrame(frame)) {\n failures.push(`frame \"${fixture.name}\": decoded frame not recognized by either guard`);\n }\n }\n\n for (const fixture of DELTA_FIXTURES) {\n checks += 1;\n const keyField = fixture.keyField ?? DEFAULT_KEY_FIELD;\n const ops = codec.encodeListDelta(fixture.previous, fixture.next, keyField);\n\n if (fixture.expected === null) {\n if (ops !== undefined) {\n failures.push(\n `delta \"${fixture.name}\": expected bail-to-snapshot, got ${JSON.stringify(ops)}`,\n );\n }\n continue;\n }\n\n if (ops === undefined) {\n failures.push(\n `delta \"${fixture.name}\": encoder bailed, expected ${JSON.stringify(fixture.expected)}`,\n );\n continue;\n }\n if (!deepEqual(ops, fixture.expected)) {\n failures.push(\n `delta \"${fixture.name}\": ops differ\\n expected ${JSON.stringify(fixture.expected)}\\n actual ${JSON.stringify(ops)}`,\n );\n continue;\n }\n\n const merged = codec.applyListDelta(fixture.previous, ops, keyField);\n if (merged === undefined || !deepEqual(merged, fixture.next)) {\n failures.push(\n `delta \"${fixture.name}\": apply(previous, ops) did not reconstruct next\\n expected ${JSON.stringify(fixture.next)}\\n actual ${JSON.stringify(merged)}`,\n );\n continue;\n }\n\n const replayed = codec.applyListDelta(merged, ops, keyField);\n if (replayed === undefined || !deepEqual(replayed, fixture.next)) {\n failures.push(`delta \"${fixture.name}\": replaying the same ops was not idempotent`);\n }\n }\n\n const random = makeRandom(0x5eed);\n for (let caseIndex = 0; caseIndex < 250; caseIndex += 1) {\n checks += 1;\n const { previous, next } = generateCase(random, caseIndex);\n const ops = codec.encodeListDelta(previous, next);\n\n if (ops === undefined) {\n // Survivor order is preserved by construction, so only rule 5 may bail.\n const referenceOps = encodeListDelta(previous, next);\n if (referenceOps !== undefined) {\n failures.push(`random #${caseIndex}: codec bailed where the reference codec succeeds`);\n }\n continue;\n }\n\n const merged = codec.applyListDelta(previous, ops);\n if (merged === undefined || !deepEqual(merged, next)) {\n failures.push(\n `random #${caseIndex}: apply(previous, ops) != next\\n previous ${JSON.stringify(previous)}\\n next ${JSON.stringify(next)}\\n ops ${JSON.stringify(ops)}\\n merged ${JSON.stringify(merged)}`,\n );\n }\n }\n\n return { failures, checks };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["isRecord","unexpected"],"sources":["../src/version.ts","../src/query.ts","../src/frames.ts","../src/delta.ts","../src/fixtures.ts","../src/conformance.ts"],"sourcesContent":["/**\n * Live-protocol wire version. Bumped ONLY on a breaking wire change (renaming\n * or removing a field, changing a delivery guarantee). Additive changes — new\n * optional fields, new frame types — do NOT bump it: receivers MUST ignore\n * unknown frame `t` values and unknown object fields.\n *\n * A client advertises the version it speaks via the `v` field on its `sub`\n * frame; a server that cannot serve that version replies\n * `{ t: 'error', code: 'unsupported_protocol', fatal: true }`.\n */\nexport const LIVE_PROTOCOL = 2;\n","/** A portable runtime schema: Zod and other parsers can implement this directly. */\nexport interface LiveQueryDefinition<Args, Result> {\n readonly args: { parse(value: unknown): Args };\n readonly result: { parse(value: unknown): Result };\n}\n\n/** Share one args/result contract between a resolver and its typed client. */\nexport function defineLiveQuery<Args, Result>(\n definition: LiveQueryDefinition<Args, Result>,\n): LiveQueryDefinition<Args, Result> {\n return definition;\n}\n","/**\n * The normative frame catalog for Vela live queries.\n *\n * Live frames ride Vela's existing WebSocket envelope `{ event, data }` under\n * the single reserved event name `$live`; the frame itself is the envelope's\n * `data`, discriminated on `t`. Classic gateway events, `$ping`→`$pong`\n * keepalive, and live frames coexist on one socket. The `$` prefix is reserved\n * for the framework: app gateways must never register a `$…` event.\n *\n * Byte-identical encoding matters: golden fixtures pin the exact wire string\n * for every frame shape, and both the server and the client encode through\n * {@link encodeLiveFrame} / {@link encodeLiveEnvelope} so the two sides cannot\n * drift. Canonical key order is the declaration order of each type below;\n * absent optionals are omitted entirely.\n */\nimport { LIVE_PROTOCOL } from './version';\n\n/** The reserved envelope event every live frame rides under. */\nexport const LIVE_EVENT = '$live';\n\n/**\n * The reserved event-name prefix. The WS dispatcher rejects app gateways that\n * register a `$…` event at bootstrap so live (and future framework) frames can\n * never collide with app events.\n */\nexport const RESERVED_EVENT_PREFIX = '$';\n\n/**\n * HTTP response headers carrying the commit cursor/epoch of the log scope a\n * mutation's invalidations landed in. The client gates optimistic-layer drops\n * on a subscription frame whose `cursor` passes this value (and whose `epoch`\n * matches) — never on HTTP response timing, which races the broadcast.\n */\nexport const COMMIT_CURSOR_HEADER = 'Vela-Commit-Cursor';\nexport const COMMIT_EPOCH_HEADER = 'Vela-Commit-Epoch';\n\n/** Default hard limits shared by every live-protocol endpoint. */\nexport const MAX_LIVE_FRAME_BYTES = 64 * 1024;\nexport const MAX_PRESENCE_METADATA_BYTES = 4 * 1024;\nexport const MAX_DELTA_OPS = 1000;\n\n/** Well-known `error` frame codes. The code space is open — receivers must tolerate unknown codes. */\nexport const LIVE_ERROR_CODES = {\n UNSUPPORTED_PROTOCOL: 'unsupported_protocol',\n DUPLICATE_SUB: 'duplicate_sub',\n UNKNOWN_QUERY: 'unknown_query',\n FORBIDDEN: 'forbidden',\n BAD_ARGS: 'bad_args',\n LIMIT_EXCEEDED: 'limit_exceeded',\n INTERNAL: 'internal',\n} as const;\n\nexport type LiveErrorCode =\n | (typeof LIVE_ERROR_CODES)[keyof typeof LIVE_ERROR_CODES]\n | (string & {});\n\n/**\n * One row change inside a `delta` frame. Ops are keyed by the query's key\n * field (default `'id'`); `insert`/`update` carry the full new row, `delete`\n * omits it. An `insert` carries `before` — the key of the row it precedes in\n * the authoritative result (`null` = append) — so the client reconstructs the\n * server's ordering exactly. Application is idempotent: `insert` on an\n * existing key replaces in place, `delete` of an absent key is a no-op.\n */\nexport type RowOp =\n | { op: 'insert'; key: string; row: Record<string, unknown>; before: string | null }\n | { op: 'update'; key: string; row: Record<string, unknown> }\n | { op: 'delete'; key: string };\n\n/** Client → server frames (the `data` of a `{ event: '$live' }` envelope). */\nexport type ClientLiveFrame =\n | {\n t: 'sub';\n /** Client-chosen subscription id, unique per socket. */\n sub: string;\n /** The live-query identifier declared by `@LiveQuery(name)`. */\n query: string;\n args?: unknown;\n /** Resume watermark: last observed cursor/epoch. Omitted = cold subscribe. */\n sinceCursor?: number;\n sinceEpoch?: string;\n /** Key-field override for list deltas (default `'id'`). */\n key?: string;\n /** Protocol version the client speaks (see LIVE_PROTOCOL). */\n v: number;\n }\n | { t: 'unsub'; sub: string }\n | { t: 'presence'; room: string; meta?: unknown };\n\n/** Server → client frames. `ack` precedes any `data`/`resume` for a sub. */\nexport type ServerLiveFrame =\n | { t: 'ack'; sub: string }\n | { t: 'data'; sub: string; snapshot: unknown; cursor?: number; epoch?: string }\n | { t: 'delta'; sub: string; ops: RowOp[]; cursor?: number; epoch?: string }\n /** Re-run result was byte-identical — no payload, but the cursor still advances (drops optimistic layers). */\n | { t: 'settled'; sub: string; cursor?: number; epoch?: string }\n /** Resume verdict: nothing relevant changed while away — keep the cached value, advance the cursor. */\n | { t: 'resume'; sub: string; cursor: number; epoch: string }\n | { t: 'error'; sub?: string; code: LiveErrorCode; message: string; fatal: boolean };\n\nexport type LiveFrame = ClientLiveFrame | ServerLiveFrame;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isCursor = (value: unknown): value is number =>\n typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;\n\nconst isBoundedString = (value: unknown, max: number, allowEmpty = false): value is string =>\n typeof value === 'string' && (allowEmpty || value.length > 0) && value.length <= max;\n\nconst isOptionalBoundedString = (value: unknown, max: number): value is string | undefined =>\n value === undefined || isBoundedString(value, max);\n\nconst hasOwn = (value: Record<string, unknown>, key: string): boolean => Object.hasOwn(value, key);\n\nconst hasCursorPair = (value: Record<string, unknown>, cursor: string, epoch: string): boolean =>\n (value[cursor] === undefined && value[epoch] === undefined) ||\n (isCursor(value[cursor]) && isBoundedString(value[epoch], 256));\n\n/** Structural guard for a single {@link RowOp}. Unknown extra fields are tolerated. */\nexport const isRowOp = (value: unknown): value is RowOp => {\n if (\n !isRecord(value) ||\n !isJsonWithin(value, MAX_LIVE_FRAME_BYTES) ||\n !isBoundedString(value['key'], 512)\n ) {\n return false;\n }\n const op = value['op'];\n if (op === 'delete') return true;\n if (op !== 'insert' && op !== 'update') return false;\n if (!isRecord(value['row'])) return false;\n if (op === 'insert') {\n const before = value['before'];\n return before === null || isBoundedString(before, 512);\n }\n return true;\n};\n\nexport const isRowOps = (value: unknown): value is RowOp[] =>\n Array.isArray(value) &&\n value.length <= MAX_DELTA_OPS &&\n isJsonWithin(value, MAX_LIVE_FRAME_BYTES) &&\n value.every(isRowOp);\n\n/**\n * Structural guard for a client frame. Frames with an unknown `t` return\n * false — per the forward-compat rule the receiver then ignores the frame.\n */\nexport const isClientLiveFrame = (value: unknown): value is ClientLiveFrame => {\n if (!isRecord(value) || !isJsonWithin(value, MAX_LIVE_FRAME_BYTES)) return false;\n switch (value['t']) {\n case 'sub':\n return (\n isBoundedString(value['sub'], 256) &&\n isBoundedString(value['query'], 256) &&\n hasCursorPair(value, 'sinceCursor', 'sinceEpoch') &&\n isOptionalBoundedString(value['key'], 128) &&\n value['v'] === LIVE_PROTOCOL &&\n (!hasOwn(value, 'args') || isJsonWithin(value['args'], 32 * 1024))\n );\n case 'unsub':\n return isBoundedString(value['sub'], 256);\n case 'presence':\n return (\n isBoundedString(value['room'], 512) &&\n (!hasOwn(value, 'meta') || isJsonWithin(value['meta'], MAX_PRESENCE_METADATA_BYTES))\n );\n default:\n return false;\n }\n};\n\n/** Structural guard for a server frame. Unknown `t` → false (receiver ignores). */\nexport const isServerLiveFrame = (value: unknown): value is ServerLiveFrame => {\n if (!isRecord(value) || !isJsonWithin(value, MAX_LIVE_FRAME_BYTES)) return false;\n switch (value['t']) {\n case 'ack':\n return isBoundedString(value['sub'], 256);\n case 'data':\n return (\n isBoundedString(value['sub'], 256) &&\n hasOwn(value, 'snapshot') &&\n hasCursorPair(value, 'cursor', 'epoch')\n );\n case 'delta':\n return (\n isBoundedString(value['sub'], 256) &&\n isRowOps(value['ops']) &&\n hasCursorPair(value, 'cursor', 'epoch')\n );\n case 'settled':\n return isBoundedString(value['sub'], 256) && hasCursorPair(value, 'cursor', 'epoch');\n case 'resume':\n return (\n isBoundedString(value['sub'], 256) &&\n isCursor(value['cursor']) &&\n isBoundedString(value['epoch'], 256)\n );\n case 'error':\n return (\n isOptionalBoundedString(value['sub'], 256) &&\n isBoundedString(value['code'], 128) &&\n isBoundedString(value['message'], 2048, true) &&\n typeof value['fatal'] === 'boolean'\n );\n default:\n return false;\n }\n};\n\n/**\n * Extract the live frame from a parsed WS envelope, or `undefined` when the\n * envelope is not a live envelope. Does NOT validate the frame — pair with\n * {@link isClientLiveFrame} / {@link isServerLiveFrame} on the receiving side.\n */\nexport const readLiveEnvelope = (envelope: unknown): unknown => {\n if (\n !isRecord(envelope) ||\n envelope['event'] !== LIVE_EVENT ||\n !hasOwn(envelope, 'data') ||\n !isJsonWithin(envelope, MAX_LIVE_FRAME_BYTES)\n ) {\n return undefined;\n }\n return envelope['data'];\n};\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\nconst UTF8_ENCODER = new TextEncoder();\n\nconst isJsonWithin = (value: unknown, maxBytes: number): boolean => {\n try {\n if (!isJsonValue(value, new WeakSet(), { nodes: 0 }, 0)) return false;\n const serialized = JSON.stringify(value);\n return serialized !== undefined && UTF8_ENCODER.encode(serialized).byteLength <= maxBytes;\n } catch {\n return false;\n }\n};\n\nconst isJsonValue = (\n value: unknown,\n seen: WeakSet<object>,\n budget: { nodes: number },\n depth: number,\n): boolean => {\n budget.nodes += 1;\n if (budget.nodes > 10_000 || depth > 32) return false;\n if (value === null || typeof value === 'string' || typeof value === 'boolean') return true;\n if (typeof value === 'number') return Number.isFinite(value);\n if (typeof value !== 'object' || seen.has(value)) return false;\n seen.add(value);\n\n const values: unknown[] = [];\n if (Array.isArray(value)) {\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Array.prototype && prototype !== null) return false;\n if (value.length > 10_000 - budget.nodes) return false;\n for (const key of Reflect.ownKeys(value)) {\n if (key === 'length') continue;\n if (typeof key !== 'string' || !/^(0|[1-9]\\d*)$/.test(key)) return false;\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n if (descriptor === undefined || !descriptor.enumerable || !('value' in descriptor)) {\n return false;\n }\n const child: unknown = descriptor.value;\n values.push(child);\n }\n // Sparse arrays serialize holes as null, which changes the source value.\n if (values.length !== value.length) return false;\n } else if (isRecord(value)) {\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) return false;\n for (const key of Reflect.ownKeys(value)) {\n if (typeof key !== 'string' || DANGEROUS_KEYS.has(key)) return false;\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n // Wire JSON has data properties. Never execute application getters while\n // checking an unknown value; they can throw or change between reads.\n if (descriptor === undefined || !descriptor.enumerable || !('value' in descriptor)) {\n return false;\n }\n const child: unknown = descriptor.value;\n values.push(child);\n }\n } else {\n return false;\n }\n\n for (const child of values) {\n if (!isJsonValue(child, seen, budget, depth + 1)) return false;\n }\n seen.delete(value);\n return true;\n};\n\n/** Wrap a frame in the `$live` envelope object. */\nexport const liveEnvelope = (frame: LiveFrame): { event: typeof LIVE_EVENT; data: LiveFrame } => ({\n event: LIVE_EVENT,\n data: frame,\n});\n\nconst unreachableVariant = (value: never): never => {\n throw new TypeError(`Unknown live protocol variant: ${JSON.stringify(value)}`);\n};\n\nconst canonicalRowOp = (op: RowOp): RowOp => {\n switch (op.op) {\n case 'insert':\n return { op: op.op, key: op.key, row: op.row, before: op.before };\n case 'update':\n return { op: op.op, key: op.key, row: op.row };\n case 'delete':\n return { op: op.op, key: op.key };\n default:\n return unreachableVariant(op);\n }\n};\n\n/**\n * Rebuild a frame with the canonical key order, dropping absent optionals.\n * `JSON.stringify` of the result is the frame's canonical wire form — the one\n * the golden fixtures pin byte-for-byte.\n */\nexport const canonicalLiveFrame = (frame: LiveFrame): LiveFrame => {\n switch (frame.t) {\n case 'sub':\n return {\n t: frame.t,\n sub: frame.sub,\n query: frame.query,\n ...(frame.args === undefined ? {} : { args: frame.args }),\n ...(frame.sinceCursor === undefined ? {} : { sinceCursor: frame.sinceCursor }),\n ...(frame.sinceEpoch === undefined ? {} : { sinceEpoch: frame.sinceEpoch }),\n ...(frame.key === undefined ? {} : { key: frame.key }),\n v: frame.v,\n };\n case 'unsub':\n case 'ack':\n return { t: frame.t, sub: frame.sub };\n case 'presence':\n return {\n t: frame.t,\n room: frame.room,\n ...(frame.meta === undefined ? {} : { meta: frame.meta }),\n };\n case 'data':\n return {\n t: frame.t,\n sub: frame.sub,\n snapshot: frame.snapshot,\n ...(frame.cursor === undefined ? {} : { cursor: frame.cursor }),\n ...(frame.epoch === undefined ? {} : { epoch: frame.epoch }),\n };\n case 'delta':\n return {\n t: frame.t,\n sub: frame.sub,\n ops: frame.ops.map(canonicalRowOp),\n ...(frame.cursor === undefined ? {} : { cursor: frame.cursor }),\n ...(frame.epoch === undefined ? {} : { epoch: frame.epoch }),\n };\n case 'settled':\n return {\n t: frame.t,\n sub: frame.sub,\n ...(frame.cursor === undefined ? {} : { cursor: frame.cursor }),\n ...(frame.epoch === undefined ? {} : { epoch: frame.epoch }),\n };\n case 'resume':\n return { t: frame.t, sub: frame.sub, cursor: frame.cursor, epoch: frame.epoch };\n case 'error':\n return {\n t: frame.t,\n ...(frame.sub === undefined ? {} : { sub: frame.sub }),\n code: frame.code,\n message: frame.message,\n fatal: frame.fatal,\n };\n default:\n return unreachableVariant(frame);\n }\n};\n\n/** Canonical JSON encoding of a bare frame (no envelope). */\nexport const encodeLiveFrame = (frame: LiveFrame): string => {\n if (!isClientLiveFrame(frame) && !isServerLiveFrame(frame)) {\n throw new TypeError('Cannot encode an invalid or oversized live frame.');\n }\n return JSON.stringify(canonicalLiveFrame(frame));\n};\n\n/**\n * Canonical JSON encoding of the full `$live` envelope — what actually goes\n * on the socket. The shared 64 KiB limit applies to this complete wire value,\n * matching {@link readLiveEnvelope} and receiver-side raw-frame checks.\n */\nexport const encodeLiveEnvelope = (frame: LiveFrame): string => {\n const encoded = `{\"event\":${JSON.stringify(LIVE_EVENT)},\"data\":${encodeLiveFrame(frame)}}`;\n if (UTF8_ENCODER.encode(encoded).byteLength > MAX_LIVE_FRAME_BYTES) {\n throw new TypeError('Cannot encode an oversized live envelope.');\n }\n return encoded;\n};\n","/**\n * The shared keyed list-delta codec — BOTH sides of the wire implement the\n * delta contract through this one module: the server encodes a previous-vs-next\n * query result into `RowOp`s ({@link encodeListDelta}), the client merges them\n * into its cached value ({@link applyListDelta}).\n *\n * Ported from lunora's `subscription-delivery.ts` (encoder) and\n * `delta-merge.ts` (merge), with two deliberate changes:\n *\n * 1. The key field defaults to `'id'` (Vela/CRUD convention, not lunora's\n * `_id`) and is configurable per query.\n * 2. `insert` ops carry an explicit `before` anchor (the key of the row they\n * precede in the authoritative result; `null` = append) instead of\n * approximating position via a `_creationTime` heuristic. Because encoder\n * and merge live in the same package, this buys the exact-reconstruction\n * property the conformance suite enforces: whenever the encoder does not\n * bail, `applyListDelta(previous, encodeListDelta(previous, next))` is\n * deep-equal to `next`, ordering included.\n *\n * Bail-to-snapshot contract (identical on both sides — the server MUST send a\n * full `data` snapshot and the client MUST fall back to full replacement when\n * any of these hold):\n *\n * 1. previous or next is not an array;\n * 2. any row is not a plain object carrying a string key (or the value cannot\n * be JSON-serialized);\n * 3. a duplicate key appears in either array;\n * 4. rows present in BOTH arrays changed relative order (the merge replaces\n * survivors in place and never reorders them).\n *\n * These are correctness conditions only. The codec deliberately does not use\n * operation count as a cost heuristic: callers that can send either encoding\n * must compare the completed delta and snapshot wire frames instead.\n *\n * Op ordering inside a delta: deletes first (previous order), then\n * inserts/updates (next order) — the merge never sees a transient over-length\n * list. Merging is idempotent (`insert` on an existing key replaces in place,\n * `delete` of an absent key is a no-op) so at-least-once replay after a\n * reconnect is harmless.\n */\n\nimport type { RowOp } from './frames';\n\n/** Default row-identity field. Per-query override rides the `sub` frame's `key`. */\nexport const DEFAULT_KEY_FIELD = 'id';\n\ntype Row = Record<string, unknown>;\n\ntype RowIndex = Map<string, Row>;\n\nconst isPlainObject = (value: unknown): value is Row =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst readRowKey = (row: Row, keyField: string): string | undefined => {\n const key = row[keyField];\n return typeof key === 'string' ? key : undefined;\n};\n\n/**\n * Index rows by key preserving order; `undefined` the moment any row is\n * unkeyable or a key repeats (bail rules 2 and 3 — a duplicated key cannot be\n * expressed as keyed deltas without silently collapsing rows).\n */\nconst indexRows = (rows: unknown[], keyField: string): RowIndex | undefined => {\n const byKey = new Map<string, Row>();\n for (const row of rows) {\n if (!isPlainObject(row)) return undefined;\n const key = readRowKey(row, keyField);\n if (key === undefined || byKey.has(key)) return undefined;\n byKey.set(key, row);\n }\n return byKey;\n};\n\n/**\n * True when rows present in BOTH lists keep the same relative order (bail\n * rule 4): the merge updates survivors in place and never reorders them, so a\n * survivor that moved cannot be expressed as deltas.\n */\nconst survivorsKeepOrder = (previous: RowIndex, next: RowIndex): boolean => {\n const survivingPrevious = [...previous.keys()].filter((key) => next.has(key));\n const survivingNext = [...next.keys()].filter((key) => previous.has(key));\n if (survivingPrevious.length !== survivingNext.length) return false;\n return survivingPrevious.every((key, index) => survivingNext[index] === key);\n};\n\n/**\n * Diff `previous` vs `next` into row ops, or `undefined` when any correctness\n * bail rule holds and the caller must send a full snapshot instead. Whether a\n * valid delta is cheaper than that snapshot is a delivery-layer decision.\n *\n * An empty array is a valid result (no row-level change — typically the server\n * catches byte-identical results earlier and sends `settled` instead).\n */\nexport const encodeListDelta = (\n previous: unknown,\n next: unknown,\n keyField: string = DEFAULT_KEY_FIELD,\n): RowOp[] | undefined => {\n try {\n if (!Array.isArray(previous) || !Array.isArray(next)) return undefined;\n\n const previousIndex = indexRows(previous, keyField);\n const nextIndex = indexRows(next, keyField);\n if (previousIndex === undefined || nextIndex === undefined) return undefined;\n if (!survivorsKeepOrder(previousIndex, nextIndex)) return undefined;\n\n const ops: RowOp[] = [];\n\n // Deletes first, in previous order.\n for (const key of previousIndex.keys()) {\n if (!nextIndex.has(key)) ops.push({ op: 'delete', key });\n }\n\n // The `before` anchor for an insert at position i is the nearest FOLLOWING\n // survivor in next order (null = append). At merge time, when the insert\n // applies, the list holds exactly the survivors (in order, updates replace\n // in place) plus earlier inserts; splicing sequentially before the anchor\n // therefore reproduces next's ordering exactly — inserts sharing an anchor\n // stack in emission order, trailing inserts append in emission order.\n const followingSurvivor = new Map<string, string | null>();\n let anchor: string | null = null;\n for (const key of [...nextIndex.keys()].toReversed()) {\n followingSurvivor.set(key, anchor);\n if (previousIndex.has(key)) anchor = key;\n }\n\n // Inserts/updates in next order. Each row is fingerprinted with a single\n // JSON.stringify reused for the changed-row compare; an unserializable row\n // throws and the whole encode bails to snapshot (rule 2).\n for (const [key, nextRow] of nextIndex) {\n const previousRow = previousIndex.get(key);\n const nextFingerprint = JSON.stringify(nextRow);\n if (previousRow === undefined) {\n ops.push({ op: 'insert', key, row: nextRow, before: followingSurvivor.get(key) ?? null });\n continue;\n }\n if (JSON.stringify(previousRow) !== nextFingerprint) {\n ops.push({ op: 'update', key, row: nextRow });\n }\n }\n\n return ops;\n } catch {\n return undefined;\n }\n};\n\n/**\n * Merge row ops into a cached array result, returning a NEW array (the input\n * is never mutated), or `undefined` when the ops cannot be applied cleanly —\n * the caller then falls back to full replacement and lets the next snapshot\n * reconcile.\n *\n * Idempotent by construction: replaying an op after a snapshot already\n * delivered its effect changes nothing.\n */\nexport const applyListDelta = (\n current: unknown,\n ops: readonly RowOp[],\n keyField: string = DEFAULT_KEY_FIELD,\n): unknown[] | undefined => {\n if (!Array.isArray(current)) return undefined;\n\n const rows: Row[] = [];\n const seen = new Set<string>();\n for (const element of current) {\n if (!isPlainObject(element)) return undefined;\n const key = readRowKey(element, keyField);\n if (key === undefined || seen.has(key)) return undefined;\n seen.add(key);\n rows.push(element);\n }\n\n const next = [...rows];\n for (const op of ops) {\n const existingIndex = next.findIndex((row) => row[keyField] === op.key);\n\n switch (op.op) {\n case 'delete':\n if (existingIndex !== -1) next.splice(existingIndex, 1);\n continue;\n case 'insert':\n case 'update':\n if (existingIndex !== -1) {\n // Present → replace in place. Covers `update`, and an `insert` whose\n // row a snapshot already delivered (replay idempotency).\n next[existingIndex] = op.row;\n continue;\n }\n\n if (op.op === 'insert' && op.before !== null) {\n const anchorIndex = next.findIndex((row) => row[keyField] === op.before);\n if (anchorIndex !== -1) {\n next.splice(anchorIndex, 0, op.row);\n continue;\n }\n }\n\n // `insert` with a null/missing anchor, or an `update` for a row this page\n // never held (degraded replay) → append.\n next.push(op.row);\n continue;\n default: {\n const unexpected: never = op;\n throw new TypeError(`Unknown live row operation: ${JSON.stringify(unexpected)}`);\n }\n }\n }\n\n return next;\n};\n","/**\n * Golden wire fixtures — the drift tripwire. The server suite (@velajs/vela)\n * and the client suite (@velajs/client) both run these through\n * `runProtocolConformance`, so an encoding change on either side fails a test\n * instead of surfacing as a production incompatibility.\n *\n * `wire` strings are byte-exact: they pin the canonical key order of\n * `encodeLiveEnvelope`. Do not reformat them.\n */\n\nimport type { LiveFrame, RowOp } from './frames';\n\nexport interface FrameFixture {\n name: string;\n frame: LiveFrame;\n /** Exact canonical envelope bytes: `encodeLiveEnvelope(frame)` must equal this. */\n wire: string;\n}\n\nexport const FRAME_FIXTURES: FrameFixture[] = [\n {\n name: 'sub (full)',\n frame: {\n t: 'sub',\n sub: 's1',\n query: 'todos.list',\n args: { listId: 'l1' },\n sinceCursor: 42,\n sinceEpoch: 'e-1',\n key: 'id',\n v: 2,\n },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"sub\",\"sub\":\"s1\",\"query\":\"todos.list\",\"args\":{\"listId\":\"l1\"},\"sinceCursor\":42,\"sinceEpoch\":\"e-1\",\"key\":\"id\",\"v\":2}}',\n },\n {\n name: 'sub (minimal)',\n frame: { t: 'sub', sub: 's2', query: 'todos.all', v: 2 },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"sub\",\"sub\":\"s2\",\"query\":\"todos.all\",\"v\":2}}',\n },\n {\n name: 'unsub',\n frame: { t: 'unsub', sub: 's1' },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"unsub\",\"sub\":\"s1\"}}',\n },\n {\n name: 'presence',\n frame: { t: 'presence', room: 'r1', meta: { name: 'kauan' } },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"presence\",\"room\":\"r1\",\"meta\":{\"name\":\"kauan\"}}}',\n },\n {\n name: 'ack',\n frame: { t: 'ack', sub: 's1' },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"ack\",\"sub\":\"s1\"}}',\n },\n {\n name: 'data',\n frame: { t: 'data', sub: 's1', snapshot: [{ id: 'a', text: 'hi' }], cursor: 7, epoch: 'e-1' },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"data\",\"sub\":\"s1\",\"snapshot\":[{\"id\":\"a\",\"text\":\"hi\"}],\"cursor\":7,\"epoch\":\"e-1\"}}',\n },\n {\n name: 'data (cold, no cursor)',\n frame: { t: 'data', sub: 's1', snapshot: null },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"data\",\"sub\":\"s1\",\"snapshot\":null}}',\n },\n {\n name: 'delta',\n frame: {\n t: 'delta',\n sub: 's1',\n ops: [\n { op: 'delete', key: 'a' },\n { op: 'insert', key: 'b', row: { id: 'b' }, before: null },\n { op: 'update', key: 'c', row: { id: 'c', n: 2 } },\n ],\n cursor: 8,\n epoch: 'e-1',\n },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"delta\",\"sub\":\"s1\",\"ops\":[{\"op\":\"delete\",\"key\":\"a\"},{\"op\":\"insert\",\"key\":\"b\",\"row\":{\"id\":\"b\"},\"before\":null},{\"op\":\"update\",\"key\":\"c\",\"row\":{\"id\":\"c\",\"n\":2}}],\"cursor\":8,\"epoch\":\"e-1\"}}',\n },\n {\n name: 'settled',\n frame: { t: 'settled', sub: 's1', cursor: 9, epoch: 'e-1' },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"settled\",\"sub\":\"s1\",\"cursor\":9,\"epoch\":\"e-1\"}}',\n },\n {\n name: 'resume',\n frame: { t: 'resume', sub: 's1', cursor: 42, epoch: 'e-1' },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"resume\",\"sub\":\"s1\",\"cursor\":42,\"epoch\":\"e-1\"}}',\n },\n {\n name: 'error (subscription)',\n frame: { t: 'error', sub: 's1', code: 'forbidden', message: 'nope', fatal: true },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"error\",\"sub\":\"s1\",\"code\":\"forbidden\",\"message\":\"nope\",\"fatal\":true}}',\n },\n {\n name: 'error (connection)',\n frame: { t: 'error', code: 'unsupported_protocol', message: 'v2 required', fatal: true },\n wire: '{\"event\":\"$live\",\"data\":{\"t\":\"error\",\"code\":\"unsupported_protocol\",\"message\":\"v2 required\",\"fatal\":true}}',\n },\n];\n\nexport interface DeltaFixture {\n name: string;\n previous: unknown;\n next: unknown;\n keyField?: string;\n /** Expected ops, or `null` when the encoder MUST bail to snapshot. */\n expected: RowOp[] | null;\n}\n\nexport const DELTA_FIXTURES: DeltaFixture[] = [\n { name: 'noop', previous: [], next: [], expected: [] },\n {\n name: 'insert into empty',\n previous: [],\n next: [{ id: 'a', n: 1 }],\n expected: [{ op: 'insert', key: 'a', row: { id: 'a', n: 1 }, before: null }],\n },\n {\n name: 'insert head',\n previous: [{ id: 'b' }],\n next: [{ id: 'a' }, { id: 'b' }],\n expected: [{ op: 'insert', key: 'a', row: { id: 'a' }, before: 'b' }],\n },\n {\n name: 'insert middle',\n previous: [{ id: 'a' }, { id: 'c' }],\n next: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],\n expected: [{ op: 'insert', key: 'b', row: { id: 'b' }, before: 'c' }],\n },\n {\n name: 'insert tail',\n previous: [{ id: 'a' }],\n next: [{ id: 'a' }, { id: 'b' }],\n expected: [{ op: 'insert', key: 'b', row: { id: 'b' }, before: null }],\n },\n {\n name: 'update in place',\n previous: [{ id: 'a', n: 1 }],\n next: [{ id: 'a', n: 2 }],\n expected: [{ op: 'update', key: 'a', row: { id: 'a', n: 2 } }],\n },\n {\n name: 'delete one',\n previous: [{ id: 'a' }, { id: 'b' }],\n next: [{ id: 'a' }],\n expected: [{ op: 'delete', key: 'b' }],\n },\n {\n name: 'mixed delete+update+insert',\n previous: [\n { id: 'a', n: 1 },\n { id: 'b', n: 1 },\n { id: 'c', n: 1 },\n ],\n next: [\n { id: 'b', n: 2 },\n { id: 'd', n: 1 },\n { id: 'c', n: 1 },\n ],\n expected: [\n { op: 'delete', key: 'a' },\n { op: 'update', key: 'b', row: { id: 'b', n: 2 } },\n { op: 'insert', key: 'd', row: { id: 'd', n: 1 }, before: 'c' },\n ],\n },\n {\n name: 'stacked inserts share an anchor in next order',\n previous: [{ id: 'z' }],\n next: [{ id: 'x' }, { id: 'y' }, { id: 'z' }],\n expected: [\n { op: 'insert', key: 'x', row: { id: 'x' }, before: 'z' },\n { op: 'insert', key: 'y', row: { id: 'y' }, before: 'z' },\n ],\n },\n {\n name: 'custom key field',\n previous: [{ _key: 'a', n: 1 }],\n next: [{ _key: 'a', n: 2 }],\n keyField: '_key',\n expected: [{ op: 'update', key: 'a', row: { _key: 'a', n: 2 } }],\n },\n {\n name: 'clear list remains expressible regardless of op count',\n previous: [{ id: 'a' }, { id: 'b' }],\n next: [],\n expected: [\n { op: 'delete', key: 'a' },\n { op: 'delete', key: 'b' },\n ],\n },\n {\n name: 'near-total change remains expressible regardless of op count',\n previous: [{ id: 'a' }, { id: 'b' }],\n next: [{ id: 'c' }, { id: 'd' }, { id: 'e' }],\n expected: [\n { op: 'delete', key: 'a' },\n { op: 'delete', key: 'b' },\n { op: 'insert', key: 'c', row: { id: 'c' }, before: null },\n { op: 'insert', key: 'd', row: { id: 'd' }, before: null },\n { op: 'insert', key: 'e', row: { id: 'e' }, before: null },\n ],\n },\n // ---- bail cases (expected: null → full snapshot) ----\n {\n name: 'bail: previous not array (rule 1)',\n previous: { id: 'a' },\n next: [{ id: 'a' }],\n expected: null,\n },\n {\n name: 'bail: next not array (rule 1)',\n previous: [{ id: 'a' }],\n next: { id: 'a' },\n expected: null,\n },\n {\n name: 'bail: row missing key (rule 2)',\n previous: [{ id: 'a' }],\n next: [{ text: 'no key' }],\n expected: null,\n },\n {\n name: 'bail: non-string key (rule 2)',\n previous: [{ id: 'a' }],\n next: [{ id: 5 }],\n expected: null,\n },\n { name: 'bail: scalar row (rule 2)', previous: [{ id: 'a' }], next: ['a'], expected: null },\n {\n name: 'bail: duplicate key in previous (rule 3)',\n previous: [{ id: 'a' }, { id: 'a' }],\n next: [{ id: 'a' }],\n expected: null,\n },\n {\n name: 'bail: duplicate key in next (rule 3)',\n previous: [{ id: 'a' }],\n next: [{ id: 'a' }, { id: 'a' }],\n expected: null,\n },\n {\n name: 'bail: survivors reordered (rule 4)',\n previous: [{ id: 'a' }, { id: 'b' }],\n next: [{ id: 'b' }, { id: 'a' }],\n expected: null,\n },\n];\n","/**\n * The protocol conformance runner. Both wire endpoints run this in their own\n * test suites (see `@velajs/testing`'s live harness) so a codec that drifts\n * from the golden fixtures — or from the shared delta semantics — fails a test\n * on the offending side.\n *\n * Checks, in order:\n * 1. Frame encoding: `encodeLiveEnvelope(fixture.frame)` is byte-identical to\n * the pinned wire string, the wire parses back to a guard-recognized frame,\n * and `readLiveEnvelope` extracts it.\n * 2. Delta fixtures: the codec's `encodeListDelta` produces exactly the pinned\n * ops (or bails where the fixture says it must), and for every mergeable\n * fixture `applyListDelta` reconstructs `next` exactly — then reapplying\n * the same ops changes nothing (at-least-once replay idempotency).\n * 3. A seeded randomized sweep of generated list pairs asserting the\n * exact-reconstruction property on cases the fixtures don't enumerate.\n */\n\nimport { DEFAULT_KEY_FIELD, applyListDelta, encodeListDelta } from './delta';\nimport {\n encodeLiveEnvelope,\n isClientLiveFrame,\n isServerLiveFrame,\n readLiveEnvelope,\n} from './frames';\nimport type { RowOp } from './frames';\nimport { DELTA_FIXTURES, FRAME_FIXTURES } from './fixtures';\n\n/** The two halves a wire endpoint must implement compatibly. */\nexport interface DeltaCodec {\n encodeListDelta: (previous: unknown, next: unknown, keyField?: string) => RowOp[] | undefined;\n applyListDelta: (\n current: unknown,\n ops: readonly RowOp[],\n keyField?: string,\n ) => unknown[] | undefined;\n}\n\nconst REFERENCE_CODEC: DeltaCodec = { encodeListDelta, applyListDelta };\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst deepEqual = (a: unknown, b: unknown): boolean => {\n if (a === b) return true;\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;\n return a.every((value, index) => deepEqual(value, b[index]));\n }\n if (isRecord(a) && isRecord(b)) {\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) => Object.hasOwn(b, key) && deepEqual(a[key], b[key]));\n }\n return false;\n};\n\n/** Deterministic LCG so the randomized sweep is reproducible (no Math.random). */\nconst makeRandom = (seed: number): (() => number) => {\n let state = seed >>> 0;\n return () => {\n state = (state * 1664525 + 1013904223) >>> 0;\n return state / 0x1_0000_0000;\n };\n};\n\ninterface GeneratedCase {\n previous: Record<string, unknown>[];\n next: Record<string, unknown>[];\n}\n\n/**\n * Generate a mergeable previous/next pair: start from a random keyed list,\n * then delete a random subset, update random payloads, and insert fresh keys\n * at random positions. Survivor order is preserved by construction, so every\n * generated case is expressible as a delta.\n */\nconst generateCase = (random: () => number, caseIndex: number): GeneratedCase => {\n const previousLength = Math.floor(random() * 8);\n const previous: Record<string, unknown>[] = [];\n for (let index = 0; index < previousLength; index += 1) {\n previous.push({ id: `k${caseIndex}-${index}`, n: Math.floor(random() * 100) });\n }\n\n const next: Record<string, unknown>[] = [];\n for (const row of previous) {\n if (random() < 0.25) continue; // delete\n next.push(random() < 0.4 ? { ...row, n: Math.floor(random() * 100) } : row);\n }\n const insertions = Math.floor(random() * 4);\n for (let index = 0; index < insertions; index += 1) {\n const position = Math.floor(random() * (next.length + 1));\n next.splice(position, 0, { id: `f${caseIndex}-${index}`, n: Math.floor(random() * 100) });\n }\n\n return { previous, next };\n};\n\nexport interface ConformanceReport {\n /** Human-readable failure descriptions; empty = conformant. */\n failures: string[];\n checks: number;\n}\n\n/**\n * Run the full conformance suite against a codec (defaults to the reference\n * codec in this package — the package's own tests run exactly this).\n */\nexport const runProtocolConformance = (codec: DeltaCodec = REFERENCE_CODEC): ConformanceReport => {\n const failures: string[] = [];\n let checks = 0;\n\n for (const fixture of FRAME_FIXTURES) {\n checks += 1;\n const encoded = encodeLiveEnvelope(fixture.frame);\n if (encoded !== fixture.wire) {\n failures.push(\n `frame \"${fixture.name}\": encoded wire differs\\n expected ${fixture.wire}\\n actual ${encoded}`,\n );\n continue;\n }\n const envelope: unknown = JSON.parse(fixture.wire);\n const frame = readLiveEnvelope(envelope);\n if (frame === undefined) {\n failures.push(`frame \"${fixture.name}\": readLiveEnvelope did not recognize the envelope`);\n continue;\n }\n if (!isClientLiveFrame(frame) && !isServerLiveFrame(frame)) {\n failures.push(`frame \"${fixture.name}\": decoded frame not recognized by either guard`);\n }\n }\n\n for (const fixture of DELTA_FIXTURES) {\n checks += 1;\n const keyField = fixture.keyField ?? DEFAULT_KEY_FIELD;\n const ops = codec.encodeListDelta(fixture.previous, fixture.next, keyField);\n\n if (fixture.expected === null) {\n if (ops !== undefined) {\n failures.push(\n `delta \"${fixture.name}\": expected bail-to-snapshot, got ${JSON.stringify(ops)}`,\n );\n }\n continue;\n }\n\n if (ops === undefined) {\n failures.push(\n `delta \"${fixture.name}\": encoder bailed, expected ${JSON.stringify(fixture.expected)}`,\n );\n continue;\n }\n if (!deepEqual(ops, fixture.expected)) {\n failures.push(\n `delta \"${fixture.name}\": ops differ\\n expected ${JSON.stringify(fixture.expected)}\\n actual ${JSON.stringify(ops)}`,\n );\n continue;\n }\n\n const merged = codec.applyListDelta(fixture.previous, ops, keyField);\n if (merged === undefined || !deepEqual(merged, fixture.next)) {\n failures.push(\n `delta \"${fixture.name}\": apply(previous, ops) did not reconstruct next\\n expected ${JSON.stringify(fixture.next)}\\n actual ${JSON.stringify(merged)}`,\n );\n continue;\n }\n\n const replayed = codec.applyListDelta(merged, ops, keyField);\n if (replayed === undefined || !deepEqual(replayed, fixture.next)) {\n failures.push(`delta \"${fixture.name}\": replaying the same ops was not idempotent`);\n }\n }\n\n const random = makeRandom(0x5eed);\n for (let caseIndex = 0; caseIndex < 250; caseIndex += 1) {\n checks += 1;\n const { previous, next } = generateCase(random, caseIndex);\n const ops = codec.encodeListDelta(previous, next);\n\n if (ops === undefined) {\n failures.push(`random #${caseIndex}: codec bailed on an expressible list change`);\n continue;\n }\n\n const merged = codec.applyListDelta(previous, ops);\n if (merged === undefined || !deepEqual(merged, next)) {\n failures.push(\n `random #${caseIndex}: apply(previous, ops) != next\\n previous ${JSON.stringify(previous)}\\n next ${JSON.stringify(next)}\\n ops ${JSON.stringify(ops)}\\n merged ${JSON.stringify(merged)}`,\n );\n }\n }\n\n return { failures, checks };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velajs/live-protocol",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "Normative wire protocol for Vela live queries: frame types, the shared keyed-delta codec, and golden conformance fixtures",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"delta",
|
|
@@ -11,15 +11,16 @@
|
|
|
11
11
|
"websocket",
|
|
12
12
|
"wire-protocol"
|
|
13
13
|
],
|
|
14
|
-
"homepage": "https://github.com/velajs/live-protocol#readme",
|
|
14
|
+
"homepage": "https://github.com/velajs/vela/tree/main/packages/live-protocol#readme",
|
|
15
15
|
"bugs": {
|
|
16
|
-
"url": "https://github.com/velajs/
|
|
16
|
+
"url": "https://github.com/velajs/vela/issues"
|
|
17
17
|
},
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"author": "ksh",
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
|
-
"url": "git+https://github.com/velajs/
|
|
22
|
+
"url": "git+https://github.com/velajs/vela.git",
|
|
23
|
+
"directory": "packages/live-protocol"
|
|
23
24
|
},
|
|
24
25
|
"files": [
|
|
25
26
|
"dist",
|
|
@@ -38,18 +39,21 @@
|
|
|
38
39
|
}
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
|
-
"@arethetypeswrong/cli": "
|
|
42
|
-
"@changesets/cli": "
|
|
43
|
-
"oxfmt": "
|
|
44
|
-
"oxlint": "
|
|
45
|
-
"publint": "
|
|
46
|
-
"tsdown": "
|
|
47
|
-
"typescript": "
|
|
48
|
-
"vitest": "
|
|
42
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
43
|
+
"@changesets/cli": "3.0.1",
|
|
44
|
+
"oxfmt": "0.58.0",
|
|
45
|
+
"oxlint": "1.73.0",
|
|
46
|
+
"publint": "0.3.21",
|
|
47
|
+
"tsdown": "0.23.0",
|
|
48
|
+
"typescript": "7.0.2",
|
|
49
|
+
"vitest": "4.1.10"
|
|
49
50
|
},
|
|
50
51
|
"engines": {
|
|
51
52
|
"node": ">=24"
|
|
52
53
|
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
53
57
|
"scripts": {
|
|
54
58
|
"build": "tsdown",
|
|
55
59
|
"test": "vitest run",
|
|
@@ -59,10 +63,6 @@
|
|
|
59
63
|
"format:check": "oxfmt --check .",
|
|
60
64
|
"publint": "publint",
|
|
61
65
|
"attw": "attw --pack . --profile esm-only",
|
|
62
|
-
"changeset": "changeset",
|
|
63
|
-
"version-packages": "changeset version",
|
|
64
|
-
"release:check": "node scripts/check-release-lock.mjs && pnpm verify && pnpm audit --audit-level=high",
|
|
65
|
-
"release": "pnpm release:check && changeset publish",
|
|
66
66
|
"verify": "pnpm lint && pnpm format:check && pnpm build && pnpm typecheck && pnpm test && pnpm publint && pnpm attw"
|
|
67
67
|
}
|
|
68
68
|
}
|