@openlfcp/shared-objects 0.1.2 → 0.1.4

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.
@@ -0,0 +1,613 @@
1
+ // The canonical change encoding (SHARED-OBJECTS-PROFILE-01 §11.3,
2
+ // SPEC-PATCH-10, ADR 0010), checked by the properties of the change format
3
+ // version 1, before the Automerge engine decodes the change. An engine does
4
+ // not keep the bytes of a change it applies: it writes the change again
5
+ // when it saves or hands it out, so a change with any other encoding of the
6
+ // same operations gets a different hash there, and the document's save
7
+ // then fails to load. The walk also gives the change's operations, read
8
+ // without the engine, for the operation references of §11.4.
9
+ //
10
+ // Each run-length column is decoded strictly (shortest LEB128, 64-bit
11
+ // range) and encoded again by the one rule of §11.3 rule 5; the two must be
12
+ // the same bytes. Everything else is checked as stated: the header, the
13
+ // columns and their presence, row counts, value metadata and bytes,
14
+ // actions, IDs and counters below 2^32, sorted predecessors.
15
+ import { ProfileInvalidError } from "./profile-invalid.js";
16
+ const refuse = (why) => {
17
+ throw new ProfileInvalidError("INVALID_AUTOMERGE_BYTES", `the change is not in its canonical encoding (§11.3): ${why}`);
18
+ };
19
+ const U64 = 1n << 64n;
20
+ const I64_MIN = -(1n << 63n);
21
+ const I64_MAX = (1n << 63n) - 1n;
22
+ const U32 = 2 ** 32;
23
+ const UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
24
+ function utf8(bytes, what) {
25
+ try {
26
+ return UTF8.decode(bytes);
27
+ }
28
+ catch {
29
+ return refuse(`${what} is not valid UTF-8`);
30
+ }
31
+ }
32
+ const hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
33
+ /** Byte-wise lexicographic order, a shorter prefix first. */
34
+ function compareBytes(a, b) {
35
+ const n = Math.min(a.length, b.length);
36
+ for (let i = 0; i < n; i++)
37
+ if (a[i] !== b[i])
38
+ return a[i] - b[i];
39
+ return a.length - b.length;
40
+ }
41
+ /** A strict reader: every LEB128 number in its shortest form and in range (rule 1). */
42
+ class Reader {
43
+ bytes;
44
+ pos = 0;
45
+ constructor(bytes) {
46
+ this.bytes = bytes;
47
+ }
48
+ get done() {
49
+ return this.pos >= this.bytes.length;
50
+ }
51
+ take(n) {
52
+ if (!(n <= this.bytes.length - this.pos))
53
+ refuse("a length runs past the end");
54
+ const out = this.bytes.subarray(this.pos, this.pos + n);
55
+ this.pos += n;
56
+ return out;
57
+ }
58
+ ubig() {
59
+ let value = 0n;
60
+ let shift = 0n;
61
+ const start = this.pos;
62
+ for (;;) {
63
+ if (this.pos >= this.bytes.length)
64
+ refuse("a LEB128 number runs past the end");
65
+ if (this.pos - start >= 10)
66
+ refuse("an unsigned number is 2^64 or more");
67
+ const b = this.bytes[this.pos++];
68
+ value |= BigInt(b & 0x7f) << shift;
69
+ shift += 7n;
70
+ if ((b & 0x80) === 0) {
71
+ if (b === 0 && this.pos - start > 1)
72
+ refuse("a LEB128 number is not in its shortest form");
73
+ if (value >= U64)
74
+ refuse("an unsigned number is 2^64 or more");
75
+ return value;
76
+ }
77
+ }
78
+ }
79
+ sbig() {
80
+ let value = 0n;
81
+ let shift = 0n;
82
+ const start = this.pos;
83
+ for (;;) {
84
+ if (this.pos >= this.bytes.length)
85
+ refuse("a LEB128 number runs past the end");
86
+ if (this.pos - start >= 10)
87
+ refuse("a signed number is outside the 64-bit range");
88
+ const b = this.bytes[this.pos++];
89
+ value |= BigInt(b & 0x7f) << shift;
90
+ shift += 7n;
91
+ if ((b & 0x80) === 0) {
92
+ if (b & 0x40)
93
+ value -= 1n << shift;
94
+ if (this.pos - start > 1) {
95
+ const prev = this.bytes[this.pos - 2];
96
+ if ((b === 0x00 && (prev & 0x40) === 0) || (b === 0x7f && (prev & 0x40) !== 0))
97
+ refuse("a LEB128 number is not in its shortest form");
98
+ }
99
+ if (value < I64_MIN || value > I64_MAX)
100
+ refuse("a signed number is outside the 64-bit range");
101
+ return value;
102
+ }
103
+ }
104
+ }
105
+ /** An unsigned count or length: a safe integer, else over every bound. */
106
+ unum() {
107
+ const v = this.ubig();
108
+ return v > BigInt(Number.MAX_SAFE_INTEGER) ? Number.POSITIVE_INFINITY : Number(v);
109
+ }
110
+ }
111
+ function uleb(v, out) {
112
+ let x = v;
113
+ do {
114
+ let b = Number(x & 0x7fn);
115
+ x >>= 7n;
116
+ if (x !== 0n)
117
+ b |= 0x80;
118
+ out.push(b);
119
+ } while (x !== 0n);
120
+ }
121
+ function sleb(v, out) {
122
+ let x = v;
123
+ for (;;) {
124
+ const b = Number(x & 0x7fn);
125
+ x >>= 7n;
126
+ const done = (x === 0n && (b & 0x40) === 0) || (x === -1n && (b & 0x40) !== 0);
127
+ out.push(done ? b : b | 0x80);
128
+ if (done)
129
+ return;
130
+ }
131
+ }
132
+ const TYPE_DELTA = 3;
133
+ const TYPE_BOOLEAN = 4;
134
+ const TYPE_STRING = 5;
135
+ const same = (a, b) => {
136
+ if (a === null || b === null)
137
+ return a === b;
138
+ if (typeof a === "bigint" || typeof b === "bigint")
139
+ return a === b;
140
+ return a.length === b.length && compareBytes(a, b) === 0;
141
+ };
142
+ /**
143
+ * No column of a change within the §11.1 limits has more rows (a group's
144
+ * entries are bounded by the group sum); the walk refuses more on its own,
145
+ * whether or not the limits ran first.
146
+ */
147
+ const MAX_ROWS = 262_144;
148
+ const room = (have, add) => {
149
+ if (add > BigInt(MAX_ROWS - have))
150
+ refuse("a column has more rows than a change may");
151
+ };
152
+ /** Decodes a run-length column (types 0, 1, 2, 3, 5, 6) strictly; a delta column's rows are absolute. */
153
+ function decodeRle(type, data) {
154
+ const r = new Reader(data);
155
+ const rows = [];
156
+ let acc = 0n;
157
+ const one = () => {
158
+ if (type === TYPE_STRING)
159
+ return r.take(r.unum());
160
+ if (type === TYPE_DELTA)
161
+ return r.sbig();
162
+ return r.ubig();
163
+ };
164
+ const push = (v) => {
165
+ if (type === TYPE_DELTA) {
166
+ acc += v;
167
+ rows.push(acc);
168
+ }
169
+ else
170
+ rows.push(v);
171
+ };
172
+ while (!r.done) {
173
+ const n = r.sbig();
174
+ room(rows.length, n < 0n ? -n : n);
175
+ if (n > 0n) {
176
+ const v = one();
177
+ for (let i = 0n; i < n; i++)
178
+ push(v);
179
+ }
180
+ else if (n < 0n)
181
+ for (let i = 0n; i < -n; i++)
182
+ push(one());
183
+ else {
184
+ const nulls = r.ubig();
185
+ room(rows.length, nulls);
186
+ for (let i = 0n; i < nulls; i++)
187
+ rows.push(null);
188
+ }
189
+ }
190
+ return rows;
191
+ }
192
+ /** The one encoding of §11.3 rule 5 of `rows` (a delta column's rows absolute). */
193
+ function encodeRle(type, rows) {
194
+ let values = rows;
195
+ if (type === TYPE_DELTA) {
196
+ let prev = 0n;
197
+ values = rows.map((v) => {
198
+ if (v === null)
199
+ return null;
200
+ const d = v - prev;
201
+ prev = v;
202
+ return d;
203
+ });
204
+ }
205
+ const out = [];
206
+ const value = (v) => {
207
+ if (type === TYPE_STRING) {
208
+ const b = v;
209
+ uleb(BigInt(b.length), out);
210
+ for (const x of b)
211
+ out.push(x);
212
+ }
213
+ else if (type === TYPE_DELTA)
214
+ sleb(v, out);
215
+ else
216
+ uleb(v, out);
217
+ };
218
+ let i = 0;
219
+ while (i < values.length) {
220
+ const v = values[i];
221
+ if (v === null) {
222
+ let j = i;
223
+ while (j < values.length && values[j] === null)
224
+ j++;
225
+ sleb(0n, out);
226
+ uleb(BigInt(j - i), out);
227
+ i = j;
228
+ continue;
229
+ }
230
+ let j = i + 1;
231
+ while (j < values.length && same(values[j], v))
232
+ j++;
233
+ if (j - i >= 2) {
234
+ sleb(BigInt(j - i), out);
235
+ value(v);
236
+ i = j;
237
+ continue;
238
+ }
239
+ // A literal run: up to a null or the start of a repetition.
240
+ let k = i;
241
+ while (k < values.length &&
242
+ values[k] !== null &&
243
+ !(k + 1 < values.length && same(values[k + 1], values[k])))
244
+ k++;
245
+ sleb(BigInt(-(k - i)), out);
246
+ for (let m = i; m < k; m++)
247
+ value(values[m]);
248
+ i = k;
249
+ }
250
+ return Uint8Array.from(out);
251
+ }
252
+ function decodeBoolean(data) {
253
+ const r = new Reader(data);
254
+ const rows = [];
255
+ let bit = false;
256
+ while (!r.done) {
257
+ const n = r.unum();
258
+ room(rows.length, BigInt(Number.isFinite(n) ? n : Number.MAX_SAFE_INTEGER));
259
+ for (let i = 0; i < n; i++)
260
+ rows.push(bit);
261
+ bit = !bit;
262
+ }
263
+ return rows;
264
+ }
265
+ function encodeBoolean(rows) {
266
+ const out = [];
267
+ let bit = false;
268
+ let i = 0;
269
+ while (i < rows.length) {
270
+ let j = i;
271
+ while (j < rows.length && rows[j] === bit)
272
+ j++;
273
+ uleb(BigInt(j - i), out);
274
+ bit = !bit;
275
+ i = j;
276
+ }
277
+ return Uint8Array.from(out);
278
+ }
279
+ /** The columns of the change format (§11.3 rule 3), by specification. */
280
+ const OBJ_ACTOR = 0x01;
281
+ const OBJ_CTR = 0x02;
282
+ const KEY_ACTOR = 0x11;
283
+ const KEY_CTR = 0x13;
284
+ const KEY_STR = 0x15;
285
+ const INSERT = 0x34;
286
+ const ACTION = 0x42;
287
+ const VAL_META = 0x56;
288
+ const VAL_RAW = 0x57;
289
+ const PRED_GROUP = 0x70;
290
+ const PRED_ACTOR = 0x71;
291
+ const PRED_CTR = 0x73;
292
+ const EXPAND = 0x94;
293
+ const MARK_NAME = 0xa5;
294
+ const KNOWN = new Set([
295
+ OBJ_ACTOR,
296
+ OBJ_CTR,
297
+ KEY_ACTOR,
298
+ KEY_CTR,
299
+ KEY_STR,
300
+ INSERT,
301
+ ACTION,
302
+ VAL_META,
303
+ VAL_RAW,
304
+ PRED_GROUP,
305
+ PRED_ACTOR,
306
+ PRED_CTR,
307
+ EXPAND,
308
+ MARK_NAME,
309
+ ]);
310
+ /** Decodes and checks one column; the rows must encode back to the same bytes (rule 5). */
311
+ function column(spec, data) {
312
+ const type = spec & 7;
313
+ if (type === TYPE_BOOLEAN) {
314
+ const rows = decodeBoolean(data);
315
+ if (compareBytes(encodeBoolean(rows), data) !== 0)
316
+ refuse(`column 0x${spec.toString(16)} is not in its one run encoding`);
317
+ return rows;
318
+ }
319
+ const rows = decodeRle(type, data);
320
+ if (compareBytes(encodeRle(type, rows), data) !== 0)
321
+ refuse(`column 0x${spec.toString(16)} is not in its one run encoding`);
322
+ return rows;
323
+ }
324
+ const num = (v) => (v === null ? null : Number(v));
325
+ /** Rule 6: a value's bytes for its metadata type. */
326
+ function checkValue(type, bytes) {
327
+ if (type >= 10)
328
+ refuse(`value type ${type} is reserved`);
329
+ if (type <= 2) {
330
+ if (bytes.length !== 0)
331
+ refuse("a null or boolean value has bytes");
332
+ return;
333
+ }
334
+ if (type === 3 || type === 4 || type === 8 || type === 9) {
335
+ const r = new Reader(bytes);
336
+ if (type === 3)
337
+ r.ubig();
338
+ else
339
+ r.sbig();
340
+ if (!r.done || bytes.length === 0)
341
+ refuse("a number value is not exactly one shortest LEB128");
342
+ return;
343
+ }
344
+ if (type === 5) {
345
+ if (bytes.length !== 8)
346
+ refuse("a float value is not 8 bytes");
347
+ return;
348
+ }
349
+ if (type === 6)
350
+ utf8(bytes, "a string value");
351
+ }
352
+ /**
353
+ * §11.3: checks that `bytes` (one change chunk within the §11.1 limits) is
354
+ * the canonical encoding of its change, and returns the change's operations
355
+ * read from it. Throws PROFILE_INVALID / INVALID_AUTOMERGE_BYTES.
356
+ */
357
+ export function checkCanonicalChange(bytes) {
358
+ const r = new Reader(bytes);
359
+ r.take(9); // magic, checksum, chunk type: checked by the framing
360
+ const length = r.ubig();
361
+ if (length !== BigInt(bytes.length - r.pos))
362
+ refuse("the chunk length is wrong");
363
+ // Rule 2: the header.
364
+ const depCount = r.unum();
365
+ const deps = [];
366
+ for (let i = 0; i < depCount; i++)
367
+ deps.push(r.take(32));
368
+ for (let i = 1; i < deps.length; i++)
369
+ if (compareBytes(deps[i - 1], deps[i]) >= 0)
370
+ refuse("the dependencies are not strictly ascending");
371
+ const actor = r.take(r.unum());
372
+ const seq = r.ubig();
373
+ const startOp = r.ubig();
374
+ if (seq < 1n || startOp < 1n)
375
+ refuse("the sequence number or the start op is 0");
376
+ r.sbig(); // time
377
+ utf8(r.take(r.unum()), "the message");
378
+ const otherCount = r.unum();
379
+ const others = [];
380
+ for (let i = 0; i < otherCount; i++)
381
+ others.push(r.take(r.unum()));
382
+ for (let i = 0; i < others.length; i++) {
383
+ const o = others[i];
384
+ if (compareBytes(o, actor) === 0)
385
+ refuse("the change's actor is among its other actors");
386
+ if (i > 0 && compareBytes(others[i - 1], o) >= 0)
387
+ refuse("the other actors are not strictly ascending");
388
+ }
389
+ // Rule 3: the columns, in specification order, each once, never empty or deflated.
390
+ const columnCount = r.unum();
391
+ const metas = [];
392
+ for (let i = 0; i < columnCount; i++) {
393
+ const spec = r.unum();
394
+ const len = r.unum();
395
+ if (!KNOWN.has(spec))
396
+ refuse(`column 0x${spec.toString(16)} is not a change column`);
397
+ if (metas.length > 0 && metas.at(-1).spec >= spec)
398
+ refuse("the columns are not in ascending specification order");
399
+ if (len === 0)
400
+ refuse(`column 0x${spec.toString(16)} is empty`);
401
+ metas.push({ spec, length: len });
402
+ }
403
+ const data = new Map();
404
+ for (const m of metas)
405
+ data.set(m.spec, r.take(m.length));
406
+ // What follows is the change's extra bytes.
407
+ const rle = (spec) => {
408
+ const d = data.get(spec);
409
+ return d === undefined ? undefined : column(spec, d);
410
+ };
411
+ const bools = (spec) => {
412
+ const d = data.get(spec);
413
+ return d === undefined ? undefined : column(spec, d);
414
+ };
415
+ const action = rle(ACTION) ?? [];
416
+ const N = action.length;
417
+ const objActor = rle(OBJ_ACTOR);
418
+ const objCtr = rle(OBJ_CTR);
419
+ const keyActor = rle(KEY_ACTOR);
420
+ const keyCtr = rle(KEY_CTR);
421
+ const keyStr = rle(KEY_STR);
422
+ const insert = bools(INSERT);
423
+ const meta = rle(VAL_META);
424
+ const predCount = rle(PRED_GROUP);
425
+ const predActor = rle(PRED_ACTOR);
426
+ const predCtr = rle(PRED_CTR);
427
+ const expand = bools(EXPAND);
428
+ const markName = rle(MARK_NAME);
429
+ // Rule 4: row counts.
430
+ for (const [spec, rows] of [
431
+ [OBJ_ACTOR, objActor],
432
+ [OBJ_CTR, objCtr],
433
+ [KEY_ACTOR, keyActor],
434
+ [KEY_CTR, keyCtr],
435
+ [KEY_STR, keyStr],
436
+ [INSERT, insert],
437
+ [VAL_META, meta],
438
+ [PRED_GROUP, predCount],
439
+ [EXPAND, expand],
440
+ [MARK_NAME, markName],
441
+ ])
442
+ if (rows !== undefined && rows.length !== N)
443
+ refuse(`column 0x${spec.toString(16)} has ${rows.length} rows, not ${N}`);
444
+ let predRows = 0;
445
+ for (const c of predCount ?? []) {
446
+ if (c === null)
447
+ refuse("a predecessor count is null");
448
+ predRows += Number(c);
449
+ }
450
+ for (const [spec, rows] of [
451
+ [PRED_ACTOR, predActor],
452
+ [PRED_CTR, predCtr],
453
+ ])
454
+ if (rows !== undefined && rows.length !== predRows)
455
+ refuse(`column 0x${spec.toString(16)} has ${rows.length} rows, not ${predRows}`);
456
+ const actorOf = (index) => {
457
+ const i = num(index);
458
+ if (i === null)
459
+ return refuse("an actor index is null");
460
+ if (i === 0)
461
+ return hex(actor);
462
+ const o = others[i - 1];
463
+ if (o === undefined)
464
+ return refuse("an actor index is out of range");
465
+ return hex(o);
466
+ };
467
+ const named = new Set();
468
+ const counter = (v, what) => {
469
+ const c = num(v);
470
+ if (c === null || c < 1 || c >= U32)
471
+ return refuse(`${what} is not a counter from 1 below 2^32`);
472
+ return c;
473
+ };
474
+ // Values (rule 6) and the value bytes column (rule 4).
475
+ const raw = data.get(VAL_RAW) ?? new Uint8Array(0);
476
+ let rawAt = 0;
477
+ const valueTypes = [];
478
+ for (let i = 0; i < N; i++) {
479
+ const m = meta?.[i] ?? null;
480
+ if (m === null)
481
+ refuse("a value metadata row is null");
482
+ const mv = m;
483
+ const type = Number(mv & 15n);
484
+ const len = mv >> 4n;
485
+ if (len > BigInt(raw.length - rawAt))
486
+ refuse("the value bytes are shorter than their metadata");
487
+ checkValue(type, raw.subarray(rawAt, rawAt + Number(len)));
488
+ rawAt += Number(len);
489
+ valueTypes.push(type);
490
+ }
491
+ if (rawAt !== raw.length)
492
+ refuse("the value bytes are longer than their metadata");
493
+ if (startOp + BigInt(N) - 1n >= BigInt(U32))
494
+ refuse("the last operation counter is 2^32 or more");
495
+ const start = Number(startOp);
496
+ const ops = [];
497
+ let p = 0;
498
+ let anyObj = false;
499
+ let anyElem = false;
500
+ let anyKeyCtr = false;
501
+ let anyProp = false;
502
+ let anyPred = false;
503
+ let anyExpand = false;
504
+ let anyMark = false;
505
+ for (let i = 0; i < N; i++) {
506
+ // Rule 7: actions.
507
+ const a = num(action[i]);
508
+ if (a === null || a > 7)
509
+ refuse("an action is not 0 to 7");
510
+ const act = a;
511
+ const ins = insert?.[i] ?? false;
512
+ const exp = expand?.[i] ?? false;
513
+ const mark = markName?.[i] ?? null;
514
+ if ((act === 0 || act === 2 || act === 4 || act === 6 || act === 3) && valueTypes[i] !== 0)
515
+ refuse("an operation that makes an object or deletes has a value");
516
+ if (act === 5 && valueTypes[i] !== 3 && valueTypes[i] !== 4)
517
+ refuse("an increment's value is not an integer");
518
+ if (act === 7 && !ins)
519
+ refuse("a mark does not insert");
520
+ if (act !== 7 && (exp || mark !== null))
521
+ refuse("only a mark has a mark name or expands");
522
+ if (mark !== null) {
523
+ anyMark = true;
524
+ utf8(mark, "a mark name");
525
+ }
526
+ if (exp)
527
+ anyExpand = true;
528
+ // Rule 8: the object and the key.
529
+ const oa = objActor?.[i] ?? null;
530
+ const oc = objCtr?.[i] ?? null;
531
+ let obj;
532
+ if (oa === null && oc === null)
533
+ obj = "_root";
534
+ else if (oa !== null && oc !== null) {
535
+ obj = `${counter(oc, "an object counter")}@${actorOf(oa)}`;
536
+ named.add(num(oa));
537
+ anyObj = true;
538
+ }
539
+ else
540
+ return refuse("an object is half null");
541
+ const ka = keyActor?.[i] ?? null;
542
+ const kc = keyCtr?.[i] ?? null;
543
+ const ks = keyStr?.[i] ?? null;
544
+ let key;
545
+ if (ks !== null && ka === null && kc === null) {
546
+ key = { kind: "prop", name: utf8(ks, "a key") };
547
+ anyProp = true;
548
+ }
549
+ else if (ks === null && ka === null && kc !== null && num(kc) === 0) {
550
+ key = { kind: "head" };
551
+ anyKeyCtr = true;
552
+ }
553
+ else if (ks === null && ka !== null && kc !== null) {
554
+ key = { kind: "elem", id: `${counter(kc, "a key counter")}@${actorOf(ka)}` };
555
+ named.add(num(ka));
556
+ anyElem = true;
557
+ anyKeyCtr = true;
558
+ }
559
+ else
560
+ return refuse("a key is neither a property, the head nor an element");
561
+ // Rule 9: the predecessors, ascending by counter then actor.
562
+ const count = Number(predCount?.[i]);
563
+ const pred = [];
564
+ let last = null;
565
+ for (let k = 0; k < count; k++, p++) {
566
+ const pa = predActor?.[p] ?? null;
567
+ const ctr = counter(predCtr?.[p] ?? null, "a predecessor counter");
568
+ const id = actorOf(pa);
569
+ const idBytes = num(pa) === 0 ? actor : others[num(pa) - 1];
570
+ if (last !== null &&
571
+ (ctr < last.ctr || (ctr === last.ctr && compareBytes(last.actor, idBytes) >= 0)))
572
+ refuse("the predecessors are not strictly ascending");
573
+ last = { ctr, actor: idBytes };
574
+ named.add(num(pa));
575
+ pred.push(`${ctr}@${id}`);
576
+ anyPred = true;
577
+ }
578
+ ops.push(Object.freeze({ obj, key, insert: ins, action: act, pred: Object.freeze(pred) }));
579
+ }
580
+ // Rule 3: each column present exactly when the table says.
581
+ const presence = [
582
+ [OBJ_ACTOR, anyObj],
583
+ [OBJ_CTR, anyObj],
584
+ [KEY_ACTOR, anyElem],
585
+ [KEY_CTR, anyKeyCtr],
586
+ [KEY_STR, anyProp],
587
+ [INSERT, N > 0],
588
+ [ACTION, N > 0],
589
+ [VAL_META, N > 0],
590
+ [VAL_RAW, raw.length > 0],
591
+ [PRED_GROUP, N > 0],
592
+ [PRED_ACTOR, anyPred],
593
+ [PRED_CTR, anyPred],
594
+ [EXPAND, anyExpand],
595
+ [MARK_NAME, anyMark],
596
+ ];
597
+ for (const [spec, want] of presence)
598
+ if (data.has(spec) !== want)
599
+ refuse(`column 0x${spec.toString(16)} is ${want ? "missing" : "present without a use"}`);
600
+ // Rule 2: the other actors are exactly those the operations name.
601
+ named.delete(0);
602
+ if (named.size !== others.length)
603
+ refuse("the other actors are not exactly those the operations name");
604
+ return Object.freeze({
605
+ actor: hex(actor),
606
+ seq: Number(seq),
607
+ startOp: start,
608
+ deps: Object.freeze(deps.map(hex)),
609
+ otherActors: Object.freeze(others.map(hex)),
610
+ ops: Object.freeze(ops),
611
+ });
612
+ }
613
+ //# sourceMappingURL=canonical.js.map
@@ -0,0 +1,58 @@
1
+ /** The exact limits of a change (§11.1): writers stay within them, receivers reject above. */
2
+ export declare const CHANGE_LIMITS: Readonly<{
3
+ maxRows: 16384;
4
+ maxGroupSum: 262144;
5
+ maxStringBytes: number;
6
+ maxDeps: 1024;
7
+ maxActors: 1024;
8
+ }>;
9
+ /**
10
+ * The floor of a Snapshot's limits (§13.1): every receiver accepts at least
11
+ * these. A receiver MAY configure higher limits; above its own it rejects
12
+ * the Snapshot before the engine and falls back to the data round.
13
+ */
14
+ export declare const SNAPSHOT_LIMITS_FLOOR: Readonly<{
15
+ maxRows: 262144;
16
+ maxGroupSum: 262144;
17
+ maxStringBytes: number;
18
+ maxInflatedBytes: number;
19
+ maxDeps: 1024;
20
+ maxActors: 1024;
21
+ }>;
22
+ export type SnapshotLimits = typeof SNAPSHOT_LIMITS_FLOOR;
23
+ /** §11.2: no object of a document is deeper than this (the root is depth 0). */
24
+ export declare const MAX_DOCUMENT_DEPTH = 256;
25
+ /** The operation actions that create an object (§11.2): makeMap, makeList, makeText, makeTable. */
26
+ export declare const OBJECT_ACTIONS: ReadonlySet<number>;
27
+ /** What a chunk expands to (for tests and diagnostics). */
28
+ export interface ChunkExpansion {
29
+ /** The largest value count of any column (a change's op count is its action column's). */
30
+ readonly maxRows: number;
31
+ readonly groupSum: number;
32
+ readonly stringBytes: number;
33
+ /** Column data bytes after inflation (a change has no deflated columns). */
34
+ readonly columnBytes: number;
35
+ /** Each column's spec (id << 4 | deflate << 3 | type) and value count, in chunk order. */
36
+ readonly columns: readonly {
37
+ readonly spec: number;
38
+ readonly rows: number;
39
+ }[];
40
+ /** A change's other actors (hex), in order: actor index i + 1 (§11.1). Empty for a Snapshot. */
41
+ readonly otherActors: readonly string[];
42
+ /** A Snapshot's deepest object (§11.2); 0 for a change (its depths depend on the document). */
43
+ readonly maxDepth: number;
44
+ }
45
+ /**
46
+ * §11.1: checks an uncompressed change chunk (type 1) against the exact
47
+ * change limits before the engine sees it. Throws PROFILE_INVALID /
48
+ * INVALID_AUTOMERGE_BYTES; returns what it expands to.
49
+ */
50
+ export declare function checkChangeExpansion(bytes: Uint8Array): ChunkExpansion;
51
+ /**
52
+ * §13.1: checks a Snapshot's save (exactly one document chunk) against
53
+ * `limits` (at least SNAPSHOT_LIMITS_FLOOR) before the engine sees it,
54
+ * inflating deflated columns under a running cap. Throws PROFILE_INVALID /
55
+ * INVALID_AUTOMERGE_BYTES.
56
+ */
57
+ export declare function checkSnapshotExpansion(bytes: Uint8Array, limits?: SnapshotLimits): ChunkExpansion;
58
+ //# sourceMappingURL=chunk-limits.d.ts.map