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