@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 +7 -0
- package/dist/index.d.ts +228 -8
- package/dist/index.js +867 -5
- package/dist/index.js.map +1 -0
- package/package.json +47 -35
- package/dist/conformance.d.ts +0 -33
- package/dist/conformance.js +0 -152
- package/dist/delta.d.ts +0 -59
- package/dist/delta.js +0 -186
- package/dist/fixtures.d.ts +0 -26
- package/dist/fixtures.js +0 -592
- package/dist/frames.d.ts +0 -154
- package/dist/frames.js +0 -190
- package/dist/version.d.ts +0 -11
- package/dist/version.js +0 -10
package/CHANGELOG.md
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,228 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|