@velajs/live-protocol 1.0.0 → 1.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ # @velajs/live-protocol
2
+
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 0fcf971: Modernize the package build, validation, and release toolchain.
package/dist/index.d.ts CHANGED
@@ -1,8 +1,228 @@
1
- export { LIVE_PROTOCOL } from './version';
2
- export { COMMIT_CURSOR_HEADER, COMMIT_EPOCH_HEADER, LIVE_ERROR_CODES, LIVE_EVENT, RESERVED_EVENT_PREFIX, canonicalLiveFrame, encodeLiveEnvelope, encodeLiveFrame, isClientLiveFrame, isRowOp, isRowOps, isServerLiveFrame, liveEnvelope, readLiveEnvelope, } from './frames';
3
- export type { ClientLiveFrame, LiveErrorCode, LiveFrame, RowOp, ServerLiveFrame } from './frames';
4
- export { DEFAULT_KEY_FIELD, applyListDelta, encodeListDelta } from './delta';
5
- export { runProtocolConformance } from './conformance';
6
- export type { ConformanceReport, DeltaCodec } from './conformance';
7
- export { DELTA_FIXTURES, FRAME_FIXTURES } from './fixtures';
8
- export type { DeltaFixture, FrameFixture } from './fixtures';
1
+ //#region src/version.d.ts
2
+ /**
3
+ * Live-protocol wire version. Bumped ONLY on a breaking wire change (renaming
4
+ * or removing a field, changing a delivery guarantee). Additive changes — new
5
+ * optional fields, new frame types — do NOT bump it: receivers MUST ignore
6
+ * unknown frame `t` values and unknown object fields.
7
+ *
8
+ * A client advertises the version it speaks via the `v` field on its `sub`
9
+ * frame; a server that cannot serve that version replies
10
+ * `{ t: 'error', code: 'unsupported_protocol', fatal: true }`.
11
+ */
12
+ declare const LIVE_PROTOCOL = 1;
13
+ //#endregion
14
+ //#region src/frames.d.ts
15
+ /**
16
+ * The normative frame catalog for Vela live queries.
17
+ *
18
+ * Live frames ride Vela's existing WebSocket envelope `{ event, data }` under
19
+ * the single reserved event name `$live`; the frame itself is the envelope's
20
+ * `data`, discriminated on `t`. Classic gateway events, `ping`→`pong`
21
+ * keepalive, and live frames coexist on one socket. The `$` prefix is reserved
22
+ * for the framework: app gateways must never register a `$…` event.
23
+ *
24
+ * Byte-identical encoding matters: golden fixtures pin the exact wire string
25
+ * for every frame shape, and both the server and the client encode through
26
+ * {@link encodeLiveFrame} / {@link encodeLiveEnvelope} so the two sides cannot
27
+ * drift. Canonical key order is the declaration order of each type below;
28
+ * absent optionals are omitted entirely.
29
+ */
30
+ /** The reserved envelope event every live frame rides under. */
31
+ declare const LIVE_EVENT = "$live";
32
+ /**
33
+ * The reserved event-name prefix. The WS dispatcher rejects app gateways that
34
+ * register a `$…` event at bootstrap so live (and future framework) frames can
35
+ * never collide with app events.
36
+ */
37
+ declare const RESERVED_EVENT_PREFIX = "$";
38
+ /**
39
+ * HTTP response headers carrying the commit cursor/epoch of the log scope a
40
+ * mutation's invalidations landed in. The client gates optimistic-layer drops
41
+ * on a subscription frame whose `cursor` passes this value (and whose `epoch`
42
+ * matches) — never on HTTP response timing, which races the broadcast.
43
+ */
44
+ declare const COMMIT_CURSOR_HEADER = "Vela-Commit-Cursor";
45
+ declare const COMMIT_EPOCH_HEADER = "Vela-Commit-Epoch";
46
+ /** Well-known `error` frame codes. The code space is open — receivers must tolerate unknown codes. */
47
+ declare const LIVE_ERROR_CODES: {
48
+ readonly UNSUPPORTED_PROTOCOL: 'unsupported_protocol';
49
+ readonly DUPLICATE_SUB: 'duplicate_sub';
50
+ readonly UNKNOWN_QUERY: 'unknown_query';
51
+ readonly FORBIDDEN: 'forbidden';
52
+ readonly BAD_ARGS: 'bad_args';
53
+ readonly INTERNAL: 'internal';
54
+ };
55
+ type LiveErrorCode = (typeof LIVE_ERROR_CODES)[keyof typeof LIVE_ERROR_CODES] | (string & {});
56
+ /**
57
+ * One row change inside a `delta` frame. Ops are keyed by the query's key
58
+ * field (default `'id'`); `insert`/`update` carry the full new row, `delete`
59
+ * omits it. An `insert` carries `before` — the key of the row it precedes in
60
+ * the authoritative result (`null` = append) — so the client reconstructs the
61
+ * server's ordering exactly. Application is idempotent: `insert` on an
62
+ * existing key replaces in place, `delete` of an absent key is a no-op.
63
+ */
64
+ type RowOp = {
65
+ op: 'insert';
66
+ key: string;
67
+ row: Record<string, unknown>;
68
+ before: string | null;
69
+ } | {
70
+ op: 'update';
71
+ key: string;
72
+ row: Record<string, unknown>;
73
+ } | {
74
+ op: 'delete';
75
+ key: string;
76
+ };
77
+ /** Client → server frames (the `data` of a `{ event: '$live' }` envelope). */
78
+ type ClientLiveFrame = {
79
+ t: 'sub';
80
+ /** Client-chosen subscription id, unique per socket. */
81
+ sub: string;
82
+ /** The live-query identifier declared by `@LiveQuery(name)`. */
83
+ query: string;
84
+ args?: unknown;
85
+ /** Resume watermark: last observed cursor/epoch. Omitted = cold subscribe. */
86
+ sinceCursor?: number;
87
+ sinceEpoch?: string;
88
+ /** Key-field override for list deltas (default `'id'`). */
89
+ key?: string;
90
+ /** Protocol version the client speaks (see LIVE_PROTOCOL). */
91
+ v?: number;
92
+ } | {
93
+ t: 'unsub';
94
+ sub: string;
95
+ } | {
96
+ t: 'presence';
97
+ room: string;
98
+ meta?: unknown;
99
+ };
100
+ /** Server → client frames. `ack` precedes any `data`/`resume` for a sub. */
101
+ type ServerLiveFrame = {
102
+ t: 'ack';
103
+ sub: string;
104
+ } | {
105
+ t: 'data';
106
+ sub: string;
107
+ snapshot: unknown;
108
+ cursor?: number;
109
+ epoch?: string;
110
+ } | {
111
+ t: 'delta';
112
+ sub: string;
113
+ ops: RowOp[];
114
+ cursor?: number;
115
+ epoch?: string;
116
+ } |
117
+ /** Re-run result was byte-identical — no payload, but the cursor still advances (drops optimistic layers). */
118
+ {
119
+ t: 'settled';
120
+ sub: string;
121
+ cursor?: number;
122
+ epoch?: string;
123
+ } |
124
+ /** Resume verdict: nothing relevant changed while away — keep the cached value, advance the cursor. */
125
+ {
126
+ t: 'resume';
127
+ sub: string;
128
+ cursor: number;
129
+ epoch: string;
130
+ } | {
131
+ t: 'error';
132
+ sub?: string;
133
+ code: LiveErrorCode;
134
+ message: string;
135
+ fatal: boolean;
136
+ };
137
+ type LiveFrame = ClientLiveFrame | ServerLiveFrame;
138
+ /** Structural guard for a single {@link RowOp}. Unknown extra fields are tolerated. */
139
+ declare const isRowOp: (value: unknown) => value is RowOp;
140
+ declare const isRowOps: (value: unknown) => value is RowOp[];
141
+ /**
142
+ * Structural guard for a client frame. Frames with an unknown `t` return
143
+ * false — per the forward-compat rule the receiver then ignores the frame.
144
+ */
145
+ declare const isClientLiveFrame: (value: unknown) => value is ClientLiveFrame;
146
+ /** Structural guard for a server frame. Unknown `t` → false (receiver ignores). */
147
+ declare const isServerLiveFrame: (value: unknown) => value is ServerLiveFrame;
148
+ /**
149
+ * Extract the live frame from a parsed WS envelope, or `undefined` when the
150
+ * envelope is not a live envelope. Does NOT validate the frame — pair with
151
+ * {@link isClientLiveFrame} / {@link isServerLiveFrame} on the receiving side.
152
+ */
153
+ declare const readLiveEnvelope: (envelope: unknown) => unknown;
154
+ /** Wrap a frame in the `$live` envelope object. */
155
+ declare const liveEnvelope: (frame: LiveFrame) => {
156
+ event: typeof LIVE_EVENT;
157
+ data: LiveFrame;
158
+ };
159
+ /**
160
+ * Rebuild a frame with the canonical key order, dropping absent optionals.
161
+ * `JSON.stringify` of the result is the frame's canonical wire form — the one
162
+ * the golden fixtures pin byte-for-byte.
163
+ */
164
+ declare const canonicalLiveFrame: (frame: LiveFrame) => Record<string, unknown>;
165
+ /** Canonical JSON encoding of a bare frame (no envelope). */
166
+ declare const encodeLiveFrame: (frame: LiveFrame) => string;
167
+ /** Canonical JSON encoding of the full `$live` envelope — what actually goes on the socket. */
168
+ declare const encodeLiveEnvelope: (frame: LiveFrame) => string;
169
+ //#endregion
170
+ //#region src/delta.d.ts
171
+ /** Default row-identity field. Per-query override rides the `sub` frame's `key`. */
172
+ declare const DEFAULT_KEY_FIELD = "id";
173
+ /**
174
+ * Diff `previous` vs `next` into row ops, or `undefined` when any bail rule
175
+ * holds and the caller must send a full snapshot instead.
176
+ *
177
+ * An empty array is a valid result (no row-level change — typically the server
178
+ * catches byte-identical results earlier and sends `settled` instead).
179
+ */
180
+ declare const encodeListDelta: (previous: unknown, next: unknown, keyField?: string) => RowOp[] | undefined;
181
+ /**
182
+ * Merge row ops into a cached array result, returning a NEW array (the input
183
+ * is never mutated), or `undefined` when the ops cannot be applied cleanly —
184
+ * the caller then falls back to full replacement and lets the next snapshot
185
+ * reconcile.
186
+ *
187
+ * Idempotent by construction: replaying an op after a snapshot already
188
+ * delivered its effect changes nothing.
189
+ */
190
+ declare const applyListDelta: (current: unknown, ops: readonly RowOp[], keyField?: string) => unknown[] | undefined;
191
+ //#endregion
192
+ //#region src/conformance.d.ts
193
+ /** The two halves a wire endpoint must implement compatibly. */
194
+ interface DeltaCodec {
195
+ encodeListDelta: (previous: unknown, next: unknown, keyField?: string) => RowOp[] | undefined;
196
+ applyListDelta: (current: unknown, ops: readonly RowOp[], keyField?: string) => unknown[] | undefined;
197
+ }
198
+ interface ConformanceReport {
199
+ /** Human-readable failure descriptions; empty = conformant. */
200
+ failures: string[];
201
+ checks: number;
202
+ }
203
+ /**
204
+ * Run the full conformance suite against a codec (defaults to the reference
205
+ * codec in this package — the package's own tests run exactly this).
206
+ */
207
+ declare const runProtocolConformance: (codec?: DeltaCodec) => ConformanceReport;
208
+ //#endregion
209
+ //#region src/fixtures.d.ts
210
+ interface FrameFixture {
211
+ name: string;
212
+ frame: LiveFrame;
213
+ /** Exact canonical envelope bytes: `encodeLiveEnvelope(frame)` must equal this. */
214
+ wire: string;
215
+ }
216
+ declare const FRAME_FIXTURES: FrameFixture[];
217
+ interface DeltaFixture {
218
+ name: string;
219
+ previous: unknown;
220
+ next: unknown;
221
+ keyField?: string;
222
+ /** Expected ops, or `null` when the encoder MUST bail to snapshot. */
223
+ expected: RowOp[] | null;
224
+ }
225
+ declare const DELTA_FIXTURES: DeltaFixture[];
226
+ //#endregion
227
+ export { COMMIT_CURSOR_HEADER, COMMIT_EPOCH_HEADER, type ClientLiveFrame, type ConformanceReport, DEFAULT_KEY_FIELD, DELTA_FIXTURES, type DeltaCodec, type DeltaFixture, FRAME_FIXTURES, type FrameFixture, LIVE_ERROR_CODES, LIVE_EVENT, LIVE_PROTOCOL, type LiveErrorCode, type LiveFrame, RESERVED_EVENT_PREFIX, type RowOp, type ServerLiveFrame, applyListDelta, canonicalLiveFrame, encodeListDelta, encodeLiveEnvelope, encodeLiveFrame, isClientLiveFrame, isRowOp, isRowOps, isServerLiveFrame, liveEnvelope, readLiveEnvelope, runProtocolConformance };
228
+ //# sourceMappingURL=index.d.ts.map