@sofa-buffers/corelib 0.10.0 → 0.11.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/dist/index.js CHANGED
@@ -10,26 +10,35 @@ __export(public_exports, {
10
10
  API_VERSION: () => API_VERSION,
11
11
  ARRAY_MAX: () => ARRAY_MAX,
12
12
  ArrayKind: () => ArrayKind,
13
- Cursor: () => Cursor,
13
+ BlobSeq: () => BlobSeq,
14
14
  DecodeStatus: () => DecodeStatus,
15
+ ElementSeq: () => ElementSeq,
15
16
  FIXLEN_MAX: () => FIXLEN_MAX,
16
17
  FixlenSubtype: () => FixlenSubtype,
18
+ FramedSeq: () => FramedSeq,
17
19
  I64_MAX: () => I64_MAX,
18
20
  I64_MIN: () => I64_MIN,
19
21
  ID_MAX: () => ID_MAX,
20
22
  IStream: () => IStream,
21
23
  Long: () => Long,
22
24
  MAX_DEPTH: () => MAX_DEPTH,
25
+ MIN_OUTPUT_BUFFER: () => MIN_OUTPUT_BUFFER,
23
26
  OStream: () => OStream,
27
+ PayloadAcc: () => PayloadAcc,
24
28
  SofabError: () => SofabError,
25
29
  SofabErrorCode: () => SofabErrorCode,
30
+ StringSeq: () => StringSeq,
26
31
  U64_MAX: () => U64_MAX,
32
+ UNBOUNDED: () => UNBOUNDED,
27
33
  WireType: () => WireType,
28
34
  decode: () => decode,
35
+ decodeUtf8: () => decodeUtf8,
36
+ elementsEqual: () => elementsEqual,
37
+ fp32RawBytes: () => fp32RawBytes,
29
38
  getKernel: () => getKernel,
39
+ growingOStream: () => growingOStream,
30
40
  jsKernel: () => jsKernel,
31
- loadNativeKernel: () => loadNativeKernel,
32
- loadWasmKernel: () => loadWasmKernel,
41
+ longElementsEqual: () => longElementsEqual,
33
42
  setKernel: () => setKernel
34
43
  });
35
44
 
@@ -80,6 +89,9 @@ var U64_MAX = 0xffffffffffffffffn;
80
89
  var I64_MIN = -0x8000000000000000n;
81
90
  var I64_MAX = 0x7fffffffffffffffn;
82
91
  var VARINT_MAX_BYTES = 10;
92
+ var MIN_OUTPUT_BUFFER = 1;
93
+ var FP32_HANDLE_MIN = 64;
94
+ var FP64_HANDLE_MIN = 16;
83
95
  var MAX_DEPTH = 255;
