@syncular/core 0.30.10 → 0.30.11
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/dist/bytes.js +12 -18
- package/dist/message.js +85 -77
- package/package.json +1 -1
- package/src/bytes.ts +17 -22
- package/src/message.ts +105 -88
package/dist/bytes.js
CHANGED
|
@@ -198,7 +198,7 @@ export function writeStringListMap(writer, map) {
|
|
|
198
198
|
writer.str(value);
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
-
|
|
201
|
+
function readCanonicalMap(reader, read) {
|
|
202
202
|
const count = reader.u32();
|
|
203
203
|
const entries = [];
|
|
204
204
|
let previousKey;
|
|
@@ -208,14 +208,19 @@ export function readStringListMap(reader) {
|
|
|
208
208
|
throw new DecodeError('sync.invalid_request', `map keys must be unique and in ascending code-unit order (saw ${JSON.stringify(key)} after ${JSON.stringify(previousKey)})`);
|
|
209
209
|
}
|
|
210
210
|
previousKey = key;
|
|
211
|
-
|
|
212
|
-
const values = [];
|
|
213
|
-
for (let j = 0; j < valueCount; j++)
|
|
214
|
-
values.push(reader.str());
|
|
215
|
-
entries.push([key, values]);
|
|
211
|
+
entries.push([key, read()]);
|
|
216
212
|
}
|
|
217
213
|
return Object.fromEntries(entries);
|
|
218
214
|
}
|
|
215
|
+
export function readStringListMap(reader) {
|
|
216
|
+
return readCanonicalMap(reader, () => {
|
|
217
|
+
const count = reader.u32();
|
|
218
|
+
const values = [];
|
|
219
|
+
for (let i = 0; i < count; i++)
|
|
220
|
+
values.push(reader.str());
|
|
221
|
+
return values;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
219
224
|
/** `map` of `str` → `str` (stored scopes on change records, SPEC.md §4.5). */
|
|
220
225
|
export function writeStringMap(writer, map) {
|
|
221
226
|
const keys = Object.keys(map).sort();
|
|
@@ -227,16 +232,5 @@ export function writeStringMap(writer, map) {
|
|
|
227
232
|
}
|
|
228
233
|
}
|
|
229
234
|
export function readStringMap(reader) {
|
|
230
|
-
|
|
231
|
-
const entries = [];
|
|
232
|
-
let previousKey;
|
|
233
|
-
for (let i = 0; i < count; i++) {
|
|
234
|
-
const key = reader.str();
|
|
235
|
-
if (previousKey !== undefined && key <= previousKey) {
|
|
236
|
-
throw new DecodeError('sync.invalid_request', `map keys must be unique and in ascending code-unit order (saw ${JSON.stringify(key)} after ${JSON.stringify(previousKey)})`);
|
|
237
|
-
}
|
|
238
|
-
previousKey = key;
|
|
239
|
-
entries.push([key, reader.str()]);
|
|
240
|
-
}
|
|
241
|
-
return Object.fromEntries(entries);
|
|
235
|
+
return readCanonicalMap(reader, () => reader.str());
|
|
242
236
|
}
|
package/dist/message.js
CHANGED
|
@@ -18,6 +18,13 @@ import { FrameType, KNOWN_FRAME_TYPES, REQUEST_FRAME_TYPES, RESPONSE_FRAME_TYPES
|
|
|
18
18
|
import { normalizeRejectionDetails, } from './rejection-details.js';
|
|
19
19
|
import { decodeRowsSegment } from './segment.js';
|
|
20
20
|
const MAGIC_BYTES = utf8Encode(SYNC_PACK_MAGIC);
|
|
21
|
+
function definedObject(record) {
|
|
22
|
+
for (const key of Object.keys(record)) {
|
|
23
|
+
if (record[key] === undefined)
|
|
24
|
+
delete record[key];
|
|
25
|
+
}
|
|
26
|
+
return record;
|
|
27
|
+
}
|
|
21
28
|
function invalid(message) {
|
|
22
29
|
throw new DecodeError('sync.invalid_request', message);
|
|
23
30
|
}
|
|
@@ -48,12 +55,12 @@ function decodeReqHeader(r, wireVersion) {
|
|
|
48
55
|
return value;
|
|
49
56
|
})
|
|
50
57
|
: undefined;
|
|
51
|
-
return {
|
|
58
|
+
return definedObject({
|
|
52
59
|
type: 'REQ_HEADER',
|
|
53
60
|
clientId,
|
|
54
61
|
schemaVersion,
|
|
55
|
-
|
|
56
|
-
};
|
|
62
|
+
logEpoch: logEpoch,
|
|
63
|
+
});
|
|
57
64
|
}
|
|
58
65
|
function decodePushCommit(r) {
|
|
59
66
|
const clientCommitId = r.str();
|
|
@@ -84,13 +91,13 @@ function decodePushCommit(r) {
|
|
|
84
91
|
if (op === 'delete' && payload !== undefined) {
|
|
85
92
|
invalid('delete operation must not carry a payload');
|
|
86
93
|
}
|
|
87
|
-
operations.push({
|
|
94
|
+
operations.push(definedObject({
|
|
88
95
|
table,
|
|
89
96
|
rowId,
|
|
90
97
|
op,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
});
|
|
98
|
+
baseVersion: baseVersion,
|
|
99
|
+
payload: payload,
|
|
100
|
+
}));
|
|
94
101
|
}
|
|
95
102
|
return { type: 'PUSH_COMMIT', clientCommitId, operations };
|
|
96
103
|
}
|
|
@@ -117,15 +124,15 @@ function decodeSubscription(r) {
|
|
|
117
124
|
const params = r.opt(() => requireJsonDocument(r.str(), 'SUBSCRIPTION.params'));
|
|
118
125
|
const cursor = r.i64();
|
|
119
126
|
const bootstrapState = r.opt(() => requireJsonDocument(r.str(), 'SUBSCRIPTION.bootstrapState'));
|
|
120
|
-
return {
|
|
127
|
+
return definedObject({
|
|
121
128
|
type: 'SUBSCRIPTION',
|
|
122
129
|
id,
|
|
123
130
|
table,
|
|
124
131
|
scopes,
|
|
125
|
-
|
|
132
|
+
params: params,
|
|
126
133
|
cursor,
|
|
127
|
-
|
|
128
|
-
};
|
|
134
|
+
bootstrapState: bootstrapState,
|
|
135
|
+
});
|
|
129
136
|
}
|
|
130
137
|
function decodeRespHeader(r, wireVersion) {
|
|
131
138
|
const requiredSchemaVersion = r.opt(() => r.i32());
|
|
@@ -135,13 +142,13 @@ function decodeRespHeader(r, wireVersion) {
|
|
|
135
142
|
invalid('RESP_HEADER.logEpoch must be non-empty');
|
|
136
143
|
}
|
|
137
144
|
const resetRequired = wireVersion >= 2 ? r.bool() : undefined;
|
|
138
|
-
return {
|
|
145
|
+
return definedObject({
|
|
139
146
|
type: 'RESP_HEADER',
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
};
|
|
147
|
+
requiredSchemaVersion: requiredSchemaVersion,
|
|
148
|
+
latestSchemaVersion: latestSchemaVersion,
|
|
149
|
+
logEpoch: logEpoch,
|
|
150
|
+
resetRequired: resetRequired,
|
|
151
|
+
});
|
|
145
152
|
}
|
|
146
153
|
function decodeLease(r) {
|
|
147
154
|
const leaseId = r.str();
|
|
@@ -201,13 +208,13 @@ function decodePushResult(r) {
|
|
|
201
208
|
invalid(`invalid push result record status byte ${recordStatus}`);
|
|
202
209
|
}
|
|
203
210
|
}
|
|
204
|
-
return {
|
|
211
|
+
return definedObject({
|
|
205
212
|
type: 'PUSH_RESULT',
|
|
206
213
|
clientCommitId,
|
|
207
214
|
status,
|
|
208
|
-
|
|
215
|
+
commitSeq: commitSeq,
|
|
209
216
|
results,
|
|
210
|
-
};
|
|
217
|
+
});
|
|
211
218
|
}
|
|
212
219
|
function decodePushResultDetails(r) {
|
|
213
220
|
const clientCommitId = r.str();
|
|
@@ -293,14 +300,14 @@ function decodeCommit(r) {
|
|
|
293
300
|
if (op === 'delete' && (rowVersion !== undefined || row !== undefined)) {
|
|
294
301
|
invalid('delete change must not carry rowVersion or row');
|
|
295
302
|
}
|
|
296
|
-
changes.push({
|
|
303
|
+
changes.push(definedObject({
|
|
297
304
|
tableIndex,
|
|
298
305
|
rowId,
|
|
299
306
|
op,
|
|
300
|
-
|
|
307
|
+
rowVersion: rowVersion,
|
|
301
308
|
scopes,
|
|
302
|
-
|
|
303
|
-
});
|
|
309
|
+
row: row,
|
|
310
|
+
}));
|
|
304
311
|
}
|
|
305
312
|
return { type: 'COMMIT', commitSeq, createdAtMs, actorId, tables, changes };
|
|
306
313
|
}
|
|
@@ -326,7 +333,7 @@ function decodeSegmentRef(r) {
|
|
|
326
333
|
if ((url === undefined) !== (urlExpiresAtMs === undefined)) {
|
|
327
334
|
invalid('SEGMENT_REF.urlExpiresAtMs must be present iff url is');
|
|
328
335
|
}
|
|
329
|
-
return {
|
|
336
|
+
return definedObject({
|
|
330
337
|
type: 'SEGMENT_REF',
|
|
331
338
|
segmentId,
|
|
332
339
|
mediaType,
|
|
@@ -335,11 +342,11 @@ function decodeSegmentRef(r) {
|
|
|
335
342
|
rowCount,
|
|
336
343
|
asOfCommitSeq,
|
|
337
344
|
scopeDigest,
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
};
|
|
345
|
+
rowCursor: rowCursor,
|
|
346
|
+
nextRowCursor: nextRowCursor,
|
|
347
|
+
url: url,
|
|
348
|
+
urlExpiresAtMs: urlExpiresAtMs,
|
|
349
|
+
});
|
|
343
350
|
}
|
|
344
351
|
function decodeSegmentInline(r) {
|
|
345
352
|
const payload = r.raw(r.remaining);
|
|
@@ -351,11 +358,11 @@ function decodeSegmentInline(r) {
|
|
|
351
358
|
function decodeSubEnd(r) {
|
|
352
359
|
const nextCursor = r.i64();
|
|
353
360
|
const bootstrapState = r.opt(() => requireJsonDocument(r.str(), 'SUB_END.bootstrapState'));
|
|
354
|
-
return {
|
|
361
|
+
return definedObject({
|
|
355
362
|
type: 'SUB_END',
|
|
356
363
|
nextCursor,
|
|
357
|
-
|
|
358
|
-
};
|
|
364
|
+
bootstrapState: bootstrapState,
|
|
365
|
+
});
|
|
359
366
|
}
|
|
360
367
|
function decodeErrorFrame(r) {
|
|
361
368
|
const code = r.str();
|
|
@@ -364,15 +371,15 @@ function decodeErrorFrame(r) {
|
|
|
364
371
|
const retryable = r.bool();
|
|
365
372
|
const recommendedAction = r.str();
|
|
366
373
|
const details = r.opt(() => requireJsonDocument(r.str(), 'ERROR.details'));
|
|
367
|
-
return {
|
|
374
|
+
return definedObject({
|
|
368
375
|
type: 'ERROR',
|
|
369
376
|
code,
|
|
370
377
|
message,
|
|
371
378
|
category,
|
|
372
379
|
retryable,
|
|
373
380
|
recommendedAction,
|
|
374
|
-
|
|
375
|
-
};
|
|
381
|
+
details: details,
|
|
382
|
+
});
|
|
376
383
|
}
|
|
377
384
|
function decodeRequestFrame(frameType, payload, wireVersion) {
|
|
378
385
|
const r = new ByteReader(payload);
|
|
@@ -595,44 +602,47 @@ const OP_BYTES = { upsert: 1, delete: 2 };
|
|
|
595
602
|
const PUSH_STATUS_BYTES = { applied: 1, cached: 2, rejected: 3 };
|
|
596
603
|
const SUB_STATUS_BYTES = { active: 1, revoked: 2, reset: 3 };
|
|
597
604
|
const MEDIA_TYPE_BYTES = { rows: 1, sqlite: 2 };
|
|
605
|
+
function encodeInvalid(message) {
|
|
606
|
+
throw new Error(message);
|
|
607
|
+
}
|
|
598
608
|
function encodeFrame(frame, wireVersion) {
|
|
599
609
|
const w = new ByteWriter();
|
|
600
610
|
switch (frame.type) {
|
|
601
611
|
case 'REQ_HEADER': {
|
|
602
612
|
if (frame.clientId.length === 0) {
|
|
603
|
-
|
|
613
|
+
encodeInvalid('REQ_HEADER.clientId must be non-empty');
|
|
604
614
|
}
|
|
605
615
|
if (frame.schemaVersion < 1) {
|
|
606
|
-
|
|
616
|
+
encodeInvalid('REQ_HEADER.schemaVersion must be >= 1');
|
|
607
617
|
}
|
|
608
618
|
w.str(frame.clientId);
|
|
609
619
|
w.i32(frame.schemaVersion);
|
|
610
620
|
if (wireVersion >= 2) {
|
|
611
621
|
if (frame.logEpoch === '') {
|
|
612
|
-
|
|
622
|
+
encodeInvalid('REQ_HEADER.logEpoch must be non-empty');
|
|
613
623
|
}
|
|
614
624
|
w.opt(frame.logEpoch, (v) => w.str(v));
|
|
615
625
|
}
|
|
616
626
|
else if (frame.logEpoch !== undefined) {
|
|
617
|
-
|
|
627
|
+
encodeInvalid('REQ_HEADER.logEpoch requires wireVersion 2');
|
|
618
628
|
}
|
|
619
|
-
|
|
629
|
+
break;
|
|
620
630
|
}
|
|
621
631
|
case 'PUSH_COMMIT': {
|
|
622
632
|
if (frame.clientCommitId.length === 0) {
|
|
623
|
-
|
|
633
|
+
encodeInvalid('PUSH_COMMIT.clientCommitId must be non-empty');
|
|
624
634
|
}
|
|
625
635
|
if (frame.operations.length === 0) {
|
|
626
|
-
|
|
636
|
+
encodeInvalid('PUSH_COMMIT must carry at least one operation');
|
|
627
637
|
}
|
|
628
638
|
w.str(frame.clientCommitId);
|
|
629
639
|
w.u32(frame.operations.length);
|
|
630
640
|
for (const operation of frame.operations) {
|
|
631
641
|
if (operation.op === 'upsert' && operation.payload === undefined) {
|
|
632
|
-
|
|
642
|
+
encodeInvalid('upsert operation requires a payload');
|
|
633
643
|
}
|
|
634
644
|
if (operation.op === 'delete' && operation.payload !== undefined) {
|
|
635
|
-
|
|
645
|
+
encodeInvalid('delete operation must not carry a payload');
|
|
636
646
|
}
|
|
637
647
|
w.str(operation.table);
|
|
638
648
|
w.str(operation.rowId);
|
|
@@ -640,17 +650,17 @@ function encodeFrame(frame, wireVersion) {
|
|
|
640
650
|
w.opt(operation.baseVersion, (v) => w.i64(v));
|
|
641
651
|
w.opt(operation.payload, (v) => w.bytes(v));
|
|
642
652
|
}
|
|
643
|
-
|
|
653
|
+
break;
|
|
644
654
|
}
|
|
645
655
|
case 'PULL_HEADER': {
|
|
646
656
|
if ((frame.accept & 0xf0) !== 0) {
|
|
647
|
-
|
|
657
|
+
encodeInvalid('PULL_HEADER.accept bits 4-7 must be zero');
|
|
648
658
|
}
|
|
649
659
|
w.i32(frame.limitCommits);
|
|
650
660
|
w.i32(frame.limitSnapshotRows);
|
|
651
661
|
w.i32(frame.maxSnapshotPages);
|
|
652
662
|
w.u8(frame.accept);
|
|
653
|
-
|
|
663
|
+
break;
|
|
654
664
|
}
|
|
655
665
|
case 'SUBSCRIPTION': {
|
|
656
666
|
w.str(frame.id);
|
|
@@ -659,41 +669,41 @@ function encodeFrame(frame, wireVersion) {
|
|
|
659
669
|
w.opt(frame.params, (v) => w.str(v));
|
|
660
670
|
w.i64(frame.cursor);
|
|
661
671
|
w.opt(frame.bootstrapState, (v) => w.str(v));
|
|
662
|
-
|
|
672
|
+
break;
|
|
663
673
|
}
|
|
664
674
|
case 'RESP_HEADER': {
|
|
665
675
|
w.opt(frame.requiredSchemaVersion, (v) => w.i32(v));
|
|
666
676
|
w.opt(frame.latestSchemaVersion, (v) => w.i32(v));
|
|
667
677
|
if (wireVersion >= 2) {
|
|
668
678
|
if (frame.logEpoch === undefined || frame.logEpoch.length === 0) {
|
|
669
|
-
|
|
679
|
+
encodeInvalid('RESP_HEADER.logEpoch is required and non-empty');
|
|
670
680
|
}
|
|
671
681
|
if (frame.resetRequired === undefined) {
|
|
672
|
-
|
|
682
|
+
encodeInvalid('RESP_HEADER.resetRequired is required');
|
|
673
683
|
}
|
|
674
684
|
w.str(frame.logEpoch);
|
|
675
685
|
w.bool(frame.resetRequired);
|
|
676
686
|
}
|
|
677
687
|
else if (frame.logEpoch !== undefined ||
|
|
678
688
|
frame.resetRequired !== undefined) {
|
|
679
|
-
|
|
689
|
+
encodeInvalid('RESP_HEADER epoch fields require wireVersion 2');
|
|
680
690
|
}
|
|
681
|
-
|
|
691
|
+
break;
|
|
682
692
|
}
|
|
683
693
|
case 'LEASE': {
|
|
684
694
|
if (frame.leaseId.length === 0) {
|
|
685
|
-
|
|
695
|
+
encodeInvalid('LEASE.leaseId must be non-empty');
|
|
686
696
|
}
|
|
687
697
|
w.str(frame.leaseId);
|
|
688
698
|
w.i64(frame.expiresAtMs);
|
|
689
|
-
|
|
699
|
+
break;
|
|
690
700
|
}
|
|
691
701
|
case 'PUSH_RESULT': {
|
|
692
702
|
if (frame.status === 'rejected' && frame.commitSeq !== undefined) {
|
|
693
|
-
|
|
703
|
+
encodeInvalid('rejected PUSH_RESULT must not carry a commitSeq');
|
|
694
704
|
}
|
|
695
705
|
if (frame.status !== 'rejected' && frame.commitSeq === undefined) {
|
|
696
|
-
|
|
706
|
+
encodeInvalid(`${frame.status} PUSH_RESULT requires a commitSeq`);
|
|
697
707
|
}
|
|
698
708
|
w.str(frame.clientCommitId);
|
|
699
709
|
w.u8(PUSH_STATUS_BYTES[frame.status]);
|
|
@@ -719,33 +729,30 @@ function encodeFrame(frame, wireVersion) {
|
|
|
719
729
|
w.bool(result.retryable);
|
|
720
730
|
}
|
|
721
731
|
}
|
|
722
|
-
|
|
732
|
+
break;
|
|
723
733
|
}
|
|
724
734
|
case 'PUSH_RESULT_DETAILS': {
|
|
725
735
|
if (frame.clientCommitId.length === 0) {
|
|
726
|
-
|
|
736
|
+
encodeInvalid('PUSH_RESULT_DETAILS.clientCommitId must be non-empty');
|
|
727
737
|
}
|
|
728
738
|
if (frame.entries.length === 0) {
|
|
729
|
-
|
|
739
|
+
encodeInvalid('PUSH_RESULT_DETAILS must carry at least one entry');
|
|
730
740
|
}
|
|
731
741
|
const seen = new Set();
|
|
732
742
|
w.str(frame.clientCommitId);
|
|
733
743
|
w.u32(frame.entries.length);
|
|
734
744
|
for (const entry of frame.entries) {
|
|
735
745
|
if (!Number.isInteger(entry.opIndex) || entry.opIndex < 0) {
|
|
736
|
-
|
|
746
|
+
encodeInvalid('PUSH_RESULT_DETAILS.opIndex must be non-negative');
|
|
737
747
|
}
|
|
738
748
|
if (seen.has(entry.opIndex)) {
|
|
739
|
-
|
|
749
|
+
encodeInvalid('PUSH_RESULT_DETAILS.opIndex must be unique');
|
|
740
750
|
}
|
|
741
751
|
seen.add(entry.opIndex);
|
|
742
752
|
w.i32(entry.opIndex);
|
|
743
753
|
w.str(JSON.stringify(normalizeRejectionDetails(entry.details)));
|
|
744
754
|
}
|
|
745
|
-
|
|
746
|
-
frameType: FrameType.PUSH_RESULT_DETAILS,
|
|
747
|
-
payload: w.finish(),
|
|
748
|
-
};
|
|
755
|
+
break;
|
|
749
756
|
}
|
|
750
757
|
case 'SUB_START': {
|
|
751
758
|
w.str(frame.id);
|
|
@@ -753,7 +760,7 @@ function encodeFrame(frame, wireVersion) {
|
|
|
753
760
|
w.str(frame.reasonCode);
|
|
754
761
|
writeStringListMap(w, frame.effectiveScopes);
|
|
755
762
|
w.bool(frame.bootstrap);
|
|
756
|
-
|
|
763
|
+
break;
|
|
757
764
|
}
|
|
758
765
|
case 'COMMIT': {
|
|
759
766
|
w.i64(frame.commitSeq);
|
|
@@ -765,15 +772,15 @@ function encodeFrame(frame, wireVersion) {
|
|
|
765
772
|
w.u32(frame.changes.length);
|
|
766
773
|
for (const change of frame.changes) {
|
|
767
774
|
if (change.tableIndex >= frame.tables.length) {
|
|
768
|
-
|
|
775
|
+
encodeInvalid(`change tableIndex ${change.tableIndex} out of range`);
|
|
769
776
|
}
|
|
770
777
|
if (change.op === 'upsert' &&
|
|
771
778
|
(change.rowVersion === undefined || change.row === undefined)) {
|
|
772
|
-
|
|
779
|
+
encodeInvalid('upsert change requires rowVersion and row');
|
|
773
780
|
}
|
|
774
781
|
if (change.op === 'delete' &&
|
|
775
782
|
(change.rowVersion !== undefined || change.row !== undefined)) {
|
|
776
|
-
|
|
783
|
+
encodeInvalid('delete change must not carry rowVersion or row');
|
|
777
784
|
}
|
|
778
785
|
w.u16(change.tableIndex);
|
|
779
786
|
w.str(change.rowId);
|
|
@@ -782,11 +789,11 @@ function encodeFrame(frame, wireVersion) {
|
|
|
782
789
|
writeStringMap(w, change.scopes);
|
|
783
790
|
w.opt(change.row, (v) => w.bytes(v));
|
|
784
791
|
}
|
|
785
|
-
|
|
792
|
+
break;
|
|
786
793
|
}
|
|
787
794
|
case 'SEGMENT_REF': {
|
|
788
795
|
if ((frame.url === undefined) !== (frame.urlExpiresAtMs === undefined)) {
|
|
789
|
-
|
|
796
|
+
encodeInvalid('SEGMENT_REF.urlExpiresAtMs must be present iff url is');
|
|
790
797
|
}
|
|
791
798
|
w.str(frame.segmentId);
|
|
792
799
|
w.u8(MEDIA_TYPE_BYTES[frame.mediaType]);
|
|
@@ -799,7 +806,7 @@ function encodeFrame(frame, wireVersion) {
|
|
|
799
806
|
w.opt(frame.nextRowCursor, (v) => w.str(v));
|
|
800
807
|
w.opt(frame.url, (v) => w.str(v));
|
|
801
808
|
w.opt(frame.urlExpiresAtMs, (v) => w.i64(v));
|
|
802
|
-
|
|
809
|
+
break;
|
|
803
810
|
}
|
|
804
811
|
case 'SEGMENT_INLINE': {
|
|
805
812
|
return { frameType: FrameType.SEGMENT_INLINE, payload: frame.payload };
|
|
@@ -807,7 +814,7 @@ function encodeFrame(frame, wireVersion) {
|
|
|
807
814
|
case 'SUB_END': {
|
|
808
815
|
w.i64(frame.nextCursor);
|
|
809
816
|
w.opt(frame.bootstrapState, (v) => w.str(v));
|
|
810
|
-
|
|
817
|
+
break;
|
|
811
818
|
}
|
|
812
819
|
case 'ERROR': {
|
|
813
820
|
w.str(frame.code);
|
|
@@ -816,25 +823,26 @@ function encodeFrame(frame, wireVersion) {
|
|
|
816
823
|
w.bool(frame.retryable);
|
|
817
824
|
w.str(frame.recommendedAction);
|
|
818
825
|
w.opt(frame.details, (v) => w.str(v));
|
|
819
|
-
|
|
826
|
+
break;
|
|
820
827
|
}
|
|
821
828
|
case 'UNKNOWN': {
|
|
822
829
|
if (!Number.isInteger(frame.frameType) ||
|
|
823
830
|
frame.frameType < 0 ||
|
|
824
831
|
frame.frameType > 0xff ||
|
|
825
832
|
KNOWN_FRAME_TYPES.has(frame.frameType)) {
|
|
826
|
-
|
|
833
|
+
encodeInvalid(`UNKNOWN frame type ${frame.frameType} collides with a defined frame type`);
|
|
827
834
|
}
|
|
828
835
|
return { frameType: frame.frameType, payload: frame.payload };
|
|
829
836
|
}
|
|
830
837
|
}
|
|
838
|
+
return { frameType: FrameType[frame.type], payload: w.finish() };
|
|
831
839
|
}
|
|
832
840
|
// ---------------------------------------------------------------------------
|
|
833
841
|
// Envelope
|
|
834
842
|
// ---------------------------------------------------------------------------
|
|
835
843
|
export function encodeMessage(message) {
|
|
836
844
|
if (!isSupportedProtocolWireVersion(message.wireVersion)) {
|
|
837
|
-
|
|
845
|
+
encodeInvalid(`unsupported wireVersion ${message.wireVersion}`);
|
|
838
846
|
}
|
|
839
847
|
validateFrameSequence(message);
|
|
840
848
|
const w = new ByteWriter();
|
package/package.json
CHANGED
package/src/bytes.ts
CHANGED
|
@@ -245,11 +245,12 @@ export function writeStringListMap(
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
|
|
248
|
+
function readCanonicalMap<T>(
|
|
249
249
|
reader: ByteReader,
|
|
250
|
-
|
|
250
|
+
read: () => T,
|
|
251
|
+
): Record<string, T> {
|
|
251
252
|
const count = reader.u32();
|
|
252
|
-
const entries: Array<[string,
|
|
253
|
+
const entries: Array<[string, T]> = [];
|
|
253
254
|
let previousKey: string | undefined;
|
|
254
255
|
for (let i = 0; i < count; i++) {
|
|
255
256
|
const key = reader.str();
|
|
@@ -260,14 +261,22 @@ export function readStringListMap(
|
|
|
260
261
|
);
|
|
261
262
|
}
|
|
262
263
|
previousKey = key;
|
|
263
|
-
|
|
264
|
-
const values: string[] = [];
|
|
265
|
-
for (let j = 0; j < valueCount; j++) values.push(reader.str());
|
|
266
|
-
entries.push([key, values]);
|
|
264
|
+
entries.push([key, read()]);
|
|
267
265
|
}
|
|
268
266
|
return Object.fromEntries(entries);
|
|
269
267
|
}
|
|
270
268
|
|
|
269
|
+
export function readStringListMap(
|
|
270
|
+
reader: ByteReader,
|
|
271
|
+
): Record<string, string[]> {
|
|
272
|
+
return readCanonicalMap(reader, () => {
|
|
273
|
+
const count = reader.u32();
|
|
274
|
+
const values: string[] = [];
|
|
275
|
+
for (let i = 0; i < count; i++) values.push(reader.str());
|
|
276
|
+
return values;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
271
280
|
/** `map` of `str` → `str` (stored scopes on change records, SPEC.md §4.5). */
|
|
272
281
|
export function writeStringMap(
|
|
273
282
|
writer: ByteWriter,
|
|
@@ -283,19 +292,5 @@ export function writeStringMap(
|
|
|
283
292
|
}
|
|
284
293
|
|
|
285
294
|
export function readStringMap(reader: ByteReader): Record<string, string> {
|
|
286
|
-
|
|
287
|
-
const entries: Array<[string, string]> = [];
|
|
288
|
-
let previousKey: string | undefined;
|
|
289
|
-
for (let i = 0; i < count; i++) {
|
|
290
|
-
const key = reader.str();
|
|
291
|
-
if (previousKey !== undefined && key <= previousKey) {
|
|
292
|
-
throw new DecodeError(
|
|
293
|
-
'sync.invalid_request',
|
|
294
|
-
`map keys must be unique and in ascending code-unit order (saw ${JSON.stringify(key)} after ${JSON.stringify(previousKey)})`,
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
previousKey = key;
|
|
298
|
-
entries.push([key, reader.str()]);
|
|
299
|
-
}
|
|
300
|
-
return Object.fromEntries(entries);
|
|
295
|
+
return readCanonicalMap(reader, () => reader.str());
|
|
301
296
|
}
|
package/src/message.ts
CHANGED
|
@@ -257,6 +257,21 @@ export interface ResponseMessage {
|
|
|
257
257
|
|
|
258
258
|
export type SyncMessage = RequestMessage | ResponseMessage;
|
|
259
259
|
|
|
260
|
+
type DefinedObject<T> = {
|
|
261
|
+
[K in keyof T as undefined extends T[K] ? never : K]: T[K];
|
|
262
|
+
} & {
|
|
263
|
+
[K in keyof T as undefined extends T[K] ? K : never]?: Exclude<
|
|
264
|
+
T[K],
|
|
265
|
+
undefined
|
|
266
|
+
>;
|
|
267
|
+
};
|
|
268
|
+
function definedObject<const T extends object>(record: T): DefinedObject<T> {
|
|
269
|
+
for (const key of Object.keys(record) as (keyof T)[]) {
|
|
270
|
+
if (record[key] === undefined) delete record[key];
|
|
271
|
+
}
|
|
272
|
+
return record as DefinedObject<T>;
|
|
273
|
+
}
|
|
274
|
+
|
|
260
275
|
function invalid(message: string): never {
|
|
261
276
|
throw new DecodeError('sync.invalid_request', message);
|
|
262
277
|
}
|
|
@@ -288,12 +303,12 @@ function decodeReqHeader(r: ByteReader, wireVersion: number): ReqHeaderFrame {
|
|
|
288
303
|
return value;
|
|
289
304
|
})
|
|
290
305
|
: undefined;
|
|
291
|
-
return {
|
|
306
|
+
return definedObject({
|
|
292
307
|
type: 'REQ_HEADER',
|
|
293
308
|
clientId,
|
|
294
309
|
schemaVersion,
|
|
295
|
-
|
|
296
|
-
};
|
|
310
|
+
logEpoch: logEpoch,
|
|
311
|
+
});
|
|
297
312
|
}
|
|
298
313
|
|
|
299
314
|
function decodePushCommit(r: ByteReader): PushCommitFrame {
|
|
@@ -325,13 +340,15 @@ function decodePushCommit(r: ByteReader): PushCommitFrame {
|
|
|
325
340
|
if (op === 'delete' && payload !== undefined) {
|
|
326
341
|
invalid('delete operation must not carry a payload');
|
|
327
342
|
}
|
|
328
|
-
operations.push(
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
343
|
+
operations.push(
|
|
344
|
+
definedObject({
|
|
345
|
+
table,
|
|
346
|
+
rowId,
|
|
347
|
+
op,
|
|
348
|
+
baseVersion: baseVersion,
|
|
349
|
+
payload: payload,
|
|
350
|
+
}),
|
|
351
|
+
);
|
|
335
352
|
}
|
|
336
353
|
return { type: 'PUSH_COMMIT', clientCommitId, operations };
|
|
337
354
|
}
|
|
@@ -364,15 +381,15 @@ function decodeSubscription(r: ByteReader): SubscriptionFrame {
|
|
|
364
381
|
const bootstrapState = r.opt(() =>
|
|
365
382
|
requireJsonDocument(r.str(), 'SUBSCRIPTION.bootstrapState'),
|
|
366
383
|
);
|
|
367
|
-
return {
|
|
384
|
+
return definedObject({
|
|
368
385
|
type: 'SUBSCRIPTION',
|
|
369
386
|
id,
|
|
370
387
|
table,
|
|
371
388
|
scopes,
|
|
372
|
-
|
|
389
|
+
params: params,
|
|
373
390
|
cursor,
|
|
374
|
-
|
|
375
|
-
};
|
|
391
|
+
bootstrapState: bootstrapState,
|
|
392
|
+
});
|
|
376
393
|
}
|
|
377
394
|
|
|
378
395
|
function decodeRespHeader(r: ByteReader, wireVersion: number): RespHeaderFrame {
|
|
@@ -383,13 +400,13 @@ function decodeRespHeader(r: ByteReader, wireVersion: number): RespHeaderFrame {
|
|
|
383
400
|
invalid('RESP_HEADER.logEpoch must be non-empty');
|
|
384
401
|
}
|
|
385
402
|
const resetRequired = wireVersion >= 2 ? r.bool() : undefined;
|
|
386
|
-
return {
|
|
403
|
+
return definedObject({
|
|
387
404
|
type: 'RESP_HEADER',
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
};
|
|
405
|
+
requiredSchemaVersion: requiredSchemaVersion,
|
|
406
|
+
latestSchemaVersion: latestSchemaVersion,
|
|
407
|
+
logEpoch: logEpoch,
|
|
408
|
+
resetRequired: resetRequired,
|
|
409
|
+
});
|
|
393
410
|
}
|
|
394
411
|
|
|
395
412
|
function decodeLease(r: ByteReader): LeaseFrame {
|
|
@@ -443,13 +460,13 @@ function decodePushResult(r: ByteReader): PushResultFrame {
|
|
|
443
460
|
invalid(`invalid push result record status byte ${recordStatus}`);
|
|
444
461
|
}
|
|
445
462
|
}
|
|
446
|
-
return {
|
|
463
|
+
return definedObject({
|
|
447
464
|
type: 'PUSH_RESULT',
|
|
448
465
|
clientCommitId,
|
|
449
466
|
status,
|
|
450
|
-
|
|
467
|
+
commitSeq: commitSeq,
|
|
451
468
|
results,
|
|
452
|
-
};
|
|
469
|
+
});
|
|
453
470
|
}
|
|
454
471
|
|
|
455
472
|
function decodePushResultDetails(r: ByteReader): PushResultDetailsFrame {
|
|
@@ -534,14 +551,16 @@ function decodeCommit(r: ByteReader): CommitFrame {
|
|
|
534
551
|
if (op === 'delete' && (rowVersion !== undefined || row !== undefined)) {
|
|
535
552
|
invalid('delete change must not carry rowVersion or row');
|
|
536
553
|
}
|
|
537
|
-
changes.push(
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
554
|
+
changes.push(
|
|
555
|
+
definedObject({
|
|
556
|
+
tableIndex,
|
|
557
|
+
rowId,
|
|
558
|
+
op,
|
|
559
|
+
rowVersion: rowVersion,
|
|
560
|
+
scopes,
|
|
561
|
+
row: row,
|
|
562
|
+
}),
|
|
563
|
+
);
|
|
545
564
|
}
|
|
546
565
|
return { type: 'COMMIT', commitSeq, createdAtMs, actorId, tables, changes };
|
|
547
566
|
}
|
|
@@ -565,7 +584,7 @@ function decodeSegmentRef(r: ByteReader): SegmentRefFrame {
|
|
|
565
584
|
if ((url === undefined) !== (urlExpiresAtMs === undefined)) {
|
|
566
585
|
invalid('SEGMENT_REF.urlExpiresAtMs must be present iff url is');
|
|
567
586
|
}
|
|
568
|
-
return {
|
|
587
|
+
return definedObject({
|
|
569
588
|
type: 'SEGMENT_REF',
|
|
570
589
|
segmentId,
|
|
571
590
|
mediaType,
|
|
@@ -574,11 +593,11 @@ function decodeSegmentRef(r: ByteReader): SegmentRefFrame {
|
|
|
574
593
|
rowCount,
|
|
575
594
|
asOfCommitSeq,
|
|
576
595
|
scopeDigest,
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
};
|
|
596
|
+
rowCursor: rowCursor,
|
|
597
|
+
nextRowCursor: nextRowCursor,
|
|
598
|
+
url: url,
|
|
599
|
+
urlExpiresAtMs: urlExpiresAtMs,
|
|
600
|
+
});
|
|
582
601
|
}
|
|
583
602
|
|
|
584
603
|
function decodeSegmentInline(r: ByteReader): SegmentInlineFrame {
|
|
@@ -594,11 +613,11 @@ function decodeSubEnd(r: ByteReader): SubEndFrame {
|
|
|
594
613
|
const bootstrapState = r.opt(() =>
|
|
595
614
|
requireJsonDocument(r.str(), 'SUB_END.bootstrapState'),
|
|
596
615
|
);
|
|
597
|
-
return {
|
|
616
|
+
return definedObject({
|
|
598
617
|
type: 'SUB_END',
|
|
599
618
|
nextCursor,
|
|
600
|
-
|
|
601
|
-
};
|
|
619
|
+
bootstrapState: bootstrapState,
|
|
620
|
+
});
|
|
602
621
|
}
|
|
603
622
|
|
|
604
623
|
function decodeErrorFrame(r: ByteReader): ErrorFrame {
|
|
@@ -608,15 +627,15 @@ function decodeErrorFrame(r: ByteReader): ErrorFrame {
|
|
|
608
627
|
const retryable = r.bool();
|
|
609
628
|
const recommendedAction = r.str();
|
|
610
629
|
const details = r.opt(() => requireJsonDocument(r.str(), 'ERROR.details'));
|
|
611
|
-
return {
|
|
630
|
+
return definedObject({
|
|
612
631
|
type: 'ERROR',
|
|
613
632
|
code,
|
|
614
633
|
message,
|
|
615
634
|
category,
|
|
616
635
|
retryable,
|
|
617
636
|
recommendedAction,
|
|
618
|
-
|
|
619
|
-
};
|
|
637
|
+
details: details,
|
|
638
|
+
});
|
|
620
639
|
}
|
|
621
640
|
|
|
622
641
|
function decodeRequestFrame(
|
|
@@ -859,6 +878,10 @@ const PUSH_STATUS_BYTES = { applied: 1, cached: 2, rejected: 3 } as const;
|
|
|
859
878
|
const SUB_STATUS_BYTES = { active: 1, revoked: 2, reset: 3 } as const;
|
|
860
879
|
const MEDIA_TYPE_BYTES = { rows: 1, sqlite: 2 } as const;
|
|
861
880
|
|
|
881
|
+
function encodeInvalid(message: string): never {
|
|
882
|
+
throw new Error(message);
|
|
883
|
+
}
|
|
884
|
+
|
|
862
885
|
function encodeFrame(
|
|
863
886
|
frame: RequestFrame | ResponseFrame,
|
|
864
887
|
wireVersion: number,
|
|
@@ -870,38 +893,38 @@ function encodeFrame(
|
|
|
870
893
|
switch (frame.type) {
|
|
871
894
|
case 'REQ_HEADER': {
|
|
872
895
|
if (frame.clientId.length === 0) {
|
|
873
|
-
|
|
896
|
+
encodeInvalid('REQ_HEADER.clientId must be non-empty');
|
|
874
897
|
}
|
|
875
898
|
if (frame.schemaVersion < 1) {
|
|
876
|
-
|
|
899
|
+
encodeInvalid('REQ_HEADER.schemaVersion must be >= 1');
|
|
877
900
|
}
|
|
878
901
|
w.str(frame.clientId);
|
|
879
902
|
w.i32(frame.schemaVersion);
|
|
880
903
|
if (wireVersion >= 2) {
|
|
881
904
|
if (frame.logEpoch === '') {
|
|
882
|
-
|
|
905
|
+
encodeInvalid('REQ_HEADER.logEpoch must be non-empty');
|
|
883
906
|
}
|
|
884
907
|
w.opt(frame.logEpoch, (v) => w.str(v));
|
|
885
908
|
} else if (frame.logEpoch !== undefined) {
|
|
886
|
-
|
|
909
|
+
encodeInvalid('REQ_HEADER.logEpoch requires wireVersion 2');
|
|
887
910
|
}
|
|
888
|
-
|
|
911
|
+
break;
|
|
889
912
|
}
|
|
890
913
|
case 'PUSH_COMMIT': {
|
|
891
914
|
if (frame.clientCommitId.length === 0) {
|
|
892
|
-
|
|
915
|
+
encodeInvalid('PUSH_COMMIT.clientCommitId must be non-empty');
|
|
893
916
|
}
|
|
894
917
|
if (frame.operations.length === 0) {
|
|
895
|
-
|
|
918
|
+
encodeInvalid('PUSH_COMMIT must carry at least one operation');
|
|
896
919
|
}
|
|
897
920
|
w.str(frame.clientCommitId);
|
|
898
921
|
w.u32(frame.operations.length);
|
|
899
922
|
for (const operation of frame.operations) {
|
|
900
923
|
if (operation.op === 'upsert' && operation.payload === undefined) {
|
|
901
|
-
|
|
924
|
+
encodeInvalid('upsert operation requires a payload');
|
|
902
925
|
}
|
|
903
926
|
if (operation.op === 'delete' && operation.payload !== undefined) {
|
|
904
|
-
|
|
927
|
+
encodeInvalid('delete operation must not carry a payload');
|
|
905
928
|
}
|
|
906
929
|
w.str(operation.table);
|
|
907
930
|
w.str(operation.rowId);
|
|
@@ -909,17 +932,17 @@ function encodeFrame(
|
|
|
909
932
|
w.opt(operation.baseVersion, (v) => w.i64(v));
|
|
910
933
|
w.opt(operation.payload, (v) => w.bytes(v));
|
|
911
934
|
}
|
|
912
|
-
|
|
935
|
+
break;
|
|
913
936
|
}
|
|
914
937
|
case 'PULL_HEADER': {
|
|
915
938
|
if ((frame.accept & 0xf0) !== 0) {
|
|
916
|
-
|
|
939
|
+
encodeInvalid('PULL_HEADER.accept bits 4-7 must be zero');
|
|
917
940
|
}
|
|
918
941
|
w.i32(frame.limitCommits);
|
|
919
942
|
w.i32(frame.limitSnapshotRows);
|
|
920
943
|
w.i32(frame.maxSnapshotPages);
|
|
921
944
|
w.u8(frame.accept);
|
|
922
|
-
|
|
945
|
+
break;
|
|
923
946
|
}
|
|
924
947
|
case 'SUBSCRIPTION': {
|
|
925
948
|
w.str(frame.id);
|
|
@@ -928,17 +951,17 @@ function encodeFrame(
|
|
|
928
951
|
w.opt(frame.params, (v) => w.str(v));
|
|
929
952
|
w.i64(frame.cursor);
|
|
930
953
|
w.opt(frame.bootstrapState, (v) => w.str(v));
|
|
931
|
-
|
|
954
|
+
break;
|
|
932
955
|
}
|
|
933
956
|
case 'RESP_HEADER': {
|
|
934
957
|
w.opt(frame.requiredSchemaVersion, (v) => w.i32(v));
|
|
935
958
|
w.opt(frame.latestSchemaVersion, (v) => w.i32(v));
|
|
936
959
|
if (wireVersion >= 2) {
|
|
937
960
|
if (frame.logEpoch === undefined || frame.logEpoch.length === 0) {
|
|
938
|
-
|
|
961
|
+
encodeInvalid('RESP_HEADER.logEpoch is required and non-empty');
|
|
939
962
|
}
|
|
940
963
|
if (frame.resetRequired === undefined) {
|
|
941
|
-
|
|
964
|
+
encodeInvalid('RESP_HEADER.resetRequired is required');
|
|
942
965
|
}
|
|
943
966
|
w.str(frame.logEpoch);
|
|
944
967
|
w.bool(frame.resetRequired);
|
|
@@ -946,24 +969,24 @@ function encodeFrame(
|
|
|
946
969
|
frame.logEpoch !== undefined ||
|
|
947
970
|
frame.resetRequired !== undefined
|
|
948
971
|
) {
|
|
949
|
-
|
|
972
|
+
encodeInvalid('RESP_HEADER epoch fields require wireVersion 2');
|
|
950
973
|
}
|
|
951
|
-
|
|
974
|
+
break;
|
|
952
975
|
}
|
|
953
976
|
case 'LEASE': {
|
|
954
977
|
if (frame.leaseId.length === 0) {
|
|
955
|
-
|
|
978
|
+
encodeInvalid('LEASE.leaseId must be non-empty');
|
|
956
979
|
}
|
|
957
980
|
w.str(frame.leaseId);
|
|
958
981
|
w.i64(frame.expiresAtMs);
|
|
959
|
-
|
|
982
|
+
break;
|
|
960
983
|
}
|
|
961
984
|
case 'PUSH_RESULT': {
|
|
962
985
|
if (frame.status === 'rejected' && frame.commitSeq !== undefined) {
|
|
963
|
-
|
|
986
|
+
encodeInvalid('rejected PUSH_RESULT must not carry a commitSeq');
|
|
964
987
|
}
|
|
965
988
|
if (frame.status !== 'rejected' && frame.commitSeq === undefined) {
|
|
966
|
-
|
|
989
|
+
encodeInvalid(`${frame.status} PUSH_RESULT requires a commitSeq`);
|
|
967
990
|
}
|
|
968
991
|
w.str(frame.clientCommitId);
|
|
969
992
|
w.u8(PUSH_STATUS_BYTES[frame.status]);
|
|
@@ -987,33 +1010,30 @@ function encodeFrame(
|
|
|
987
1010
|
w.bool(result.retryable);
|
|
988
1011
|
}
|
|
989
1012
|
}
|
|
990
|
-
|
|
1013
|
+
break;
|
|
991
1014
|
}
|
|
992
1015
|
case 'PUSH_RESULT_DETAILS': {
|
|
993
1016
|
if (frame.clientCommitId.length === 0) {
|
|
994
|
-
|
|
1017
|
+
encodeInvalid('PUSH_RESULT_DETAILS.clientCommitId must be non-empty');
|
|
995
1018
|
}
|
|
996
1019
|
if (frame.entries.length === 0) {
|
|
997
|
-
|
|
1020
|
+
encodeInvalid('PUSH_RESULT_DETAILS must carry at least one entry');
|
|
998
1021
|
}
|
|
999
1022
|
const seen = new Set<number>();
|
|
1000
1023
|
w.str(frame.clientCommitId);
|
|
1001
1024
|
w.u32(frame.entries.length);
|
|
1002
1025
|
for (const entry of frame.entries) {
|
|
1003
1026
|
if (!Number.isInteger(entry.opIndex) || entry.opIndex < 0) {
|
|
1004
|
-
|
|
1027
|
+
encodeInvalid('PUSH_RESULT_DETAILS.opIndex must be non-negative');
|
|
1005
1028
|
}
|
|
1006
1029
|
if (seen.has(entry.opIndex)) {
|
|
1007
|
-
|
|
1030
|
+
encodeInvalid('PUSH_RESULT_DETAILS.opIndex must be unique');
|
|
1008
1031
|
}
|
|
1009
1032
|
seen.add(entry.opIndex);
|
|
1010
1033
|
w.i32(entry.opIndex);
|
|
1011
1034
|
w.str(JSON.stringify(normalizeRejectionDetails(entry.details)));
|
|
1012
1035
|
}
|
|
1013
|
-
|
|
1014
|
-
frameType: FrameType.PUSH_RESULT_DETAILS,
|
|
1015
|
-
payload: w.finish(),
|
|
1016
|
-
};
|
|
1036
|
+
break;
|
|
1017
1037
|
}
|
|
1018
1038
|
case 'SUB_START': {
|
|
1019
1039
|
w.str(frame.id);
|
|
@@ -1021,7 +1041,7 @@ function encodeFrame(
|
|
|
1021
1041
|
w.str(frame.reasonCode);
|
|
1022
1042
|
writeStringListMap(w, frame.effectiveScopes);
|
|
1023
1043
|
w.bool(frame.bootstrap);
|
|
1024
|
-
|
|
1044
|
+
break;
|
|
1025
1045
|
}
|
|
1026
1046
|
case 'COMMIT': {
|
|
1027
1047
|
w.i64(frame.commitSeq);
|
|
@@ -1032,21 +1052,19 @@ function encodeFrame(
|
|
|
1032
1052
|
w.u32(frame.changes.length);
|
|
1033
1053
|
for (const change of frame.changes) {
|
|
1034
1054
|
if (change.tableIndex >= frame.tables.length) {
|
|
1035
|
-
|
|
1036
|
-
`change tableIndex ${change.tableIndex} out of range`,
|
|
1037
|
-
);
|
|
1055
|
+
encodeInvalid(`change tableIndex ${change.tableIndex} out of range`);
|
|
1038
1056
|
}
|
|
1039
1057
|
if (
|
|
1040
1058
|
change.op === 'upsert' &&
|
|
1041
1059
|
(change.rowVersion === undefined || change.row === undefined)
|
|
1042
1060
|
) {
|
|
1043
|
-
|
|
1061
|
+
encodeInvalid('upsert change requires rowVersion and row');
|
|
1044
1062
|
}
|
|
1045
1063
|
if (
|
|
1046
1064
|
change.op === 'delete' &&
|
|
1047
1065
|
(change.rowVersion !== undefined || change.row !== undefined)
|
|
1048
1066
|
) {
|
|
1049
|
-
|
|
1067
|
+
encodeInvalid('delete change must not carry rowVersion or row');
|
|
1050
1068
|
}
|
|
1051
1069
|
w.u16(change.tableIndex);
|
|
1052
1070
|
w.str(change.rowId);
|
|
@@ -1055,13 +1073,11 @@ function encodeFrame(
|
|
|
1055
1073
|
writeStringMap(w, change.scopes);
|
|
1056
1074
|
w.opt(change.row, (v) => w.bytes(v));
|
|
1057
1075
|
}
|
|
1058
|
-
|
|
1076
|
+
break;
|
|
1059
1077
|
}
|
|
1060
1078
|
case 'SEGMENT_REF': {
|
|
1061
1079
|
if ((frame.url === undefined) !== (frame.urlExpiresAtMs === undefined)) {
|
|
1062
|
-
|
|
1063
|
-
'SEGMENT_REF.urlExpiresAtMs must be present iff url is',
|
|
1064
|
-
);
|
|
1080
|
+
encodeInvalid('SEGMENT_REF.urlExpiresAtMs must be present iff url is');
|
|
1065
1081
|
}
|
|
1066
1082
|
w.str(frame.segmentId);
|
|
1067
1083
|
w.u8(MEDIA_TYPE_BYTES[frame.mediaType]);
|
|
@@ -1074,7 +1090,7 @@ function encodeFrame(
|
|
|
1074
1090
|
w.opt(frame.nextRowCursor, (v) => w.str(v));
|
|
1075
1091
|
w.opt(frame.url, (v) => w.str(v));
|
|
1076
1092
|
w.opt(frame.urlExpiresAtMs, (v) => w.i64(v));
|
|
1077
|
-
|
|
1093
|
+
break;
|
|
1078
1094
|
}
|
|
1079
1095
|
case 'SEGMENT_INLINE': {
|
|
1080
1096
|
return { frameType: FrameType.SEGMENT_INLINE, payload: frame.payload };
|
|
@@ -1082,7 +1098,7 @@ function encodeFrame(
|
|
|
1082
1098
|
case 'SUB_END': {
|
|
1083
1099
|
w.i64(frame.nextCursor);
|
|
1084
1100
|
w.opt(frame.bootstrapState, (v) => w.str(v));
|
|
1085
|
-
|
|
1101
|
+
break;
|
|
1086
1102
|
}
|
|
1087
1103
|
case 'ERROR': {
|
|
1088
1104
|
w.str(frame.code);
|
|
@@ -1091,7 +1107,7 @@ function encodeFrame(
|
|
|
1091
1107
|
w.bool(frame.retryable);
|
|
1092
1108
|
w.str(frame.recommendedAction);
|
|
1093
1109
|
w.opt(frame.details, (v) => w.str(v));
|
|
1094
|
-
|
|
1110
|
+
break;
|
|
1095
1111
|
}
|
|
1096
1112
|
case 'UNKNOWN': {
|
|
1097
1113
|
if (
|
|
@@ -1100,13 +1116,14 @@ function encodeFrame(
|
|
|
1100
1116
|
frame.frameType > 0xff ||
|
|
1101
1117
|
KNOWN_FRAME_TYPES.has(frame.frameType)
|
|
1102
1118
|
) {
|
|
1103
|
-
|
|
1119
|
+
encodeInvalid(
|
|
1104
1120
|
`UNKNOWN frame type ${frame.frameType} collides with a defined frame type`,
|
|
1105
1121
|
);
|
|
1106
1122
|
}
|
|
1107
1123
|
return { frameType: frame.frameType, payload: frame.payload };
|
|
1108
1124
|
}
|
|
1109
1125
|
}
|
|
1126
|
+
return { frameType: FrameType[frame.type], payload: w.finish() };
|
|
1110
1127
|
}
|
|
1111
1128
|
|
|
1112
1129
|
// ---------------------------------------------------------------------------
|
|
@@ -1115,7 +1132,7 @@ function encodeFrame(
|
|
|
1115
1132
|
|
|
1116
1133
|
export function encodeMessage(message: SyncMessage): Uint8Array {
|
|
1117
1134
|
if (!isSupportedProtocolWireVersion(message.wireVersion)) {
|
|
1118
|
-
|
|
1135
|
+
encodeInvalid(`unsupported wireVersion ${message.wireVersion}`);
|
|
1119
1136
|
}
|
|
1120
1137
|
validateFrameSequence(message);
|
|
1121
1138
|
const w = new ByteWriter();
|