@sofa-buffers/corelib 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -1
- package/README.md +55 -0
- package/dist/index.cjs +262 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +204 -26
- package/dist/index.d.ts +204 -26
- package/dist/index.global.js +262 -47
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +262 -47
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.d.cts
CHANGED
|
@@ -123,16 +123,14 @@ type DecodeStatus = (typeof DecodeStatus)[keyof typeof DecodeStatus];
|
|
|
123
123
|
* bytes — so it is kept distinct from `InvalidMsg`.
|
|
124
124
|
*/
|
|
125
125
|
/**
|
|
126
|
-
* The cause of a {@link SofabError}. `Argument`, `
|
|
127
|
-
*
|
|
126
|
+
* The cause of a {@link SofabError}. `Argument`, `BufferFull` and `InvalidMsg`
|
|
127
|
+
* match the C reference's `sofab_ret_t` codes; `Incomplete` is the
|
|
128
128
|
* finish-less INCOMPLETE decode outcome (MESSAGE_SPEC §7), a distinct,
|
|
129
129
|
* more-bytes-could-complete-it signal split out from `InvalidMsg`.
|
|
130
130
|
*/
|
|
131
131
|
declare const SofabErrorCode: {
|
|
132
132
|
/** A caller argument was invalid (e.g. id out of range, empty array). */
|
|
133
133
|
readonly Argument: "ARGUMENT";
|
|
134
|
-
/** The API was used incorrectly (e.g. unbalanced sequence end). */
|
|
135
|
-
readonly Usage: "USAGE";
|
|
136
134
|
/** The output buffer is full and no flush sink was provided. */
|
|
137
135
|
readonly BufferFull: "BUFFER_FULL";
|
|
138
136
|
/** The input being decoded is malformed regardless of what follows (`INVALID`). */
|
|
@@ -231,6 +229,12 @@ type FlushSink = (chunk: Uint8Array) => void;
|
|
|
231
229
|
*
|
|
232
230
|
* Generated code typically writes one field per message field; the methods map
|
|
233
231
|
* one-to-one onto the wire types. Problems throw {@link SofabError}.
|
|
232
|
+
*
|
|
233
|
+
* Nested sequences are opened with {@link OStream.writeSequenceBeginLazy}, which
|
|
234
|
+
* holds the header back until the sequence proves it has content, so an
|
|
235
|
+
* all-default one is omitted rather than framed empty (MESSAGE_SPEC §2). Close a
|
|
236
|
+
* `struct`/`union` field or an array wrapper with {@link OStream.writeSequenceEnd}
|
|
237
|
+
* and a wrapper-array *element* with {@link OStream.writeSequenceEndKeep}.
|
|
234
238
|
*/
|
|
235
239
|
|
|
236
240
|
/**
|
|
@@ -248,6 +252,26 @@ declare class OStream {
|
|
|
248
252
|
private readonly flushSink;
|
|
249
253
|
private readonly canGrow;
|
|
250
254
|
private depth;
|
|
255
|
+
/**
|
|
256
|
+
* Ids of the innermost open sequences whose header has not been written yet
|
|
257
|
+
* (MESSAGE_SPEC §2 lazy framing, {@link OStream.writeSequenceBeginLazy}).
|
|
258
|
+
* Always a contiguous suffix of the open sequences: writing any field commits
|
|
259
|
+
* the whole run at once, so {@link OStream.writeSequenceEnd} can drop the
|
|
260
|
+
* innermost one by dropping the last entry. Held-back ids are encoder state,
|
|
261
|
+
* never buffer content, so a flush can never split a run.
|
|
262
|
+
*
|
|
263
|
+
* Storage plus an explicit count rather than `push`/`pop`, so the slots are
|
|
264
|
+
* reused across messages (no allocation on a pooled encoder) and so
|
|
265
|
+
* {@link OStream.commitPending} can zero the count *before* it writes.
|
|
266
|
+
*
|
|
267
|
+
* The array grows on demand and is bounded only by `MAX_DEPTH` — there is no
|
|
268
|
+
* fixed hold-back window and hence no eager-framing fallback, which is what
|
|
269
|
+
* CORELIB_PLAN §6 ("How deep the hold-back reaches") demands of an
|
|
270
|
+
* implementation that can allocate: canonical output at *every* depth.
|
|
271
|
+
*/
|
|
272
|
+
private readonly pending;
|
|
273
|
+
/** Valid entries in {@link OStream.pending}. */
|
|
274
|
+
private nPending;
|
|
251
275
|
private kernel;
|
|
252
276
|
/** In-memory encoder backed by an auto-growing buffer. */
|
|
253
277
|
constructor();
|
|
@@ -311,17 +335,100 @@ declare class OStream {
|
|
|
311
335
|
writeSignedArrayLong(id: number, values: readonly Long[]): void;
|
|
312
336
|
/** Write an array of IEEE-754 32-bit floats. */
|
|
313
337
|
writeFp32Array(id: number, values: ArrayLike<number>): void;
|
|
338
|
+
/**
|
|
339
|
+
* Write an fp32 array from its raw little-endian element payload. The bytes
|
|
340
|
+
* are emitted verbatim — no per-element `setFloat32` — so a signaling NaN
|
|
341
|
+
* survives bit-for-bit (§4.6), which {@link writeFp32Array} cannot guarantee
|
|
342
|
+
* because it re-quantizes each JS `number`. `payload.length` must be a
|
|
343
|
+
* multiple of 4; the element count is `payload.length / 4`.
|
|
344
|
+
*/
|
|
345
|
+
writeFp32ArrayRaw(id: number, payload: Uint8Array): void;
|
|
314
346
|
/** Write an array of IEEE-754 64-bit doubles. */
|
|
315
347
|
writeFp64Array(id: number, values: ArrayLike<number>): void;
|
|
316
|
-
/**
|
|
317
|
-
|
|
318
|
-
|
|
348
|
+
/**
|
|
349
|
+
* Open a nested sequence (a fresh id scope) whose header is **held back**
|
|
350
|
+
* until the sequence turns out to have content.
|
|
351
|
+
*
|
|
352
|
+
* MESSAGE_SPEC §2 omits a sequence-typed field whose value equals its declared
|
|
353
|
+
* default, and "not one child was written" is exactly that condition —
|
|
354
|
+
* evaluated per child field, recursively, for free, because the message layer
|
|
355
|
+
* already omits every child equal to its default. A sequence closed with
|
|
356
|
+
* nothing in it therefore emits **nothing** instead of a two-byte empty frame,
|
|
357
|
+
* and an all-default message becomes the empty byte string. No byte image is
|
|
358
|
+
* ever compared, so in-memory layout never enters the decision.
|
|
359
|
+
*
|
|
360
|
+
* This is the only way to open a sequence. How it closes decides whether a
|
|
361
|
+
* contentless one survives: {@link OStream.writeSequenceEnd} drops it,
|
|
362
|
+
* {@link OStream.writeSequenceEndKeep} forces the frame out.
|
|
363
|
+
*/
|
|
364
|
+
writeSequenceBeginLazy(id: number): void;
|
|
365
|
+
/**
|
|
366
|
+
* Close the current sequence, letting it **vanish** if it received no content.
|
|
367
|
+
*
|
|
368
|
+
* Use it wherever absence encodes the same value as an empty frame: a
|
|
369
|
+
* `struct`/`union` field, and an array field whose declared `default` is the
|
|
370
|
+
* empty collection (MESSAGE_SPEC §2). Where the frame must be visible, close
|
|
371
|
+
* with {@link OStream.writeSequenceEndKeep} instead.
|
|
372
|
+
*
|
|
373
|
+
* An end with no matching begin is not rejected: the encoder writes what it is
|
|
374
|
+
* told, and the resulting bytes are then malformed, which is the decoder's
|
|
375
|
+
* verdict to make. No other port refuses it. The depth counter stops at zero
|
|
376
|
+
* so the MAX_DEPTH check on begin cannot be fooled by an underflow.
|
|
377
|
+
*/
|
|
319
378
|
writeSequenceEnd(): void;
|
|
379
|
+
/**
|
|
380
|
+
* Close the current sequence, **keeping** its frame even when it received no
|
|
381
|
+
* content.
|
|
382
|
+
*
|
|
383
|
+
* Behaves like a write: it first emits any held-back headers — this frame's
|
|
384
|
+
* and every enclosing one's — and then the end marker, so an empty sequence
|
|
385
|
+
* reaches the wire as `begin` + `end`.
|
|
386
|
+
*
|
|
387
|
+
* Required wherever the frame carries information beyond its contents:
|
|
388
|
+
* - a **wrapper-array element** (`struct`/`union`/nested row): element
|
|
389
|
+
* presence is what carries a dynamic array's length — *highest present id +
|
|
390
|
+
* 1* (MESSAGE_SPEC §5.1) — so dropping an all-default element would change
|
|
391
|
+
* the decoded length, not just the bytes;
|
|
392
|
+
* - an array field already known to **differ from a non-empty declared
|
|
393
|
+
* `default`**: absence would reconstruct that default, so the empty frame is
|
|
394
|
+
* the only encoding of "explicitly empty" (§2, §3).
|
|
395
|
+
*
|
|
396
|
+
* The two failure directions are not symmetric, which is why this is the safe
|
|
397
|
+
* choice when in doubt: using it where {@link OStream.writeSequenceEnd} would
|
|
398
|
+
* do costs one non-canonical empty frame that a decoder normalizes away, while
|
|
399
|
+
* the reverse silently changes an array's length.
|
|
400
|
+
*/
|
|
401
|
+
writeSequenceEndKeep(): void;
|
|
320
402
|
/** Ensure exactly `value`'s varint size, then write it (bigint path). */
|
|
321
403
|
private putVarint;
|
|
322
404
|
/** Ensure exactly `value`'s varint size, then write it (number fast path). */
|
|
323
405
|
private putVarintNum;
|
|
406
|
+
/**
|
|
407
|
+
* Write a field header, the `(id << 3) | wireType` tag, as a varint.
|
|
408
|
+
*
|
|
409
|
+
* This is the single choke point every field write passes through — the
|
|
410
|
+
* scalar, fixlen, float, string, blob and both array writers all reach the
|
|
411
|
+
* wire through `header` / `fixlenHead` / `arrayHead`, and `fixlenHead` and
|
|
412
|
+
* `arrayHead` are themselves nothing but `header` plus a follow-up varint. So
|
|
413
|
+
* this is also where a held-back sequence run is committed: the field about to
|
|
414
|
+
* be written is content, which means every enclosing sequence is non-default
|
|
415
|
+
* and must be framed after all (MESSAGE_SPEC §2).
|
|
416
|
+
*
|
|
417
|
+
* The only writers that do *not* pass through here are the two sequence
|
|
418
|
+
* closers, which must not commit ({@link OStream.writeSequenceEnd}) or commit
|
|
419
|
+
* explicitly ({@link OStream.writeSequenceEndKeep}), and
|
|
420
|
+
* {@link OStream.writeSequenceBeginLazy}, which writes no byte at all.
|
|
421
|
+
*/
|
|
324
422
|
private header;
|
|
423
|
+
/**
|
|
424
|
+
* Write out the held-back sequence headers, **outermost first**, and clear the
|
|
425
|
+
* run. Runs at most once per non-default sequence, never per field — the cost
|
|
426
|
+
* on the hot path is the single `nPending` test in {@link header}.
|
|
427
|
+
*
|
|
428
|
+
* The count is zeroed before the first byte goes out, so a write re-entered
|
|
429
|
+
* from a flush sink cannot emit the same run twice.
|
|
430
|
+
*/
|
|
431
|
+
private commitPending;
|
|
325
432
|
private fixlenHead;
|
|
326
433
|
private arrayHead;
|
|
327
434
|
/** Copy `data` out, flushing/growing as needed (large payloads stay chunked). */
|
|
@@ -417,9 +524,29 @@ interface Visitor {
|
|
|
417
524
|
unsigned?(id: number, value: number | bigint): void;
|
|
418
525
|
/** A signed integer field. Number-first like {@link unsigned} (`|value| ≤ 2^53-1` ⇒ `number`). */
|
|
419
526
|
signed?(id: number, value: number | bigint): void;
|
|
420
|
-
/**
|
|
421
|
-
|
|
422
|
-
|
|
527
|
+
/**
|
|
528
|
+
* Opt in to the raw-bytes channel on {@link fp32} / {@link arrayFp32}. Off by
|
|
529
|
+
* default so a value-only consumer pays nothing: when this is not `true` the
|
|
530
|
+
* decoder never allocates the per-value little-endian view (which, per fp32
|
|
531
|
+
* element, roughly quartered array-decode throughput in a microbenchmark). Set
|
|
532
|
+
* it `true` only in a bit-exact consumer (transcode / raw-bits oracle) that
|
|
533
|
+
* needs `raw` to preserve a signaling NaN.
|
|
534
|
+
*/
|
|
535
|
+
readonly fp32Raw?: boolean;
|
|
536
|
+
/**
|
|
537
|
+
* An IEEE-754 32-bit float field. When you set {@link fp32Raw} to `true`,
|
|
538
|
+
* `raw` is a zero-copy little-endian view of the exact 4 wire bytes; use it —
|
|
539
|
+
* not `value` — when the bytes must round-trip bit-for-bit (§4.6). `value` is
|
|
540
|
+
* a JS `number` (a 64-bit double), and widening a *signaling* NaN into a
|
|
541
|
+
* double quiets it (sets the is-quiet bit), so `value` cannot represent an
|
|
542
|
+
* fp32 sNaN faithfully. The view aliases the decoder's working buffer and is
|
|
543
|
+
* valid only for the duration of the call — copy it if you retain it, exactly
|
|
544
|
+
* as with a string/blob `chunk`. Without {@link fp32Raw}, `raw` is `undefined`
|
|
545
|
+
* (no allocation). fp64 needs no such channel: a double holds all 64 bits
|
|
546
|
+
* verbatim (see {@link fp64}).
|
|
547
|
+
*/
|
|
548
|
+
fp32?(id: number, value: number, raw?: Uint8Array): void;
|
|
549
|
+
/** An IEEE-754 64-bit double field. `value` is exact — a double is 64 bits wide. */
|
|
423
550
|
fp64?(id: number, value: number): void;
|
|
424
551
|
/** A chunk of a UTF-8 string field. */
|
|
425
552
|
string?(id: number, total: number, offset: number, chunk: Uint8Array): void;
|
|
@@ -431,9 +558,9 @@ interface Visitor {
|
|
|
431
558
|
arrayUnsigned?(id: number, index: number, value: number | bigint): void;
|
|
432
559
|
/** One signed array element. Number-first like {@link signed}. */
|
|
433
560
|
arraySigned?(id: number, index: number, value: number | bigint): void;
|
|
434
|
-
/** One fp32 array element. */
|
|
435
|
-
arrayFp32?(id: number, index: number, value: number): void;
|
|
436
|
-
/** One fp64 array element. */
|
|
561
|
+
/** One fp32 array element. `raw` (the element's 4 wire bytes) is present only under {@link fp32Raw} — see {@link fp32}. */
|
|
562
|
+
arrayFp32?(id: number, index: number, value: number, raw?: Uint8Array): void;
|
|
563
|
+
/** One fp64 array element. `value` is exact — see {@link fp64}. */
|
|
437
564
|
arrayFp64?(id: number, index: number, value: number): void;
|
|
438
565
|
/** End of an array. */
|
|
439
566
|
arrayEnd?(id: number): void;
|
|
@@ -612,31 +739,70 @@ declare class Cursor {
|
|
|
612
739
|
readSigned(): number | bigint;
|
|
613
740
|
/** Read a 32-bit float scalar (wire {@link WireType.Fixlen}, subtype fp32). */
|
|
614
741
|
readFp32(): number;
|
|
742
|
+
/**
|
|
743
|
+
* Read a 32-bit float scalar as its raw 4 wire bytes (little-endian), zero-copy
|
|
744
|
+
* — the bit-preserving companion to {@link readFp32}.
|
|
745
|
+
*
|
|
746
|
+
* {@link readFp32} returns a JS `number` (a 64-bit double), and widening an
|
|
747
|
+
* fp32 *signaling* NaN into a double quiets it (0x7F800001 → 0x7FC00001), so a
|
|
748
|
+
* value consumer can never round-trip one bit-for-bit (§4.6). Generated
|
|
749
|
+
* bit-exact decode reads the bytes here instead and re-emits them verbatim with
|
|
750
|
+
* {@link OStream.writeFixlen} (subtype fp32) — mirroring the visitor `raw`
|
|
751
|
+
* channel on the push paths (fast.ts / state.ts), which the pull path was
|
|
752
|
+
* missing (corelib-ts#66).
|
|
753
|
+
*
|
|
754
|
+
* The header (subtype fp32, length 4) is validated exactly as in
|
|
755
|
+
* {@link readFp32}; the returned view aliases the source buffer, valid only
|
|
756
|
+
* until it is reused, like {@link readBlob}.
|
|
757
|
+
*/
|
|
758
|
+
readFp32Raw(): Uint8Array;
|
|
615
759
|
/** Read a 64-bit float scalar (wire {@link WireType.Fixlen}, subtype fp64). */
|
|
616
760
|
readFp64(): number;
|
|
617
|
-
/**
|
|
618
|
-
|
|
761
|
+
/**
|
|
762
|
+
* Read a UTF-8 string scalar (wire {@link WireType.Fixlen}, subtype string).
|
|
763
|
+
* Pass the schema `maxlen` (byte length) for a bounded string so an
|
|
764
|
+
* over-length is rejected as `INVALID` at the header, before the payload is
|
|
765
|
+
* taken (see {@link fixlenLen}); the wire length is exactly the UTF-8 byte
|
|
766
|
+
* length, so the check is exact. Omit for an unbounded string.
|
|
767
|
+
*/
|
|
768
|
+
readString(schemaMaxlen?: number): string;
|
|
619
769
|
/**
|
|
620
770
|
* Read a blob scalar (wire {@link WireType.Fixlen}, subtype blob) as a
|
|
621
771
|
* zero-copy {@link Uint8Array} view into the source buffer.
|
|
622
772
|
*/
|
|
623
|
-
readBlob(): Uint8Array;
|
|
624
|
-
/**
|
|
625
|
-
|
|
773
|
+
readBlob(schemaMaxlen?: number): Uint8Array;
|
|
774
|
+
/**
|
|
775
|
+
* Read an unsigned array (wire {@link WireType.ArrayUnsigned}), number-first
|
|
776
|
+
* per element. Pass the schema `count` for a bounded array so an over-count is
|
|
777
|
+
* rejected as `INVALID` at the header (see {@link arrayCount}); omit it for an
|
|
778
|
+
* unbounded array (today's behavior).
|
|
779
|
+
*/
|
|
780
|
+
readUnsignedArray(schemaCount?: number): (number | bigint)[];
|
|
626
781
|
/** Read a signed array (wire {@link WireType.ArraySigned}), zig-zag, number-first per element. */
|
|
627
|
-
readSignedArray(): (number | bigint)[];
|
|
782
|
+
readSignedArray(schemaCount?: number): (number | bigint)[];
|
|
628
783
|
/**
|
|
629
784
|
* Read an unsigned 64-bit array into {@link Long}[] — the `bigint`-free path.
|
|
630
785
|
* Each element keeps the raw lo/hi halves; call {@link Long.toBigInt} to
|
|
631
786
|
* materialise only the values the caller actually needs.
|
|
632
787
|
*/
|
|
633
|
-
readUnsignedArrayLong(): Long[];
|
|
788
|
+
readUnsignedArrayLong(schemaCount?: number): Long[];
|
|
634
789
|
/** Read a signed 64-bit array (zig-zag) into {@link Long}[] — the `bigint`-free path. */
|
|
635
|
-
readSignedArrayLong(): Long[];
|
|
790
|
+
readSignedArrayLong(schemaCount?: number): Long[];
|
|
636
791
|
/** Read an fp32 array (wire {@link WireType.ArrayFixlen}, element subtype fp32). */
|
|
637
|
-
readFp32Array(): number[];
|
|
792
|
+
readFp32Array(schemaCount?: number): number[];
|
|
793
|
+
/**
|
|
794
|
+
* Read an fp32 array as its raw little-endian element payload (`count * 4`
|
|
795
|
+
* bytes), zero-copy — the bit-preserving companion to {@link readFp32Array}.
|
|
796
|
+
* Widening each element to a JS `number` quiets an fp32 *signaling* NaN just as
|
|
797
|
+
* on the scalar path (§4.6; see {@link readFp32Raw}), so bit-exact decode reads
|
|
798
|
+
* the whole payload here and re-emits it with {@link OStream.writeFp32ArrayRaw}
|
|
799
|
+
* (corelib-ts#66). The header (element subtype fp32, size 4) is validated
|
|
800
|
+
* exactly as in {@link readFp32Array}; the returned view aliases the source
|
|
801
|
+
* buffer, like {@link readBlob}.
|
|
802
|
+
*/
|
|
803
|
+
readFp32ArrayRaw(schemaCount?: number): Uint8Array;
|
|
638
804
|
/** Read an fp64 array (wire {@link WireType.ArrayFixlen}, element subtype fp64). */
|
|
639
|
-
readFp64Array(): number[];
|
|
805
|
+
readFp64Array(schemaCount?: number): number[];
|
|
640
806
|
/**
|
|
641
807
|
* Consume the value of the field whose header {@link readHeader} just accepted,
|
|
642
808
|
* discarding it — for a `default:` branch that keeps the cursor in sync on an
|
|
@@ -658,13 +824,25 @@ declare class Cursor {
|
|
|
658
824
|
* reads one byte, it never decodes a varint.
|
|
659
825
|
*/
|
|
660
826
|
private peekFixSub;
|
|
661
|
-
/**
|
|
827
|
+
/**
|
|
828
|
+
* Read and validate an array count word (0..ARRAY_MAX; §4.7/§4.8). When a
|
|
829
|
+
* `schemaCount` is given, a count above it is a schema-bound violation and is
|
|
830
|
+
* rejected as `INVALID` — see the check below.
|
|
831
|
+
*/
|
|
662
832
|
private arrayCount;
|
|
663
833
|
/** Read a scalar fixlen sub-header, asserting subtype and exact byte length (floats). */
|
|
664
834
|
private fixlenHeader;
|
|
665
|
-
/**
|
|
835
|
+
/**
|
|
836
|
+
* Read a scalar fixlen sub-header for a string/blob, asserting subtype;
|
|
837
|
+
* returns the byte length. When a `schemaMaxlen` is given, a length above it
|
|
838
|
+
* is a schema-bound violation and is rejected as `INVALID` — see below.
|
|
839
|
+
*/
|
|
666
840
|
private fixlenLen;
|
|
667
|
-
/**
|
|
841
|
+
/**
|
|
842
|
+
* Read an array fixlen element header (count + element type); returns the
|
|
843
|
+
* count. When a `schemaCount` is given, a count above it is a schema-bound
|
|
844
|
+
* violation and is rejected as `INVALID` — see below.
|
|
845
|
+
*/
|
|
668
846
|
private arrayFixlenHeader;
|
|
669
847
|
/** Hand back a zero-copy view of the next `len` bytes, advancing the cursor. */
|
|
670
848
|
private take;
|
package/dist/index.d.ts
CHANGED
|
@@ -123,16 +123,14 @@ type DecodeStatus = (typeof DecodeStatus)[keyof typeof DecodeStatus];
|
|
|
123
123
|
* bytes — so it is kept distinct from `InvalidMsg`.
|
|
124
124
|
*/
|
|
125
125
|
/**
|
|
126
|
-
* The cause of a {@link SofabError}. `Argument`, `
|
|
127
|
-
*
|
|
126
|
+
* The cause of a {@link SofabError}. `Argument`, `BufferFull` and `InvalidMsg`
|
|
127
|
+
* match the C reference's `sofab_ret_t` codes; `Incomplete` is the
|
|
128
128
|
* finish-less INCOMPLETE decode outcome (MESSAGE_SPEC §7), a distinct,
|
|
129
129
|
* more-bytes-could-complete-it signal split out from `InvalidMsg`.
|
|
130
130
|
*/
|
|
131
131
|
declare const SofabErrorCode: {
|
|
132
132
|
/** A caller argument was invalid (e.g. id out of range, empty array). */
|
|
133
133
|
readonly Argument: "ARGUMENT";
|
|
134
|
-
/** The API was used incorrectly (e.g. unbalanced sequence end). */
|
|
135
|
-
readonly Usage: "USAGE";
|
|
136
134
|
/** The output buffer is full and no flush sink was provided. */
|
|
137
135
|
readonly BufferFull: "BUFFER_FULL";
|
|
138
136
|
/** The input being decoded is malformed regardless of what follows (`INVALID`). */
|
|
@@ -231,6 +229,12 @@ type FlushSink = (chunk: Uint8Array) => void;
|
|
|
231
229
|
*
|
|
232
230
|
* Generated code typically writes one field per message field; the methods map
|
|
233
231
|
* one-to-one onto the wire types. Problems throw {@link SofabError}.
|
|
232
|
+
*
|
|
233
|
+
* Nested sequences are opened with {@link OStream.writeSequenceBeginLazy}, which
|
|
234
|
+
* holds the header back until the sequence proves it has content, so an
|
|
235
|
+
* all-default one is omitted rather than framed empty (MESSAGE_SPEC §2). Close a
|
|
236
|
+
* `struct`/`union` field or an array wrapper with {@link OStream.writeSequenceEnd}
|
|
237
|
+
* and a wrapper-array *element* with {@link OStream.writeSequenceEndKeep}.
|
|
234
238
|
*/
|
|
235
239
|
|
|
236
240
|
/**
|
|
@@ -248,6 +252,26 @@ declare class OStream {
|
|
|
248
252
|
private readonly flushSink;
|
|
249
253
|
private readonly canGrow;
|
|
250
254
|
private depth;
|
|
255
|
+
/**
|
|
256
|
+
* Ids of the innermost open sequences whose header has not been written yet
|
|
257
|
+
* (MESSAGE_SPEC §2 lazy framing, {@link OStream.writeSequenceBeginLazy}).
|
|
258
|
+
* Always a contiguous suffix of the open sequences: writing any field commits
|
|
259
|
+
* the whole run at once, so {@link OStream.writeSequenceEnd} can drop the
|
|
260
|
+
* innermost one by dropping the last entry. Held-back ids are encoder state,
|
|
261
|
+
* never buffer content, so a flush can never split a run.
|
|
262
|
+
*
|
|
263
|
+
* Storage plus an explicit count rather than `push`/`pop`, so the slots are
|
|
264
|
+
* reused across messages (no allocation on a pooled encoder) and so
|
|
265
|
+
* {@link OStream.commitPending} can zero the count *before* it writes.
|
|
266
|
+
*
|
|
267
|
+
* The array grows on demand and is bounded only by `MAX_DEPTH` — there is no
|
|
268
|
+
* fixed hold-back window and hence no eager-framing fallback, which is what
|
|
269
|
+
* CORELIB_PLAN §6 ("How deep the hold-back reaches") demands of an
|
|
270
|
+
* implementation that can allocate: canonical output at *every* depth.
|
|
271
|
+
*/
|
|
272
|
+
private readonly pending;
|
|
273
|
+
/** Valid entries in {@link OStream.pending}. */
|
|
274
|
+
private nPending;
|
|
251
275
|
private kernel;
|
|
252
276
|
/** In-memory encoder backed by an auto-growing buffer. */
|
|
253
277
|
constructor();
|
|
@@ -311,17 +335,100 @@ declare class OStream {
|
|
|
311
335
|
writeSignedArrayLong(id: number, values: readonly Long[]): void;
|
|
312
336
|
/** Write an array of IEEE-754 32-bit floats. */
|
|
313
337
|
writeFp32Array(id: number, values: ArrayLike<number>): void;
|
|
338
|
+
/**
|
|
339
|
+
* Write an fp32 array from its raw little-endian element payload. The bytes
|
|
340
|
+
* are emitted verbatim — no per-element `setFloat32` — so a signaling NaN
|
|
341
|
+
* survives bit-for-bit (§4.6), which {@link writeFp32Array} cannot guarantee
|
|
342
|
+
* because it re-quantizes each JS `number`. `payload.length` must be a
|
|
343
|
+
* multiple of 4; the element count is `payload.length / 4`.
|
|
344
|
+
*/
|
|
345
|
+
writeFp32ArrayRaw(id: number, payload: Uint8Array): void;
|
|
314
346
|
/** Write an array of IEEE-754 64-bit doubles. */
|
|
315
347
|
writeFp64Array(id: number, values: ArrayLike<number>): void;
|
|
316
|
-
/**
|
|
317
|
-
|
|
318
|
-
|
|
348
|
+
/**
|
|
349
|
+
* Open a nested sequence (a fresh id scope) whose header is **held back**
|
|
350
|
+
* until the sequence turns out to have content.
|
|
351
|
+
*
|
|
352
|
+
* MESSAGE_SPEC §2 omits a sequence-typed field whose value equals its declared
|
|
353
|
+
* default, and "not one child was written" is exactly that condition —
|
|
354
|
+
* evaluated per child field, recursively, for free, because the message layer
|
|
355
|
+
* already omits every child equal to its default. A sequence closed with
|
|
356
|
+
* nothing in it therefore emits **nothing** instead of a two-byte empty frame,
|
|
357
|
+
* and an all-default message becomes the empty byte string. No byte image is
|
|
358
|
+
* ever compared, so in-memory layout never enters the decision.
|
|
359
|
+
*
|
|
360
|
+
* This is the only way to open a sequence. How it closes decides whether a
|
|
361
|
+
* contentless one survives: {@link OStream.writeSequenceEnd} drops it,
|
|
362
|
+
* {@link OStream.writeSequenceEndKeep} forces the frame out.
|
|
363
|
+
*/
|
|
364
|
+
writeSequenceBeginLazy(id: number): void;
|
|
365
|
+
/**
|
|
366
|
+
* Close the current sequence, letting it **vanish** if it received no content.
|
|
367
|
+
*
|
|
368
|
+
* Use it wherever absence encodes the same value as an empty frame: a
|
|
369
|
+
* `struct`/`union` field, and an array field whose declared `default` is the
|
|
370
|
+
* empty collection (MESSAGE_SPEC §2). Where the frame must be visible, close
|
|
371
|
+
* with {@link OStream.writeSequenceEndKeep} instead.
|
|
372
|
+
*
|
|
373
|
+
* An end with no matching begin is not rejected: the encoder writes what it is
|
|
374
|
+
* told, and the resulting bytes are then malformed, which is the decoder's
|
|
375
|
+
* verdict to make. No other port refuses it. The depth counter stops at zero
|
|
376
|
+
* so the MAX_DEPTH check on begin cannot be fooled by an underflow.
|
|
377
|
+
*/
|
|
319
378
|
writeSequenceEnd(): void;
|
|
379
|
+
/**
|
|
380
|
+
* Close the current sequence, **keeping** its frame even when it received no
|
|
381
|
+
* content.
|
|
382
|
+
*
|
|
383
|
+
* Behaves like a write: it first emits any held-back headers — this frame's
|
|
384
|
+
* and every enclosing one's — and then the end marker, so an empty sequence
|
|
385
|
+
* reaches the wire as `begin` + `end`.
|
|
386
|
+
*
|
|
387
|
+
* Required wherever the frame carries information beyond its contents:
|
|
388
|
+
* - a **wrapper-array element** (`struct`/`union`/nested row): element
|
|
389
|
+
* presence is what carries a dynamic array's length — *highest present id +
|
|
390
|
+
* 1* (MESSAGE_SPEC §5.1) — so dropping an all-default element would change
|
|
391
|
+
* the decoded length, not just the bytes;
|
|
392
|
+
* - an array field already known to **differ from a non-empty declared
|
|
393
|
+
* `default`**: absence would reconstruct that default, so the empty frame is
|
|
394
|
+
* the only encoding of "explicitly empty" (§2, §3).
|
|
395
|
+
*
|
|
396
|
+
* The two failure directions are not symmetric, which is why this is the safe
|
|
397
|
+
* choice when in doubt: using it where {@link OStream.writeSequenceEnd} would
|
|
398
|
+
* do costs one non-canonical empty frame that a decoder normalizes away, while
|
|
399
|
+
* the reverse silently changes an array's length.
|
|
400
|
+
*/
|
|
401
|
+
writeSequenceEndKeep(): void;
|
|
320
402
|
/** Ensure exactly `value`'s varint size, then write it (bigint path). */
|
|
321
403
|
private putVarint;
|
|
322
404
|
/** Ensure exactly `value`'s varint size, then write it (number fast path). */
|
|
323
405
|
private putVarintNum;
|
|
406
|
+
/**
|
|
407
|
+
* Write a field header, the `(id << 3) | wireType` tag, as a varint.
|
|
408
|
+
*
|
|
409
|
+
* This is the single choke point every field write passes through — the
|
|
410
|
+
* scalar, fixlen, float, string, blob and both array writers all reach the
|
|
411
|
+
* wire through `header` / `fixlenHead` / `arrayHead`, and `fixlenHead` and
|
|
412
|
+
* `arrayHead` are themselves nothing but `header` plus a follow-up varint. So
|
|
413
|
+
* this is also where a held-back sequence run is committed: the field about to
|
|
414
|
+
* be written is content, which means every enclosing sequence is non-default
|
|
415
|
+
* and must be framed after all (MESSAGE_SPEC §2).
|
|
416
|
+
*
|
|
417
|
+
* The only writers that do *not* pass through here are the two sequence
|
|
418
|
+
* closers, which must not commit ({@link OStream.writeSequenceEnd}) or commit
|
|
419
|
+
* explicitly ({@link OStream.writeSequenceEndKeep}), and
|
|
420
|
+
* {@link OStream.writeSequenceBeginLazy}, which writes no byte at all.
|
|
421
|
+
*/
|
|
324
422
|
private header;
|
|
423
|
+
/**
|
|
424
|
+
* Write out the held-back sequence headers, **outermost first**, and clear the
|
|
425
|
+
* run. Runs at most once per non-default sequence, never per field — the cost
|
|
426
|
+
* on the hot path is the single `nPending` test in {@link header}.
|
|
427
|
+
*
|
|
428
|
+
* The count is zeroed before the first byte goes out, so a write re-entered
|
|
429
|
+
* from a flush sink cannot emit the same run twice.
|
|
430
|
+
*/
|
|
431
|
+
private commitPending;
|
|
325
432
|
private fixlenHead;
|
|
326
433
|
private arrayHead;
|
|
327
434
|
/** Copy `data` out, flushing/growing as needed (large payloads stay chunked). */
|
|
@@ -417,9 +524,29 @@ interface Visitor {
|
|
|
417
524
|
unsigned?(id: number, value: number | bigint): void;
|
|
418
525
|
/** A signed integer field. Number-first like {@link unsigned} (`|value| ≤ 2^53-1` ⇒ `number`). */
|
|
419
526
|
signed?(id: number, value: number | bigint): void;
|
|
420
|
-
/**
|
|
421
|
-
|
|
422
|
-
|
|
527
|
+
/**
|
|
528
|
+
* Opt in to the raw-bytes channel on {@link fp32} / {@link arrayFp32}. Off by
|
|
529
|
+
* default so a value-only consumer pays nothing: when this is not `true` the
|
|
530
|
+
* decoder never allocates the per-value little-endian view (which, per fp32
|
|
531
|
+
* element, roughly quartered array-decode throughput in a microbenchmark). Set
|
|
532
|
+
* it `true` only in a bit-exact consumer (transcode / raw-bits oracle) that
|
|
533
|
+
* needs `raw` to preserve a signaling NaN.
|
|
534
|
+
*/
|
|
535
|
+
readonly fp32Raw?: boolean;
|
|
536
|
+
/**
|
|
537
|
+
* An IEEE-754 32-bit float field. When you set {@link fp32Raw} to `true`,
|
|
538
|
+
* `raw` is a zero-copy little-endian view of the exact 4 wire bytes; use it —
|
|
539
|
+
* not `value` — when the bytes must round-trip bit-for-bit (§4.6). `value` is
|
|
540
|
+
* a JS `number` (a 64-bit double), and widening a *signaling* NaN into a
|
|
541
|
+
* double quiets it (sets the is-quiet bit), so `value` cannot represent an
|
|
542
|
+
* fp32 sNaN faithfully. The view aliases the decoder's working buffer and is
|
|
543
|
+
* valid only for the duration of the call — copy it if you retain it, exactly
|
|
544
|
+
* as with a string/blob `chunk`. Without {@link fp32Raw}, `raw` is `undefined`
|
|
545
|
+
* (no allocation). fp64 needs no such channel: a double holds all 64 bits
|
|
546
|
+
* verbatim (see {@link fp64}).
|
|
547
|
+
*/
|
|
548
|
+
fp32?(id: number, value: number, raw?: Uint8Array): void;
|
|
549
|
+
/** An IEEE-754 64-bit double field. `value` is exact — a double is 64 bits wide. */
|
|
423
550
|
fp64?(id: number, value: number): void;
|
|
424
551
|
/** A chunk of a UTF-8 string field. */
|
|
425
552
|
string?(id: number, total: number, offset: number, chunk: Uint8Array): void;
|
|
@@ -431,9 +558,9 @@ interface Visitor {
|
|
|
431
558
|
arrayUnsigned?(id: number, index: number, value: number | bigint): void;
|
|
432
559
|
/** One signed array element. Number-first like {@link signed}. */
|
|
433
560
|
arraySigned?(id: number, index: number, value: number | bigint): void;
|
|
434
|
-
/** One fp32 array element. */
|
|
435
|
-
arrayFp32?(id: number, index: number, value: number): void;
|
|
436
|
-
/** One fp64 array element. */
|
|
561
|
+
/** One fp32 array element. `raw` (the element's 4 wire bytes) is present only under {@link fp32Raw} — see {@link fp32}. */
|
|
562
|
+
arrayFp32?(id: number, index: number, value: number, raw?: Uint8Array): void;
|
|
563
|
+
/** One fp64 array element. `value` is exact — see {@link fp64}. */
|
|
437
564
|
arrayFp64?(id: number, index: number, value: number): void;
|
|
438
565
|
/** End of an array. */
|
|
439
566
|
arrayEnd?(id: number): void;
|
|
@@ -612,31 +739,70 @@ declare class Cursor {
|
|
|
612
739
|
readSigned(): number | bigint;
|
|
613
740
|
/** Read a 32-bit float scalar (wire {@link WireType.Fixlen}, subtype fp32). */
|
|
614
741
|
readFp32(): number;
|
|
742
|
+
/**
|
|
743
|
+
* Read a 32-bit float scalar as its raw 4 wire bytes (little-endian), zero-copy
|
|
744
|
+
* — the bit-preserving companion to {@link readFp32}.
|
|
745
|
+
*
|
|
746
|
+
* {@link readFp32} returns a JS `number` (a 64-bit double), and widening an
|
|
747
|
+
* fp32 *signaling* NaN into a double quiets it (0x7F800001 → 0x7FC00001), so a
|
|
748
|
+
* value consumer can never round-trip one bit-for-bit (§4.6). Generated
|
|
749
|
+
* bit-exact decode reads the bytes here instead and re-emits them verbatim with
|
|
750
|
+
* {@link OStream.writeFixlen} (subtype fp32) — mirroring the visitor `raw`
|
|
751
|
+
* channel on the push paths (fast.ts / state.ts), which the pull path was
|
|
752
|
+
* missing (corelib-ts#66).
|
|
753
|
+
*
|
|
754
|
+
* The header (subtype fp32, length 4) is validated exactly as in
|
|
755
|
+
* {@link readFp32}; the returned view aliases the source buffer, valid only
|
|
756
|
+
* until it is reused, like {@link readBlob}.
|
|
757
|
+
*/
|
|
758
|
+
readFp32Raw(): Uint8Array;
|
|
615
759
|
/** Read a 64-bit float scalar (wire {@link WireType.Fixlen}, subtype fp64). */
|
|
616
760
|
readFp64(): number;
|
|
617
|
-
/**
|
|
618
|
-
|
|
761
|
+
/**
|
|
762
|
+
* Read a UTF-8 string scalar (wire {@link WireType.Fixlen}, subtype string).
|
|
763
|
+
* Pass the schema `maxlen` (byte length) for a bounded string so an
|
|
764
|
+
* over-length is rejected as `INVALID` at the header, before the payload is
|
|
765
|
+
* taken (see {@link fixlenLen}); the wire length is exactly the UTF-8 byte
|
|
766
|
+
* length, so the check is exact. Omit for an unbounded string.
|
|
767
|
+
*/
|
|
768
|
+
readString(schemaMaxlen?: number): string;
|
|
619
769
|
/**
|
|
620
770
|
* Read a blob scalar (wire {@link WireType.Fixlen}, subtype blob) as a
|
|
621
771
|
* zero-copy {@link Uint8Array} view into the source buffer.
|
|
622
772
|
*/
|
|
623
|
-
readBlob(): Uint8Array;
|
|
624
|
-
/**
|
|
625
|
-
|
|
773
|
+
readBlob(schemaMaxlen?: number): Uint8Array;
|
|
774
|
+
/**
|
|
775
|
+
* Read an unsigned array (wire {@link WireType.ArrayUnsigned}), number-first
|
|
776
|
+
* per element. Pass the schema `count` for a bounded array so an over-count is
|
|
777
|
+
* rejected as `INVALID` at the header (see {@link arrayCount}); omit it for an
|
|
778
|
+
* unbounded array (today's behavior).
|
|
779
|
+
*/
|
|
780
|
+
readUnsignedArray(schemaCount?: number): (number | bigint)[];
|
|
626
781
|
/** Read a signed array (wire {@link WireType.ArraySigned}), zig-zag, number-first per element. */
|
|
627
|
-
readSignedArray(): (number | bigint)[];
|
|
782
|
+
readSignedArray(schemaCount?: number): (number | bigint)[];
|
|
628
783
|
/**
|
|
629
784
|
* Read an unsigned 64-bit array into {@link Long}[] — the `bigint`-free path.
|
|
630
785
|
* Each element keeps the raw lo/hi halves; call {@link Long.toBigInt} to
|
|
631
786
|
* materialise only the values the caller actually needs.
|
|
632
787
|
*/
|
|
633
|
-
readUnsignedArrayLong(): Long[];
|
|
788
|
+
readUnsignedArrayLong(schemaCount?: number): Long[];
|
|
634
789
|
/** Read a signed 64-bit array (zig-zag) into {@link Long}[] — the `bigint`-free path. */
|
|
635
|
-
readSignedArrayLong(): Long[];
|
|
790
|
+
readSignedArrayLong(schemaCount?: number): Long[];
|
|
636
791
|
/** Read an fp32 array (wire {@link WireType.ArrayFixlen}, element subtype fp32). */
|
|
637
|
-
readFp32Array(): number[];
|
|
792
|
+
readFp32Array(schemaCount?: number): number[];
|
|
793
|
+
/**
|
|
794
|
+
* Read an fp32 array as its raw little-endian element payload (`count * 4`
|
|
795
|
+
* bytes), zero-copy — the bit-preserving companion to {@link readFp32Array}.
|
|
796
|
+
* Widening each element to a JS `number` quiets an fp32 *signaling* NaN just as
|
|
797
|
+
* on the scalar path (§4.6; see {@link readFp32Raw}), so bit-exact decode reads
|
|
798
|
+
* the whole payload here and re-emits it with {@link OStream.writeFp32ArrayRaw}
|
|
799
|
+
* (corelib-ts#66). The header (element subtype fp32, size 4) is validated
|
|
800
|
+
* exactly as in {@link readFp32Array}; the returned view aliases the source
|
|
801
|
+
* buffer, like {@link readBlob}.
|
|
802
|
+
*/
|
|
803
|
+
readFp32ArrayRaw(schemaCount?: number): Uint8Array;
|
|
638
804
|
/** Read an fp64 array (wire {@link WireType.ArrayFixlen}, element subtype fp64). */
|
|
639
|
-
readFp64Array(): number[];
|
|
805
|
+
readFp64Array(schemaCount?: number): number[];
|
|
640
806
|
/**
|
|
641
807
|
* Consume the value of the field whose header {@link readHeader} just accepted,
|
|
642
808
|
* discarding it — for a `default:` branch that keeps the cursor in sync on an
|
|
@@ -658,13 +824,25 @@ declare class Cursor {
|
|
|
658
824
|
* reads one byte, it never decodes a varint.
|
|
659
825
|
*/
|
|
660
826
|
private peekFixSub;
|
|
661
|
-
/**
|
|
827
|
+
/**
|
|
828
|
+
* Read and validate an array count word (0..ARRAY_MAX; §4.7/§4.8). When a
|
|
829
|
+
* `schemaCount` is given, a count above it is a schema-bound violation and is
|
|
830
|
+
* rejected as `INVALID` — see the check below.
|
|
831
|
+
*/
|
|
662
832
|
private arrayCount;
|
|
663
833
|
/** Read a scalar fixlen sub-header, asserting subtype and exact byte length (floats). */
|
|
664
834
|
private fixlenHeader;
|
|
665
|
-
/**
|
|
835
|
+
/**
|
|
836
|
+
* Read a scalar fixlen sub-header for a string/blob, asserting subtype;
|
|
837
|
+
* returns the byte length. When a `schemaMaxlen` is given, a length above it
|
|
838
|
+
* is a schema-bound violation and is rejected as `INVALID` — see below.
|
|
839
|
+
*/
|
|
666
840
|
private fixlenLen;
|
|
667
|
-
/**
|
|
841
|
+
/**
|
|
842
|
+
* Read an array fixlen element header (count + element type); returns the
|
|
843
|
+
* count. When a `schemaCount` is given, a count above it is a schema-bound
|
|
844
|
+
* violation and is rejected as `INVALID` — see below.
|
|
845
|
+
*/
|
|
668
846
|
private arrayFixlenHeader;
|
|
669
847
|
/** Hand back a zero-copy view of the next `len` bytes, advancing the cursor. */
|
|
670
848
|
private take;
|