84
96
  var DecodeStatus = {
85
97
  /** The bytes ended exactly at a field boundary — a valid message. */
@@ -108,7 +120,8 @@ var SofabErrorCode = {
108
120
  /**
109
121
  * A receiver-configured decode limit was exceeded — a dynamic array, string or
110
122
  * blob on the wire claims more elements / bytes than the caller's
111
- * {@link DecodeLimits} (`maxArrayCount` / `maxStringLen` / `maxBlobLen`)
123
+ * receiver cap (`max_dyn_array_count` / `max_dyn_string_len` /
124
+ * `max_dyn_blob_len`, §6.2.1 — stated by generated code, never by this codec)
112
125
  * allows. Deliberately distinct from {@link SofabErrorCode.InvalidMsg}:
113
126
  * exceeding a limit is *policy*, not wire malformation — the identical bytes
114
127
  * decode fine under a looser limit — so differential fuzzing must not read it
@@ -141,6 +154,34 @@ function limitExceededError(message) {
141
154
  return new SofabError(SofabErrorCode.LimitExceeded, message);
142
155
  }
143
156
 
157
+ // src/varint/bits64.ts
158
+ var SCRATCH = new ArrayBuffer(8);
159
+ var S_U64 = new BigUint64Array(SCRATCH);
160
+ var S_I64 = new BigInt64Array(SCRATCH);
161
+ var S_U32 = new Uint32Array(SCRATCH);
162
+ S_U64[0] = 0n;
163
+ S_U32[0] = 1;
164
+ var LO = S_U64[0] === 1n ? 0 : 1;
165
+ var HI = LO ^ 1;
166
+ function splitU64(value) {
167
+ S_U64[0] = value;
168
+ return S_U64[0] === value;
169
+ }
170
+ function splitI64(value) {
171
+ S_I64[0] = value;
172
+ return S_I64[0] === value;
173
+ }
174
+ function joinU64(lo, hi) {
175
+ S_U32[LO] = lo;
176
+ S_U32[HI] = hi;
177
+ return S_U64[0];
178
+ }
179
+ function joinI64(lo, hi) {
180
+ S_U32[LO] = lo;
181
+ S_U32[HI] = hi;
182
+ return S_I64[0];
183
+ }
184
+
144
185
  // src/long.ts
145
186
  var _Long = class _Long {
146
187
  constructor(low, high) {
@@ -151,9 +192,14 @@ var _Long = class _Long {
151
192
  static fromBits(low, high) {
152
193
  return new _Long(low, high);
153
194
  }
154
- /** Split a `bigint` into its low/high 32-bit halves (two's complement). */
195
+ /**
196
+ * Split a `bigint` into its low/high 32-bit halves (two's complement). The
197
+ * `BigInt64Array` store *is* `ToBigInt64` — reduction modulo 2^64 — so an
198
+ * out-of-range value keeps exactly the halves the masks kept (bits64).
199
+ */
155
200
  static fromBigInt(value) {
156
- return new _Long(Number(value & 0xffffffffn) >>> 0, Number(value >> 32n & 0xffffffffn) >>> 0);
201
+ S_I64[0] = value;
202
+ return new _Long(S_U32[LO], S_U32[HI]);
157
203
  }
158
204
  /** From an integer `number` (`|n| < 2^53`); sign handled via `bigint` once. */
159
205
  static fromNumber(n) {
@@ -166,9 +212,7 @@ var _Long = class _Long {
166
212
  }
167
213
  /** Materialise as a `bigint`. `signed` reads the high bit as two's complement. */
168
214
  toBigInt(signed = false) {
169
- let r = BigInt(this.high >>> 0) << 32n | BigInt(this.low >>> 0);
170
- if (signed && (this.high & 2147483648) !== 0) r -= 0x10000000000000000n;
171
- return r;
215
+ return signed ? joinI64(this.low, this.high) : joinU64(this.low, this.high);
172
216
  }
173
217
  /** Decimal string (`signed` interprets the high bit as two's complement). */
174
218
  toString(signed = false) {
@@ -186,50 +230,40 @@ _Long.ZERO = new _Long(0, 0);
186
230
  var Long = _Long;
187
231
 
188
232
  // src/varint/leb128.ts
189
- function varintSize(value) {
190
- let lo = Number(value & 0xffffffffn) >>> 0;
191
- let hi = Number(value >> 32n & 0xffffffffn) >>> 0;
192
- let n = 0;
193
- while (hi !== 0) {
194
- n++;
195
- const next = (lo >>> 7 | hi << 25) >>> 0;
196
- hi >>>= 7;
197
- lo = next;
233
+ function varintSizeLoHi(lo, hi) {
234
+ if (hi === 0) {
235
+ return lo < 128 ? 1 : lo < 16384 ? 2 : lo < 2097152 ? 3 : lo < 268435456 ? 4 : 5;
198
236
  }
199
- while (lo > 127) {
200
- n++;
201
- lo >>>= 7;
202
- }
203
- return n + 1;
204
- }
205
- function encodeVarint(value, out, pos) {
206
- let lo = Number(value & 0xffffffffn) >>> 0;
207
- let hi = Number(value >> 32n & 0xffffffffn) >>> 0;
208
- while (hi !== 0) {
209
- out[pos++] = lo & 127 | 128;
210
- lo = (lo >>> 7 | hi << 25) >>> 0;
211
- hi >>>= 7;
212
- }
213
- while (lo > 127) {
214
- out[pos++] = lo & 127 | 128;
215
- lo >>>= 7;
216
- }
217
- out[pos++] = lo;
218
- return pos;
237
+ const h = hi >>> 3;
238
+ return h === 0 ? 5 : 5 + (h < 128 ? 1 : h < 16384 ? 2 : h < 2097152 ? 3 : h < 268435456 ? 4 : 5);
219
239
  }
220
240
  function encodeVarintLoHi(lo, hi, out, pos) {
221
241
  lo >>>= 0;
222
242
  hi >>>= 0;
223
- while (hi !== 0) {
224
- out[pos++] = lo & 127 | 128;
225
- lo = (lo >>> 7 | hi << 25) >>> 0;
226
- hi >>>= 7;
243
+ if (hi === 0) {
244
+ while (lo > 127) {
245
+ out[pos++] = lo & 127 | 128;
246
+ lo >>>= 7;
247
+ }
248
+ out[pos++] = lo;
249
+ return pos;
250
+ }
251
+ out[pos++] = lo & 127 | 128;
252
+ out[pos++] = lo >>> 7 & 127 | 128;
253
+ out[pos++] = lo >>> 14 & 127 | 128;
254
+ out[pos++] = lo >>> 21 & 127 | 128;
255
+ const b4 = lo >>> 28 | (hi & 7) << 4;
256
+ let h = hi >>> 3;
257
+ if (h === 0) {
258
+ out[pos++] = b4;
259
+ return pos;
227
260
  }
228
- while (lo > 127) {
229
- out[pos++] = lo & 127 | 128;
230
- lo >>>= 7;
261
+ out[pos++] = b4 | 128;
262
+ while (h > 127) {
263
+ out[pos++] = h & 127 | 128;
264
+ h >>>= 7;
231
265
  }
232
- out[pos++] = lo;
266
+ out[pos++] = h;
233
267
  return pos;
234
268
  }
235
269
  function varintSizeNum(value) {
@@ -259,51 +293,78 @@ function encodeVarintNum(value, out, pos) {
259
293
  }
260
294
 
261
295
  // src/varint/num64.ts
262
- var SCRATCH = new DataView(new ArrayBuffer(8));
296
+ var SCRATCH_BUF = new ArrayBuffer(8);
297
+ var SCRATCH2 = new DataView(SCRATCH_BUF);
298
+ var SCRATCH_BYTES = new Uint8Array(SCRATCH_BUF);
299
+ var SCRATCH_U32 = new Uint32Array(SCRATCH_BUF);
300
+ var SCRATCH_F32 = new Float32Array(SCRATCH_BUF, 0, 1);
301
+ var SCRATCH_F64 = new Float64Array(SCRATCH_BUF);
263
302
  function toBigInt(value) {
264
303
  if (typeof value === "bigint") return value;
265
304
  if (!Number.isInteger(value)) {
266
- throw new RangeError(`expected an integer, got ${value}`);
305
+ throw argumentError(`expected an integer, got ${value}`);
267
306
  }
268
307
  return BigInt(value);
269
308
  }
270
- function inU64(value) {
271
- return value >= 0n && value <= U64_MAX;
272
- }
273
- function inI64(value) {
274
- return value >= I64_MIN && value <= I64_MAX;
275
- }
276
309
  function packFp32(out, pos, value) {
277
- SCRATCH.setFloat32(0, value, true);
278
- out[pos] = SCRATCH.getUint8(0);
279
- out[pos + 1] = SCRATCH.getUint8(1);
280
- out[pos + 2] = SCRATCH.getUint8(2);
281
- out[pos + 3] = SCRATCH.getUint8(3);
310
+ SCRATCH2.setFloat32(0, value, true);
311
+ out[pos] = SCRATCH_BYTES[0];
312
+ out[pos + 1] = SCRATCH_BYTES[1];
313
+ out[pos + 2] = SCRATCH_BYTES[2];
314
+ out[pos + 3] = SCRATCH_BYTES[3];
282
315
  return pos + 4;
283
316
  }
284
317
  function packFp64(out, pos, value) {
285
- SCRATCH.setFloat64(0, value, true);
286
- for (let i = 0; i < 8; i++) out[pos + i] = SCRATCH.getUint8(i);
318
+ SCRATCH2.setFloat64(0, value, true);
319
+ out[pos] = SCRATCH_BYTES[0];
320
+ out[pos + 1] = SCRATCH_BYTES[1];
321
+ out[pos + 2] = SCRATCH_BYTES[2];
322
+ out[pos + 3] = SCRATCH_BYTES[3];
323
+ out[pos + 4] = SCRATCH_BYTES[4];
324
+ out[pos + 5] = SCRATCH_BYTES[5];
325
+ out[pos + 6] = SCRATCH_BYTES[6];
326
+ out[pos + 7] = SCRATCH_BYTES[7];
287
327
  return pos + 8;
288
328
  }
289
- function unpackFp32(buf, pos) {
290
- SCRATCH.setUint8(0, buf[pos]);
291
- SCRATCH.setUint8(1, buf[pos + 1]);
292
- SCRATCH.setUint8(2, buf[pos + 2]);
293
- SCRATCH.setUint8(3, buf[pos + 3]);
294
- return SCRATCH.getFloat32(0, true);
329
+ function fp32Bits(value) {
330
+ SCRATCH2.setFloat32(0, value, true);
331
+ return SCRATCH2.getUint32(0, true);
332
+ }
333
+ function fp64BitsLo(value) {
334
+ SCRATCH2.setFloat64(0, value, true);
335
+ return SCRATCH2.getUint32(0, true);
336
+ }
337
+ function fp64BitsHi(value) {
338
+ SCRATCH2.setFloat64(0, value, true);
339
+ return SCRATCH2.getUint32(4, true);
295
340
  }
296
- function unpackFp64(buf, pos) {
297
- for (let i = 0; i < 8; i++) SCRATCH.setUint8(i, buf[pos + i]);
298
- return SCRATCH.getFloat64(0, true);
341
+ function fp32FromBits(bits) {
342
+ SCRATCH_U32[0] = bits;
343
+ return SCRATCH_F32[0];
344
+ }
345
+ function fp32RawBytes(bits) {
346
+ const out = new Uint8Array(4);
347
+ out[0] = bits & 255;
348
+ out[1] = bits >>> 8 & 255;
349
+ out[2] = bits >>> 16 & 255;
350
+ out[3] = bits >>> 24 & 255;
351
+ return out;
352
+ }
353
+ function fp64FromBits(lo, hi) {
354
+ SCRATCH_U32[LO] = lo;
355
+ SCRATCH_U32[HI] = hi;
356
+ return SCRATCH_F64[0];
299
357
  }
300
358
 
301
359
  // src/varint/zigzag.ts
302
- function zigzagEncode(value) {
303
- return (value << 1n ^ value >> 63n) & U64_MAX;
304
- }
305
- function zigzagDecode(value) {
306
- return value >> 1n ^ -(value & 1n);
360
+ function encodeZigzagVarintLoHi(lo, hi, out, pos) {
361
+ const sgn = -(hi >>> 31) >>> 0;
362
+ return encodeVarintLoHi(
363
+ (lo << 1 >>> 0 ^ sgn) >>> 0,
364
+ ((hi << 1 | lo >>> 31) >>> 0 ^ sgn) >>> 0,
365
+ out,
366
+ pos
367
+ );
307
368
  }
308
369
 
309
370
  // src/backend/js.ts
@@ -311,36 +372,161 @@ var SIGNED_FAST_MAX = 4503599627370496;
311
372
  var jsKernel = {
312
373
  name: "js",
313
374
  encodeUnsignedVarints(values, out, pos) {
314
- for (let i = 0; i < values.length; i++) {
375
+ const n = values.length;
376
+ const ctor = values.constructor;
377
+ if (ctor === BigUint64Array) {
378
+ const a = values;
379
+ const h = new Uint32Array(a.buffer, a.byteOffset, a.length * 2);
380
+ for (let i = 0; i < n; i++) {
381
+ pos = encodeVarintLoHi(h[i * 2 + LO], h[i * 2 + HI], out, pos);
382
+ }
383
+ return pos;
384
+ }
385
+ if (ctor === Uint8Array || ctor === Uint16Array || ctor === Uint32Array) {
386
+ const a = values;
387
+ for (let i = 0; i < n; i++) {
388
+ let v = a[i];
389
+ while (v > 127) {
390
+ out[pos++] = v & 127 | 128;
391
+ v >>>= 7;
392
+ }
393
+ out[pos++] = v;
394
+ }
395
+ return pos;
396
+ }
397
+ for (let i = 0; i < n; i++) {
315
398
  const v = values[i];
316
399
  if (typeof v === "number" && v >= 0 && v <= Number.MAX_SAFE_INTEGER && Number.isInteger(v)) {
317
400
  pos = encodeVarintNum(v, out, pos);
318
- } else {
319
- pos = encodeVarint(toBigInt(v), out, pos);
401
+ continue;
402
+ }
403
+ const b = toBigInt(v);
404
+ if (!splitU64(b)) throw argumentError(`unsigned value ${b} out of range`);
405
+ let lo = S_U32[LO];
406
+ const hi = S_U32[HI];
407
+ if (hi === 0) {
408
+ while (lo > 127) {
409
+ out[pos++] = lo & 127 | 128;
410
+ lo >>>= 7;
411
+ }
412
+ out[pos++] = lo;
413
+ continue;
320
414
  }
415
+ out[pos++] = lo & 127 | 128;
416
+ out[pos++] = lo >>> 7 & 127 | 128;
417
+ out[pos++] = lo >>> 14 & 127 | 128;
418
+ out[pos++] = lo >>> 21 & 127 | 128;
419
+ const b4 = lo >>> 28 | (hi & 7) << 4;
420
+ let h = hi >>> 3;
421
+ if (h === 0) {
422
+ out[pos++] = b4;
423
+ continue;
424
+ }
425
+ out[pos++] = b4 | 128;
426
+ while (h > 127) {
427
+ out[pos++] = h & 127 | 128;
428
+ h >>>= 7;
429
+ }
430
+ out[pos++] = h;
321
431
  }
322
432
  return pos;
323
433
  },
324
434
  encodeSignedVarints(values, out, pos) {
435
+ const ctorS = values.constructor;
436
+ if (ctorS === BigInt64Array) {
437
+ const a = values;
438
+ const h = new Uint32Array(a.buffer, a.byteOffset, a.length * 2);
439
+ for (let i = 0; i < a.length; i++) {
440
+ pos = encodeZigzagVarintLoHi(h[i * 2 + LO], h[i * 2 + HI], out, pos);
441
+ }
442
+ return pos;
443
+ }
444
+ if (ctorS === Int8Array || ctorS === Int16Array || ctorS === Int32Array) {
445
+ const a = values;
446
+ const n = a.length;
447
+ for (let i = 0; i < n; i++) {
448
+ const x = a[i];
449
+ let z = (x << 1 ^ x >> 31) >>> 0;
450
+ while (z > 127) {
451
+ out[pos++] = z & 127 | 128;
452
+ z >>>= 7;
453
+ }
454
+ out[pos++] = z;
455
+ }
456
+ return pos;
457
+ }
325
458
  for (let i = 0; i < values.length; i++) {
326
459
  const v = values[i];
327
460
  if (typeof v === "number" && v >= -SIGNED_FAST_MAX && v <= SIGNED_FAST_MAX && Number.isInteger(v)) {
328
461
  pos = encodeVarintNum(v >= 0 ? v * 2 : -v * 2 - 1, out, pos);
329
462
  } else {
330
- pos = encodeVarint(zigzagEncode(toBigInt(v)), out, pos);
463
+ const b = toBigInt(v);
464
+ if (!splitI64(b)) throw argumentError(`signed value ${b} out of range`);
465
+ pos = encodeZigzagVarintLoHi(S_U32[LO], S_U32[HI], out, pos);
331
466
  }
332
467
  }
333
468
  return pos;
334
469
  },
470
+ // Both float packers take one `DataView` over the destination *once the run is
471
+ // long enough to pay for it* — the language-forced handle of CORELIB_PLAN §6.6.2,
472
+ // and the only way to place an IEEE-754 value at a byte offset. It carries no
473
+ // message bytes (the storage is the caller's) and no wire number sizes it.
474
+ //
475
+ // The threshold is arithmetic, not taste. Measured on Node 24:
476
+ // `new DataView(buf.buffer, off, len)` costs **129 ns**, while the handle saves
477
+ // 1.9 ns per `fp32` (4.11 -> 2.23) and 7.3 ns per `fp64` (10.48 -> 3.20). So it
478
+ // breaks even at ~68 `fp32` and ~18 `fp64` elements; below that the scratch route
479
+ // wins, and a two-element array through a handle is a pessimisation.
335
480
  packFp32Array(values, out, pos) {
336
- for (let i = 0; i < values.length; i++) {
337
- pos = packFp32(out, pos, values[i]);
481
+ const n = values.length;
482
+ if (values instanceof Float32Array) {
483
+ if (n < FP32_HANDLE_MIN) {
484
+ let w2 = null;
485
+ for (let i = 0; i < n; i++) {
486
+ const v = values[i];
487
+ if (v === v) {
488
+ pos = packFp32(out, pos, v);
489
+ continue;
490
+ }
491
+ if (w2 === null) w2 = new Uint32Array(values.buffer, values.byteOffset, n);
492
+ const x = w2[i];
493
+ out[pos] = x;
494
+ out[pos + 1] = x >>> 8;
495
+ out[pos + 2] = x >>> 16;
496
+ out[pos + 3] = x >>> 24;
497
+ pos += 4;
498
+ }
499
+ return pos;
500
+ }
501
+ const w = new Uint32Array(values.buffer, values.byteOffset, n);
502
+ const dv2 = new DataView(out.buffer, out.byteOffset, out.byteLength);
503
+ for (let i = 0; i < n; i++) {
504
+ dv2.setUint32(pos, w[i], true);
505
+ pos += 4;
506
+ }
507
+ return pos;
508
+ }
509
+ if (n < FP32_HANDLE_MIN) {
510
+ for (let i = 0; i < n; i++) pos = packFp32(out, pos, values[i]);
511
+ return pos;
512
+ }
513
+ const dv = new DataView(out.buffer, out.byteOffset, out.byteLength);
514
+ for (let i = 0; i < n; i++) {
515
+ dv.setFloat32(pos, values[i], true);
516
+ pos += 4;
338
517
  }
339
518
  return pos;
340
519
  },
341
520
  packFp64Array(values, out, pos) {
342
- for (let i = 0; i < values.length; i++) {
343
- pos = packFp64(out, pos, values[i]);
521
+ const n = values.length;
522
+ if (n < FP64_HANDLE_MIN) {
523
+ for (let i = 0; i < n; i++) pos = packFp64(out, pos, values[i]);
524
+ return pos;
525
+ }
526
+ const dv = new DataView(out.buffer, out.byteOffset, out.byteLength);
527
+ for (let i = 0; i < n; i++) {
528
+ dv.setFloat64(pos, values[i], true);
529
+ pos += 8;
344
530
  }
345
531
  return pos;
346
532
  }
@@ -370,19 +556,18 @@ function validateKernel(kernel) {
370
556
  }
371
557
 
372
558
  // src/encode/fixlen.ts
373
- var UTF8 = new TextEncoder();
374
559
  function unpairedSurrogate(index) {
375
560
  return argumentError(
376
561
  `unpaired surrogate at index ${index}: string value is not valid UTF-8`
377
562
  );
378
563
  }
379
- function encodeUtf8(text) {
380
- utf8Length(text);
381
- return UTF8.encode(text);
382
- }
383
564
  function utf8Length(text) {
384
- let len = 0;
385
- for (let i = 0; i < text.length; i++) {
565
+ const n = text.length;
566
+ let a = 0;
567
+ while (a < n && text.charCodeAt(a) < 128) a++;
568
+ if (a === n) return n;
569
+ let len = a;
570
+ for (let i = a; i < n; i++) {
386
571
  const c = text.charCodeAt(i);
387
572
  if (c < 128) {
388
573
  len += 1;
@@ -405,7 +590,17 @@ function utf8Length(text) {
405
590
  return len;
406
591
  }
407
592
  function utf8Write(text, out, pos) {
408
- for (let i = 0; i < text.length; i++) {
593
+ const n = text.length;
594
+ let a = 0;
595
+ while (a < n) {
596
+ const c = text.charCodeAt(a);
597
+ if (c >= 128) break;
598
+ out[pos + a] = c;
599
+ a++;
600
+ }
601
+ pos += a;
602
+ if (a === n) return pos;
603
+ for (let i = a; i < n; i++) {
409
604
  let c = text.charCodeAt(i);
410
605
  if (c < 128) {
411
606
  out[pos++] = c;
@@ -434,68 +629,139 @@ function utf8Write(text, out, pos) {
434
629
  }
435
630
  return pos;
436
631
  }
632
+ function utf8WriteSink(text, sink) {
633
+ const n = text.length;
634
+ for (let i = 0; i < n; i++) {
635
+ let c = text.charCodeAt(i);
636
+ if (c < 128) {
637
+ sink.putByte(c);
638
+ } else if (c < 2048) {
639
+ sink.putByte(192 | c >> 6);
640
+ sink.putByte(128 | c & 63);
641
+ } else if (c >= 55296 && c <= 56319) {
642
+ const c2 = i + 1 < n ? text.charCodeAt(i + 1) : 0;
643
+ if (c2 >= 56320 && c2 <= 57343) {
644
+ i++;
645
+ c = 65536 + (c - 55296 << 10) + (c2 - 56320);
646
+ sink.putByte(240 | c >> 18);
647
+ sink.putByte(128 | c >> 12 & 63);
648
+ sink.putByte(128 | c >> 6 & 63);
649
+ sink.putByte(128 | c & 63);
650
+ } else {
651
+ throw unpairedSurrogate(i);
652
+ }
653
+ } else if (c >= 56320 && c <= 57343) {
654
+ throw unpairedSurrogate(i);
655
+ } else {
656
+ sink.putByte(224 | c >> 12);
657
+ sink.putByte(128 | c >> 6 & 63);
658
+ sink.putByte(128 | c & 63);
659
+ }
660
+ }
661
+ }
437
662
 
438
663
  // src/encode/ostream.ts
439
- var DEFAULT_CAPACITY = 256;
440
664
  var SIGNED_FAST_MAX2 = 4503599627370496;
665
+ function checkHandover(buffer, offset, streaming, carried) {
666
+ if (offset < 0 || offset > buffer.length) {
667
+ throw argumentError(`offset ${offset} out of range`);
668
+ }
669
+ if (!Number.isInteger(carried) || carried < 0 || carried > offset) {
670
+ throw argumentError(`carried ${carried} out of range 0..${offset}`);
671
+ }
672
+ if (streaming && buffer.length - offset < MIN_OUTPUT_BUFFER) {
673
+ throw argumentError(
674
+ `output buffer with a flush sink has ${buffer.length - offset} usable byte(s), below MIN_OUTPUT_BUFFER (${MIN_OUTPUT_BUFFER})`
675
+ );
676
+ }
677
+ }
441
678
  var OStream = class {
679
+ /**
680
+ * Encoder over a caller buffer, optionally draining to `flush` as it fills.
681
+ *
682
+ * Those are the only two buffer models there are (CORELIB_PLAN §5.1.1): the
683
+ * buffer the caller hands over, and whatever a sink installs in its place
684
+ * (§5.1.5). There is no third parameter through which the encoder could be
685
+ * given something to enlarge — growing a destination from a write path is what
686
+ * §6.6's second violation row names, whoever owns the allocator.
687
+ *
688
+ * Constructing one is the only allocating step (§6.6): it sizes the hold-back
689
+ * run from `MAX_DEPTH` and reads the active kernel once. No `write*` call after
690
+ * that allocates anything.
691
+ */
442
692
  constructor(buffer, offset = 0, flush) {
443
693
  this.depth = 0;
444
- /**
445
- * Ids of the innermost open sequences whose header has not been written yet
446
- * (MESSAGE_SPEC §2 lazy framing, {@link OStream.writeSequenceBeginLazy}).
447
- * Always a contiguous suffix of the open sequences: writing any field commits
448
- * the whole run at once, so {@link OStream.writeSequenceEnd} can drop the
449
- * innermost one by dropping the last entry. Held-back ids are encoder state,
450
- * never buffer content, so a flush can never split a run.
451
- *
452
- * Storage plus an explicit count rather than `push`/`pop`, so the slots are
453
- * reused across messages (no allocation on a pooled encoder) and so
454
- * {@link OStream.commitPending} can zero the count *before* it writes.
455
- *
456
- * The array grows on demand and is bounded only by `MAX_DEPTH` — there is no
457
- * fixed hold-back window and hence no eager-framing fallback, which is what
458
- * CORELIB_PLAN §6 ("How deep the hold-back reaches") demands of an
459
- * implementation that can allocate: canonical output at *every* depth.
460
- */
461
- this.pending = [];
462
694
  /** Valid entries in {@link OStream.pending}. */
463
695
  this.nPending = 0;
696
+ /**
697
+ * How many buffer installations {@link OStream.setBuffer} has made. Only ever
698
+ * compared for equality across the flush callback, which is how
699
+ * {@link OStream.flush} tells "the sink copied and returned" from "the sink
700
+ * took the buffer and installed a replacement" — the one distinction §5.1
701
+ * rests the handover contract on, and the one that decides whether the start
702
+ * offset was consumed or re-armed.
703
+ */
704
+ this.installs = 0;
464
705
  this.kernel = getKernel();
465
- if (buffer === void 0) {
466
- this.buf = new Uint8Array(DEFAULT_CAPACITY);
467
- this.start = 0;
468
- this.pos = 0;
469
- this.flushSink = void 0;
470
- this.canGrow = true;
471
- } else {
472
- if (offset < 0 || offset > buffer.length) {
473
- throw argumentError(`offset ${offset} out of range`);
474
- }
475
- this.buf = buffer;
476
- this.start = offset;
477
- this.pos = offset;
478
- this.flushSink = flush;
479
- this.canGrow = false;
480
- }
706
+ this.pending = new Array(MAX_DEPTH);
707
+ checkHandover(buffer, offset, flush !== void 0, 0);
708
+ this.buf = buffer;
709
+ this.start = offset;
710
+ this.origin = offset;
711
+ this.pos = offset;
712
+ this.flushSink = flush;
481
713
  }
482
- /** Bytes currently held in the buffer (since construction or the last flush). */
714
+ /** Bytes of the message currently held in the buffer (see {@link bytes}). */
483
715
  get bytesUsed() {
484
- return this.pos - this.start;
716
+ return this.pos - this.origin;
485
717
  }
486
718
  /**
487
- * The encoded message so far, as a view into the working buffer.
488
- * Meaningful for the in-memory mode; in streaming mode it is only the
489
- * not-yet-flushed tail. The view is valid until the next write.
719
+ * The encoded message so far, as a view into the working buffer: everything
720
+ * written since construction, the last {@link reset}, or the last flush the
721
+ * sink returned from without installing a buffer. With a flush sink that is
722
+ * normally only the not-yet-flushed tail; on a stream whose sink hands back a
723
+ * replacement carrying the earlier bytes — the accumulator `growingOStream`
724
+ * builds — it is the whole message. The view is valid until the next write.
490
725
  */
491
726
  bytes() {
492
- return this.buf.subarray(this.start, this.pos);
727
+ return this.buf.subarray(this.origin, this.pos);
493
728
  }
494
- /** Drain buffered bytes to the flush sink (no-op without one). */
729
+ /**
730
+ * Drain buffered bytes to the flush sink (no-op without one).
731
+ *
732
+ * A sink that returns **without** installing a buffer has copied what it was
733
+ * handed, so the encoder keeps writing into the same buffer — resuming at
734
+ * offset **0**. The start offset belongs to the *installation*, not to the
735
+ * buffer (CORELIB_PLAN §5.1): the buffer-set that armed it — the constructor
736
+ * or {@link OStream.setBuffer} — reserved room in the unit it began, and
737
+ * handing that unit over consumes the reservation. A sink that wants header
738
+ * room in *every* unit re-arms it by calling `setBuffer(buf, offset)` from
739
+ * inside the callback, a new installation like any other; a bare return must
740
+ * not do it implicitly, or the leading bytes would be capacity the rest of the
741
+ * stream could never use and the two shapes would be indistinguishable.
742
+ */
495
743
  flush() {
744
+ this.drain(0);
745
+ }
746
+ /**
747
+ * {@link flush}, plus the number of contiguous bytes the caller wants at the
748
+ * cursor afterwards — `0` when it wants none in particular.
749
+ *
750
+ * Split out from the public `flush` so the figure cannot be invented by a
751
+ * caller: it is the encoder's own reserve request, and a sink that sizes a
752
+ * replacement from it (`growingOStream`) has to be able to trust it. Everything
753
+ * else is identical, including that a sink which returns without installing has
754
+ * *copied* and the cursor resumes at `0`.
755
+ */
756
+ drain(needed) {
496
757
  if (this.flushSink && this.pos > this.start) {
497
- this.flushSink(this.buf.subarray(this.start, this.pos));
498
- this.pos = this.start;
758
+ const installed = this.installs;
759
+ this.flushSink(this.buf, this.start, this.pos, needed);
760
+ if (this.installs === installed) {
761
+ this.start = 0;
762
+ this.origin = 0;
763
+ this.pos = 0;
764
+ }
499
765
  }
500
766
  }
501
767
  /**
@@ -505,14 +771,42 @@ var OStream = class {
505
771
  * without interruption. `offset` reserves space at the front of the new
506
772
  * buffer. Any not-yet-flushed bytes in the old buffer are dropped, so
507
773
  * {@link flush} first (the flush callback fires before you swap).
774
+ *
775
+ * Every call is a **new installation**, and its `offset` applies to the unit
776
+ * it begins and is consumed when that unit is flushed (CORELIB_PLAN §5.1).
777
+ * Passing the buffer the encoder already has is an installation like any
778
+ * other: that is how a sink gets header room in *every* flushed unit — one
779
+ * framing header per packet — where returning bare would resume at `0`.
780
+ *
781
+ * On a stream that has a flush sink the new buffer must leave at least
782
+ * {@link MIN_OUTPUT_BUFFER} usable bytes (`buffer.length - offset`); a smaller
783
+ * one is rejected here, with {@link SofabErrorCode.Argument}, leaving the
784
+ * encoder on the buffer it already had. A sink-less stream has no minimum.
785
+ *
786
+ * `carried` says how many bytes **immediately before `offset`** the
787
+ * replacement already holds of *this* message — normally `0`, because a fresh
788
+ * buffer holds none. A caller that keeps the whole message in one growing
789
+ * store passes the length it copied across, so {@link bytes} keeps reporting
790
+ * the message rather than only the piece written after the swap. It changes
791
+ * nothing on the wire and nothing about flushing: the next flush still begins
792
+ * at `offset`. `growingOStream` is the caller this exists for; it is the
793
+ * §5.1.5 handover, not a growth hook inside the encoder (§6.6).
794
+ *
795
+ * The accumulating stream `growingOStream` builds is an ordinary streaming
796
+ * stream, so this works on it too, and means exactly what it means anywhere
797
+ * else: the not-yet-flushed bytes in the old buffer are dropped, encoding
798
+ * continues into yours, and its sink takes over growing *that* one from the
799
+ * next flush — reserve offset included, since what it copies out is the
800
+ * message rather than the buffer. Encode into a plain
801
+ * `new OStream(buffer, offset, flush?)` to keep the buffer yours instead.
508
802
  */
509
- setBuffer(buffer, offset = 0) {
510
- if (offset < 0 || offset > buffer.length) {
511
- throw argumentError(`offset ${offset} out of range`);
512
- }
803
+ setBuffer(buffer, offset = 0, carried = 0) {
804
+ checkHandover(buffer, offset, this.flushSink !== void 0, carried);
513
805
  this.buf = buffer;
514
806
  this.start = offset;
807
+ this.origin = offset - carried;
515
808
  this.pos = offset;
809
+ this.installs++;
516
810
  }
517
811
  /**
518
812
  * Rewind the encoder to empty, reusing the existing buffer. Lets a caller pool
@@ -520,7 +814,8 @@ var OStream = class {
520
814
  * encode. Any view previously returned by {@link bytes} is invalidated.
521
815
  */
522
816
  reset() {
523
- this.pos = this.start;
817
+ this.pos = this.origin;
818
+ this.start = this.origin;
524
819
  this.depth = 0;
525
820
  this.nPending = 0;
526
821
  }
@@ -533,9 +828,11 @@ var OStream = class {
533
828
  return;
534
829
  }
535
830
  const v = toBigInt(value);
536
- if (!inU64(v)) throw argumentError(`unsigned value ${v} out of 64-bit range`);
831
+ if (!splitU64(v)) throw argumentError(`unsigned value ${v} out of 64-bit range`);
832
+ const lo = S_U32[LO];
833
+ const hi = S_U32[HI];
537
834
  this.header(id, WireType.Unsigned);
538
- this.putVarint(v);
835
+ this.putVarintLoHi(lo, hi);
539
836
  }
540
837
  /** Write a signed integer field (zig-zag encoded). */
541
838
  writeSigned(id, value) {
@@ -545,9 +842,44 @@ var OStream = class {
545
842
  return;
546
843
  }
547
844
  const v = toBigInt(value);
548
- if (!inI64(v)) throw argumentError(`signed value ${v} out of 64-bit range`);
845
+ if (!splitI64(v)) throw argumentError(`signed value ${v} out of 64-bit range`);
846
+ const lo = S_U32[LO];
847
+ const hi = S_U32[HI];
848
+ const sgn = -(hi >>> 31) >>> 0;
849
+ const zLo = (lo << 1 >>> 0 ^ sgn) >>> 0;
850
+ const zHi = ((hi << 1 | lo >>> 31) >>> 0 ^ sgn) >>> 0;
851
+ this.header(id, WireType.Signed);
852
+ this.putVarintLoHi(zLo, zHi);
853
+ }
854
+ /**
855
+ * Write an unsigned 64-bit scalar from a {@link Long} — the `bigint`-free twin
856
+ * of {@link writeUnsigned}, and the scalar counterpart of
857
+ * {@link writeUnsignedArrayLong}. Produces the identical wire.
858
+ *
859
+ * There is no range check and no scratch round-trip: a `Long` *is* two 32-bit
860
+ * halves, so it is in the `uint64` domain by construction — which is the whole
861
+ * of what `splitU64` decides for a `number | bigint`. The halves go straight
862
+ * into the varint writer, so nothing is allocated per value. Nothing needs
863
+ * copying out ahead of `header` either, for the same reason the array writers
864
+ * do not: the halves come off a caller-owned immutable `Long`, not the shared
865
+ * scratch a re-entrant flush sink could overwrite.
866
+ */
867
+ writeUnsignedLong(id, value) {
868
+ this.header(id, WireType.Unsigned);
869
+ this.putVarintLoHi(value.low, value.high);
870
+ }
871
+ /**
872
+ * Write a signed 64-bit scalar (zig-zag) from a {@link Long} — the
873
+ * `bigint`-free twin of {@link writeSigned}, and the scalar counterpart of
874
+ * {@link writeSignedArrayLong}. Zig-zag `(n << 1) ^ (n >> 63)` is computed on
875
+ * the lo/hi pair, so the varint goes out at its exact size (a fixed caller
876
+ * buffer must not see a 10-byte demand for a 2-byte field) and no `bigint` is
877
+ * created. A `Long` carries exactly 64 bits, so as in {@link writeUnsignedLong}
878
+ * there is nothing left to range-check.
879
+ */
880
+ writeSignedLong(id, value) {
549
881
  this.header(id, WireType.Signed);
550
- this.putVarint(zigzagEncode(v));
882
+ this.putZigzagVarintLoHi(value.low, value.high);
551
883
  }
552
884
  /** Write a boolean field (encoded as the unsigned value 0 or 1). */
553
885
  writeBoolean(id, value) {
@@ -557,69 +889,161 @@ var OStream = class {
557
889
  /** Write an IEEE-754 32-bit float field. */
558
890
  writeFp32(id, value) {
559
891
  this.fixlenHead(id, 4, FixlenSubtype.Fp32);
560
- this.ensure(4);
561
- this.pos = packFp32(this.buf, this.pos, value);
892
+ this.putFp32(value);
893
+ }
894
+ /**
895
+ * Write an fp32 field from its raw wire bits — the 4 little-endian payload
896
+ * bytes as one 32-bit word, which is exactly what {@link Visitor.fp32} delivers
897
+ * as `bits`.
898
+ *
899
+ * This is the re-encode half of the bit-exactness rule (CORELIB_PLAN §6.5). A JS
900
+ * `number` is a 64-bit double, and widening an fp32 **signaling** NaN into one
901
+ * quiets it, so re-encoding through {@link writeFp32} cannot reproduce such a
902
+ * payload; the bits go out verbatim here, so decode → re-encode is byte-for-byte
903
+ * for every fp32 value, sNaN included. §6.5 requires this path of every
904
+ * double-only target, and names it: "a 32-bit bits accessor".
905
+ */
906
+ writeFp32Bits(id, bits) {
907
+ this.fixlenHead(id, 4, FixlenSubtype.Fp32);
908
+ this.putFp32Bits(bits >>> 0);
562
909
  }
563
910
  /** Write an IEEE-754 64-bit double field. */
564
911
  writeFp64(id, value) {
565
912
  this.fixlenHead(id, 8, FixlenSubtype.Fp64);
566
- this.ensure(8);
567
- this.pos = packFp64(this.buf, this.pos, value);
913
+ this.putFp64(value);
568
914
  }
569
915
  /** Write a UTF-8 string field. */
570
916
  writeString(id, text) {
571
- if (this.canGrow) {
572
- const byteLen = utf8Length(text);
573
- if (byteLen > FIXLEN_MAX) {
574
- throw argumentError(`fixlen length ${byteLen} exceeds ${FIXLEN_MAX}`);
917
+ const n = text.length;
918
+ let a = 0;
919
+ while (a < n && text.charCodeAt(a) < 128) a++;
920
+ if (a === n) {
921
+ if (n > FIXLEN_MAX) {
922
+ throw argumentError(`fixlen length ${n} exceeds ${FIXLEN_MAX}`);
923
+ }
924
+ this.fixlenHead(id, n, FixlenSubtype.String);
925
+ if (this.reserveBulk(n)) {
926
+ const buf = this.buf;
927
+ const p = this.pos;
928
+ for (let k = 0; k < n; k++) buf[p + k] = text.charCodeAt(k);
929
+ this.pos = p + n;
930
+ return;
575
931
  }
576
- this.fixlenHead(id, byteLen, FixlenSubtype.String);
577
- this.ensure(byteLen);
932
+ utf8WriteSink(text, this);
933
+ return;
934
+ }
935
+ const byteLen = utf8Length(text);
936
+ if (byteLen > FIXLEN_MAX) {
937
+ throw argumentError(`fixlen length ${byteLen} exceeds ${FIXLEN_MAX}`);
938
+ }
939
+ this.fixlenHead(id, byteLen, FixlenSubtype.String);
940
+ if (this.reserveBulk(byteLen)) {
578
941
  this.pos = utf8Write(text, this.buf, this.pos);
579
942
  return;
580
943
  }
581
- this.writeFixlen(id, encodeUtf8(text), FixlenSubtype.String);
944
+ utf8WriteSink(text, this);
582
945
  }
583
946
  /** Write a blob (arbitrary bytes) field. */
584
947
  writeBlob(id, data) {
585
948
  this.writeFixlen(id, data, FixlenSubtype.Blob);
586
949
  }
587
- /** Write a fixed-length field of the given subtype from raw bytes. */
950
+ /**
951
+ * Write a fixed-length field of the given subtype from raw bytes.
952
+ *
953
+ * This is the byte-level entry point — the one writer that takes the subtype
954
+ * from the caller rather than picking it — so the payload is checked
955
+ * **against that subtype** before a byte is written, and it cannot emit a
956
+ * `fixlen_word` a conformant decoder must reject (`ARGUMENT`, §6.3):
957
+ *
958
+ * * subtypes `0x4`–`0x7` are **reserved** — a decoder must treat a field
959
+ * carrying one as malformed (`INVALID`, §4.6/§5.2);
960
+ * * `Fp32` / `Fp64` payloads are **exactly** 4 / 8 bytes — any other declared
961
+ * length for those subtypes is malformed, rejected the moment the word is
962
+ * read (§4.6);
963
+ * * `String` / `Blob` take any length up to `FIXLEN_MAX`.
964
+ *
965
+ * The typed writers ({@link writeFp32}, {@link writeFp64},
966
+ * {@link writeString}) are correct by construction and go straight to the
967
+ * header; only {@link writeBlob}, whose subtype is unconstrained anyway,
968
+ * shares this path.
969
+ */
588
970
  writeFixlen(id, data, subtype) {
589
- if (data.length > FIXLEN_MAX) {
971
+ if (subtype >>> 0 > FixlenSubtype.Blob || !Number.isInteger(subtype)) {
972
+ throw argumentError(`fixlen subtype ${subtype} is reserved (\xA74.6: 0x0..0x3)`);
973
+ }
974
+ if (subtype === FixlenSubtype.Fp32 || subtype === FixlenSubtype.Fp64) {
975
+ const want = subtype === FixlenSubtype.Fp32 ? 4 : 8;
976
+ if (data.length !== want) {
977
+ throw argumentError(
978
+ `fixlen ${subtype === FixlenSubtype.Fp32 ? "fp32" : "fp64"} payload must be exactly ${want} bytes, got ${data.length}`
979
+ );
980
+ }
981
+ } else if (data.length > FIXLEN_MAX) {
590
982
  throw argumentError(`fixlen length ${data.length} exceeds ${FIXLEN_MAX}`);
591
983
  }
592
984
  this.fixlenHead(id, data.length, subtype);
593
985
  this.writeRaw(data);
594
986
  }
595
987
  // --- arrays -------------------------------------------------------------
596
- /** Write an array of unsigned integers (each a varint). */
988
+ /**
989
+ * Write an array of unsigned integers (each a varint).
990
+ *
991
+ * **The bulk kernel writes a whole array in one pass and cannot flush**, so it
992
+ * runs only where everything is known to fit. Three cases, and none of them
993
+ * asks the source how wide its elements are:
994
+ *
995
+ * * **block mode** (no sink) — the buffer is meant to hold the whole message,
996
+ * so the kernel always runs and the buffer's length is the bound, checked
997
+ * after the fact. A message that does not fit is `BUFFER_FULL`, which is
998
+ * precisely this mode's answer.
999
+ * * **a stream with room** — `VARINT_MAX_BYTES` per element is the true
1000
+ * worst case for any 64-bit value, and a *growing* stream always satisfies
1001
+ * it because it grows to whatever is asked.
1002
+ * * **a chunk too small for that** — fill it to the last byte, hand it over,
1003
+ * carry on, splitting an element where it falls. That is not a fallback but
1004
+ * the mode's contract: `MIN_OUTPUT_BUFFER` is 1.
1005
+ *
1006
+ * What is gone is the fourth case, which asked `values.constructor` for a
1007
+ * narrower bound so the kernel would run on a tightly-sized chunk. `constructor`
1008
+ * is an ordinary property, an `ArrayLike` can claim any width, and a wrong
1009
+ * answer silently truncated the message — §5.1's "partial output handed back as
1010
+ * complete". The block mode now reaches the kernel without needing the number
1011
+ * at all, which is where a tightly-sized buffer actually lives.
1012
+ */
597
1013
  writeUnsignedArray(id, values) {
598
1014
  this.arrayHead(id, WireType.ArrayUnsigned, values.length);
599
- if (this.canGrow) {
600
- this.ensure(values.length * VARINT_MAX_BYTES);
1015
+ if (this.flushSink === void 0) {
1016
+ this.pos = this.bulkEnd(
1017
+ this.kernel.encodeUnsignedVarints(values, this.buf, this.pos),
1018
+ values.length
1019
+ );
1020
+ } else if (this.reserveBulk(values.length * VARINT_MAX_BYTES)) {
601
1021
  this.pos = this.kernel.encodeUnsignedVarints(values, this.buf, this.pos);
602
1022
  } else {
603
1023
  for (let i = 0; i < values.length; i++) {
604
1024
  const v = toBigInt(values[i]);
605
- if (!inU64(v)) throw argumentError(`unsigned value ${v} out of range`);
606
- this.ensure(VARINT_MAX_BYTES);
607
- this.pos = encodeVarint(v, this.buf, this.pos);
1025
+ if (!splitU64(v)) throw argumentError(`unsigned value ${v} out of range`);
1026
+ const lo = S_U32[LO];
1027
+ const hi = S_U32[HI];
1028
+ this.putVarintLoHi(lo, hi);
608
1029
  }
609
1030
  }
610
1031
  }
611
- /** Write an array of signed integers (each zig-zag + varint). */
1032
+ /** Write an array of signed integers (each zig-zag + varint). See {@link writeUnsignedArray}. */
612
1033
  writeSignedArray(id, values) {
613
1034
  this.arrayHead(id, WireType.ArraySigned, values.length);
614
- if (this.canGrow) {
615
- this.ensure(values.length * VARINT_MAX_BYTES);
1035
+ if (this.flushSink === void 0) {
1036
+ this.pos = this.bulkEnd(
1037
+ this.kernel.encodeSignedVarints(values, this.buf, this.pos),
1038
+ values.length
1039
+ );
1040
+ } else if (this.reserveBulk(values.length * VARINT_MAX_BYTES)) {
616
1041
  this.pos = this.kernel.encodeSignedVarints(values, this.buf, this.pos);
617
1042
  } else {
618
1043
  for (let i = 0; i < values.length; i++) {
619
1044
  const v = toBigInt(values[i]);
620
- if (!inI64(v)) throw argumentError(`signed value ${v} out of range`);
621
- this.ensure(VARINT_MAX_BYTES);
622
- this.pos = encodeVarint(zigzagEncode(v), this.buf, this.pos);
1045
+ if (!splitI64(v)) throw argumentError(`signed value ${v} out of range`);
1046
+ this.putZigzagVarintLoHi(S_U32[LO], S_U32[HI]);
623
1047
  }
624
1048
  }
625
1049
  }
@@ -630,14 +1054,20 @@ var OStream = class {
630
1054
  */
631
1055
  writeUnsignedArrayLong(id, values) {
632
1056
  this.arrayHead(id, WireType.ArrayUnsigned, values.length);
633
- this.ensure(values.length * VARINT_MAX_BYTES);
634
- let pos = this.pos;
635
- const buf = this.buf;
636
- for (let i = 0; i < values.length; i++) {
637
- const v = values[i];
638
- pos = encodeVarintLoHi(v.low, v.high, buf, pos);
1057
+ if (this.reserveBulk(values.length * VARINT_MAX_BYTES)) {
1058
+ let pos = this.pos;
1059
+ const buf = this.buf;
1060
+ for (let i = 0; i < values.length; i++) {
1061
+ const v = values[i];
1062
+ pos = encodeVarintLoHi(v.low, v.high, buf, pos);
1063
+ }
1064
+ this.pos = pos;
1065
+ } else {
1066
+ for (let i = 0; i < values.length; i++) {
1067
+ const v = values[i];
1068
+ this.putVarintLoHi(v.low, v.high);
1069
+ }
639
1070
  }
640
- this.pos = pos;
641
1071
  }
642
1072
  /**
643
1073
  * Write a signed 64-bit array (zig-zag) from {@link Long}[] — the `bigint`-free
@@ -645,32 +1075,33 @@ var OStream = class {
645
1075
  */
646
1076
  writeSignedArrayLong(id, values) {
647
1077
  this.arrayHead(id, WireType.ArraySigned, values.length);
648
- this.ensure(values.length * VARINT_MAX_BYTES);
649
- let pos = this.pos;
650
- const buf = this.buf;
651
- for (let i = 0; i < values.length; i++) {
652
- const v = values[i];
653
- const lo = v.low;
654
- const hi = v.high;
655
- const sgn = -(hi >>> 31) >>> 0;
656
- const zLo = (lo << 1 >>> 0 ^ sgn) >>> 0;
657
- const zHi = ((hi << 1 | lo >>> 31) >>> 0 ^ sgn) >>> 0;
658
- pos = encodeVarintLoHi(zLo, zHi, buf, pos);
1078
+ if (this.reserveBulk(values.length * VARINT_MAX_BYTES)) {
1079
+ let pos = this.pos;
1080
+ const buf = this.buf;
1081
+ for (let i = 0; i < values.length; i++) {
1082
+ const v = values[i];
1083
+ pos = encodeZigzagVarintLoHi(v.low, v.high, buf, pos);
1084
+ }
1085
+ this.pos = pos;
1086
+ } else {
1087
+ for (let i = 0; i < values.length; i++) {
1088
+ const v = values[i];
1089
+ this.putZigzagVarintLoHi(v.low, v.high);
1090
+ }
659
1091
  }
660
- this.pos = pos;
661
1092
  }
662
1093
  /** Write an array of IEEE-754 32-bit floats. */
663
1094
  writeFp32Array(id, values) {
1095
+ if (values instanceof Float32Array) {
1096
+ this.writeFp32Words(id, values);
1097
+ return;
1098
+ }
664
1099
  this.arrayHead(id, WireType.ArrayFixlen, values.length);
665
1100
  this.putVarintNum(4 * 8 + FixlenSubtype.Fp32);
666
- if (this.canGrow) {
667
- this.ensure(values.length * 4);
1101
+ if (this.reserveBulk(values.length * 4)) {
668
1102
  this.pos = this.kernel.packFp32Array(values, this.buf, this.pos);
669
1103
  } else {
670
- for (let i = 0; i < values.length; i++) {
671
- this.ensure(4);
672
- this.pos = packFp32(this.buf, this.pos, values[i]);
673
- }
1104
+ for (let i = 0; i < values.length; i++) this.putFp32(values[i]);
674
1105
  }
675
1106
  }
676
1107
  /**
@@ -694,14 +1125,10 @@ var OStream = class {
694
1125
  writeFp64Array(id, values) {
695
1126
  this.arrayHead(id, WireType.ArrayFixlen, values.length);
696
1127
  this.putVarintNum(8 * 8 + FixlenSubtype.Fp64);
697
- if (this.canGrow) {
698
- this.ensure(values.length * 8);
1128
+ if (this.reserveBulk(values.length * 8)) {
699
1129
  this.pos = this.kernel.packFp64Array(values, this.buf, this.pos);
700
1130
  } else {
701
- for (let i = 0; i < values.length; i++) {
702
- this.ensure(8);
703
- this.pos = packFp64(this.buf, this.pos, values[i]);
704
- }
1131
+ for (let i = 0; i < values.length; i++) this.putFp64(values[i]);
705
1132
  }
706
1133
  }
707
1134
  // --- sequences ----------------------------------------------------------
@@ -725,7 +1152,7 @@ var OStream = class {
725
1152
  if (this.depth >= MAX_DEPTH) {
726
1153
  throw argumentError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
727
1154
  }
728
- if (id < 0 || id > ID_MAX || !Number.isInteger(id)) {
1155
+ if (id >>> 0 !== id || id > ID_MAX) {
729
1156
  throw argumentError(`field id ${id} out of range 0..${ID_MAX}`);
730
1157
  }
731
1158
  this.pending[this.nPending++] = id;
@@ -783,15 +1210,230 @@ var OStream = class {
783
1210
  if (this.depth > 0) this.depth--;
784
1211
  }
785
1212
  // --- internals ----------------------------------------------------------
786
- /** Ensure exactly `value`'s varint size, then write it (bigint path). */
787
- putVarint(value) {
788
- this.ensure(varintSize(value));
789
- this.pos = encodeVarint(value, this.buf, this.pos);
790
- }
791
1213
  /** Ensure exactly `value`'s varint size, then write it (number fast path). */
792
1214
  putVarintNum(value) {
793
- this.ensure(varintSizeNum(value));
794
- this.pos = encodeVarintNum(value, this.buf, this.pos);
1215
+ const pos = this.pos;
1216
+ if (value < 128 && pos < this.buf.length) {
1217
+ this.buf[pos] = value;
1218
+ this.pos = pos + 1;
1219
+ return;
1220
+ }
1221
+ this.putVarintNumSlow(value);
1222
+ }
1223
+ /**
1224
+ * The multi-byte / needs-room tail of {@link putVarintNum}, kept in its own
1225
+ * method so the single-byte test above stays small enough for the JIT to
1226
+ * inline into every `header` / `fixlenHead` / `arrayHead` call site.
1227
+ */
1228
+ putVarintNumSlow(value) {
1229
+ if (this.tryEnsure(varintSizeNum(value))) {
1230
+ this.pos = encodeVarintNum(value, this.buf, this.pos);
1231
+ return;
1232
+ }
1233
+ while (value > 127) {
1234
+ this.putByte(value % 128 | 128);
1235
+ value = Math.floor(value / 128);
1236
+ }
1237
+ this.putByte(value);
1238
+ }
1239
+ /**
1240
+ * Write a 64-bit value, held as two 32-bit halves, as a varint.
1241
+ *
1242
+ * Deliberately just a bounds check and two calls: this is what the array
1243
+ * loops call per element, and it only pays its way while it is small enough
1244
+ * for the JIT to inline. Spelling the drain case out here instead — three
1245
+ * more lines — cost 16-19% on an array streamed through a 32/64-byte buffer,
1246
+ * where most elements take the fast path and want it inlined.
1247
+ */
1248
+ putVarintLoHi(lo, hi) {
1249
+ if (this.buf.length - this.pos >= VARINT_MAX_BYTES) {
1250
+ this.pos = encodeVarintLoHi(lo, hi, this.buf, this.pos);
1251
+ return;
1252
+ }
1253
+ this.putVarintLoHiSlow(lo, hi);
1254
+ }
1255
+ /**
1256
+ * A full buffer that is nonetheless wide enough to hold any varint: drain it
1257
+ * and retry the worst case, which is exactly what `ensure(VARINT_MAX_BYTES)`
1258
+ * did before §5.1 and is still the common streaming case. Sizing the value
1259
+ * first instead — the narrow-buffer path in {@link putVarintLoHiTight} — cost
1260
+ * +125 instructions on every element of an array streamed through a
1261
+ * one-element-wide buffer.
1262
+ */
1263
+ putVarintLoHiSlow(lo, hi) {
1264
+ if (this.buf.length - this.start >= VARINT_MAX_BYTES) {
1265
+ this.flush();
1266
+ if (this.buf.length - this.pos >= VARINT_MAX_BYTES) {
1267
+ this.pos = encodeVarintLoHi(lo, hi, this.buf, this.pos);
1268
+ return;
1269
+ }
1270
+ }
1271
+ this.putVarintLoHiTight(lo, hi);
1272
+ }
1273
+ /**
1274
+ * Zig-zag {@link putVarintLoHi}: `(n << 1) ^ (n >> 63)` on the halves. Keeps
1275
+ * its own worst-case fast path so the signed array loop reaches the combined
1276
+ * zig-zag-and-encode writer directly, exactly as it did before; the drain and
1277
+ * narrow-buffer cases are the same for both signs, so they are shared.
1278
+ */
1279
+ putZigzagVarintLoHi(lo, hi) {
1280
+ if (this.buf.length - this.pos >= VARINT_MAX_BYTES) {
1281
+ this.pos = encodeZigzagVarintLoHi(lo, hi, this.buf, this.pos);
1282
+ return;
1283
+ }
1284
+ const sgn = -(hi >>> 31) >>> 0;
1285
+ this.putVarintLoHi(
1286
+ (lo << 1 >>> 0 ^ sgn) >>> 0,
1287
+ ((hi << 1 | lo >>> 31) >>> 0 ^ sgn) >>> 0
1288
+ );
1289
+ }
1290
+ /**
1291
+ * The narrow-buffer tail of {@link putVarintLoHi}: the buffer could not hold a
1292
+ * worst-case varint even empty. Sizing the value exactly keeps such a buffer
1293
+ * from flushing for room it does not need; only when it cannot hold the varint
1294
+ * *at all* does the value get split across flushes, seven bits at a time.
1295
+ */
1296
+ putVarintLoHiTight(lo, hi) {
1297
+ lo >>>= 0;
1298
+ hi >>>= 0;
1299
+ if (this.tryEnsure(varintSizeLoHi(lo, hi))) {
1300
+ this.pos = encodeVarintLoHi(lo, hi, this.buf, this.pos);
1301
+ return;
1302
+ }
1303
+ for (; ; ) {
1304
+ const more = hi !== 0 || lo > 127;
1305
+ this.putByte(more ? lo & 127 | 128 : lo);
1306
+ if (!more) return;
1307
+ lo = (lo >>> 7 | hi << 25) >>> 0;
1308
+ hi >>>= 7;
1309
+ }
1310
+ }
1311
+ /**
1312
+ * Write the 4 little-endian bytes of an fp32 (§4.6).
1313
+ *
1314
+ * Through the shared scratch, not a `DataView` over the buffer: building that
1315
+ * handle costs ~129 ns against the ~2 ns it saves on one value (§6.6.2 allows the
1316
+ * handle, arithmetic forbids it here). The bulk array path amortizes one over the
1317
+ * whole run instead — see the kernel.
1318
+ */
1319
+ putFp32(value) {
1320
+ if (this.buf.length - this.pos >= 4) {
1321
+ this.pos = packFp32(this.buf, this.pos, value);
1322
+ return;
1323
+ }
1324
+ this.putFp32Slow(value);
1325
+ }
1326
+ putFp32Slow(value) {
1327
+ if (this.tryEnsure(4)) {
1328
+ this.pos = packFp32(this.buf, this.pos, value);
1329
+ return;
1330
+ }
1331
+ this.putFp32Bits(fp32Bits(value));
1332
+ }
1333
+ /**
1334
+ * Write the 4 little-endian bytes of an fp32 held as a 32-bit word (§4.6) — the
1335
+ * path {@link OStream.writeFp32Bits} takes, and the tail of
1336
+ * {@link OStream.putFp32Slow}.
1337
+ */
1338
+ putFp32Bits(bits) {
1339
+ if (this.buf.length - this.pos >= 4) {
1340
+ const buf = this.buf;
1341
+ let p = this.pos;
1342
+ buf[p++] = bits & 255;
1343
+ buf[p++] = bits >>> 8 & 255;
1344
+ buf[p++] = bits >>> 16 & 255;
1345
+ buf[p++] = bits >>> 24;
1346
+ this.pos = p;
1347
+ return;
1348
+ }
1349
+ this.putByte(bits & 255);
1350
+ this.putByte(bits >>> 8 & 255);
1351
+ this.putByte(bits >>> 16 & 255);
1352
+ this.putByte(bits >>> 24);
1353
+ }
1354
+ /**
1355
+ * {@link writeFp32Array} for a `Float32Array` source: its own 32-bit words go
1356
+ * out, never a `number`, which is what keeps a signaling NaN intact (§6.5) —
1357
+ * on the bulk path (the kernel copies words too) and on the streamed one alike,
1358
+ * so a small buffer produces the one-shot bytes (§5.1.4).
1359
+ *
1360
+ * Kept to the header and the bulk call, the streaming loop in its own method:
1361
+ * with the loop in this body an 8-element one-shot array cost +416 Ir/op
1362
+ * (+8.8%) over the pre-#185 encoder, split like this +1.1% (Callgrind).
1363
+ */
1364
+ writeFp32Words(id, values) {
1365
+ this.arrayHead(id, WireType.ArrayFixlen, values.length);
1366
+ this.putVarintNum(4 * 8 + FixlenSubtype.Fp32);
1367
+ if (this.reserveBulk(values.length * 4)) {
1368
+ this.pos = this.kernel.packFp32Array(values, this.buf, this.pos);
1369
+ } else {
1370
+ this.putFp32Words(values);
1371
+ }
1372
+ }
1373
+ /**
1374
+ * The streamed half of {@link writeFp32Words}: one `Uint32Array` over the
1375
+ * source per call, the same handle the bulk kernel takes (§6.6.2); reading it gives each word's value whatever the
1376
+ * host byte order, and the shifts below store it little-endian. The words go
1377
+ * out one run per stretch of free buffer, with `buf`/`pos` in locals, and the
1378
+ * split points are {@link putFp32}'s: drain when fewer than 4 bytes are free,
1379
+ * split an element byte by byte only when the buffer itself is narrower than 4.
1380
+ */
1381
+ putFp32Words(values) {
1382
+ const w = new Uint32Array(values.buffer, values.byteOffset, values.length);
1383
+ const n = w.length;
1384
+ let i = 0;
1385
+ while (i < n) {
1386
+ if (this.buf.length - this.pos < 4 && !this.tryEnsure(4)) {
1387
+ this.putFp32Bits(w[i++]);
1388
+ continue;
1389
+ }
1390
+ const buf = this.buf;
1391
+ let p = this.pos;
1392
+ const end = Math.min(n, i + (buf.length - p >> 2));
1393
+ for (; i < end; i++) {
1394
+ const b = w[i];
1395
+ buf[p] = b & 255;
1396
+ buf[p + 1] = b >>> 8 & 255;
1397
+ buf[p + 2] = b >>> 16 & 255;
1398
+ buf[p + 3] = b >>> 24;
1399
+ p += 4;
1400
+ }
1401
+ this.pos = p;
1402
+ }
1403
+ }
1404
+ /** Write the 8 little-endian bytes of an fp64 (§4.6) — see {@link putFp32}. */
1405
+ putFp64(value) {
1406
+ if (this.buf.length - this.pos >= 8) {
1407
+ this.pos = packFp64(this.buf, this.pos, value);
1408
+ return;
1409
+ }
1410
+ this.putFp64Slow(value);
1411
+ }
1412
+ putFp64Slow(value) {
1413
+ if (this.tryEnsure(8)) {
1414
+ this.pos = packFp64(this.buf, this.pos, value);
1415
+ return;
1416
+ }
1417
+ const lo = fp64BitsLo(value);
1418
+ const hi = fp64BitsHi(value);
1419
+ this.putByte(lo & 255);
1420
+ this.putByte(lo >>> 8 & 255);
1421
+ this.putByte(lo >>> 16 & 255);
1422
+ this.putByte(lo >>> 24);
1423
+ this.putByte(hi & 255);
1424
+ this.putByte(hi >>> 8 & 255);
1425
+ this.putByte(hi >>> 16 & 255);
1426
+ this.putByte(hi >>> 24);
1427
+ }
1428
+ /**
1429
+ * Append one byte, draining to the sink first when the buffer is full.
1430
+ *
1431
+ * @internal Public only because {@link utf8WriteSink} writes through it — the
1432
+ * narrow-buffer string path (§5.1.3). Not part of the field-writing API.
1433
+ */
1434
+ putByte(b) {
1435
+ if (this.pos === this.buf.length) this.ensureSome(1);
1436
+ this.buf[this.pos++] = b;
795
1437
  }
796
1438
  /**
797
1439
  * Write a field header, the `(id << 3) | wireType` tag, as a varint.
@@ -810,7 +1452,7 @@ var OStream = class {
810
1452
  * {@link OStream.writeSequenceBeginLazy}, which writes no byte at all.
811
1453
  */
812
1454
  header(id, type) {
813
- if (id < 0 || id > ID_MAX || !Number.isInteger(id)) {
1455
+ if (id >>> 0 !== id || id > ID_MAX) {
814
1456
  throw argumentError(`field id ${id} out of range 0..${ID_MAX}`);
815
1457
  }
816
1458
  if (this.nPending !== 0) this.commitPending();
@@ -827,8 +1469,9 @@ var OStream = class {
827
1469
  commitPending() {
828
1470
  const n = this.nPending;
829
1471
  this.nPending = 0;
1472
+ const pending = this.pending;
830
1473
  for (let i = 0; i < n; i++) {
831
- this.putVarintNum(this.pending[i] * 8 + WireType.SequenceStart);
1474
+ this.putVarintNum(pending[i] * 8 + WireType.SequenceStart);
832
1475
  }
833
1476
  }
834
1477
  fixlenHead(id, length, subtype) {
@@ -842,332 +1485,335 @@ var OStream = class {
842
1485
  this.header(id, type);
843
1486
  this.putVarintNum(count);
844
1487
  }
845
- /** Copy `data` out, flushing/growing as needed (large payloads stay chunked). */
1488
+ /**
1489
+ * Copy `data` out, flushing/growing as needed (large payloads stay chunked).
1490
+ *
1491
+ * The whole-payload case — the common one, and the only one on a buffer sized
1492
+ * from `MAX_SIZE` or on the accumulator — is a single `set` of the caller's
1493
+ * array: a `memcpy`, and no view at all.
1494
+ *
1495
+ * **A payload split across flushes takes a per-piece view: a language-forced
1496
+ * handle under CORELIB_PLAN §6.6.2.** `TypedArray.set` is the only `memcpy`
1497
+ * this language exposes and it takes a *typed array* as its source, so copying a
1498
+ * *range* of one needs a `subarray` — §6.6.2's "the only way to name a region of
1499
+ * the caller's buffer is a wrapper over it". It has the two properties that
1500
+ * clause requires: it carries no message bytes (the storage is the caller's, on
1501
+ * both ends) and no wire number sizes it (a handle over a thousand bytes costs
1502
+ * what a handle over ten costs). The allocation-free alternative is a byte loop,
1503
+ * measured at 358 MB/s against 10,963 MB/s for `set`.
1504
+ *
1505
+ * It never leaves this method: `set` consumes it and no caller can reach it, so
1506
+ * §6.7's ban on exposing a value that outlives its callback is untouched — and so
1507
+ * is §5.1.6, which is why the copy happens at all rather than the payload being
1508
+ * handed to the sink. §6.6.2 asks the port to make such handles visible rather
1509
+ * than invisible: the README itemises it (§9.6) and `heap-free-codec.test.ts`
1510
+ * pins its count and kind.
1511
+ */
846
1512
  writeRaw(data) {
1513
+ const total = data.length;
847
1514
  let off = 0;
848
- while (off < data.length) {
849
- const room = this.ensureSome(data.length - off);
850
- this.buf.set(data.subarray(off, off + room), this.pos);
1515
+ while (off < total) {
1516
+ const room = this.ensureSome(total - off);
1517
+ if (off === 0 && room === total) {
1518
+ this.buf.set(data, this.pos);
1519
+ } else {
1520
+ this.buf.set(data.subarray(off, off + room), this.pos);
1521
+ }
851
1522
  this.pos += room;
852
1523
  off += room;
853
1524
  }
854
1525
  }
855
- /** Ensure `n` contiguous bytes are free at `pos`; returns `pos` for chaining. */
856
- ensure(n) {
857
- if (this.buf.length - this.pos >= n) return this.pos;
858
- this.flush();
859
- if (this.buf.length - this.pos >= n) return this.pos;
860
- if (this.canGrow) {
861
- this.growTo(this.pos + n);
862
- return this.pos;
863
- }
864
- throw bufferFullError(
865
- `output buffer full: need ${n} more bytes, have ${this.buf.length - this.pos}`
866
- );
1526
+ /**
1527
+ * Make room for `n` contiguous bytes at `pos` if the buffer can hold them at
1528
+ * all: `true` when it can (flushing or growing as needed), `false` when a
1529
+ * fixed caller buffer is simply smaller than `n` and the value must be split
1530
+ * across flushes instead — CORELIB_PLAN §5.1 puts the floor on the output
1531
+ * buffer at a single byte, so no write may demand a contiguous run.
1532
+ *
1533
+ * The one case that still fails is a buffer with no sink to drain to: there is
1534
+ * nowhere for a split to put the earlier bytes, so it reports BufferFull here,
1535
+ * before anything is written, exactly as {@link ensure} did.
1536
+ */
1537
+ tryEnsure(n) {
1538
+ if (this.buf.length - this.pos >= n) return true;
1539
+ this.drain(n);
1540
+ if (this.buf.length - this.pos >= n) return true;
1541
+ if (this.flushSink === void 0) {
1542
+ throw bufferFullError(
1543
+ `output buffer full: need ${n} more bytes, have ${this.buf.length - this.pos}`
1544
+ );
1545
+ }
1546
+ return false;
867
1547
  }
868
- /** Ensure *some* room (up to `want`); returns how many bytes are available. */
869
- ensureSome(want) {
870
- let room = this.buf.length - this.pos;
871
- if (room === 0) {
872
- this.flush();
873
- room = this.buf.length - this.pos;
874
- if (room === 0) {
875
- if (this.canGrow) {
876
- this.growTo(this.pos + want);
877
- room = this.buf.length - this.pos;
878
- } else {
879
- throw bufferFullError("output buffer full");
880
- }
881
- }
882
- }
883
- return Math.min(room, want);
884
- }
885
- growTo(needed) {
886
- let cap = this.buf.length * 2;
887
- if (cap < needed) cap = needed;
888
- const next = new Uint8Array(cap);
889
- next.set(this.buf.subarray(0, this.pos));
890
- this.buf = next;
1548
+ /**
1549
+ * Reserve `n` contiguous bytes for a bulk write — the whole payload of an
1550
+ * array or a string, written in one pass into a buffer that cannot move under
1551
+ * it. Every caller has an element-at-a-time route to fall back on when this
1552
+ * says no, producing the identical bytes, so a `false` here is never an error.
1553
+ *
1554
+ * The room already at the cursor counts on **any** buffer, which is what makes
1555
+ * the one-shot `new OStream(buf)` case — a caller buffer sized from the
1556
+ * schema's `MAX_SIZE`, the shape CORELIB_PLAN §5.1 puts first — the fast one:
1557
+ * without it a message encoded into a caller's own buffer took `TextEncoder`
1558
+ * for every string and the element loop for every array, measured at 1.9 µs
1559
+ * against 0.16 µs for the same five-field message.
1560
+ *
1561
+ * Beyond that room only a **sink** may be asked, and only by draining. Emptying
1562
+ * the buffer is the one thing that can produce room without an allocation
1563
+ * anywhere (§6.6), and it is legal wherever this is called — every caller has
1564
+ * just written a complete header, so the cursor is on a boundary between atomic
1565
+ * units (§5.1.3). A sink that installs a larger replacement (§5.1.5) therefore
1566
+ * re-opens the bulk route by itself, which is how the accumulating helper keeps
1567
+ * it. Nothing is *demanded*: a `false` sends the caller down its
1568
+ * element-at-a-time route, which produces the identical bytes, so a fixed
1569
+ * buffer too narrow for the worst case never turns into a spurious
1570
+ * `BUFFER_FULL`.
1571
+ */
1572
+ reserveBulk(n) {
1573
+ if (this.buf.length - this.pos >= n) return true;
1574
+ if (this.flushSink === void 0) return false;
1575
+ this.drain(n);
1576
+ return this.buf.length - this.pos >= n;
891
1577
  }
892
- };
893
-
894
- // src/decode/fast.ts
895
- var TWO32 = 4294967296;
896
- function decodeContiguous(buf, root, limits) {
897
- new FastDecoder(buf, limits).run(root);
898
- }
899
- var FastDecoder = class {
900
- constructor(buf, limits) {
901
- this.p = 0;
902
- // Last varint, as two unsigned 32-bit halves (see readVarint).
903
- this.lo = 0;
904
- this.hi = 0;
905
- this.buf = buf;
906
- this.n = buf.length;
907
- this.view = new DataView(buf.buffer, buf.byteOffset, buf.length);
908
- this.maxArrayCount = limits?.maxArrayCount ?? Infinity;
909
- this.maxStringLen = limits?.maxStringLen ?? Infinity;
910
- this.maxBlobLen = limits?.maxBlobLen ?? Infinity;
911
- }
912
- run(root) {
913
- const stack = [root];
914
- let top = root;
915
- while (this.p < this.n) {
916
- this.readVarint();
917
- const type = this.lo & 7;
918
- if (type === WireType.SequenceEnd) {
919
- if (stack.length <= 1) throw invalidMsgError("unbalanced sequence end");
920
- top.sequenceEnd?.();
921
- stack.pop();
922
- top = stack[stack.length - 1];
923
- continue;
924
- }
925
- const id = this.upper();
926
- if (id > ID_MAX) throw invalidMsgError(`field id ${id} out of range`);
927
- switch (type) {
928
- case WireType.Unsigned: {
929
- this.readVarint();
930
- top.unsigned?.(id, this.unsignedValue());
931
- break;
932
- }
933
- case WireType.Signed: {
934
- this.readVarint();
935
- top.signed?.(id, this.signedValue());
936
- break;
937
- }
938
- case WireType.Fixlen: {
939
- this.readVarint();
940
- const sub = this.lo & 7;
941
- const len = this.upper();
942
- if (sub > FixlenSubtype.Blob) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
943
- if (len > FIXLEN_MAX) throw invalidMsgError("fixlen length out of range");
944
- if (sub === FixlenSubtype.String && len > this.maxStringLen) {
945
- throw limitExceededError(`string length ${len} exceeds maxStringLen ${this.maxStringLen}`);
946
- }
947
- if (sub === FixlenSubtype.Blob && len > this.maxBlobLen) {
948
- throw limitExceededError(`blob length ${len} exceeds maxBlobLen ${this.maxBlobLen}`);
949
- }
950
- if (sub === FixlenSubtype.Fp32 || sub === FixlenSubtype.Fp64) {
951
- const want = sub === FixlenSubtype.Fp32 ? 4 : 8;
952
- if (len !== want) throw invalidMsgError("fixlen float length mismatch");
953
- if (sub === FixlenSubtype.Fp32) {
954
- const p = this.p;
955
- const value = this.readFp32();
956
- top.fp32?.(id, value, top.fp32Raw ? this.buf.subarray(p, p + 4) : void 0);
957
- } else {
958
- const value = this.readFp64();
959
- top.fp64?.(id, value);
960
- }
961
- } else {
962
- const chunk = this.take(len);
963
- if (sub === FixlenSubtype.String) top.string?.(id, len, 0, chunk);
964
- else top.blob?.(id, len, 0, chunk);
965
- }
966
- break;
967
- }
968
- case WireType.ArrayUnsigned: {
969
- const count = this.arrayCount();
970
- top.arrayBegin?.(id, ArrayKind.Unsigned, count);
971
- for (let i = 0; i < count; i++) {
972
- this.readVarint();
973
- top.arrayUnsigned?.(id, i, this.unsignedValue());
974
- }
975
- top.arrayEnd?.(id);
976
- break;
977
- }
978
- case WireType.ArraySigned: {
979
- const count = this.arrayCount();
980
- top.arrayBegin?.(id, ArrayKind.Signed, count);
981
- for (let i = 0; i < count; i++) {
982
- this.readVarint();
983
- top.arraySigned?.(id, i, this.signedValue());
984
- }
985
- top.arrayEnd?.(id);
986
- break;
987
- }
988
- case WireType.ArrayFixlen: {
989
- const count = this.arrayCount();
990
- this.readVarint();
991
- const sub = this.lo & 7;
992
- const size = this.upper();
993
- let kind;
994
- if (sub === FixlenSubtype.Fp32 && size === 4) kind = ArrayKind.Fp32;
995
- else if (sub === FixlenSubtype.Fp64 && size === 8) kind = ArrayKind.Fp64;
996
- else throw invalidMsgError("invalid fixlen array element type");
997
- top.arrayBegin?.(id, kind, count);
998
- if (kind === ArrayKind.Fp32) {
999
- const wantRaw = top.fp32Raw === true;
1000
- for (let i = 0; i < count; i++) {
1001
- const p = this.p;
1002
- const value = this.readFp32();
1003
- top.arrayFp32?.(id, i, value, wantRaw ? this.buf.subarray(p, p + 4) : void 0);
1004
- }
1005
- } else {
1006
- for (let i = 0; i < count; i++) {
1007
- const value = this.readFp64();
1008
- top.arrayFp64?.(id, i, value);
1009
- }
1010
- }
1011
- top.arrayEnd?.(id);
1012
- break;
1013
- }
1014
- case WireType.SequenceStart: {
1015
- if (stack.length - 1 >= MAX_DEPTH) {
1016
- throw invalidMsgError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
1017
- }
1018
- const child = top.sequenceBegin?.(id);
1019
- top = child ?? top;
1020
- stack.push(top);
1021
- break;
1022
- }
1023
- default:
1024
- throw invalidMsgError(`invalid wire type ${type}`);
1025
- }
1026
- }
1027
- if (stack.length > 1) throw incompleteError("truncated message: unbalanced sequence");
1028
- }
1029
- // --- field helpers ------------------------------------------------------
1030
- /** Read and validate an array count word (0..ARRAY_MAX; §4.7/§4.8). */
1031
- arrayCount() {
1032
- this.readVarint();
1033
- const count = this.num();
1034
- if (count > ARRAY_MAX) throw invalidMsgError("array count out of range");
1035
- if (count > this.maxArrayCount) {
1036
- throw limitExceededError(`array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`);
1037
- }
1038
- return count;
1039
- }
1040
- /** Hand back a zero-copy view of the next `len` bytes, advancing the cursor. */
1041
- take(len) {
1042
- const start = this.p;
1043
- const end = start + len;
1044
- if (end > this.n) throw incompleteError("truncated fixlen payload");
1045
- this.p = end;
1046
- return this.buf.subarray(start, end);
1047
- }
1048
- readFp32() {
1049
- const p = this.p;
1050
- if (p + 4 > this.n) throw incompleteError("truncated fp32");
1051
- this.p = p + 4;
1052
- return this.view.getFloat32(p, true);
1053
- }
1054
- readFp64() {
1055
- const p = this.p;
1056
- if (p + 8 > this.n) throw incompleteError("truncated fp64");
1057
- this.p = p + 8;
1058
- return this.view.getFloat64(p, true);
1059
- }
1060
- // --- varint reading -----------------------------------------------------
1061
- /** The last varint's full value as a `bigint` (64-bit fidelity). */
1062
- big() {
1063
- return this.hi === 0 ? BigInt(this.lo >>> 0) : BigInt(this.hi >>> 0) << 32n | BigInt(this.lo >>> 0);
1064
- }
1065
- /**
1066
- * The last varint as an unsigned value, number-first: a `number` when it fits
1067
- * exactly (`≤ 2^53-1` — all ids, u8..u32 and small u64s), a `bigint` only
1068
- * beyond that. Skips the per-value bigint allocation on the common path.
1069
- */
1070
- unsignedValue() {
1071
- const hi = this.hi >>> 0;
1072
- return hi <= 2097151 ? hi * TWO32 + (this.lo >>> 0) : this.big();
1073
- }
1074
- /** The last zig-zag varint as a signed value, number-first (see {@link unsignedValue}). */
1075
- signedValue() {
1076
- const hi = this.hi >>> 0;
1077
- if (hi <= 2097151) {
1078
- const r = hi * TWO32 + (this.lo >>> 0);
1079
- return r % 2 === 0 ? r / 2 : -(r + 1) / 2;
1578
+ /**
1579
+ * Commit the position a bulk kernel returned, in the **block** mode.
1580
+ *
1581
+ * Nothing reserves room in front of that kernel, and nothing could: the bytes an
1582
+ * array takes are only known once it is encoded, and the estimate that used to
1583
+ * stand in — the source's own `constructor`, which any object can set — silently
1584
+ * truncated the message when it was wrong (§5.1). Here the bound is the buffer's
1585
+ * own length, applied afterwards. The varint kernels stop writing at
1586
+ * `out.length` and keep counting (see {@link Kernel}), so a `pos` past the end
1587
+ * is the exact shortfall and nothing was written outside the buffer.
1588
+ *
1589
+ * This is the block mode's error and only its: the buffer is meant to hold the
1590
+ * whole message, so one that does not fit is exactly what `BUFFER_FULL` means.
1591
+ * The streaming mode never gets here, and `BUFFER_FULL` is unreachable there by
1592
+ * contract.
1593
+ */
1594
+ bulkEnd(pos, count) {
1595
+ if (pos > this.buf.length) {
1596
+ throw bufferFullError(
1597
+ `output buffer full: an array of ${count} elements needs ${pos - this.buf.length} more bytes`
1598
+ );
1080
1599
  }
1081
- return zigzagDecode(this.big());
1082
- }
1083
- /** The last varint's value as a JS number — exact for ids/lengths/counts. */
1084
- num() {
1085
- return this.hi * TWO32 + (this.lo >>> 0);
1086
- }
1087
- /** The last varint with its low 3 tag bits stripped (`value >> 3`). */
1088
- upper() {
1089
- return (this.hi >>> 0) * (TWO32 / 8) + (this.lo >>> 3);
1600
+ return pos;
1090
1601
  }
1091
1602
  /**
1092
- * Decode one LEB128 varint at the cursor into {@link lo} / {@link hi} (each an
1093
- * unsigned 32-bit half), advancing {@link p}. Throws on truncation or a value
1094
- * spilling past 64 bits (>10 bytes). Unrolled, number-only — no `bigint`.
1603
+ * Ensure `n` contiguous bytes are free at `pos`; returns `pos` for chaining.
1604
+ *
1605
+ * The only remaining caller is the one-byte sequence-end marker, which is
1606
+ * indivisible: there is no smaller piece to split it into, so a buffer that
1607
+ * cannot take it has nothing left to report but `BUFFER_FULL`.
1095
1608
  */
1096
- readVarint() {
1097
- const buf = this.buf;
1098
- const n = this.n;
1099
- let p = this.p;
1100
- let b;
1101
- let lo;
1102
- let hi = 0;
1103
- if (p >= n) throw incompleteError("truncated varint");
1104
- b = buf[p++];
1105
- lo = b & 127;
1106
- if (b < 128) return this.set(lo, 0, p);
1107
- if (p >= n) throw incompleteError("truncated varint");
1108
- b = buf[p++];
1109
- lo |= (b & 127) << 7;
1110
- if (b < 128) return this.set(lo, 0, p);
1111
- if (p >= n) throw incompleteError("truncated varint");
1112
- b = buf[p++];
1113
- lo |= (b & 127) << 14;
1114
- if (b < 128) return this.set(lo, 0, p);
1115
- if (p >= n) throw incompleteError("truncated varint");
1116
- b = buf[p++];
1117
- lo |= (b & 127) << 21;
1118
- if (b < 128) return this.set(lo, 0, p);
1119
- if (p >= n) throw incompleteError("truncated varint");
1120
- b = buf[p++];
1121
- lo |= (b & 15) << 28;
1122
- hi = b >> 4 & 7;
1123
- if (b < 128) return this.set(lo, hi, p);
1124
- if (p >= n) throw incompleteError("truncated varint");
1125
- b = buf[p++];
1126
- hi |= (b & 127) << 3;
1127
- if (b < 128) return this.set(lo, hi, p);
1128
- if (p >= n) throw incompleteError("truncated varint");
1129
- b = buf[p++];
1130
- hi |= (b & 127) << 10;
1131
- if (b < 128) return this.set(lo, hi, p);
1132
- if (p >= n) throw incompleteError("truncated varint");
1133
- b = buf[p++];
1134
- hi |= (b & 127) << 17;
1135
- if (b < 128) return this.set(lo, hi, p);
1136
- if (p >= n) throw incompleteError("truncated varint");
1137
- b = buf[p++];
1138
- hi |= (b & 127) << 24;
1139
- if (b < 128) return this.set(lo, hi, p);
1140
- if (p >= n) throw incompleteError("truncated varint");
1141
- b = buf[p++];
1142
- if ((b & 127) >> 1 !== 0) throw invalidMsgError("varint overflow");
1143
- hi |= (b & 127) << 31;
1144
- if (b < 128) return this.set(lo, hi, p);
1145
- throw invalidMsgError("varint overflow");
1609
+ ensure(n) {
1610
+ if (!this.tryEnsure(n)) {
1611
+ throw bufferFullError(
1612
+ `output buffer full: need ${n} more bytes, have ${this.buf.length - this.pos}`
1613
+ );
1614
+ }
1615
+ return this.pos;
1146
1616
  }
1147
- set(lo, hi, p) {
1148
- this.lo = lo;
1149
- this.hi = hi;
1150
- this.p = p;
1617
+ /** Ensure *some* room (up to `want`); returns how many bytes are available. */
1618
+ ensureSome(want) {
1619
+ let room = this.buf.length - this.pos;
1620
+ if (room === 0) {
1621
+ this.flush();
1622
+ room = this.buf.length - this.pos;
1623
+ if (room === 0) throw bufferFullError("output buffer full");
1624
+ }
1625
+ return Math.min(room, want);
1151
1626
  }
1152
1627
  };
1153
1628
 
1629
+ // src/encode/accumulate.ts
1630
+ var DEFAULT_CAPACITY = 256;
1631
+ var MIN_WINDOW = 16;
1632
+ var SLAB_BYTES = 8192;
1633
+ var SLAB_MAX_TAKE = SLAB_BYTES >>> 1;
1634
+ var slab = null;
1635
+ var slabUsed = 0;
1636
+ function accumulatorBuffer(n) {
1637
+ if (n > SLAB_MAX_TAKE) return new Uint8Array(n);
1638
+ let s = slab;
1639
+ if (s === null || s.length - slabUsed < n) {
1640
+ s = slab = new Uint8Array(SLAB_BYTES);
1641
+ slabUsed = 0;
1642
+ }
1643
+ const from = slabUsed;
1644
+ slabUsed = from + n;
1645
+ return s.subarray(from, slabUsed);
1646
+ }
1647
+ var ACCUMULATE = function(buffer, _start, end, needed = 0) {
1648
+ const used = this.bytesUsed;
1649
+ const window = needed > MIN_WINDOW ? needed : MIN_WINDOW;
1650
+ if (buffer.length - end >= window) {
1651
+ this.setBuffer(buffer, end, used);
1652
+ return;
1653
+ }
1654
+ let cap = buffer.length * 2;
1655
+ const want = used + window;
1656
+ if (cap < want) cap = want;
1657
+ const next = accumulatorBuffer(cap);
1658
+ next.set(buffer.subarray(end - used, end));
1659
+ this.setBuffer(next, used, used);
1660
+ };
1661
+ function growingOStream(initialCapacity = DEFAULT_CAPACITY) {
1662
+ if (!Number.isInteger(initialCapacity) || initialCapacity < 1) {
1663
+ throw argumentError(`initial capacity ${initialCapacity} must be a positive integer`);
1664
+ }
1665
+ return new OStream(accumulatorBuffer(initialCapacity), 0, ACCUMULATE);
1666
+ }
1667
+
1668
+ // src/decode/skip.ts
1669
+ var SKIP = Object.freeze({});
1670
+
1154
1671
  // src/decode/state.ts
1155
- var TWO322 = 4294967296;
1672
+ var TWO32 = 4294967296;
1673
+ var TYPED_CAPACITY = /* @__PURE__ */ new Map([
1674
+ [Uint8Array, { min: 0, max: 255 }],
1675
+ [Uint16Array, { min: 0, max: 65535 }],
1676
+ [Uint32Array, { min: 0, max: 4294967295 }],
1677
+ [Int8Array, { min: -128, max: 127 }],
1678
+ [Int16Array, { min: -32768, max: 32767 }],
1679
+ [Int32Array, { min: -2147483648, max: 2147483647 }],
1680
+ // The 64-bit pair is the accumulator's own range on both sides, so any bound a
1681
+ // caller can state fits: `min`/`max` here are the sentinels the fit test needs,
1682
+ // not numbers anything is compared against (the fill writes raw halves).
1683
+ [BigUint64Array, { min: 0, max: Number.POSITIVE_INFINITY }],
1684
+ [BigInt64Array, { min: Number.NEGATIVE_INFINITY, max: Number.POSITIVE_INFINITY }]
1685
+ ]);
1686
+ function typedCapacity(ctor) {
1687
+ if (ctor === Uint8Array) return CAP_U8;
1688
+ if (ctor === Int8Array) return CAP_I8;
1689
+ if (ctor === Uint16Array) return CAP_U16;
1690
+ if (ctor === Int16Array) return CAP_I16;
1691
+ if (ctor === Uint32Array) return CAP_U32;
1692
+ if (ctor === Int32Array) return CAP_I32;
1693
+ return TYPED_CAPACITY.get(ctor);
1694
+ }
1695
+ var CAP_U8 = TYPED_CAPACITY.get(Uint8Array);
1696
+ var CAP_I8 = TYPED_CAPACITY.get(Int8Array);
1697
+ var CAP_U16 = TYPED_CAPACITY.get(Uint16Array);
1698
+ var CAP_I16 = TYPED_CAPACITY.get(Int16Array);
1699
+ var CAP_U32 = TYPED_CAPACITY.get(Uint32Array);
1700
+ var CAP_I32 = TYPED_CAPACITY.get(Int32Array);
1701
+ function is64(d) {
1702
+ return d instanceof BigUint64Array || d instanceof BigInt64Array;
1703
+ }
1156
1704
  var DecoderState = class {
1157
- constructor(limits) {
1705
+ constructor(visitor = SKIP) {
1706
+ /**
1707
+ * Depth at which the current skipped subtree was opened, or `-1` when nothing
1708
+ * is being skipped. The scope that returned `false` from
1709
+ * {@link Visitor.sequenceBegin} closes at this depth, and skipping ends there.
1710
+ */
1711
+ this.skipFrom = -1;
1712
+ /**
1713
+ * **No receiver cap lives here** (§6.2.1). This decoder used to hold
1714
+ * `maxArrayCount` / `maxStringLen` / `maxBlobLen`, defaulted to the format
1715
+ * ceilings when the caller supplied none — which is precisely the shape §6.2.1
1716
+ * forbids: "a codec **MUST NOT** hold a limit of its own, **MUST NOT** supply a
1717
+ * default for one it was not given, **MUST NOT** read an omitted argument as
1718
+ * *unlimited*, and **MUST NOT** clamp to one", and "a format ceiling (§6.2)
1719
+ * reached because no cap was stated is the **format's** bound, not a receiver
1720
+ * cap, and a port **MUST NOT** present it as one". Reporting `LimitExceeded`
1721
+ * against `ARRAY_MAX`/`FIXLEN_MAX` did exactly that.
1722
+ *
1723
+ * The caps now have **one implementation** and it is not here: generated code
1724
+ * compares them in its own flat visitor, at `arrayBegin` / `fixlenBegin` — both
1725
+ * of which this decoder raises at the count / length header, before a byte of
1726
+ * payload is emitted and behind the MESSAGE_SPEC §7.3 tag test — and a wrapper
1727
+ * array's elements, which reach no visitor callback, are capped by the
1728
+ * `StringSeq` / `BlobSeq` collectors from bounds passed to their
1729
+ * constructors. §6.2.1: "A port whose codec offers the check **MUST NOT** also
1730
+ * emit it into the generated layer, and a port that enforces it in generated
1731
+ * code **MUST NOT** ask the codec to enforce it too."
1732
+ *
1733
+ * The format ceilings below (`ARRAY_MAX`, `FIXLEN_MAX`, `MAX_DEPTH`, the varint
1734
+ * bound) are *not* receiver caps and stay here: they bound what the wire may
1735
+ * express, and exceeding one is `INVALID` (§6.2).
1736
+ */
1737
+ /**
1738
+ * The id of every open sequence, indexed by the depth it was opened at, so a
1739
+ * scope close can name the sequence it closes. Fixed size, sized from
1740
+ * {@link MAX_DEPTH} at construction — the "fixed-size parse stack" §6.6.2
1741
+ * allows, and the reason nothing here grows a stack per message.
1742
+ *
1743
+ * A plain array rather than an `Int32Array`, and the difference is 3 µs: V8
1744
+ * keeps a typed array's bytes inside the JS heap only up to 64 bytes, so a
1745
+ * 255-slot one is an *external* allocation — measured at ~3.1 µs on Node 24,
1746
+ * against ~5 ns for this. That is paid per decoder, and the one-shot
1747
+ * {@link decode} builds a decoder per message, so it was the whole cost of
1748
+ * decoding a small one. Slots are written before they are read (a scope is
1749
+ * opened before it closes), so the array is never read holey.
1750
+ */
1751
+ this.seqIds = new Array(MAX_DEPTH);
1752
+ /** Number of nested sequences currently open — 0 at the root scope. */
1753
+ this.depth = 0;
1158
1754
  this.state = 0 /* Header */;
1159
- this.stack = [];
1160
- // current field
1755
+ /**
1756
+ * The terminal-refusal latch: the rejection this stream was stopped by, or
1757
+ * `null` while it is still healthy. **One latch, two codes** — §5.3.1 gives the
1758
+ * rule one implementation, and §6.3 makes both of these rejections terminal:
1759
+ *
1760
+ * - `INVALID_MSG` (§5.2.1: "malformed **regardless of what follows** … no —
1761
+ * terminal") — no later bytes can make malformed input valid;
1762
+ * - `LIMIT_EXCEEDED` (§6.3: "A **terminal**, receiver-local **policy**
1763
+ * rejection") — well-formed bytes the receiver's cap refuses.
1764
+ *
1765
+ * They are latched together and **kept apart by their code**, which is the
1766
+ * distinction §6.3 requires: a limit rejection "**MUST NOT** be reported as
1767
+ * `InvalidMessage`", so the code is re-raised as itself and each stays the
1768
+ * refusal it was.
1769
+ *
1770
+ * Latching is not bookkeeping, it is the terminality: both rejections are
1771
+ * raised *mid-field* — the UTF-8 check inside a payload piece, a cap inside the
1772
+ * count or length callback — so the machine is left at a position the visitor
1773
+ * never finished. Resume it and the refused field's own bytes are re-read as
1774
+ * headers, delivering fields that were never on the wire.
1775
+ *
1776
+ * The rejection is written here by {@link latch} and nowhere else: this
1777
+ * machine's own malformation findings arrive through {@link fail}, and the ones
1778
+ * raised above it — the §6.4.5 UTF-8 verdict, and the receiver caps §6.2.1
1779
+ * keeps out of this codec — arrive as a throw out of a visitor callback, caught
1780
+ * once in {@link push}.
1781
+ */
1782
+ this.refusal = null;
1783
+ /**
1784
+ * A `DataView` over the chunk currently being fed, and the chunk it addresses —
1785
+ * the **language-forced handle** of CORELIB_PLAN §6.6.2, and the only way
1786
+ * JavaScript lets a reader take an IEEE-754 value from a byte offset. Without it
1787
+ * a float costs four or eight byte loads, a shift ladder and a round trip
1788
+ * through a scratch word; with it, one call.
1789
+ *
1790
+ * It qualifies because it carries no message bytes (it addresses the *caller's*
1791
+ * chunk) and no wire number sizes it (its extent is the chunk's). Built lazily
1792
+ * and only where it pays: a float **array** whose remaining run inside this chunk
1793
+ * clears {@link FP32_HANDLE_MIN} / {@link FP64_HANDLE_MIN}. A scalar float, a
1794
+ * short array, a message with no float at all, and a byte-at-a-time feed each
1795
+ * build none. Once built it is kept while the same chunk is being fed, and it
1796
+ * never leaves this class.
1797
+ */
1798
+ this.view = null;
1799
+ this.viewOf = null;
1800
+ /** The current field's id. */
1161
1801
  this.id = 0;
1162
- // Resumable varint accumulator, as two unsigned 32-bit halves (vLo / vHi)
1163
- // plus the byte count so far. Number-only: a `bigint` is built once, at the
1164
- // end, and only for full 64-bit *values* (not ids, lengths or counts).
1802
+ // Resumable varint accumulator, as two unsigned 32-bit halves (vLo / vHi) plus
1803
+ // the byte count so far. Number-only: a `bigint` is built once, at the end, and
1804
+ // only for full 64-bit *values* (never for ids, lengths or counts).
1165
1805
  this.vLo = 0;
1166
1806
  this.vHi = 0;
1167
1807
  this.vBytes = 0;
1168
1808
  this.vComplete = false;
1169
- // fixlen / fp scratch
1170
- this.scratch = new Uint8Array(8);
1809
+ /**
1810
+ * Resumable fp32 / fp64 accumulator, as two little-endian 32-bit words rather
1811
+ * than a byte array: a typed array would be a per-decoder allocation, and two
1812
+ * number fields hold the same 8 bytes. Byte `k` lands in bits `8*k` of `fpLo`
1813
+ * (k < 4) or of `fpHi`, so the pair is already in wire (little-endian) order.
1814
+ */
1815
+ this.fpLo = 0;
1816
+ this.fpHi = 0;
1171
1817
  this.need = 0;
1172
1818
  this.have = 0;
1173
1819
  // fixlen string/blob streaming
@@ -1179,98 +1825,249 @@ var DecoderState = class {
1179
1825
  this.arrIsFixlen = false;
1180
1826
  this.arrCount = 0;
1181
1827
  this.arrIndex = 0;
1182
- this.maxArrayCount = limits?.maxArrayCount ?? Infinity;
1183
- this.maxStringLen = limits?.maxStringLen ?? Infinity;
1184
- this.maxBlobLen = limits?.maxBlobLen ?? Infinity;
1828
+ /**
1829
+ * The destination the visitor handed over for the array being decoded, or
1830
+ * `null` while no hand-off is in force — which is every array the visitor
1831
+ * declined, and every array at all for a visitor that declares no
1832
+ * {@link Visitor.arrayBulk}.
1833
+ *
1834
+ * **The one reference this machine keeps into the caller's storage between
1835
+ * `feed` calls.** Everything else it holds past a callback is its own (§6.6):
1836
+ * a payload is reported as coordinates into the caller's chunk and never
1837
+ * retained. An array that straddles chunks has to remember where its elements
1838
+ * go, so this lives from the hand-off to `arrayEnd` — and is dropped there,
1839
+ * by {@link endArray}, and by {@link release} for a machine going back to the
1840
+ * pool.
1841
+ */
1842
+ this.bulk = null;
1843
+ /** Which destination {@link bulk} named; meaningless while it is `null`. */
1844
+ this.bulkMode = 0 /* Values */;
1845
+ /**
1846
+ * The `Uint32Array` over a 64-bit typed destination (see {@link BM.Typed64}),
1847
+ * or over an `f32` destination once it has taken a NaN (see {@link f32StoreWord}).
1848
+ */
1849
+ this.bulk64 = null;
1850
+ this.root = visitor;
1851
+ this.cur = visitor;
1852
+ this.begin(visitor);
1185
1853
  }
1186
- /** Feed `input` to the machine, dispatching to `root` and its sub-visitors. */
1187
- push(input, root) {
1188
- if (this.stack.length === 0) this.stack.push(root);
1189
- let i = 0;
1854
+ /**
1855
+ * (Re)bind this machine to a visitor, clearing every trace of whatever it
1856
+ * decoded before.
1857
+ *
1858
+ * Constructing a decoder is the one allocating step (§6.6), and the one-shot
1859
+ * {@link decode} would otherwise pay it per message — which on a 37-byte message
1860
+ * is most of the cost. So {@link decode} keeps one machine and re-binds it here;
1861
+ * this method is what makes that indistinguishable from a fresh one.
1862
+ *
1863
+ * **Only the fields a fresh decode can *read* are cleared**, and the list is
1864
+ * exhaustive by construction rather than by inspection: every other field is
1865
+ * written before it is read, on every path that reads it — `id` at each header,
1866
+ * `vLo`/`vHi` by whichever varint reader ran (a fresh accumulation starts from
1867
+ * zero because `vBytes` is 0), `vComplete` by the `varintStep` whose result is
1868
+ * being tested, `fpLo`/`fpHi`/`need` by {@link fpBegin}, `fix*` by
1869
+ * {@link fixlenWord}, `arrKind`/`arrIsFixlen` by {@link dispatch} and
1870
+ * `arrCount`/`arrIndex` by the count word and `bulkMode` by the hand-off the
1871
+ * same word makes (`bulk` *is* cleared: it is the caller's storage, and a
1872
+ * machine that starts a fresh decode must not still be pointing at the last
1873
+ * one's), `seqIds[d]` when the scope at `d`
1874
+ * opens. `have` is the one exception and *is* cleared: the `S.ArrayFp` bulk loop
1875
+ * reads it to decide whether an element is half-arrived.
1876
+ *
1877
+ * `view`/`viewOf` are not cleared either, and deliberately: {@link dataView}
1878
+ * keys the cached handle on the chunk it addresses, so a stale one is rebuilt the
1879
+ * moment a different chunk arrives and reused — correctly — when the same buffer
1880
+ * is decoded again. {@link release} still drops it, which is where it matters:
1881
+ * a pooled machine must not keep the caller's last chunk alive between calls.
1882
+ *
1883
+ * Clearing them all anyway cost 5573 → 5324 Ir/op on `decode: typical message`
1884
+ * (~4.5%), which is what a 21-field wipe costs when the message is 37 bytes. The
1885
+ * invariant is held by `pooled-decoder-state.test.ts`, which aborts a decode in
1886
+ * every construct and then reuses the machine.
1887
+ */
1888
+ begin(visitor) {
1889
+ this.root = visitor;
1890
+ this.cur = visitor;
1891
+ this.skipFrom = -1;
1892
+ this.depth = 0;
1893
+ this.state = 0 /* Header */;
1894
+ this.refusal = null;
1895
+ this.vBytes = 0;
1896
+ this.have = 0;
1897
+ this.bulk = null;
1898
+ this.bulk64 = null;
1899
+ }
1900
+ /**
1901
+ * Drop the references a pooled machine would otherwise keep alive: the caller's
1902
+ * visitor, and the handle onto the caller's chunk.
1903
+ */
1904
+ release() {
1905
+ this.root = SKIP;
1906
+ this.cur = SKIP;
1907
+ this.view = null;
1908
+ this.viewOf = null;
1909
+ this.bulk = null;
1910
+ this.bulk64 = null;
1911
+ }
1912
+ /** Feed `input` to the machine, dispatching to the bound visitor. */
1913
+ push(input) {
1914
+ const latched = this.refusal;
1915
+ if (latched !== null) throw new SofabError(latched.code, latched.message);
1916
+ try {
1917
+ this.run(input);
1918
+ } catch (e) {
1919
+ if (this.refusal === null && e instanceof SofabError && (e.code === SofabErrorCode.InvalidMsg || e.code === SofabErrorCode.LimitExceeded || e.code === SofabErrorCode.Argument)) {
1920
+ this.latch(e);
1921
+ }
1922
+ throw e;
1923
+ }
1924
+ }
1925
+ /**
1926
+ * The decode loop itself. {@link push} wraps it, so the terminal-refusal latch
1927
+ * is applied in exactly one place — including to a rejection the visitor raised
1928
+ * — and this stays the plain state machine.
1929
+ */
1930
+ run(input) {
1190
1931
  const n = input.length;
1932
+ let i = 0;
1191
1933
  while (i < n) {
1934
+ if (this.state === 0 /* Header */ && this.vBytes === 0) {
1935
+ let h;
1936
+ const hb = input[i];
1937
+ if (hb < 128) {
1938
+ this.vLo = hb;
1939
+ this.vHi = 0;
1940
+ h = i + 1;
1941
+ } else h = this.varintQuick(input, i, n);
1942
+ if (h >= 0) {
1943
+ i = h;
1944
+ const type = this.vLo & 7;
1945
+ const id = this.vHi === 0 ? this.vLo >>> 3 : this.vUpper();
1946
+ if (id > ID_MAX) this.fail(`field id ${id} out of range`);
1947
+ if (type === WireType.SequenceEnd) {
1948
+ this.endSequence();
1949
+ continue;
1950
+ }
1951
+ this.id = id;
1952
+ this.cur.fieldBegin?.(id, type);
1953
+ if (type === WireType.Unsigned) {
1954
+ let v;
1955
+ const b0 = i < n ? input[i] : 128;
1956
+ if (b0 < 128) {
1957
+ this.vLo = b0;
1958
+ this.vHi = 0;
1959
+ v = i + 1;
1960
+ } else v = this.varintQuick(input, i, n);
1961
+ if (v < 0) {
1962
+ this.state = 1 /* ScalarU */;
1963
+ continue;
1964
+ }
1965
+ i = v;
1966
+ const lo = this.vLo >>> 0;
1967
+ const hi = this.vHi >>> 0;
1968
+ this.cur.unsigned?.(
1969
+ id,
1970
+ hi <= 2097151 ? hi * TWO32 + lo : joinU64(lo, hi),
1971
+ lo,
1972
+ hi
1973
+ );
1974
+ continue;
1975
+ }
1976
+ if (type === WireType.Signed) {
1977
+ let v;
1978
+ const b0 = i < n ? input[i] : 128;
1979
+ if (b0 < 128) {
1980
+ this.vLo = b0;
1981
+ this.vHi = 0;
1982
+ v = i + 1;
1983
+ } else v = this.varintQuick(input, i, n);
1984
+ if (v < 0) {
1985
+ this.state = 2 /* ScalarS */;
1986
+ continue;
1987
+ }
1988
+ i = v;
1989
+ const lo = this.vLo >>> 0;
1990
+ const hi = this.vHi >>> 0;
1991
+ const mask = -(lo & 1) >>> 0;
1992
+ this.cur.signed?.(
1993
+ id,
1994
+ this.vSigned(),
1995
+ ((lo >>> 1 | hi << 31) >>> 0 ^ mask) >>> 0,
1996
+ (hi >>> 1 ^ mask) >>> 0
1997
+ );
1998
+ continue;
1999
+ }
2000
+ if (type === WireType.Fixlen) {
2001
+ const w = this.varintQuick(input, i, n);
2002
+ if (w < 0) {
2003
+ this.state = 3 /* FixlenWord */;
2004
+ continue;
2005
+ }
2006
+ i = this.fixlenWord(input, w, n);
2007
+ continue;
2008
+ }
2009
+ this.dispatch(type);
2010
+ continue;
2011
+ }
2012
+ }
1192
2013
  switch (this.state) {
1193
2014
  case 0 /* Header */: {
1194
2015
  i = this.varintStep(input, i);
1195
2016
  if (!this.vComplete) return;
1196
- const type = this.vTag();
2017
+ const type = this.vLo & 7;
2018
+ const id = this.vUpper();
2019
+ if (id > ID_MAX) this.fail(`field id ${id} out of range`);
1197
2020
  if (type === WireType.SequenceEnd) {
1198
- this.resetVarint();
1199
2021
  this.endSequence();
1200
2022
  break;
1201
2023
  }
1202
- const id = this.vUpper();
1203
- this.resetVarint();
1204
- if (id > ID_MAX) throw invalidMsgError(`field id ${id} out of range`);
1205
2024
  this.id = id;
2025
+ this.cur.fieldBegin?.(id, type);
1206
2026
  this.dispatch(type);
1207
2027
  break;
1208
2028
  }
1209
2029
  case 1 /* ScalarU */: {
1210
2030
  i = this.varintStep(input, i);
1211
2031
  if (!this.vComplete) return;
1212
- const value = this.vUnsigned();
1213
- this.resetVarint();
1214
- this.top().unsigned?.(this.id, value);
1215
2032
  this.state = 0 /* Header */;
2033
+ this.cur.unsigned?.(this.id, this.vUnsigned(), this.vLo >>> 0, this.vHi >>> 0);
1216
2034
  break;
1217
2035
  }
1218
2036
  case 2 /* ScalarS */: {
1219
2037
  i = this.varintStep(input, i);
1220
2038
  if (!this.vComplete) return;
1221
- const value = this.vSigned();
1222
- this.resetVarint();
1223
- this.top().signed?.(this.id, value);
1224
2039
  this.state = 0 /* Header */;
2040
+ const lo = this.vLo >>> 0;
2041
+ const hi = this.vHi >>> 0;
2042
+ const mask = -(lo & 1) >>> 0;
2043
+ this.cur.signed?.(
2044
+ this.id,
2045
+ this.vSigned(),
2046
+ ((lo >>> 1 | hi << 31) >>> 0 ^ mask) >>> 0,
2047
+ (hi >>> 1 ^ mask) >>> 0
2048
+ );
1225
2049
  break;
1226
2050
  }
1227
- case 3 /* FixlenLen */: {
2051
+ case 3 /* FixlenWord */: {
1228
2052
  i = this.varintStep(input, i);
1229
2053
  if (!this.vComplete) return;
1230
- const sub = this.vTag();
1231
- const len = this.vUpper();
1232
- this.resetVarint();
1233
- if (sub > FixlenSubtype.Blob) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
1234
- if (len > FIXLEN_MAX) throw invalidMsgError("fixlen length out of range");
1235
- if (sub === FixlenSubtype.String && len > this.maxStringLen) {
1236
- throw limitExceededError(`string length ${len} exceeds maxStringLen ${this.maxStringLen}`);
1237
- }
1238
- if (sub === FixlenSubtype.Blob && len > this.maxBlobLen) {
1239
- throw limitExceededError(`blob length ${len} exceeds maxBlobLen ${this.maxBlobLen}`);
1240
- }
1241
- this.fixSub = sub;
1242
- this.fixLen = len;
1243
- this.fixOff = 0;
1244
- if (sub === FixlenSubtype.Fp32 || sub === FixlenSubtype.Fp64) {
1245
- const want = sub === FixlenSubtype.Fp32 ? 4 : 8;
1246
- if (this.fixLen !== want) throw invalidMsgError("fixlen float length mismatch");
1247
- this.need = want;
1248
- this.have = 0;
1249
- this.state = 4 /* FixlenFp */;
1250
- } else {
1251
- if (this.fixLen === 0) {
1252
- this.emitBytes(input.subarray(0, 0));
1253
- this.state = 0 /* Header */;
1254
- } else {
1255
- this.state = 5 /* FixlenBytes */;
1256
- }
1257
- }
2054
+ i = this.fixlenWord(input, i, n);
1258
2055
  break;
1259
2056
  }
1260
2057
  case 4 /* FixlenFp */: {
1261
- i = this.fpStep(input, i);
2058
+ i = this.fpStep(input, i, n);
1262
2059
  if (this.have < this.need) return;
1263
- const value = this.fixSub === FixlenSubtype.Fp32 ? unpackFp32(this.scratch, 0) : unpackFp64(this.scratch, 0);
1264
- if (this.fixSub === FixlenSubtype.Fp32) {
1265
- const top = this.top();
1266
- top.fp32?.(this.id, value, top.fp32Raw ? this.scratch.subarray(0, 4) : void 0);
1267
- } else this.top().fp64?.(this.id, value);
1268
2060
  this.state = 0 /* Header */;
2061
+ if (this.fixSub === FixlenSubtype.Fp32) {
2062
+ this.cur.fp32?.(this.id, fp32FromBits(this.fpLo), this.fpLo >>> 0);
2063
+ } else {
2064
+ this.cur.fp64?.(this.id, fp64FromBits(this.fpLo, this.fpHi));
2065
+ }
1269
2066
  break;
1270
2067
  }
1271
2068
  case 5 /* FixlenBytes */: {
1272
2069
  const take = Math.min(n - i, this.fixLen - this.fixOff);
1273
- this.emitBytes(input.subarray(i, i + take));
2070
+ this.emitBytes(input, i, i + take);
1274
2071
  i += take;
1275
2072
  this.fixOff += take;
1276
2073
  if (this.fixOff === this.fixLen) this.state = 0 /* Header */;
@@ -1280,77 +2077,95 @@ var DecoderState = class {
1280
2077
  i = this.varintStep(input, i);
1281
2078
  if (!this.vComplete) return;
1282
2079
  const count = this.vNum();
1283
- this.resetVarint();
1284
- if (count > ARRAY_MAX) throw invalidMsgError("array count out of range");
1285
- if (count > this.maxArrayCount) {
1286
- throw limitExceededError(`array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`);
1287
- }
2080
+ if (count > ARRAY_MAX) this.fail("array count out of range");
1288
2081
  this.arrCount = count;
1289
2082
  this.arrIndex = 0;
1290
2083
  if (this.arrIsFixlen) {
1291
- this.state = 9 /* ArrayElemLen */;
2084
+ this.state = 8 /* ArrayElemWord */;
1292
2085
  } else if (count === 0) {
1293
- this.top().arrayBegin?.(this.id, this.arrKind, 0);
1294
- this.top().arrayEnd?.(this.id);
1295
- this.state = 0 /* Header */;
2086
+ this.cur.arrayBegin?.(this.id, this.arrKind, 0);
2087
+ this.askBulk(this.arrKind, 0);
2088
+ this.endArray();
1296
2089
  } else {
1297
- this.top().arrayBegin?.(this.id, this.arrKind, this.arrCount);
1298
- this.state = this.arrKind === ArrayKind.Unsigned ? 7 /* ArrayUElem */ : 8 /* ArraySElem */;
2090
+ this.state = 7 /* ArrayElem */;
2091
+ this.cur.arrayBegin?.(this.id, this.arrKind, count);
2092
+ this.askBulk(this.arrKind, count);
1299
2093
  }
1300
2094
  break;
1301
2095
  }
1302
- case 7 /* ArrayUElem */: {
1303
- i = this.varintStep(input, i);
1304
- if (!this.vComplete) return;
1305
- const value = this.vUnsigned();
1306
- this.resetVarint();
1307
- this.top().arrayUnsigned?.(this.id, this.arrIndex, value);
1308
- this.advanceArray();
1309
- break;
1310
- }
1311
- case 8 /* ArraySElem */: {
2096
+ case 7 /* ArrayElem */: {
2097
+ const count = this.arrCount;
2098
+ const safeEnd = n - VARINT_MAX_BYTES;
2099
+ if (this.bulk !== null) {
2100
+ i = this.arrKind === ArrayKind.Unsigned ? this.uBulkArm(input, i, n, count, safeEnd) : this.sBulkArm(input, i, n, count, safeEnd);
2101
+ break;
2102
+ }
2103
+ let idx = this.arrIndex;
2104
+ if (this.vBytes === 0) {
2105
+ while (idx < count && i <= safeEnd) {
2106
+ i = this.varintFull(input, i);
2107
+ idx++;
2108
+ }
2109
+ this.arrIndex = idx;
2110
+ }
2111
+ if (idx === count) {
2112
+ this.state = 0 /* Header */;
2113
+ this.cur.arrayEnd?.(this.id);
2114
+ break;
2115
+ }
2116
+ if (i >= n) break;
1312
2117
  i = this.varintStep(input, i);
1313
2118
  if (!this.vComplete) return;
1314
- const value = this.vSigned();
1315
- this.resetVarint();
1316
- this.top().arraySigned?.(this.id, this.arrIndex, value);
1317
2119
  this.advanceArray();
1318
2120
  break;
1319
2121
  }
1320
- case 9 /* ArrayElemLen */: {
2122
+ case 8 /* ArrayElemWord */: {
1321
2123
  i = this.varintStep(input, i);
1322
2124
  if (!this.vComplete) return;
1323
- const sub = this.vTag();
2125
+ const sub = this.vLo & 7;
1324
2126
  const size = this.vUpper();
1325
- this.resetVarint();
1326
2127
  if (sub === FixlenSubtype.Fp32 && size === 4) {
1327
2128
  this.arrKind = ArrayKind.Fp32;
1328
- this.need = 4;
2129
+ this.fpBegin(4);
1329
2130
  } else if (sub === FixlenSubtype.Fp64 && size === 8) {
1330
2131
  this.arrKind = ArrayKind.Fp64;
1331
- this.need = 8;
2132
+ this.fpBegin(8);
1332
2133
  } else {
1333
- throw invalidMsgError("invalid fixlen array element type");
2134
+ this.fail("invalid fixlen array element type");
1334
2135
  }
1335
- this.top().arrayBegin?.(this.id, this.arrKind, this.arrCount);
1336
2136
  if (this.arrCount === 0) {
1337
- this.top().arrayEnd?.(this.id);
1338
- this.state = 0 /* Header */;
2137
+ this.cur.arrayBegin?.(this.id, this.arrKind, 0);
2138
+ this.askBulk(this.arrKind, 0);
2139
+ this.endArray();
1339
2140
  } else {
1340
- this.have = 0;
1341
- this.state = 10 /* ArrayFp */;
2141
+ this.state = 9 /* ArrayFp */;
2142
+ this.cur.arrayBegin?.(this.id, this.arrKind, this.arrCount);
2143
+ this.askBulk(this.arrKind, this.arrCount);
1342
2144
  }
1343
2145
  break;
1344
2146
  }
1345
- case 10 /* ArrayFp */: {
1346
- i = this.fpStep(input, i);
2147
+ case 9 /* ArrayFp */: {
2148
+ const count = this.arrCount;
2149
+ const size = this.need;
2150
+ if (this.bulk !== null) {
2151
+ const is32 = this.arrKind === ArrayKind.Fp32;
2152
+ i = this.fpBulkArm(input, i, n, count, size, is32);
2153
+ break;
2154
+ }
2155
+ if (this.have === 0) {
2156
+ const run = Math.min(count - this.arrIndex, (n - i) / size | 0);
2157
+ i += run * size;
2158
+ this.arrIndex += run;
2159
+ }
2160
+ if (this.arrIndex === count) {
2161
+ this.state = 0 /* Header */;
2162
+ this.cur.arrayEnd?.(this.id);
2163
+ break;
2164
+ }
2165
+ if (i >= n) break;
2166
+ i = this.fpStep(input, i, n);
1347
2167
  if (this.have < this.need) return;
1348
- const value = this.arrKind === ArrayKind.Fp32 ? unpackFp32(this.scratch, 0) : unpackFp64(this.scratch, 0);
1349
- if (this.arrKind === ArrayKind.Fp32) {
1350
- const top = this.top();
1351
- top.arrayFp32?.(this.id, this.arrIndex, value, top.fp32Raw ? this.scratch.subarray(0, 4) : void 0);
1352
- } else this.top().arrayFp64?.(this.id, this.arrIndex, value);
1353
- this.have = 0;
2168
+ this.fpBegin(size);
1354
2169
  this.advanceArray();
1355
2170
  break;
1356
2171
  }
@@ -1358,20 +2173,104 @@ var DecoderState = class {
1358
2173
  }
1359
2174
  }
1360
2175
  /**
1361
- * Report the terminal decode outcome (MESSAGE_SPEC §7) *without* promoting it
1362
- * to an error. Returns {@link DecodeStatus.Complete} when the stream ended
1363
- * exactly at a field boundary, or {@link DecodeStatus.Incomplete} when it
1364
- * ended inside a field (a partial varint, an unfinished payload / array, or a
1365
- * still-open nested sequence). This is a pure accessor — the finish-less spec
1366
- * has no finalize step, and a trailing `Incomplete` is a truncation the caller
1367
- * decides how to treat, not an error this machine raises. A genuinely
1368
- * malformed message has already thrown from {@link push}.
2176
+ * Where the decode stands (§5.2.1), *without* promoting it to an error:
2177
+ * {@link DecodeStatus.Complete} when the stream ended exactly at a field
2178
+ * boundary, {@link DecodeStatus.Incomplete} when it ended inside a field (a
2179
+ * partial varint, an unfinished payload / array, or a still-open nested
2180
+ * sequence). A trailing `Incomplete` is a truncation the caller decides how to
2181
+ * treat (§5.2.4), not an error this machine raises.
2182
+ *
2183
+ * **Only these two, and only on a healthy machine.** This is read in exactly
2184
+ * one place — after a {@link push} that returned — and a push that met a
2185
+ * terminal refusal does not return: it throws, this call is never reached, and
2186
+ * the verdict travels out on the error it threw. So `Invalid` is not a value
2187
+ * this can produce, and neither is the "refused by a cap" state the outcome
2188
+ * triple cannot name (§6.3). Nothing here re-reports a latched refusal, because
2189
+ * nothing needs to: the throw is the whole report, and a report with one
2190
+ * carrier has nothing to drift out of step with.
2191
+ *
2192
+ * The cursor's own answer would be wrong for a refused stream, which is why the
2193
+ * refusal must not fall through to it: a cap is compared in `arrayBegin` /
2194
+ * `fixlenBegin`, raised with the header and the count / length word consumed and
2195
+ * the payload not yet entered — a cursor that reads as a clean field boundary,
2196
+ * and so as `Complete`. {@link push}'s latch is what keeps that unreachable.
1369
2197
  */
1370
- finish() {
1371
- const atBoundary = this.state === 0 /* Header */ && this.vBytes === 0 && this.stack.length <= 1;
2198
+ outcome() {
2199
+ const atBoundary = this.state === 0 /* Header */ && this.vBytes === 0 && this.depth === 0;
1372
2200
  return atBoundary ? DecodeStatus.Complete : DecodeStatus.Incomplete;
1373
2201
  }
1374
2202
  // --- helpers ------------------------------------------------------------
2203
+ /**
2204
+ * Reject the input as malformed: latch the terminal `INVALID` verdict and throw
2205
+ * `INVALID_MSG`. Every malformation *this machine* finds goes through here, so
2206
+ * none of them can be caught and then decoded past — §5.2.1's "no — terminal".
2207
+ * Declared `never` so a call ends control flow exactly like the `throw` it
2208
+ * replaced.
2209
+ */
2210
+ fail(message) {
2211
+ throw this.latch(invalidMsgError(message));
2212
+ }
2213
+ /**
2214
+ * Record `e` as the terminal refusal that stopped this stream, and hand it back
2215
+ * to be thrown. The only writer of {@link refusal} — §5.3.1's one
2216
+ * implementation of the rule — for a rejection raised here and for one raised
2217
+ * above this machine and caught in {@link push} alike. The error is stored, not
2218
+ * its text: the code travels with it, so `INVALID_MSG` and `LIMIT_EXCEEDED`
2219
+ * each stay themselves (§6.3).
2220
+ */
2221
+ latch(e) {
2222
+ this.refusal = e;
2223
+ this.bulk = null;
2224
+ this.bulk64 = null;
2225
+ return e;
2226
+ }
2227
+ /**
2228
+ * Act on a complete `fixlen_word` sitting in the varint accumulator: validate
2229
+ * it, announce the field, and either read the float inline (when its bytes are
2230
+ * in this chunk) or set up the resumable payload state. Shared by the fast lane
2231
+ * and the `S.FixlenWord` arm, so the §4.6 rules exist once.
2232
+ */
2233
+ fixlenWord(input, i, n) {
2234
+ const sub = this.vLo & 7;
2235
+ const len = this.vUpper();
2236
+ if (sub > FixlenSubtype.Blob) this.fail(`invalid fixlen subtype ${sub}`);
2237
+ if (len > FIXLEN_MAX) this.fail("fixlen length out of range");
2238
+ if (sub === FixlenSubtype.Fp32 || sub === FixlenSubtype.Fp64) {
2239
+ const want = sub === FixlenSubtype.Fp32 ? 4 : 8;
2240
+ if (len !== want) this.fail("fixlen float length mismatch");
2241
+ this.fixSub = sub;
2242
+ if (n - i >= want) {
2243
+ this.state = 0 /* Header */;
2244
+ const lo = u32le(input, i);
2245
+ if (want === 4) {
2246
+ this.cur.fp32?.(this.id, fp32FromBits(lo), lo);
2247
+ } else {
2248
+ this.cur.fp64?.(this.id, fp64FromBits(lo, u32le(input, i + 4)));
2249
+ }
2250
+ return i + want;
2251
+ }
2252
+ this.fpBegin(want);
2253
+ this.state = 4 /* FixlenFp */;
2254
+ return i;
2255
+ }
2256
+ this.fixSub = sub;
2257
+ this.fixLen = len;
2258
+ this.fixOff = 0;
2259
+ this.cur.fixlenBegin?.(this.id, this.fixSub, len);
2260
+ if (len === 0) {
2261
+ this.emitBytes(input, i, i);
2262
+ this.state = 0 /* Header */;
2263
+ return i;
2264
+ }
2265
+ const take = Math.min(n - i, len);
2266
+ if (take > 0) {
2267
+ this.emitBytes(input, i, i + take);
2268
+ this.fixOff = take;
2269
+ i += take;
2270
+ }
2271
+ this.state = this.fixOff === len ? 0 /* Header */ : 5 /* FixlenBytes */;
2272
+ return i;
2273
+ }
1375
2274
  dispatch(type) {
1376
2275
  switch (type) {
1377
2276
  case WireType.Unsigned:
@@ -1381,7 +2280,7 @@ var DecoderState = class {
1381
2280
  this.state = 2 /* ScalarS */;
1382
2281
  break;
1383
2282
  case WireType.Fixlen:
1384
- this.state = 3 /* FixlenLen */;
2283
+ this.state = 3 /* FixlenWord */;
1385
2284
  break;
1386
2285
  case WireType.ArrayUnsigned:
1387
2286
  this.arrKind = ArrayKind.Unsigned;
@@ -1397,755 +2296,1261 @@ var DecoderState = class {
1397
2296
  this.arrIsFixlen = true;
1398
2297
  this.state = 6 /* ArrayCount */;
1399
2298
  break;
1400
- case WireType.SequenceStart: {
1401
- if (this.stack.length - 1 >= MAX_DEPTH) {
1402
- throw invalidMsgError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
1403
- }
1404
- const child = this.top().sequenceBegin?.(this.id);
1405
- this.stack.push(child ?? this.top());
1406
- this.state = 0 /* Header */;
2299
+ case WireType.SequenceStart:
2300
+ this.beginSequence();
1407
2301
  break;
1408
- }
1409
2302
  default:
1410
- throw invalidMsgError(`invalid wire type ${type}`);
2303
+ this.fail(`invalid wire type ${type}`);
2304
+ }
2305
+ }
2306
+ /**
2307
+ * Open a nested sequence: offer it to the visitor, note its id, and descend.
2308
+ *
2309
+ * A visitor that answers `false` declines the whole subtree — no callback of
2310
+ * any kind fires inside it, its own {@link Visitor.sequenceEnd} included, and a
2311
+ * scope opened within it is never offered either. The subtree is still *parsed*
2312
+ * (a sequence is framed by markers, not by a length, so its end has to be
2313
+ * found) and every format ceiling still applies; what stops is delivery.
2314
+ */
2315
+ beginSequence() {
2316
+ const d = this.depth;
2317
+ if (d >= MAX_DEPTH) this.fail(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
2318
+ if (this.cur !== SKIP && this.root.sequenceBegin?.(this.id, d + 1) === false) {
2319
+ this.skipFrom = d;
2320
+ this.cur = SKIP;
1411
2321
  }
2322
+ this.seqIds[d] = this.id;
2323
+ this.depth = d + 1;
2324
+ this.state = 0 /* Header */;
1412
2325
  }
1413
2326
  endSequence() {
1414
- if (this.stack.length <= 1) throw invalidMsgError("unbalanced sequence end");
1415
- this.top().sequenceEnd?.();
1416
- this.stack.pop();
2327
+ const d = this.depth;
2328
+ if (d === 0) this.fail("unbalanced sequence end");
1417
2329
  this.state = 0 /* Header */;
2330
+ const closed = d - 1;
2331
+ this.depth = closed;
2332
+ if (this.skipFrom >= 0) {
2333
+ if (this.skipFrom === closed) {
2334
+ this.skipFrom = -1;
2335
+ this.cur = this.root;
2336
+ }
2337
+ return;
2338
+ }
2339
+ this.root.sequenceEnd?.(this.seqIds[closed], d);
1418
2340
  }
1419
2341
  advanceArray() {
1420
- this.arrIndex++;
1421
- if (this.arrIndex === this.arrCount) {
1422
- this.top().arrayEnd?.(this.id);
2342
+ if (++this.arrIndex === this.arrCount) {
1423
2343
  this.state = 0 /* Header */;
2344
+ this.cur.arrayEnd?.(this.id);
1424
2345
  }
1425
2346
  }
1426
- emitBytes(chunk) {
1427
- const v = this.top();
1428
- if (this.fixSub === FixlenSubtype.String) v.string?.(this.id, this.fixLen, this.fixOff, chunk);
1429
- else v.blob?.(this.id, this.fixLen, this.fixOff, chunk);
2347
+ /**
2348
+ * Close an array delivered through the hand-off: back to the header state, drop
2349
+ * the visitor's destination, announce the end. Every bulk array ends here, so
2350
+ * the reference into the caller's storage cannot outlive the array it was made
2351
+ * for (§6.6; see {@link bulk}).
2352
+ *
2353
+ * The per-element arms keep their own two lines instead of calling this: they
2354
+ * can never hold a destination — the hand-off's arms have already taken over
2355
+ * before they run — and the call showed up on `decode: typical`.
2356
+ */
2357
+ endArray() {
2358
+ this.state = 0 /* Header */;
2359
+ const t = this.bulk;
2360
+ if (t !== null) this.trimPlain(t, this.arrIndex);
2361
+ this.bulk = null;
2362
+ this.bulk64 = null;
2363
+ this.cur.arrayEnd?.(this.id);
2364
+ }
2365
+ /**
2366
+ * Cut a plain-array destination to the `written` elements this array actually
2367
+ * put in it, so a destination reused across arrays — the shape the hand-off
2368
+ * exists for — can never hand back the previous array's tail, and its `length`
2369
+ * always means "elements of *this* array".
2370
+ *
2371
+ * At the end rather than at the hand-off, and that is measured: clearing it up
2372
+ * front turns every element write into a grow (`array<u16>` 181 583 -> 231 815
2373
+ * Ir/op, worse than the per-element path it replaces), where one length store
2374
+ * per array costs nothing. A pre-sized destination — the fast shape, every write
2375
+ * in bounds — is left exactly as it was.
2376
+ *
2377
+ * The typed destinations are not trimmed and cannot be: their length is the
2378
+ * caller's allocation, checked against `count` at the hand-off, and `count` is
2379
+ * how far this fill wrote.
2380
+ */
2381
+ trimPlain(t, written) {
2382
+ const mode = this.bulkMode;
2383
+ if (mode !== 0 /* Values */ && mode !== 1 /* Values32 */ && mode !== 5 /* Longs */) return;
2384
+ const out = mode === 5 /* Longs */ ? t.longs : t.values;
2385
+ if (out.length > written) out.length = written;
1430
2386
  }
1431
- top() {
1432
- return this.stack[this.stack.length - 1];
2387
+ /**
2388
+ * Offer the bulk hand-off for the array that just began, and resolve the answer
2389
+ * once (CORELIB_PLAN §5.3.1's one implementation, and {@link ArrayTarget}'s
2390
+ * contract): after this, the element loops know which destination to write to
2391
+ * from {@link bulkMode} and re-decide nothing per element.
2392
+ *
2393
+ * Raised after `arrayBegin` and before the first element, so a receiver cap on
2394
+ * the count (§6.2.1) is still compared in `arrayBegin` and a rejected array is
2395
+ * never offered. A visitor that declares no `arrayBulk` pays one short-circuited
2396
+ * optional call per array.
2397
+ *
2398
+ * **An empty array is offered too**, with `count` of 0. There is nothing to
2399
+ * write, but there is something to say: a destination held across fields would
2400
+ * otherwise still be holding the last array's elements, and its length is the
2401
+ * only place this one's emptiness could show up (see {@link trimPlain}).
2402
+ */
2403
+ askBulk(kind, count) {
2404
+ const t = this.cur.arrayBulk?.(this.id, kind, count) ?? null;
2405
+ if (t === null) {
2406
+ this.bulk = null;
2407
+ this.bulk64 = null;
2408
+ return;
2409
+ }
2410
+ this.bulk64 = null;
2411
+ this.bulkMode = this.resolveTarget(t, kind, count);
2412
+ this.bulk = t;
1433
2413
  }
1434
2414
  /**
1435
- * Consume varint bytes from `input` at `i` into the {@link vLo} / {@link vHi}
1436
- * accumulator, resuming across chunk boundaries; sets {@link vComplete} when a
1437
- * terminator byte arrives. Number-only — no per-byte `bigint`.
2415
+ * Which destination `t` names — or an `Argument` refusal if it names none, more
2416
+ * than one, one that contradicts the array's element kind, or one too short to
2417
+ * hold `count` elements.
2418
+ *
2419
+ * These are caller mistakes rather than message verdicts, so they are
2420
+ * `InvalidArgument` (§6.3) and not `INVALID_MSG`: the same bytes decode fine for
2421
+ * a visitor that hands over a destination it can actually fill. Checked once per
2422
+ * array, which is why the fill loops need no guard of their own.
1438
2423
  */
1439
- varintStep(input, i) {
1440
- let lo = this.vLo;
1441
- let hi = this.vHi;
1442
- let k = this.vBytes;
1443
- const n = input.length;
1444
- while (i < n) {
1445
- if (k >= VARINT_MAX_BYTES) throw invalidMsgError("varint overflow");
1446
- const b = input[i++];
1447
- if (k < 4) lo |= (b & 127) << 7 * k;
1448
- else if (k === 4) {
1449
- lo |= (b & 15) << 28;
1450
- hi |= b >> 4 & 7;
1451
- } else {
1452
- if (k === 9 && (b & 127) >> 1 !== 0) throw invalidMsgError("varint overflow");
1453
- hi |= (b & 127) << 7 * k - 32;
2424
+ resolveTarget(t, kind, count) {
2425
+ if (kind === ArrayKind.Fp32 || kind === ArrayKind.Fp64) {
2426
+ const f = t;
2427
+ const is32 = kind === ArrayKind.Fp32;
2428
+ const dest = is32 ? f.f32 ?? f.bits : f.f64;
2429
+ const named2 = (f.f32 !== void 0 ? 1 : 0) + (f.bits !== void 0 ? 1 : 0) + (f.f64 !== void 0 ? 1 : 0) + (t.bool !== void 0 ? 1 : 0);
2430
+ const want = is32 ? "f32 or bits" : "f64";
2431
+ if (dest === void 0 || named2 !== 1) {
2432
+ throw argumentError(
2433
+ `array ${this.id}: an ${is32 ? "fp32" : "fp64"} array needs exactly the ${want} destination`
2434
+ );
1454
2435
  }
1455
- k++;
1456
- if ((b & 128) === 0) {
1457
- this.vLo = lo;
1458
- this.vHi = hi;
1459
- this.vBytes = k;
1460
- this.vComplete = true;
1461
- return i;
2436
+ if (dest.length < count) {
2437
+ throw argumentError(
2438
+ `array ${this.id}: ${is32 ? "fp32" : "fp64"} destination holds ${dest.length} of ${count} elements`
2439
+ );
1462
2440
  }
2441
+ if (!is32) return 9 /* F64 */;
2442
+ return f.bits !== void 0 ? 8 /* F32Bits */ : 7 /* F32 */;
1463
2443
  }
1464
- this.vLo = lo;
1465
- this.vHi = hi;
1466
- this.vBytes = k;
1467
- this.vComplete = false;
1468
- return i;
2444
+ const bt = t;
2445
+ if (bt.bool !== void 0) {
2446
+ const other = t;
2447
+ const float = t;
2448
+ if (other.values !== void 0 || other.longs !== void 0 || other.typed !== void 0 || other.lo !== void 0 || other.hi !== void 0 || float.f32 !== void 0 || float.bits !== void 0 || float.f64 !== void 0) {
2449
+ throw argumentError(`array ${this.id}: an array target needs exactly one destination`);
2450
+ }
2451
+ if (kind !== ArrayKind.Unsigned) {
2452
+ throw argumentError(`array ${this.id}: a bool destination needs an unsigned array`);
2453
+ }
2454
+ if (bt.bool.length < count) {
2455
+ throw argumentError(
2456
+ `array ${this.id}: bool destination holds ${bt.bool.length} of ${count} elements`
2457
+ );
2458
+ }
2459
+ return 4 /* Bool */;
2460
+ }
2461
+ const it = t;
2462
+ const halves = it.lo !== void 0 || it.hi !== void 0;
2463
+ const named = (it.values !== void 0 ? 1 : 0) + (it.longs !== void 0 ? 1 : 0) + (it.typed !== void 0 ? 1 : 0) + (halves ? 1 : 0);
2464
+ if (named !== 1) {
2465
+ throw argumentError(
2466
+ `array ${this.id}: an integer array target needs exactly one destination (values, typed, longs, or lo+hi), got ${named}`
2467
+ );
2468
+ }
2469
+ if (it.minLo >>> 0 !== it.minLo || it.minHi >>> 0 !== it.minHi || it.maxLo >>> 0 !== it.maxLo || it.maxHi >>> 0 !== it.maxHi) {
2470
+ throw argumentError(
2471
+ `array ${this.id}: the element bound must be four unsigned 32-bit halves`
2472
+ );
2473
+ }
2474
+ const signed = kind === ArrayKind.Signed;
2475
+ const minHi = signed ? it.minHi | 0 : it.minHi;
2476
+ const maxHi = signed ? it.maxHi | 0 : it.maxHi;
2477
+ if (minHi > maxHi || minHi === maxHi && it.minLo > it.maxLo) {
2478
+ throw argumentError(
2479
+ `array ${this.id}: the element bound is empty (min ${it.minHi}:${it.minLo} > max ${it.maxHi}:${it.maxLo})`
2480
+ );
2481
+ }
2482
+ if (it.typed !== void 0) {
2483
+ return kind === ArrayKind.Signed ? this.typedModeS(it, it.typed, kind, count) : this.typedModeU(it, it.typed, kind, count);
2484
+ }
2485
+ if (halves) {
2486
+ const lo = it.lo;
2487
+ const hi = it.hi;
2488
+ if (lo === void 0 || hi === void 0) {
2489
+ throw argumentError(`array ${this.id}: the halves destination needs both lo and hi`);
2490
+ }
2491
+ if (lo.length < count || hi.length < count) {
2492
+ throw argumentError(
2493
+ `array ${this.id}: halves destination holds ${Math.min(lo.length, hi.length)} of ${count} elements`
2494
+ );
2495
+ }
2496
+ return 6 /* Halves */;
2497
+ }
2498
+ if (it.longs !== void 0) {
2499
+ if (!Array.isArray(it.longs)) {
2500
+ throw argumentError(`array ${this.id}: the longs destination must be an Array`);
2501
+ }
2502
+ return 5 /* Longs */;
2503
+ }
2504
+ if (!Array.isArray(it.values)) {
2505
+ throw argumentError(`array ${this.id}: the values destination must be an Array`);
2506
+ }
2507
+ return it.minHi === 0 && it.maxHi === 0 ? 1 /* Values32 */ : 0 /* Values */;
1469
2508
  }
1470
- resetVarint() {
1471
- this.vLo = 0;
1472
- this.vHi = 0;
1473
- this.vBytes = 0;
2509
+ /** The typed-destination check of {@link resolveTarget} for an unsigned array. */
2510
+ typedModeU(it, d, kind, count) {
2511
+ if (d.length < count) {
2512
+ throw argumentError(
2513
+ `array ${this.id}: typed destination holds ${d.length} of ${count} elements`
2514
+ );
2515
+ }
2516
+ const cap = typedCapacity(d.constructor);
2517
+ const signed = kind === ArrayKind.Signed;
2518
+ if ((cap === void 0 || cap.max === Number.POSITIVE_INFINITY) && is64(d)) {
2519
+ if (signed !== d instanceof BigInt64Array) {
2520
+ throw argumentError(
2521
+ `array ${this.id}: a 64-bit typed destination must match the array's signedness`
2522
+ );
2523
+ }
2524
+ this.bulk64 = new Uint32Array(d.buffer, d.byteOffset, d.length * 2);
2525
+ return 3 /* Typed64 */;
2526
+ }
2527
+ const minV = signed ? (it.minHi | 0) * TWO32 + it.minLo : it.minHi * TWO32 + it.minLo;
2528
+ const maxV = signed ? (it.maxHi | 0) * TWO32 + it.maxLo : it.maxHi * TWO32 + it.maxLo;
2529
+ if (cap === void 0) {
2530
+ throw argumentError(`array ${this.id}: unsupported typed destination`);
2531
+ }
2532
+ if (minV < cap.min || maxV > cap.max) {
2533
+ throw argumentError(
2534
+ `array ${this.id}: the element bound ${minV}..${maxV} does not fit the typed destination (${cap.min}..${cap.max})`
2535
+ );
2536
+ }
2537
+ return 2 /* Typed */;
2538
+ }
2539
+ /** The signed twin of {@link typedModeU}: textually identical, for its own inline caches. */
2540
+ typedModeS(it, d, kind, count) {
2541
+ if (d.length < count) {
2542
+ throw argumentError(
2543
+ `array ${this.id}: typed destination holds ${d.length} of ${count} elements`
2544
+ );
2545
+ }
2546
+ const cap = typedCapacity(d.constructor);
2547
+ const signed = kind === ArrayKind.Signed;
2548
+ if ((cap === void 0 || cap.max === Number.POSITIVE_INFINITY) && is64(d)) {
2549
+ if (signed !== d instanceof BigInt64Array) {
2550
+ throw argumentError(
2551
+ `array ${this.id}: a 64-bit typed destination must match the array's signedness`
2552
+ );
2553
+ }
2554
+ this.bulk64 = new Uint32Array(d.buffer, d.byteOffset, d.length * 2);
2555
+ return 3 /* Typed64 */;
2556
+ }
2557
+ const minV = signed ? (it.minHi | 0) * TWO32 + it.minLo : it.minHi * TWO32 + it.minLo;
2558
+ const maxV = signed ? (it.maxHi | 0) * TWO32 + it.maxLo : it.maxHi * TWO32 + it.maxLo;
2559
+ if (cap === void 0) {
2560
+ throw argumentError(`array ${this.id}: unsupported typed destination`);
2561
+ }
2562
+ if (minV < cap.min || maxV > cap.max) {
2563
+ throw argumentError(
2564
+ `array ${this.id}: the element bound ${minV}..${maxV} does not fit the typed destination (${cap.min}..${cap.max})`
2565
+ );
2566
+ }
2567
+ return 2 /* Typed */;
2568
+ }
2569
+ /**
2570
+ * The whole `S.ArrayUElem` arm for an array whose destination was handed over:
2571
+ * drain what this chunk holds, store a straddling tail element, close the array
2572
+ * when it ends. Returns the new read position; the caller's `break` then either
2573
+ * re-enters this state for the next chunk or leaves it, exactly as before.
2574
+ *
2575
+ * **Out of line, for the reason {@link fpDrain} already gives.** Everything
2576
+ * written into {@link push}'s switch is paid for by every decode that never
2577
+ * reaches it, so this arm is three lines at the call site and a method here.
2578
+ * Carrying its body in the switch instead cost `decode: typical` 5092 -> 5099
2579
+ * Ir/op and `decode: u64 array` 705.0k -> 708.5k; giving each bulk arm a state
2580
+ * of its own — which keeps the per-element arms untouched but adds three labels
2581
+ * — cost 5163 on `typical`, worse than either. What is left is what the
2582
+ * hand-off costs a decode that never takes it: 0.34% (5075 -> 5092, 702.5k ->
2583
+ * 705.0k), in the loop's own size.
2584
+ *
2585
+ * Suspension needs no signal of its own: a varint that did not complete consumed
2586
+ * the chunk to its end, so the returned position is `n` and the decode loop exits
2587
+ * on its own condition.
2588
+ *
2589
+ * **One loop per destination**, chosen once (see {@link bulkMode}) rather than
2590
+ * branched on per element: the shapes differ only in what a stored element *is*,
2591
+ * and a loop deciding that per element would give back what the hand-off saves.
2592
+ * `arrIndex` is likewise written once per drain instead of once per element,
2593
+ * which is part of why a bulk fill costs less than *discarding* the same
2594
+ * elements. A refusal mid-loop leaves it at the last committed value; nothing
2595
+ * reads it afterwards, because that verdict is terminal (§5.2.1).
2596
+ */
2597
+ uBulkArm(input, i, n, count, safeEnd) {
2598
+ const t = this.bulk;
2599
+ const mode = this.bulkMode;
2600
+ const minLo = t.minLo;
2601
+ const minHi = t.minHi;
2602
+ const maxLo = t.maxLo;
2603
+ const maxHi = t.maxHi;
2604
+ for (; ; ) {
2605
+ if (this.vBytes === 0) {
2606
+ let idx = this.arrIndex;
2607
+ if (mode === 1 /* Values32 */) {
2608
+ const out = t.values;
2609
+ while (idx < count && i <= safeEnd) {
2610
+ i = this.varintFull(input, i);
2611
+ const lo = this.vLo >>> 0;
2612
+ if (this.vHi !== 0 || lo < minLo || lo > maxLo) this.outOfBound(idx);
2613
+ out[idx++] = lo;
2614
+ }
2615
+ } else if (mode === 2 /* Typed */) {
2616
+ const out = t.typed;
2617
+ while (idx < count && i <= safeEnd) {
2618
+ i = this.varintFull(input, i);
2619
+ const lo = this.vLo >>> 0;
2620
+ if (this.vHi !== 0 || lo < minLo || lo > maxLo) this.outOfBound(idx);
2621
+ out[idx++] = lo;
2622
+ }
2623
+ } else if (mode === 3 /* Typed64 */) {
2624
+ const out = this.bulk64;
2625
+ while (idx < count && i <= safeEnd) {
2626
+ i = this.varintFull(input, i);
2627
+ const lo = this.vLo >>> 0;
2628
+ const hi = this.vHi >>> 0;
2629
+ if (hi < minHi || hi === minHi && lo < minLo || hi > maxHi || hi === maxHi && lo > maxLo) {
2630
+ this.outOfBound(idx);
2631
+ }
2632
+ out[idx * 2 + LO] = lo;
2633
+ out[idx * 2 + HI] = hi;
2634
+ idx++;
2635
+ }
2636
+ } else if (mode === 4 /* Bool */) {
2637
+ const out = this.bulk.bool;
2638
+ while (idx < count && i <= safeEnd) {
2639
+ i = this.varintFull(input, i);
2640
+ out[idx++] = this.vLo !== 0 || this.vHi !== 0 ? 1 : 0;
2641
+ }
2642
+ } else if (mode === 0 /* Values */) {
2643
+ const out = t.values;
2644
+ while (idx < count && i <= safeEnd) {
2645
+ i = this.varintFull(input, i);
2646
+ const lo = this.vLo >>> 0;
2647
+ const hi = this.vHi >>> 0;
2648
+ if (hi < minHi || hi === minHi && lo < minLo || hi > maxHi || hi === maxHi && lo > maxLo) {
2649
+ this.outOfBound(idx);
2650
+ }
2651
+ out[idx++] = hi <= 2097151 ? hi * TWO32 + lo : joinU64(lo, hi);
2652
+ }
2653
+ } else if (mode === 5 /* Longs */) {
2654
+ const out = t.longs;
2655
+ while (idx < count && i <= safeEnd) {
2656
+ i = this.varintFull(input, i);
2657
+ const lo = this.vLo >>> 0;
2658
+ const hi = this.vHi >>> 0;
2659
+ if (hi < minHi || hi === minHi && lo < minLo || hi > maxHi || hi === maxHi && lo > maxLo) {
2660
+ this.outOfBound(idx);
2661
+ }
2662
+ out[idx++] = new Long(lo, hi);
2663
+ }
2664
+ } else {
2665
+ const oLo = t.lo;
2666
+ const oHi = t.hi;
2667
+ while (idx < count && i <= safeEnd) {
2668
+ i = this.varintFull(input, i);
2669
+ const lo = this.vLo >>> 0;
2670
+ const hi = this.vHi >>> 0;
2671
+ if (hi < minHi || hi === minHi && lo < minLo || hi > maxHi || hi === maxHi && lo > maxLo) {
2672
+ this.outOfBound(idx);
2673
+ }
2674
+ oLo[idx] = lo;
2675
+ oHi[idx] = hi;
2676
+ idx++;
2677
+ }
2678
+ }
2679
+ this.arrIndex = idx;
2680
+ }
2681
+ if (this.arrIndex === count) {
2682
+ this.endArray();
2683
+ return i;
2684
+ }
2685
+ if (i >= n) return i;
2686
+ i = this.varintStep(input, i);
2687
+ if (!this.vComplete) return i;
2688
+ this.bulkStoreU(this.arrIndex, this.vLo >>> 0, this.vHi >>> 0);
2689
+ if (++this.arrIndex === count) {
2690
+ this.endArray();
2691
+ return i;
2692
+ }
2693
+ }
2694
+ }
2695
+ /** The signed twin of {@link uBulkArm}: the same shape, with the zig-zag undone. */
2696
+ sBulkArm(input, i, n, count, safeEnd) {
2697
+ const t = this.bulk;
2698
+ const mode = this.bulkMode;
2699
+ const minLo = t.minLo;
2700
+ const minHi = t.minHi | 0;
2701
+ const maxLo = t.maxLo;
2702
+ const maxHi = t.maxHi | 0;
2703
+ for (; ; ) {
2704
+ if (this.vBytes === 0) {
2705
+ let idx = this.arrIndex;
2706
+ while (idx < count && i <= safeEnd) {
2707
+ i = this.varintFull(input, i);
2708
+ const raw = this.vLo >>> 0;
2709
+ const rawHi = this.vHi >>> 0;
2710
+ const mask = -(raw & 1) >>> 0;
2711
+ const lo = ((raw >>> 1 | rawHi << 31) >>> 0 ^ mask) >>> 0;
2712
+ const hi = (rawHi >>> 1 ^ mask) >>> 0;
2713
+ const shi = hi | 0;
2714
+ if (shi < minHi || shi === minHi && lo < minLo || shi > maxHi || shi === maxHi && lo > maxLo) {
2715
+ this.outOfBound(idx);
2716
+ }
2717
+ if (mode === 2 /* Typed */) {
2718
+ t.typed[idx] = lo | 0;
2719
+ } else if (mode === 3 /* Typed64 */) {
2720
+ const o = this.bulk64;
2721
+ o[idx * 2 + LO] = lo;
2722
+ o[idx * 2 + HI] = hi;
2723
+ } else if (mode === 0 /* Values */ || mode === 1 /* Values32 */) {
2724
+ let v;
2725
+ if (rawHi <= 2097151) {
2726
+ const r = rawHi * TWO32 + raw;
2727
+ v = r % 2 === 0 ? r / 2 : -(r + 1) / 2;
2728
+ } else {
2729
+ v = joinI64(lo, hi);
2730
+ }
2731
+ t.values[idx] = v;
2732
+ } else if (mode === 5 /* Longs */) {
2733
+ t.longs[idx] = new Long(lo, hi);
2734
+ } else {
2735
+ t.lo[idx] = lo;
2736
+ t.hi[idx] = hi;
2737
+ }
2738
+ idx++;
2739
+ }
2740
+ this.arrIndex = idx;
2741
+ }
2742
+ if (this.arrIndex === count) {
2743
+ this.endArray();
2744
+ return i;
2745
+ }
2746
+ if (i >= n) return i;
2747
+ i = this.varintStep(input, i);
2748
+ if (!this.vComplete) return i;
2749
+ this.bulkStoreS(this.arrIndex, this.vLo >>> 0, this.vHi >>> 0);
2750
+ if (++this.arrIndex === count) {
2751
+ this.endArray();
2752
+ return i;
2753
+ }
2754
+ }
2755
+ }
2756
+ /** The float twin of {@link uBulkArm}; a straddling element resumes through {@link fpStep}. */
2757
+ fpBulkArm(input, i, n, count, size, isFp32) {
2758
+ if (this.have === 0 && this.arrIndex < count && n - i >= size) {
2759
+ const t = this.bulk;
2760
+ const run = Math.min(count - this.arrIndex, (n - i) / size | 0);
2761
+ let idx = this.arrIndex;
2762
+ const bitsOut = this.bulkMode === 8 /* F32Bits */ ? t.bits : null;
2763
+ if (run >= (isFp32 ? FP32_HANDLE_MIN : FP64_HANDLE_MIN)) {
2764
+ const dv = this.dataView(input);
2765
+ if (bitsOut !== null) {
2766
+ for (let k = 0; k < run; k++) {
2767
+ bitsOut[idx++] = dv.getUint32(i, true);
2768
+ i += 4;
2769
+ }
2770
+ } else if (isFp32) {
2771
+ const out = t.f32;
2772
+ for (let k = 0; k < run; k++) {
2773
+ const v = dv.getFloat32(i, true);
2774
+ if (v === v) out[idx] = v;
2775
+ else this.f32StoreWord(out, idx, dv.getUint32(i, true));
2776
+ idx++;
2777
+ i += 4;
2778
+ }
2779
+ } else {
2780
+ const out = t.f64;
2781
+ for (let k = 0; k < run; k++) {
2782
+ out[idx++] = dv.getFloat64(i, true);
2783
+ i += 8;
2784
+ }
2785
+ }
2786
+ } else if (bitsOut !== null) {
2787
+ for (let k = 0; k < run; k++) {
2788
+ bitsOut[idx++] = u32le(input, i);
2789
+ i += 4;
2790
+ }
2791
+ } else if (isFp32) {
2792
+ const out = t.f32;
2793
+ for (let k = 0; k < run; k++) {
2794
+ const w = u32le(input, i);
2795
+ if ((w & 2147483647) > 2139095040) this.f32StoreWord(out, idx, w);
2796
+ else out[idx] = fp32FromBits(w);
2797
+ idx++;
2798
+ i += 4;
2799
+ }
2800
+ } else {
2801
+ const out = t.f64;
2802
+ for (let k = 0; k < run; k++) {
2803
+ out[idx++] = fp64FromBits(u32le(input, i), u32le(input, i + 4));
2804
+ i += 8;
2805
+ }
2806
+ }
2807
+ this.arrIndex = idx;
2808
+ }
2809
+ if (this.arrIndex === count) {
2810
+ this.endArray();
2811
+ return i;
2812
+ }
2813
+ if (i >= n) return i;
2814
+ i = this.fpStep(input, i, n);
2815
+ if (this.have < this.need) return i;
2816
+ const lo = this.fpLo;
2817
+ const hi = this.fpHi;
2818
+ this.fpBegin(size);
2819
+ this.bulkStoreFp(this.arrIndex, lo, hi, isFp32);
2820
+ if (++this.arrIndex === count) this.endArray();
2821
+ return i;
2822
+ }
2823
+ /**
2824
+ * Store an fp32 NaN into a `Float32Array` destination by its wire word (§4.6).
2825
+ * Widening a NaN to a double quiets a signaling one (and an engine may purify
2826
+ * the payload of any NaN it reads from a typed array), so a NaN never goes
2827
+ * through a value: it is written through a word view over the destination's own
2828
+ * storage. Every other fp32 survives the double exactly, which is why the view
2829
+ * is built lazily, on the array's first NaN, and never on the common path —
2830
+ * touching `.buffer` moves a small typed array's storage off the heap. It is
2831
+ * kept in {@link bulk64} for the rest of the array, so the count is one per
2832
+ * array however many NaNs the wire carries (§6.6.2).
2833
+ */
2834
+ f32StoreWord(out, idx, word) {
2835
+ let w = this.bulk64;
2836
+ if (w === null) w = this.bulk64 = new Uint32Array(out.buffer, out.byteOffset, out.length);
2837
+ w[idx] = word;
2838
+ }
2839
+ /** Store one float element that straddled a chunk boundary (see {@link bulkStoreU}). */
2840
+ bulkStoreFp(idx, lo, hi, isFp32) {
2841
+ const t = this.bulk;
2842
+ if (!isFp32) t.f64[idx] = fp64FromBits(lo, hi);
2843
+ else if (this.bulkMode === 8 /* F32Bits */) t.bits[idx] = lo >>> 0;
2844
+ else if ((lo & 2147483647) > 2139095040) this.f32StoreWord(t.f32, idx, lo >>> 0);
2845
+ else t.f32[idx] = fp32FromBits(lo);
2846
+ }
2847
+ /**
2848
+ * Store one unsigned element that straddled a chunk boundary — the tail a drain
2849
+ * loop cannot take, resumed from the varint accumulator. At most one per chunk,
2850
+ * so it re-decides the destination rather than duplicating three loops.
2851
+ */
2852
+ bulkStoreU(idx, lo, hi) {
2853
+ const t = this.bulk;
2854
+ if (hi < t.minHi || hi === t.minHi && lo < t.minLo || hi > t.maxHi || hi === t.maxHi && lo > t.maxLo) {
2855
+ this.outOfBound(idx);
2856
+ }
2857
+ if (this.bulkMode === 0 /* Values */ || this.bulkMode === 1 /* Values32 */) {
2858
+ t.values[idx] = this.vUnsigned();
2859
+ } else if (this.bulkMode === 2 /* Typed */) {
2860
+ t.typed[idx] = lo;
2861
+ } else if (this.bulkMode === 3 /* Typed64 */) {
2862
+ const o = this.bulk64;
2863
+ o[idx * 2 + LO] = lo;
2864
+ o[idx * 2 + HI] = hi;
2865
+ } else if (this.bulkMode === 4 /* Bool */) {
2866
+ this.bulk.bool[idx] = lo !== 0 || hi !== 0 ? 1 : 0;
2867
+ } else if (this.bulkMode === 5 /* Longs */) {
2868
+ t.longs[idx] = new Long(lo, hi);
2869
+ } else {
2870
+ t.lo[idx] = lo;
2871
+ t.hi[idx] = hi;
2872
+ }
2873
+ }
2874
+ /** The signed twin of {@link bulkStoreU}; `raw`/`rawHi` are the zig-zag halves. */
2875
+ bulkStoreS(idx, raw, rawHi) {
2876
+ const t = this.bulk;
2877
+ const mask = -(raw & 1) >>> 0;
2878
+ const lo = ((raw >>> 1 | rawHi << 31) >>> 0 ^ mask) >>> 0;
2879
+ const hi = (rawHi >>> 1 ^ mask) >>> 0;
2880
+ const shi = hi | 0;
2881
+ if (shi < (t.minHi | 0) || shi === (t.minHi | 0) && lo < t.minLo || shi > (t.maxHi | 0) || shi === (t.maxHi | 0) && lo > t.maxLo) {
2882
+ this.outOfBound(idx);
2883
+ }
2884
+ if (this.bulkMode === 0 /* Values */ || this.bulkMode === 1 /* Values32 */) {
2885
+ t.values[idx] = this.vSigned();
2886
+ } else if (this.bulkMode === 2 /* Typed */) t.typed[idx] = lo | 0;
2887
+ else if (this.bulkMode === 3 /* Typed64 */) {
2888
+ const o = this.bulk64;
2889
+ o[idx * 2 + LO] = lo;
2890
+ o[idx * 2 + HI] = hi;
2891
+ } else if (this.bulkMode === 5 /* Longs */) t.longs[idx] = new Long(lo, hi);
2892
+ else {
2893
+ t.lo[idx] = lo;
2894
+ t.hi[idx] = hi;
2895
+ }
2896
+ }
2897
+ /**
2898
+ * Refuse an element outside the bound its target declared (§7.3): the message
2899
+ * says a thing the schema does not allow, so it is `INVALID` and terminal, the
2900
+ * same verdict at the same point as a generated per-element guard raises today.
2901
+ * The destination keeps everything written before this element — see
2902
+ * {@link ArrayTarget}.
2903
+ */
2904
+ outOfBound(index) {
2905
+ const t = this.bulk;
2906
+ if (t !== null) this.trimPlain(t, index);
2907
+ this.fail(`array ${this.id}: element ${index} outside the schema bound`);
2908
+ }
2909
+ /**
2910
+ * Report one payload piece: the caller's own fed chunk plus the coordinates of
2911
+ * the piece inside it (§6.6.3). No view is created — this decoder allocates
2912
+ * nothing (§6.6) and exposes no borrowed slice of its own (§6.7); `src` is the
2913
+ * very array the caller passed to `feed`, so whoever wants the bytes copies
2914
+ * them out of memory it already owns, during the call.
2915
+ */
2916
+ emitBytes(src, start, end) {
2917
+ const v = this.cur;
2918
+ if (this.fixSub === FixlenSubtype.String) {
2919
+ v.string?.(this.id, this.fixLen, this.fixOff, src, start, end);
2920
+ } else {
2921
+ v.blob?.(this.id, this.fixLen, this.fixOff, src, start, end);
2922
+ }
2923
+ }
2924
+ /**
2925
+ * Consume varint bytes from `input` at `i` into the {@link vLo} / {@link vHi}
2926
+ * accumulator, resuming across chunk boundaries; sets {@link vComplete} when a
2927
+ * terminator byte arrives. Number-only — no per-byte `bigint`.
2928
+ */
2929
+ varintStep(input, i) {
2930
+ if (this.vBytes === 0) {
2931
+ const n0 = input.length;
2932
+ const b0 = input[i];
2933
+ if (b0 < 128) {
2934
+ this.vLo = b0;
2935
+ this.vHi = 0;
2936
+ this.vComplete = true;
2937
+ return i + 1;
2938
+ }
2939
+ if (i + 1 < n0) {
2940
+ const b1 = input[i + 1];
2941
+ if (b1 < 128) {
2942
+ this.vLo = b0 & 127 | b1 << 7;
2943
+ this.vHi = 0;
2944
+ this.vComplete = true;
2945
+ return i + 2;
2946
+ }
2947
+ if (i + 2 < n0) {
2948
+ const b2 = input[i + 2];
2949
+ if (b2 < 128) {
2950
+ this.vLo = b0 & 127 | (b1 & 127) << 7 | b2 << 14;
2951
+ this.vHi = 0;
2952
+ this.vComplete = true;
2953
+ return i + 3;
2954
+ }
2955
+ }
2956
+ }
2957
+ if (n0 - i >= VARINT_MAX_BYTES) return this.varintFull(input, i);
2958
+ }
2959
+ const k0 = this.vBytes;
2960
+ let lo = k0 === 0 ? 0 : this.vLo;
2961
+ let hi = k0 === 0 ? 0 : this.vHi;
2962
+ let k = k0;
2963
+ const n = input.length;
2964
+ while (i < n) {
2965
+ if (k >= VARINT_MAX_BYTES) this.fail("varint overflow");
2966
+ const b = input[i++];
2967
+ if (k < 4) lo |= (b & 127) << 7 * k;
2968
+ else if (k === 4) {
2969
+ lo |= (b & 15) << 28;
2970
+ hi |= b >> 4 & 7;
2971
+ } else {
2972
+ if (k === 9 && (b & 127) >> 1 !== 0) this.fail("varint overflow");
2973
+ hi |= (b & 127) << 7 * k - 32;
2974
+ }
2975
+ k++;
2976
+ if ((b & 128) === 0) {
2977
+ this.vLo = lo;
2978
+ this.vHi = hi;
2979
+ this.vBytes = 0;
2980
+ this.vComplete = true;
2981
+ return i;
2982
+ }
2983
+ }
2984
+ if (k >= VARINT_MAX_BYTES) this.fail("varint overflow");
2985
+ this.vLo = lo;
2986
+ this.vHi = hi;
2987
+ this.vBytes = k;
1474
2988
  this.vComplete = false;
2989
+ return i;
1475
2990
  }
1476
- /** The accumulated varint as a `bigint` (full 64-bit fidelity). */
1477
- vBig() {
1478
- return this.vHi === 0 ? BigInt(this.vLo >>> 0) : BigInt(this.vHi >>> 0) << 32n | BigInt(this.vLo >>> 0);
2991
+ /**
2992
+ * Decode the varint at `i` **if that needs no resume bookkeeping**, returning
2993
+ * the index past it, or `-1` when it does (leaving the accumulator untouched,
2994
+ * so the resumable ladder starts the word from scratch).
2995
+ *
2996
+ * Three cases, in the order they are worth testing:
2997
+ *
2998
+ * * ten bytes in hand — the unrolled {@link varintFull}, no bounds checks at
2999
+ * all;
3000
+ * * a one-byte varint — every small id, length, count and scalar;
3001
+ * * a two-byte one — every id past 15, and mid-sized lengths and counts.
3002
+ *
3003
+ * Past that the word may straddle the chunk, and only the ladder can carry the
3004
+ * state across `feed` calls.
3005
+ */
3006
+ varintQuick(input, i, n) {
3007
+ if (n - i >= VARINT_MAX_BYTES) return this.varintFull(input, i);
3008
+ if (i >= n) return -1;
3009
+ const b0 = input[i];
3010
+ if (b0 < 128) return this.setVarint(b0, 0, i + 1);
3011
+ if (i + 1 < n) {
3012
+ const b1 = input[i + 1];
3013
+ if (b1 < 128) return this.setVarint(b0 & 127 | b1 << 7, 0, i + 2);
3014
+ if (i + 2 < n) {
3015
+ const b2 = input[i + 2];
3016
+ if (b2 < 128) {
3017
+ return this.setVarint(b0 & 127 | (b1 & 127) << 7 | b2 << 14, 0, i + 3);
3018
+ }
3019
+ }
3020
+ }
3021
+ return -1;
1479
3022
  }
1480
3023
  /**
1481
- * The accumulated varint as an unsigned value, number-first: a `number` when
1482
- * it fits exactly (`≤ 2^53-1`, which covers all ids, u8..u32 and small u64s),
1483
- * a `bigint` only beyond that. Avoids a bigint allocation on the common path.
3024
+ * Decode one varint that is **guaranteed** to lie wholly within `input` — the
3025
+ * caller has checked that {@link VARINT_MAX_BYTES} bytes remain — into
3026
+ * {@link vLo} / {@link vHi}, returning the index past it.
3027
+ *
3028
+ * Unrolled and branch-per-byte: with the bytes known to be present there is no
3029
+ * bounds check, no resume counter and no `k`-dispatch per byte, which is the
3030
+ * whole per-byte cost of the resumable loop above. Reports the same `>64-bit`
3031
+ * overflow as that loop.
3032
+ */
3033
+ varintFull(input, i) {
3034
+ let b = input[i];
3035
+ let lo = b & 127;
3036
+ let hi = 0;
3037
+ if (b < 128) return this.setVarint(lo, 0, i + 1);
3038
+ b = input[i + 1];
3039
+ lo |= (b & 127) << 7;
3040
+ if (b < 128) return this.setVarint(lo, 0, i + 2);
3041
+ b = input[i + 2];
3042
+ lo |= (b & 127) << 14;
3043
+ if (b < 128) return this.setVarint(lo, 0, i + 3);
3044
+ b = input[i + 3];
3045
+ lo |= (b & 127) << 21;
3046
+ if (b < 128) return this.setVarint(lo, 0, i + 4);
3047
+ b = input[i + 4];
3048
+ lo |= (b & 15) << 28;
3049
+ hi = b >> 4 & 7;
3050
+ if (b < 128) return this.setVarint(lo, hi, i + 5);
3051
+ b = input[i + 5];
3052
+ hi |= (b & 127) << 3;
3053
+ if (b < 128) return this.setVarint(lo, hi, i + 6);
3054
+ b = input[i + 6];
3055
+ hi |= (b & 127) << 10;
3056
+ if (b < 128) return this.setVarint(lo, hi, i + 7);
3057
+ b = input[i + 7];
3058
+ hi |= (b & 127) << 17;
3059
+ if (b < 128) return this.setVarint(lo, hi, i + 8);
3060
+ b = input[i + 8];
3061
+ hi |= (b & 127) << 24;
3062
+ if (b < 128) return this.setVarint(lo, hi, i + 9);
3063
+ b = input[i + 9];
3064
+ if ((b & 127) >> 1 !== 0) this.fail("varint overflow");
3065
+ hi |= (b & 127) << 31;
3066
+ if (b < 128) return this.setVarint(lo, hi, i + 10);
3067
+ this.fail("varint overflow");
3068
+ }
3069
+ /** Publish a fully-decoded varint and the cursor past it (see {@link varintFull}). */
3070
+ setVarint(lo, hi, i) {
3071
+ this.vLo = lo;
3072
+ this.vHi = hi;
3073
+ this.vBytes = 0;
3074
+ this.vComplete = true;
3075
+ return i;
3076
+ }
3077
+ /**
3078
+ * The accumulated varint as an unsigned value, number-first: a `number` when it
3079
+ * fits exactly (`≤ 2^53-1`, which covers all ids, u8..u32 and small u64s), a
3080
+ * `bigint` only beyond that.
1484
3081
  */
1485
3082
  vUnsigned() {
1486
3083
  const hi = this.vHi >>> 0;
1487
- return hi <= 2097151 ? hi * TWO322 + (this.vLo >>> 0) : this.vBig();
3084
+ return hi <= 2097151 ? hi * TWO32 + (this.vLo >>> 0) : joinU64(this.vLo >>> 0, hi);
1488
3085
  }
1489
3086
  /** The accumulated zig-zag varint as a signed value, number-first (see {@link vUnsigned}). */
1490
3087
  vSigned() {
1491
3088
  const hi = this.vHi >>> 0;
1492
3089
  if (hi <= 2097151) {
1493
- const r = hi * TWO322 + (this.vLo >>> 0);
3090
+ const r = hi * TWO32 + (this.vLo >>> 0);
1494
3091
  return r % 2 === 0 ? r / 2 : -(r + 1) / 2;
1495
3092
  }
1496
- return zigzagDecode(this.vBig());
3093
+ const lo = this.vLo >>> 0;
3094
+ const mask = -(lo & 1) >>> 0;
3095
+ return joinI64(
3096
+ ((lo >>> 1 | hi << 31) >>> 0 ^ mask) >>> 0,
3097
+ (hi >>> 1 ^ mask) >>> 0
3098
+ );
1497
3099
  }
1498
- /** The accumulated varint as a JS number — exact for ids/lengths/counts. */
3100
+ /**
3101
+ * The accumulated varint as a JS number — exact for ids/lengths/counts.
3102
+ *
3103
+ * The `>>> 0` is load-bearing: `vHi` is accumulated with 32-bit bitwise ops, so
3104
+ * bit 63 of the varint lands on its sign bit and the value reads back negative,
3105
+ * sliding past the `count > ARRAY_MAX` guard.
3106
+ */
1499
3107
  vNum() {
1500
- return this.vHi * TWO322 + (this.vLo >>> 0);
1501
- }
1502
- /** The accumulated varint's low 3 tag bits (the wire type / fixlen subtype). */
1503
- vTag() {
1504
- return this.vLo & 7;
3108
+ return (this.vHi >>> 0) * TWO32 + (this.vLo >>> 0);
1505
3109
  }
1506
3110
  /** The accumulated varint with its low 3 tag bits stripped (`value >> 3`). */
1507
3111
  vUpper() {
1508
- return (this.vHi >>> 0) * (TWO322 / 8) + (this.vLo >>> 3);
3112
+ return (this.vHi >>> 0) * (TWO32 / 8) + (this.vLo >>> 3);
1509
3113
  }
1510
- /** Accumulate `need` raw bytes into {@link scratch}. */
1511
- fpStep(input, i) {
1512
- while (this.have < this.need && i < input.length) {
1513
- this.scratch[this.have++] = input[i++];
3114
+ /**
3115
+ * The handle over `input`, built on demand and reused while the same chunk is
3116
+ * being fed (see {@link view}).
3117
+ */
3118
+ dataView(input) {
3119
+ if (this.viewOf !== input) {
3120
+ this.view = new DataView(input.buffer, input.byteOffset, input.byteLength);
3121
+ this.viewOf = input;
1514
3122
  }
3123
+ return this.view;
3124
+ }
3125
+ /** Accumulate up to `need` raw float bytes into {@link fpLo} / {@link fpHi}. */
3126
+ fpStep(input, i, n) {
3127
+ let have = this.have;
3128
+ const need = this.need;
3129
+ while (have < need && i < n) {
3130
+ const b = input[i++];
3131
+ if (have < 4) this.fpLo |= b << (have << 3);
3132
+ else this.fpHi |= b << (have - 4 << 3);
3133
+ have++;
3134
+ }
3135
+ this.have = have;
1515
3136
  return i;
1516
3137
  }
3138
+ /** Start a fresh fp accumulation of `need` bytes. */
3139
+ fpBegin(need) {
3140
+ this.need = need;
3141
+ this.have = 0;
3142
+ this.fpLo = 0;
3143
+ this.fpHi = 0;
3144
+ }
1517
3145
  };
3146
+ function u32le(b, i) {
3147
+ return (b[i] | b[i + 1] << 8 | b[i + 2] << 16 | b[i + 3] << 24) >>> 0;
3148
+ }
1518
3149
 
1519
3150
  // src/decode/istream.ts
1520
3151
  var IStream = class {
1521
3152
  /**
1522
- * @param limits Optional opt-in decode caps ({@link DecodeLimits}). An
1523
- * over-limit array count or string / blob length throws {@link SofabError}
1524
- * (`LIMIT_EXCEEDED`) from {@link feed}, at the offending field's header and
1525
- * before any of its payload is streamed to the visitor. Omit for no caps.
1526
- */
1527
- constructor(limits) {
1528
- this.state = new DecoderState(limits);
1529
- }
1530
- /**
1531
- * Feed a chunk of bytes, dispatching decoded fields to `visitor`. Throws
1532
- * {@link SofabError} (`INVALID_MSG`) only if the bytes are *malformed*;
1533
- * running out of bytes mid-field is not an error — it simply suspends until
1534
- * the next chunk (see {@link end}).
3153
+ * @param visitor The field handler this stream drives, for its whole life — and
3154
+ * the layer that holds the receiver caps, if any (§6.2.1; see the class doc).
1535
3155
  */
1536
- feed(chunk, visitor) {
1537
- this.state.push(chunk, visitor);
3156
+ constructor(visitor) {
3157
+ this.state = new DecoderState(visitor);
1538
3158
  }
1539
3159
  /**
1540
- * Report whether the stream ended exactly at a field boundary. Call after the
1541
- * final {@link feed}: returns {@link DecodeStatus.Complete} at a clean field
1542
- * boundary, or {@link DecodeStatus.Incomplete} if the last chunk ended inside
1543
- * a field (a partial varint, an unfinished payload / array, or a still-open
1544
- * nested sequence).
3160
+ * Feed a chunk of bytes, dispatching decoded fields to the bound visitor, and
3161
+ * **return** where the decode stands after them (§5.2.1):
3162
+ * {@link DecodeStatus.Complete} when they end exactly at a field boundary,
3163
+ * {@link DecodeStatus.Incomplete} when they end *inside* a field (a partial
3164
+ * varint, an unfinished payload / array, or a still-open nested sequence).
3165
+ * Running out of bytes mid-field is not an error — the decode merely suspends
3166
+ * until the next chunk, and the caller owns end-of-input.
1545
3167
  *
1546
- * Per the finish-less spec (MESSAGE_SPEC §7) this is a pure accessor: it never
1547
- * throws and never promotes an incomplete decode to an error — the caller owns
1548
- * end-of-input and decides whether a trailing `Incomplete` is a truncation
1549
- * error. (A *malformed* message has already thrown from {@link feed}.)
3168
+ * **This call is the only place the answer is.** There is no finish / finalize
3169
+ * step (§5.2.4) and no status accessor: what this returns, or throws, is the
3170
+ * whole of what the stream has to say, so a caller is never one question short
3171
+ * after it and never has two answers to reconcile. Feeding an empty chunk
3172
+ * re-reads the same value without consuming anything, for a caller that wants
3173
+ * the outcome again without holding on to it.
3174
+ *
3175
+ * The chunk is borrowed **only for the duration of this call** (§6.0): once it
3176
+ * returns, the caller may reuse, overwrite or free that memory, and the decoded
3177
+ * message is unaffected — the decoder retains nothing that points into it.
3178
+ *
3179
+ * `INVALID` travels on the error channel — this port's idiomatic surfacing of
3180
+ * it: *malformed* bytes throw {@link SofabError} (`INVALID_MSG`) instead of
3181
+ * returning a status, which is why the return type names only the other two.
3182
+ * That verdict is **terminal** (§5.2.1): the stream latches it, so a caller that
3183
+ * catches the throw and feeds on gets the same error again from every later
3184
+ * call — no further byte is consumed and no visitor method is invoked. A caller
3185
+ * that caught it already holds the verdict, in the code on the error it caught.
3186
+ *
3187
+ * A receiver-limit rejection (`LIMIT_EXCEEDED`, §6.2.1) travels the same
3188
+ * channel — thrown out of the visitor callback that compared the cap — but it
3189
+ * is **not** the `INVALID` outcome and never becomes one: the bytes are
3190
+ * well-formed and the same message decodes under a looser cap, so it is a
3191
+ * policy rejection (§6.2.1, §6.3). The two stay distinguishable by their code,
3192
+ * which is what §6.3 requires; §6.3 leaves the surfacing open between "a fourth
3193
+ * decode outcome" and "a terminal failure carrying the `LimitExceeded` code on
3194
+ * the error channel", and this port takes the second. **Terminal** is the other
3195
+ * half of that sentence and holds exactly as it does for `INVALID`: the stream
3196
+ * latches the rejection, so every later call re-throws it under the same code,
3197
+ * consumes no byte and drives no visitor method. It is *only* on the error
3198
+ * channel — the three-valued outcome has no value for "valid, but more than I am
3199
+ * configured to accept", so there is nothing about it to read back as a status,
3200
+ * and nothing that has to be kept in step with the throw.
1550
3201
  */
1551
- end() {
1552
- return this.state.finish();
3202
+ feed(chunk) {
3203
+ this.state.push(chunk);
3204
+ return this.state.outcome();
1553
3205
  }
1554
3206
  };
1555
- function decode(bytes, visitor, limits) {
1556
- decodeContiguous(bytes, visitor, limits);
3207
+ function decode(bytes, visitor) {
3208
+ const state = pooled ?? new DecoderState();
3209
+ pooled = null;
3210
+ try {
3211
+ state.begin(visitor);
3212
+ state.push(bytes);
3213
+ if (state.outcome() !== DecodeStatus.Complete) {
3214
+ throw incompleteError("truncated message: input ends inside a field");
3215
+ }
3216
+ } finally {
3217
+ state.release();
3218
+ pooled = state;
3219
+ }
1557
3220
  }
3221
+ var pooled = null;
1558
3222
 
1559
- // src/decode/cursor.ts
1560
- var TWO323 = 4294967296;
3223
+ // src/decode/text.ts
1561
3224
  var _utf8 = new TextDecoder("utf-8", { fatal: true });
1562
- var Cursor = class {
1563
- constructor(buf, limits) {
1564
- /** Field id of the header last accepted by {@link readHeader}. */
1565
- this.id = 0;
1566
- /** Wire type of the header last accepted by {@link readHeader}. */
1567
- this.wire = 0;
1568
- /**
1569
- * Fixlen subtype of the header last accepted by {@link readHeader} — one of
1570
- * {@link FixlenSubtype} — when its {@link wire} is {@link WireType.Fixlen} or
1571
- * {@link WireType.ArrayFixlen}; `-1` otherwise (a non-fixlen field, or a
1572
- * fixlen field whose subtype word is truncated away).
1573
- *
1574
- * The four fixlen subtypes (`fp32`, `fp64`, `string`, `blob`) all share one
1575
- * {@link wire} type, so {@link wire} alone cannot separate them. This is the
1576
- * companion accessor that can: a generated guard reads it right after
1577
- * {@link readHeader} to skip a field whose delivered subtype contradicts the
1578
- * schema (MESSAGE_SPEC §7.3), exactly as it already does on {@link wire} for
1579
- * the other kinds:
1580
- *
1581
- * ```ts
1582
- * case 9: if (c.wire !== WireType.Fixlen || c.fixSub !== FixlenSubtype.Fp64) {
1583
- * c.skip(c.wire); break;
1584
- * } o.somefp64 = c.readFp64(); break;
1585
- * ```
1586
- *
1587
- * It is *peeked* — the subtype word is not consumed — so the matching typed
1588
- * reader (or {@link skip}) still reads and validates it, and a malformed or
1589
- * truncated word surfaces `INVALID` / `INCOMPLETE` there as before.
1590
- */
1591
- this.fixSub = -1;
1592
- this.p = 0;
1593
- // Last varint, as two unsigned 32-bit halves (see readVarint).
1594
- this.lo = 0;
1595
- this.hi = 0;
1596
- // Number of nested sequences currently open (0 = root). Incremented when
1597
- // readHeader accepts a SequenceStart, decremented when it consumes the matching
1598
- // SequenceEnd (or when skip() discards a whole nested sequence). Lets the pull
1599
- // parser tell a root-level dangling sequence-end (INVALID) and an unclosed
1600
- // sequence at end-of-buffer (INCOMPLETE) apart from a clean boundary.
1601
- this.depth = 0;
1602
- this.buf = buf;
1603
- this.n = buf.length;
1604
- this.view = new DataView(buf.buffer, buf.byteOffset, buf.length);
1605
- this.maxArrayCount = limits?.maxArrayCount ?? Infinity;
1606
- this.maxStringLen = limits?.maxStringLen ?? Infinity;
1607
- this.maxBlobLen = limits?.maxBlobLen ?? Infinity;
1608
- }
1609
- /**
1610
- * Advance to the next field header. Returns `true` and sets {@link id} /
1611
- * {@link wire} when a field follows; returns `false` — consuming the marker —
1612
- * at the end of the buffer *or* at the sequence-end that closes the sequence
1613
- * this decoder is reading. So a generated per-type decoder loops uniformly:
1614
- *
1615
- * ```ts
1616
- * while (c.readHeader()) {
1617
- * switch (c.id) {
1618
- * case 4: this.u32 = Number(c.readUnsigned()); break;
1619
- * case 10: this.child = Child.decodeFrom(c); break; // nested sequence
1620
- * default: c.skip(c.wire); break; // unknown field
1621
- * }
1622
- * }
1623
- * ```
1624
- *
1625
- * At the root the loop ends at end-of-buffer; inside a nested sequence it ends
1626
- * at the matching {@link WireType.SequenceEnd} (which is consumed). A field
1627
- * whose id is out of range throws {@link SofabError} (`INVALID_MSG`).
1628
- */
1629
- readHeader() {
1630
- if (this.p >= this.n) {
1631
- if (this.depth > 0) {
1632
- throw incompleteError("truncated message: unbalanced sequence");
3225
+ var FLAT_MAX = 16;
3226
+ function decodeUtf8(buf, start = 0, end = buf.length) {
3227
+ const n = end - start;
3228
+ if (n <= 0) return "";
3229
+ if (n <= FLAT_MAX) {
3230
+ let ascii = true;
3231
+ for (let k = start; k < end; k++) {
3232
+ if (buf[k] & 128) {
3233
+ ascii = false;
3234
+ break;
1633
3235
  }
1634
- return false;
1635
3236
  }
1636
- this.readVarint();
1637
- const wire = this.lo & 7;
1638
- if (wire === WireType.SequenceEnd) {
1639
- if (this.depth === 0) {
1640
- throw invalidMsgError("unbalanced sequence end");
1641
- }
1642
- this.depth--;
1643
- return false;
1644
- }
1645
- const id = this.upper();
1646
- if (id > ID_MAX) throw invalidMsgError(`field id ${id} out of range`);
1647
- if (wire === WireType.SequenceStart) {
1648
- if (this.depth >= MAX_DEPTH) {
1649
- throw invalidMsgError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
3237
+ if (ascii) {
3238
+ switch (n) {
3239
+ case 1:
3240
+ return String.fromCharCode(buf[start]);
3241
+ case 2:
3242
+ return String.fromCharCode(buf[start], buf[start + 1]);
3243
+ case 3:
3244
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2]);
3245
+ case 4:
3246
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3]);
3247
+ case 5:
3248
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4]);
3249
+ case 6:
3250
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5]);
3251
+ case 7:
3252
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6]);
3253
+ case 8:
3254
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7]);
3255
+ case 9:
3256
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8]);
3257
+ case 10:
3258
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9]);
3259
+ case 11:
3260
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9], buf[start + 10]);
3261
+ case 12:
3262
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9], buf[start + 10], buf[start + 11]);
3263
+ case 13:
3264
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9], buf[start + 10], buf[start + 11], buf[start + 12]);
3265
+ case 14:
3266
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9], buf[start + 10], buf[start + 11], buf[start + 12], buf[start + 13]);
3267
+ case 15:
3268
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9], buf[start + 10], buf[start + 11], buf[start + 12], buf[start + 13], buf[start + 14]);
3269
+ case 16:
3270
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3], buf[start + 4], buf[start + 5], buf[start + 6], buf[start + 7], buf[start + 8], buf[start + 9], buf[start + 10], buf[start + 11], buf[start + 12], buf[start + 13], buf[start + 14], buf[start + 15]);
1650
3271
  }
1651
- this.depth++;
1652
- }
1653
- this.id = id;
1654
- this.wire = wire;
1655
- this.fixSub = this.peekFixSub(wire);
1656
- return true;
1657
- }
1658
- /** Read an unsigned scalar (wire {@link WireType.Unsigned}), number-first. */
1659
- readUnsigned() {
1660
- this.readVarint();
1661
- return this.unsignedValue();
1662
- }
1663
- /** Read a signed scalar (wire {@link WireType.Signed}), zig-zag, number-first. */
1664
- readSigned() {
1665
- this.readVarint();
1666
- return this.signedValue();
1667
- }
1668
- /** Read a 32-bit float scalar (wire {@link WireType.Fixlen}, subtype fp32). */
1669
- readFp32() {
1670
- this.fixlenHeader(FixlenSubtype.Fp32, 4);
1671
- return this.rawFp32();
1672
- }
1673
- /**
1674
- * Read a 32-bit float scalar as its raw 4 wire bytes (little-endian), zero-copy
1675
- * — the bit-preserving companion to {@link readFp32}.
1676
- *
1677
- * {@link readFp32} returns a JS `number` (a 64-bit double), and widening an
1678
- * fp32 *signaling* NaN into a double quiets it (0x7F800001 → 0x7FC00001), so a
1679
- * value consumer can never round-trip one bit-for-bit (§4.6). Generated
1680
- * bit-exact decode reads the bytes here instead and re-emits them verbatim with
1681
- * {@link OStream.writeFixlen} (subtype fp32) — mirroring the visitor `raw`
1682
- * channel on the push paths (fast.ts / state.ts), which the pull path was
1683
- * missing (corelib-ts#66).
1684
- *
1685
- * The header (subtype fp32, length 4) is validated exactly as in
1686
- * {@link readFp32}; the returned view aliases the source buffer, valid only
1687
- * until it is reused, like {@link readBlob}.
1688
- */
1689
- readFp32Raw() {
1690
- this.fixlenHeader(FixlenSubtype.Fp32, 4);
1691
- return this.take(4);
1692
- }
1693
- /** Read a 64-bit float scalar (wire {@link WireType.Fixlen}, subtype fp64). */
1694
- readFp64() {
1695
- this.fixlenHeader(FixlenSubtype.Fp64, 8);
1696
- return this.rawFp64();
1697
- }
1698
- /**
1699
- * Read a UTF-8 string scalar (wire {@link WireType.Fixlen}, subtype string).
1700
- * Pass the schema `maxlen` (byte length) for a bounded string so an
1701
- * over-length is rejected as `INVALID` at the header, before the payload is
1702
- * taken (see {@link fixlenLen}); the wire length is exactly the UTF-8 byte
1703
- * length, so the check is exact. Omit for an unbounded string.
1704
- */
1705
- readString(schemaMaxlen) {
1706
- const len = this.fixlenLen(FixlenSubtype.String, schemaMaxlen);
1707
- const bytes = this.take(len);
1708
- try {
1709
- return _utf8.decode(bytes);
1710
- } catch {
1711
- throw invalidMsgError("invalid UTF-8 in string");
1712
- }
1713
- }
1714
- /**
1715
- * Read a blob scalar (wire {@link WireType.Fixlen}, subtype blob) as a
1716
- * zero-copy {@link Uint8Array} view into the source buffer.
1717
- */
1718
- readBlob(schemaMaxlen) {
1719
- const len = this.fixlenLen(FixlenSubtype.Blob, schemaMaxlen);
1720
- return this.take(len);
1721
- }
1722
- /**
1723
- * Read an unsigned array (wire {@link WireType.ArrayUnsigned}), number-first
1724
- * per element. Pass the schema `count` for a bounded array so an over-count is
1725
- * rejected as `INVALID` at the header (see {@link arrayCount}); omit it for an
1726
- * unbounded array (today's behavior).
1727
- */
1728
- readUnsignedArray(schemaCount) {
1729
- const count = this.arrayCount(schemaCount);
1730
- const out = new Array(count);
1731
- for (let i = 0; i < count; i++) {
1732
- this.readVarint();
1733
- out[i] = this.unsignedValue();
1734
- }
1735
- return out;
1736
- }
1737
- /** Read a signed array (wire {@link WireType.ArraySigned}), zig-zag, number-first per element. */
1738
- readSignedArray(schemaCount) {
1739
- const count = this.arrayCount(schemaCount);
1740
- const out = new Array(count);
1741
- for (let i = 0; i < count; i++) {
1742
- this.readVarint();
1743
- out[i] = this.signedValue();
1744
- }
1745
- return out;
1746
- }
1747
- /**
1748
- * Read an unsigned 64-bit array into {@link Long}[] — the `bigint`-free path.
1749
- * Each element keeps the raw lo/hi halves; call {@link Long.toBigInt} to
1750
- * materialise only the values the caller actually needs.
1751
- */
1752
- readUnsignedArrayLong(schemaCount) {
1753
- const count = this.arrayCount(schemaCount);
1754
- const out = new Array(count);
1755
- for (let i = 0; i < count; i++) {
1756
- this.readVarint();
1757
- out[i] = new Long(this.lo, this.hi);
1758
- }
1759
- return out;
1760
- }
1761
- /** Read a signed 64-bit array (zig-zag) into {@link Long}[] — the `bigint`-free path. */
1762
- readSignedArrayLong(schemaCount) {
1763
- const count = this.arrayCount(schemaCount);
1764
- const out = new Array(count);
1765
- for (let i = 0; i < count; i++) {
1766
- this.readVarint();
1767
- const lo = this.lo >>> 0;
1768
- const hi = this.hi >>> 0;
1769
- const mask = -(lo & 1) >>> 0;
1770
- out[i] = new Long((lo >>> 1 | hi << 31) >>> 0 ^ mask, hi >>> 1 >>> 0 ^ mask);
1771
- }
1772
- return out;
1773
- }
1774
- /** Read an fp32 array (wire {@link WireType.ArrayFixlen}, element subtype fp32). */
1775
- readFp32Array(schemaCount) {
1776
- const count = this.arrayFixlenHeader(FixlenSubtype.Fp32, 4, schemaCount);
1777
- const out = new Array(count);
1778
- for (let i = 0; i < count; i++) out[i] = this.rawFp32();
1779
- return out;
1780
- }
1781
- /**
1782
- * Read an fp32 array as its raw little-endian element payload (`count * 4`
1783
- * bytes), zero-copy — the bit-preserving companion to {@link readFp32Array}.
1784
- * Widening each element to a JS `number` quiets an fp32 *signaling* NaN just as
1785
- * on the scalar path (§4.6; see {@link readFp32Raw}), so bit-exact decode reads
1786
- * the whole payload here and re-emits it with {@link OStream.writeFp32ArrayRaw}
1787
- * (corelib-ts#66). The header (element subtype fp32, size 4) is validated
1788
- * exactly as in {@link readFp32Array}; the returned view aliases the source
1789
- * buffer, like {@link readBlob}.
1790
- */
1791
- readFp32ArrayRaw(schemaCount) {
1792
- const count = this.arrayFixlenHeader(FixlenSubtype.Fp32, 4, schemaCount);
1793
- return this.take(count * 4);
1794
- }
1795
- /** Read an fp64 array (wire {@link WireType.ArrayFixlen}, element subtype fp64). */
1796
- readFp64Array(schemaCount) {
1797
- const count = this.arrayFixlenHeader(FixlenSubtype.Fp64, 8, schemaCount);
1798
- const out = new Array(count);
1799
- for (let i = 0; i < count; i++) out[i] = this.rawFp64();
1800
- return out;
1801
- }
1802
- /**
1803
- * Consume the value of the field whose header {@link readHeader} just accepted,
1804
- * discarding it — for a `default:` branch that keeps the cursor in sync on an
1805
- * unknown id. Pass {@link wire}. A {@link WireType.SequenceStart} skips the
1806
- * whole nested sequence.
1807
- */
1808
- skip(wire) {
1809
- if (wire === WireType.SequenceStart) {
1810
- this.skipSequence();
1811
- this.depth--;
1812
- return;
1813
3272
  }
1814
- this.skipValue(wire);
1815
3273
  }
1816
- // --- value skipping -----------------------------------------------------
1817
- skipValue(wire) {
1818
- switch (wire) {
1819
- case WireType.Unsigned:
1820
- case WireType.Signed:
1821
- this.readVarint();
1822
- return;
1823
- case WireType.Fixlen: {
1824
- this.readVarint();
1825
- const sub = this.lo & 7;
1826
- const len = this.upper();
1827
- if (sub > FixlenSubtype.Blob) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
1828
- if (sub === FixlenSubtype.Fp32 || sub === FixlenSubtype.Fp64) {
1829
- if (len !== (sub === FixlenSubtype.Fp32 ? 4 : 8)) {
1830
- throw invalidMsgError("fixlen float length mismatch");
1831
- }
1832
- } else if (len > FIXLEN_MAX) {
1833
- throw invalidMsgError("fixlen length out of range");
1834
- }
1835
- this.take(len);
1836
- return;
1837
- }
1838
- case WireType.ArrayUnsigned:
1839
- case WireType.ArraySigned: {
1840
- const count = this.arrayCount();
1841
- for (let i = 0; i < count; i++) this.readVarint();
1842
- return;
1843
- }
1844
- case WireType.ArrayFixlen: {
1845
- this.readVarint();
1846
- const count = this.num();
1847
- if (count > ARRAY_MAX) throw invalidMsgError("array count out of range");
1848
- if (count > this.maxArrayCount) {
1849
- throw limitExceededError(
1850
- `array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`
1851
- );
1852
- }
1853
- this.readVarint();
1854
- const sub = this.lo & 7;
1855
- const size = this.upper();
1856
- const ok = sub === FixlenSubtype.Fp32 && size === 4 || sub === FixlenSubtype.Fp64 && size === 8;
1857
- if (!ok) throw invalidMsgError("invalid fixlen array element type");
1858
- this.take(count * size);
1859
- return;
1860
- }
1861
- default:
1862
- throw invalidMsgError(`invalid wire type ${wire}`);
1863
- }
3274
+ try {
3275
+ return _utf8.decode(buf.subarray(start, end));
3276
+ } catch {
3277
+ throw invalidMsgError("invalid UTF-8 in string");
1864
3278
  }
1865
- skipSequence() {
1866
- let depth = 1;
1867
- while (depth > 0) {
1868
- if (this.p >= this.n) throw incompleteError("truncated message: unbalanced sequence");
1869
- this.readVarint();
1870
- const wire = this.lo & 7;
1871
- if (wire === WireType.SequenceEnd) {
1872
- depth--;
1873
- continue;
1874
- }
1875
- const id = this.upper();
1876
- if (id > ID_MAX) throw invalidMsgError(`field id ${id} out of range`);
1877
- if (wire === WireType.SequenceStart) {
1878
- if (this.depth + depth - 1 >= MAX_DEPTH) {
1879
- throw invalidMsgError(`nesting exceeds MAX_DEPTH (${MAX_DEPTH})`);
1880
- }
1881
- depth++;
1882
- } else {
1883
- this.skipValue(wire);
1884
- }
1885
- }
3279
+ }
3280
+
3281
+ // src/decode/acc.ts
3282
+ var PayloadAcc = class {
3283
+ constructor() {
3284
+ this.buf = null;
3285
+ this.len = 0;
1886
3286
  }
1887
- // --- field helpers ------------------------------------------------------
1888
3287
  /**
1889
- * Peek the delivered fixlen subtype of the field {@link readHeader} just
1890
- * accepted, **without advancing the cursor** — the readers / {@link skip}
1891
- * still re-read and validate the word. Returns one of {@link FixlenSubtype}
1892
- * (0..3), a reserved value (4..7), or `-1` when the wire is not a fixlen kind
1893
- * or the subtype word is truncated away.
3288
+ * Contribute one piece — the bytes `src[start..end)` at `offset` of a
3289
+ * `total`-byte payload — and return the **whole** payload once it is complete,
3290
+ * or `null` while bytes are still outstanding.
3291
+ *
3292
+ * `offset === 0` starts a payload — and *resets* the accumulator, so a decode
3293
+ * that was abandoned mid-payload (an `INVALID` field, a declined subtree) leaves
3294
+ * nothing behind to corrupt the next one.
1894
3295
  *
1895
- * The subtype is the low 3 bits of the fixlen sub-header word, and the low
1896
- * bits of a LEB128 word live entirely in its **first** byte — so this only
1897
- * reads one byte, it never decodes a varint.
3296
+ * A late piece with no payload in progress returns `null` rather than writing
3297
+ * anywhere: the accumulator has no buffer to append to, and inventing one would
3298
+ * fabricate a payload out of a fragment.
1898
3299
  */
1899
- peekFixSub(wire) {
1900
- if (wire === WireType.Fixlen) {
1901
- return this.p < this.n ? this.buf[this.p] & 7 : -1;
3300
+ take(total, offset, src, start, end) {
3301
+ if (offset === 0) {
3302
+ this.buf = new Uint8Array(total);
3303
+ this.len = 0;
1902
3304
  }
1903
- if (wire === WireType.ArrayFixlen) {
1904
- let p = this.p;
1905
- while (p < this.n && this.buf[p] >= 128) p++;
1906
- p++;
1907
- return p < this.n ? this.buf[p] & 7 : -1;
3305
+ const b = this.buf;
3306
+ if (b === null) return null;
3307
+ let n = end - start;
3308
+ const room = total - this.len;
3309
+ if (n > room) n = room;
3310
+ if (n > 0) {
3311
+ b.set(src.subarray(start, start + n), this.len);
3312
+ this.len += n;
1908
3313
  }
1909
- return -1;
3314
+ if (this.len < total) return null;
3315
+ this.buf = null;
3316
+ return b;
3317
+ }
3318
+ };
3319
+
3320
+ // src/decode/seq.ts
3321
+ var NO_BYTES = new Uint8Array(0);
3322
+ var UNBOUNDED = -1;
3323
+ function requireReceiverBound(schemaBound, receiver, what, name) {
3324
+ if (schemaBound >= 0) return;
3325
+ if (Number.isFinite(receiver) && receiver >= 0) return;
3326
+ throw argumentError(
3327
+ `${name}: ${what} is ${receiver}, which states no cap \u2014 the schema left this field unbounded, and \xA76.2.1 admits no unset state and no unlimited mode. The number is generated code's to supply.`
3328
+ );
3329
+ }
3330
+ function indexBound(cap, receiverCap) {
3331
+ return cap >= 0 ? cap : receiverCap;
3332
+ }
3333
+ function rejectIndex(id, cap, receiverCap, name) {
3334
+ if (cap >= 0) {
3335
+ throw invalidMsgError(`${name}: array index above schema capacity ${cap}`);
3336
+ }
3337
+ throw limitExceededError(`${name}: array index ${id} exceeds the receiver cap ${receiverCap}`);
3338
+ }
3339
+ var ElementSeq = class {
3340
+ constructor(out, def, cap, name, receiverCap) {
3341
+ this.out = out;
3342
+ this.def = def;
3343
+ this.cap = cap;
3344
+ this.name = name;
3345
+ this.receiverCap = receiverCap;
3346
+ if (cap < 0) requireReceiverBound(cap, receiverCap, "the receiver array-index cap", name);
3347
+ this.bound = indexBound(cap, receiverCap);
1910
3348
  }
1911
3349
  /**
1912
- * Read and validate an array count word (0..ARRAY_MAX; §4.7/§4.8). When a
1913
- * `schemaCount` is given, a count above it is a schema-bound violation and is
1914
- * rejected as `INVALID` — see the check below.
3350
+ * Bound-check `id` and grow `out` to `id + 1`, filling any gap — and the slot
3351
+ * itself — with the element default.
3352
+ *
3353
+ * The check runs **before** the growth, which is the whole of §7.2 item 8's
3354
+ * "after a rejected id the container is not left partially extended": a
3355
+ * rejection leaves `out` exactly as it was, so a lower id delivered afterwards
3356
+ * still lands at its own index.
1915
3357
  */
1916
- arrayCount(schemaCount) {
1917
- this.readVarint();
1918
- const count = this.num();
1919
- if (count > ARRAY_MAX) throw invalidMsgError("array count out of range");
1920
- if (schemaCount !== void 0 && count > schemaCount) {
1921
- throw invalidMsgError("array count above schema capacity");
1922
- }
1923
- if (count > this.maxArrayCount) {
1924
- throw limitExceededError(
1925
- `array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`
1926
- );
1927
- }
1928
- if (count > this.n - this.p) throw incompleteError("truncated array");
1929
- return count;
1930
- }
1931
- /** Read a scalar fixlen sub-header, asserting subtype and exact byte length (floats). */
1932
- fixlenHeader(wantSub, wantLen) {
1933
- this.readVarint();
1934
- const sub = this.lo & 7;
1935
- const len = this.upper();
1936
- if (sub !== wantSub) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
1937
- if (len !== wantLen) throw invalidMsgError("fixlen float length mismatch");
1938
- }
1939
- /**
1940
- * Read a scalar fixlen sub-header for a string/blob, asserting subtype;
1941
- * returns the byte length. When a `schemaMaxlen` is given, a length above it
1942
- * is a schema-bound violation and is rejected as `INVALID` — see below.
1943
- */
1944
- fixlenLen(wantSub, schemaMaxlen) {
1945
- this.readVarint();
1946
- const sub = this.lo & 7;
1947
- const len = this.upper();
1948
- if (sub !== wantSub) throw invalidMsgError(`invalid fixlen subtype ${sub}`);
1949
- if (len > FIXLEN_MAX) throw invalidMsgError("fixlen length out of range");
1950
- if (schemaMaxlen !== void 0 && len > schemaMaxlen) {
1951
- throw invalidMsgError("fixlen length above schema maxlen");
1952
- }
1953
- const limit = wantSub === FixlenSubtype.String ? this.maxStringLen : this.maxBlobLen;
1954
- if (len > limit) {
1955
- const what = wantSub === FixlenSubtype.String ? "string" : "blob";
1956
- const name = wantSub === FixlenSubtype.String ? "maxStringLen" : "maxBlobLen";
1957
- throw limitExceededError(
1958
- `${what} length ${len} exceeds ${name} ${limit}`
1959
- );
1960
- }
1961
- return len;
3358
+ reserve(id) {
3359
+ if (id >= this.bound) rejectIndex(id, this.cap, this.receiverCap, this.name);
3360
+ while (this.out.length <= id) this.out.push(this.def);
1962
3361
  }
1963
3362
  /**
1964
- * Read an array fixlen element header (count + element type); returns the
1965
- * count. When a `schemaCount` is given, a count above it is a schema-bound
1966
- * violation and is rejected as `INVALID` — see below.
3363
+ * The two index bounds, without growing: the schema `count` as validity
3364
+ * (`INVALID`) or, where the schema left the array open, the receiver cap as
3365
+ * capacity (`LIMIT_EXCEEDED`). Never both — §6.2.1 keeps a cap off a field the
3366
+ * schema already bounds, which is why one `bound` can stand for both.
3367
+ *
3368
+ * Split out from {@link reserve} because a leaf element is bound-checked at its
3369
+ * length word, before its payload has arrived and so before there is anything to
3370
+ * place ({@link StringSeq.begin}).
1967
3371
  */
1968
- arrayFixlenHeader(wantSub, wantSize, schemaCount) {
1969
- this.readVarint();
1970
- const count = this.num();
1971
- if (count > ARRAY_MAX) throw invalidMsgError("array count out of range");
1972
- if (schemaCount !== void 0 && count > schemaCount) {
1973
- throw invalidMsgError("array count above schema capacity");
1974
- }
1975
- if (count > this.maxArrayCount) {
1976
- throw limitExceededError(
1977
- `array count ${count} exceeds maxArrayCount ${this.maxArrayCount}`
1978
- );
1979
- }
1980
- this.readVarint();
1981
- const sub = this.lo & 7;
1982
- const size = this.upper();
1983
- if (sub !== wantSub || size !== wantSize) {
1984
- throw invalidMsgError("invalid fixlen array element type");
1985
- }
1986
- if (count > (this.n - this.p) / wantSize) {
1987
- throw incompleteError("truncated fixlen array");
1988
- }
1989
- return count;
1990
- }
1991
- /** Hand back a zero-copy view of the next `len` bytes, advancing the cursor. */
1992
- take(len) {
1993
- const start = this.p;
1994
- const end = start + len;
1995
- if (end > this.n) throw incompleteError("truncated fixlen payload");
1996
- this.p = end;
1997
- return this.buf.subarray(start, end);
3372
+ checkIndex(id) {
3373
+ if (id >= this.bound) rejectIndex(id, this.cap, this.receiverCap, this.name);
1998
3374
  }
1999
- rawFp32() {
2000
- const p = this.p;
2001
- if (p + 4 > this.n) throw incompleteError("truncated fp32");
2002
- this.p = p + 4;
2003
- return this.view.getFloat32(p, true);
3375
+ /**
3376
+ * What {@link reserve} does, then `value` written into the slot. A repeat
3377
+ * replaces (§7.4).
3378
+ *
3379
+ * Written out rather than delegating to {@link reserve}: on the baseline tier a
3380
+ * call is not free, and this is the per-element path. The check still precedes
3381
+ * the growth, which is the property §7.2 item 8 asks for.
3382
+ */
3383
+ place(id, value) {
3384
+ if (id >= this.bound) rejectIndex(id, this.cap, this.receiverCap, this.name);
3385
+ while (this.out.length <= id) this.out.push(this.def);
3386
+ this.out[id] = value;
2004
3387
  }
2005
- rawFp64() {
2006
- const p = this.p;
2007
- if (p + 8 > this.n) throw incompleteError("truncated fp64");
2008
- this.p = p + 8;
2009
- return this.view.getFloat64(p, true);
3388
+ };
3389
+ var FramedSeq = class {
3390
+ constructor(out, make, cap, name, receiverCap) {
3391
+ this.out = out;
3392
+ this.make = make;
3393
+ this.cap = cap;
3394
+ this.name = name;
3395
+ this.receiverCap = receiverCap;
3396
+ if (cap < 0) requireReceiverBound(cap, receiverCap, "the receiver array-index cap", name);
3397
+ this.bound = indexBound(cap, receiverCap);
2010
3398
  }
2011
- // --- varint reading (shared verbatim with ./fast) -----------------------
2012
3399
  /**
2013
- * The last varint's full value as a `bigint` (64-bit fidelity). Only ever
2014
- * called from {@link unsignedValue} / {@link signedValue} on the `hi` overflow
2015
- * path (`this.hi >>> 0 > 0x1fffff`), so `hi` is always non-zero here.
3400
+ * The two index bounds, without growing — see {@link ElementSeq.checkIndex}.
3401
+ *
3402
+ * Split out for the same reason it is there: a caller may have a second bound
3403
+ * to take before anything is allocated. A native matrix row is that case in this
3404
+ * port — its element `count` is rejected at the array header, and §7.2 item 8
3405
+ * wants that rejection to leave the row container exactly as it was.
2016
3406
  */
2017
- big() {
2018
- return BigInt(this.hi >>> 0) << 32n | BigInt(this.lo >>> 0);
3407
+ checkIndex(id) {
3408
+ if (id >= this.bound) rejectIndex(id, this.cap, this.receiverCap, this.name);
2019
3409
  }
2020
3410
  /**
2021
- * The last varint as an unsigned value, number-first: a `number` when it fits
2022
- * exactly (`≤ 2^53-1`), a `bigint` only beyond that.
3411
+ * Bound-check `id`, **then** grow `out` to `id + 1`, each new slot its own
3412
+ * `make()`.
3413
+ *
3414
+ * The order is §7.2 item 8's "after a rejected id the container is not left
3415
+ * partially extended": a rejection leaves `out` exactly as it was, so a lower id
3416
+ * delivered afterwards still lands at its own index. A slot already present is
3417
+ * left alone — a re-opened `struct` / `union` element merges into the object it
3418
+ * already built, which is what §7.4's last-occurrence-wins means for a scope
3419
+ * whose value *is* the scope.
2023
3420
  */
2024
- unsignedValue() {
2025
- const hi = this.hi >>> 0;
2026
- return hi <= 2097151 ? hi * TWO323 + (this.lo >>> 0) : this.big();
3421
+ reserve(id) {
3422
+ if (id >= this.bound) rejectIndex(id, this.cap, this.receiverCap, this.name);
3423
+ while (this.out.length <= id) this.out.push(this.make());
2027
3424
  }
2028
- /** The last zig-zag varint as a signed value, number-first. */
2029
- signedValue() {
2030
- const hi = this.hi >>> 0;
2031
- if (hi <= 2097151) {
2032
- const r = hi * TWO323 + (this.lo >>> 0);
2033
- return r % 2 === 0 ? r / 2 : -(r + 1) / 2;
2034
- }
2035
- return zigzagDecode(this.big());
2036
- }
2037
- /** The last varint's value as a JS number — exact for ids/lengths/counts. */
2038
- num() {
2039
- return this.hi * TWO323 + (this.lo >>> 0);
3425
+ /**
3426
+ * Bound-check `id`, fill the gap below it, then write `value` into the slot —
3427
+ * what a nested **row** needs, an array wrapper *replacing* whatever an earlier
3428
+ * opening built at that index (§7.4) rather than merging into it.
3429
+ *
3430
+ * The gap fill stops one short of `id` on purpose: the slot is about to be
3431
+ * overwritten, so calling `make()` for it would allocate an element default
3432
+ * nobody ever reads. Assigning at `out.length` extends the array by exactly one,
3433
+ * which is the same array a {@link reserve} would have left.
3434
+ */
3435
+ place(id, value) {
3436
+ if (id >= this.bound) rejectIndex(id, this.cap, this.receiverCap, this.name);
3437
+ while (this.out.length < id) this.out.push(this.make());
3438
+ this.out[id] = value;
2040
3439
  }
2041
- /** The last varint with its low 3 tag bits stripped (`value >> 3`). */
2042
- upper() {
2043
- return (this.hi >>> 0) * (TWO323 / 8) + (this.lo >>> 3);
3440
+ };
3441
+ var StringSeq = class {
3442
+ constructor(out, acc, cap, elemMax, name, receiverCap, receiverElemMax) {
3443
+ this.out = out;
3444
+ this.acc = acc;
3445
+ this.cap = cap;
3446
+ this.elemMax = elemMax;
3447
+ this.name = name;
3448
+ this.receiverCap = receiverCap;
3449
+ this.receiverElemMax = receiverElemMax;
3450
+ requireReceiverBound(elemMax, receiverElemMax, "the receiver element-length cap", name);
3451
+ this.slots = new ElementSeq(out, "", cap, name, receiverCap);
2044
3452
  }
2045
3453
  /**
2046
- * Decode one LEB128 varint at the cursor into {@link lo} / {@link hi} (each an
2047
- * unsigned 32-bit half), advancing {@link p}. Throws on truncation or a value
2048
- * spilling past 64 bits (>10 bytes). Unrolled, number-only — no `bigint`.
3454
+ * The element's fixlen length word ({@link Visitor.fixlenBegin}).
3455
+ *
3456
+ * The bounds are decided by this word, so they are checked here — before any
3457
+ * payload byte — and again in {@link element} below.
3458
+ *
3459
+ * That is not redundancy for its own sake: a message that ends *inside* an
3460
+ * over-long element must still be `INVALID`, and only this event runs early
3461
+ * enough to say so. Without it the verdict would degrade to `INCOMPLETE`, which
3462
+ * §5.2.3 forbids for input already known to be malformed and which §6.4 forbids
3463
+ * a chunk boundary from changing.
3464
+ *
3465
+ * An element of the wrong fixlen subtype is left alone: §7.3 requires it to be
3466
+ * *skipped*, not rejected, and it is skipped by this class simply ignoring it.
2049
3467
  */
2050
- readVarint() {
2051
- const buf = this.buf;
2052
- const n = this.n;
2053
- let p = this.p;
2054
- let b;
2055
- let lo;
2056
- let hi = 0;
2057
- if (p >= n) throw incompleteError("truncated varint");
2058
- b = buf[p++];
2059
- lo = b & 127;
2060
- if (b < 128) return this.set(lo, 0, p);
2061
- if (p >= n) throw incompleteError("truncated varint");
2062
- b = buf[p++];
2063
- lo |= (b & 127) << 7;
2064
- if (b < 128) return this.set(lo, 0, p);
2065
- if (p >= n) throw incompleteError("truncated varint");
2066
- b = buf[p++];
2067
- lo |= (b & 127) << 14;
2068
- if (b < 128) return this.set(lo, 0, p);
2069
- if (p >= n) throw incompleteError("truncated varint");
2070
- b = buf[p++];
2071
- lo |= (b & 127) << 21;
2072
- if (b < 128) return this.set(lo, 0, p);
2073
- if (p >= n) throw incompleteError("truncated varint");
2074
- b = buf[p++];
2075
- lo |= (b & 15) << 28;
2076
- hi = b >> 4 & 7;
2077
- if (b < 128) return this.set(lo, hi, p);
2078
- if (p >= n) throw incompleteError("truncated varint");
2079
- b = buf[p++];
2080
- hi |= (b & 127) << 3;
2081
- if (b < 128) return this.set(lo, hi, p);
2082
- if (p >= n) throw incompleteError("truncated varint");
2083
- b = buf[p++];
2084
- hi |= (b & 127) << 10;
2085
- if (b < 128) return this.set(lo, hi, p);
2086
- if (p >= n) throw incompleteError("truncated varint");
2087
- b = buf[p++];
2088
- hi |= (b & 127) << 17;
2089
- if (b < 128) return this.set(lo, hi, p);
2090
- if (p >= n) throw incompleteError("truncated varint");
2091
- b = buf[p++];
2092
- hi |= (b & 127) << 24;
2093
- if (b < 128) return this.set(lo, hi, p);
2094
- if (p >= n) throw incompleteError("truncated varint");
2095
- b = buf[p++];
2096
- if ((b & 127) >> 1 !== 0) throw invalidMsgError("varint overflow");
2097
- hi |= (b & 127) << 31;
2098
- if (b < 128) return this.set(lo, hi, p);
2099
- throw invalidMsgError("varint overflow");
2100
- }
2101
- set(lo, hi, p) {
2102
- this.lo = lo;
2103
- this.hi = hi;
2104
- this.p = p;
3468
+ begin(id, subtype, total) {
3469
+ if (subtype !== FixlenSubtype.String) return;
3470
+ this.check(id, total);
3471
+ }
3472
+ /** One payload piece of element `id` ({@link Visitor.string}). */
3473
+ element(id, total, offset, src, start, end) {
3474
+ this.check(id, total);
3475
+ const text = offset === 0 && end - start === total ? decodeUtf8(src, start, end) : decodeStringPiece(this.acc, total, offset, src, start, end);
3476
+ if (text === null) return;
3477
+ this.slots.place(id, text);
3478
+ }
3479
+ /** Every bound for one element. Rejects **before** the destination grows. */
3480
+ check(id, total) {
3481
+ this.slots.checkIndex(id);
3482
+ if (this.elemMax >= 0) {
3483
+ if (total > this.elemMax) {
3484
+ throw invalidMsgError(
3485
+ `${this.name} element: string byte length above schema maxlen ${this.elemMax}`
3486
+ );
3487
+ }
3488
+ } else if (total > this.receiverElemMax) {
3489
+ throw limitExceededError(
3490
+ `${this.name} element: string byte length ${total} exceeds the receiver cap ${this.receiverElemMax}`
3491
+ );
3492
+ }
2105
3493
  }
2106
3494
  };
2107
-
2108
- // src/backend/native.ts
2109
- var NATIVE_PACKAGE = "@sofa-buffers/corelib-native";
2110
- function isNode() {
2111
- return typeof process !== "undefined" && !!process.versions?.node;
2112
- }
2113
- async function loadNativeKernel() {
2114
- if (!isNode()) return false;
2115
- try {
2116
- const { createRequire } = await import('module');
2117
- const require2 = createRequire(import.meta.url);
2118
- const mod = require2(NATIVE_PACKAGE);
2119
- const kernel = mod.kernel ?? mod;
2120
- setKernel(kernel);
2121
- return true;
2122
- } catch {
2123
- return false;
3495
+ var BlobSeq = class {
3496
+ constructor(out, acc, cap, elemMax, name, receiverCap, receiverElemMax) {
3497
+ this.out = out;
3498
+ this.acc = acc;
3499
+ this.cap = cap;
3500
+ this.elemMax = elemMax;
3501
+ this.name = name;
3502
+ this.receiverCap = receiverCap;
3503
+ this.receiverElemMax = receiverElemMax;
3504
+ requireReceiverBound(elemMax, receiverElemMax, "the receiver element-length cap", name);
3505
+ this.slots = new ElementSeq(out, NO_BYTES, cap, name, receiverCap);
3506
+ }
3507
+ /** See {@link StringSeq.begin} — the early bound check, for subtype `blob`. */
3508
+ begin(id, subtype, total) {
3509
+ if (subtype !== FixlenSubtype.Blob) return;
3510
+ this.check(id, total);
3511
+ }
3512
+ /** One payload piece of element `id` ({@link Visitor.blob}). */
3513
+ element(id, total, offset, src, start, end) {
3514
+ this.check(id, total);
3515
+ const payload = this.acc.take(total, offset, src, start, end);
3516
+ if (payload === null) return;
3517
+ this.slots.place(id, payload);
3518
+ }
3519
+ /** Every bound for one element. Rejects **before** the destination grows. */
3520
+ check(id, total) {
3521
+ this.slots.checkIndex(id);
3522
+ if (this.elemMax >= 0) {
3523
+ if (total > this.elemMax) {
3524
+ throw invalidMsgError(
3525
+ `${this.name} element: blob byte length above schema maxlen ${this.elemMax}`
3526
+ );
3527
+ }
3528
+ } else if (total > this.receiverElemMax) {
3529
+ throw limitExceededError(
3530
+ `${this.name} element: blob byte length ${total} exceeds the receiver cap ${this.receiverElemMax}`
3531
+ );
3532
+ }
2124
3533
  }
3534
+ };
3535
+ function decodeStringPiece(acc, total, offset, src, start, end) {
3536
+ const payload = acc.take(total, offset, src, start, end);
3537
+ return payload === null ? null : decodeUtf8(payload);
2125
3538
  }
2126
3539
 
2127
- // src/backend/wasm.ts
2128
- async function loadWasmKernel(source, factory, imports = {}) {
2129
- let instance;
2130
- if (source instanceof WebAssembly.Module) {
2131
- instance = await WebAssembly.instantiate(source, imports);
2132
- } else if (typeof Response !== "undefined" && (source instanceof Response || isThenable(source))) {
2133
- const result = await WebAssembly.instantiateStreaming(
2134
- source,
2135
- imports
2136
- );
2137
- instance = result.instance;
2138
- } else {
2139
- const result = await WebAssembly.instantiate(source, imports);
2140
- instance = result.instance;
2141
- }
2142
- setKernel(factory(instance.exports));
3540
+ // src/encode/equal.ts
3541
+ function elementsEqual(a, b) {
3542
+ if (a.length !== b.length) return false;
3543
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
2143
3544
  return true;
2144
3545
  }
2145
- function isThenable(x) {
2146
- return typeof x === "object" && x !== null && typeof x.then === "function";
3546
+ function longElementsEqual(a, b) {
3547
+ if (a.length !== b.length) return false;
3548
+ for (let i = 0; i < a.length; i++) {
3549
+ if (a[i].low !== b[i].low || a[i].high !== b[i].high) return false;
3550
+ }
3551
+ return true;
2147
3552
  }
2148
3553
 
2149
- export { API_VERSION, ARRAY_MAX, ArrayKind, Cursor, DecodeStatus, FIXLEN_MAX, FixlenSubtype, I64_MAX, I64_MIN, ID_MAX, IStream, Long, MAX_DEPTH, OStream, SofabError, SofabErrorCode, U64_MAX, WireType, decode, getKernel, jsKernel, loadNativeKernel, loadWasmKernel, setKernel, public_exports as sofab };
3554
+ export { API_VERSION, ARRAY_MAX, ArrayKind, BlobSeq, DecodeStatus, ElementSeq, FIXLEN_MAX, FixlenSubtype, FramedSeq, I64_MAX, I64_MIN, ID_MAX, IStream, Long, MAX_DEPTH, MIN_OUTPUT_BUFFER, OStream, PayloadAcc, SofabError, SofabErrorCode, StringSeq, U64_MAX, UNBOUNDED, WireType, decode, decodeUtf8, elementsEqual, fp32RawBytes, getKernel, growingOStream, jsKernel, longElementsEqual, setKernel, public_exports as sofab };
2150
3555
  //# sourceMappingURL=index.js.map
2151
3556
  //# sourceMappingURL=index.js.map