@sofa-buffers/corelib 0.8.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.
@@ -0,0 +1,825 @@
1
+ /**
2
+ * Wire-format constants for SofaBuffers.
3
+ *
4
+ * The format is specified, language-neutrally, in the
5
+ * {@link https://github.com/sofa-buffers/documentation | SofaBuffers documentation}.
6
+ * Every field on the wire begins with a varint header `(id << 3) | wireType`,
7
+ * so the wire type lives in the low three bits and the field id in the rest.
8
+ */
9
+ /**
10
+ * The SofaBuffers API version this library implements. The generator and other
11
+ * tooling read it to verify compatibility. Bumped only on a breaking API change.
12
+ */
13
+ declare const API_VERSION = 1;
14
+ /** The three low bits of a field header: what kind of field follows. */
15
+ declare const WireType: {
16
+ /** Unsigned varint scalar. */
17
+ readonly Unsigned: 0;
18
+ /** Signed varint scalar (zig-zag encoded). */
19
+ readonly Signed: 1;
20
+ /** Fixed-length value: fp32, fp64, string or blob (see {@link FixlenSubtype}). */
21
+ readonly Fixlen: 2;
22
+ /** Array of unsigned varints. */
23
+ readonly ArrayUnsigned: 3;
24
+ /** Array of signed (zig-zag) varints. */
25
+ readonly ArraySigned: 4;
26
+ /** Array of fixed-length values (fp32 / fp64 only). */
27
+ readonly ArrayFixlen: 5;
28
+ /** Opens a nested sequence (new id scope). */
29
+ readonly SequenceStart: 6;
30
+ /** Closes the current sequence. Encoded as the single byte `0x07`. */
31
+ readonly SequenceEnd: 7;
32
+ };
33
+ /** A field's wire type: one of the {@link WireType} values. */
34
+ type WireType = (typeof WireType)[keyof typeof WireType];
35
+ /** The three low bits of a fixlen length header: which fixed-length type. */
36
+ declare const FixlenSubtype: {
37
+ /** IEEE-754 32-bit float, little-endian. */
38
+ readonly Fp32: 0;
39
+ /** IEEE-754 64-bit double, little-endian. */
40
+ readonly Fp64: 1;
41
+ /** UTF-8 string (no null terminator). */
42
+ readonly String: 2;
43
+ /** Arbitrary binary data. */
44
+ readonly Blob: 3;
45
+ };
46
+ /** A fixed-length value's type: one of the {@link FixlenSubtype} values. */
47
+ type FixlenSubtype = (typeof FixlenSubtype)[keyof typeof FixlenSubtype];
48
+ /** Which element kind an array field carries (reported to {@link Visitor.arrayBegin}). */
49
+ declare const ArrayKind: {
50
+ /** Unsigned-integer elements. */
51
+ readonly Unsigned: 0;
52
+ /** Signed-integer (zig-zag) elements. */
53
+ readonly Signed: 1;
54
+ /** IEEE-754 32-bit float elements. */
55
+ readonly Fp32: 2;
56
+ /** IEEE-754 64-bit double elements. */
57
+ readonly Fp64: 3;
58
+ };
59
+ /** An array field's element kind: one of the {@link ArrayKind} values. */
60
+ type ArrayKind = (typeof ArrayKind)[keyof typeof ArrayKind];
61
+ /** Largest permitted field id and fixlen length / array count: `INT32_MAX`. */
62
+ declare const ID_MAX = 2147483647;
63
+ /** Largest permitted fixlen byte length: `INT32_MAX`. */
64
+ declare const FIXLEN_MAX = 2147483647;
65
+ /** Largest permitted array element count: `INT32_MAX`. */
66
+ declare const ARRAY_MAX = 2147483647;
67
+ /** Largest unsigned 64-bit value (`2^64 - 1`). */
68
+ declare const U64_MAX = 18446744073709551615n;
69
+ /** Smallest signed 64-bit value (`-2^63`). */
70
+ declare const I64_MIN = -9223372036854775808n;
71
+ /** Largest signed 64-bit value (`2^63 - 1`). */
72
+ declare const I64_MAX = 9223372036854775807n;
73
+ /**
74
+ * Maximum nested-sequence depth (§4.9 / §6.2). An encoder must not open more
75
+ * than this many nested sequences, and a decoder must reject a message that
76
+ * nests deeper with an `InvalidMessage` error rather than risk unbounded
77
+ * recursion / stack growth.
78
+ */
79
+ declare const MAX_DEPTH = 255;
80
+ /**
81
+ * The terminal outcome of a decode (MESSAGE_SPEC §7), reported identically for
82
+ * one-shot and streaming decode with **no** finish / finalize / end promotion
83
+ * step:
84
+ *
85
+ * - `Complete` — the bytes ended exactly at a field boundary: a valid message.
86
+ * - `Incomplete` — the bytes ended *inside* a field (an unterminated varint, a
87
+ * payload shorter than its declared length, an array that runs off the end, or
88
+ * a nested sequence never closed). **Not an error** — more bytes could
89
+ * complete it, and the caller owns end-of-input.
90
+ * - `Invalid` — the bytes are malformed regardless of what follows.
91
+ *
92
+ * {@link IStream.end} returns `Complete` or `Incomplete` (an `Invalid` message
93
+ * has already thrown from {@link IStream.feed}); the one-shot {@link decode} /
94
+ * {@link Cursor} path signals `Incomplete` and `Invalid` by throwing a
95
+ * {@link SofabError} whose `code` is {@link SofabErrorCode.Incomplete} or
96
+ * {@link SofabErrorCode.InvalidMsg}, and `Complete` by returning normally.
97
+ */
98
+ declare const DecodeStatus: {
99
+ /** The bytes ended exactly at a field boundary — a valid message. */
100
+ readonly Complete: "COMPLETE";
101
+ /** The bytes ended inside a field; more bytes could complete it (not an error). */
102
+ readonly Incomplete: "INCOMPLETE";
103
+ /** The bytes are malformed regardless of what follows. */
104
+ readonly Invalid: "INVALID";
105
+ };
106
+ /** A decode's terminal outcome: one of the {@link DecodeStatus} values. */
107
+ type DecodeStatus = (typeof DecodeStatus)[keyof typeof DecodeStatus];
108
+
109
+ /**
110
+ * Error handling for SofaBuffers.
111
+ *
112
+ * Both encoder and decoder report problems through a single {@link SofabError}
113
+ * carrying a {@link SofabErrorCode}, mirroring the C reference's `sofab_ret_t`
114
+ * return codes so the failure modes line up across the language family.
115
+ *
116
+ * The decoder distinguishes two *kinds* of decode failure (MESSAGE_SPEC §7):
117
+ * {@link SofabErrorCode.InvalidMsg} for input that is malformed regardless of
118
+ * what follows (`INVALID`), and {@link SofabErrorCode.Incomplete} for input that
119
+ * merely ends inside a field (`INCOMPLETE`) — a truncation that more bytes could
120
+ * complete, and so is *not* the same as a malformed message. A third code,
121
+ * {@link SofabErrorCode.LimitExceeded}, is orthogonal to both: it reports a
122
+ * receiver-configured decode limit being hit — *policy*, not a property of the
123
+ * bytes — so it is kept distinct from `InvalidMsg`.
124
+ */
125
+ /**
126
+ * The cause of a {@link SofabError}. `Argument`, `Usage`, `BufferFull` and
127
+ * `InvalidMsg` match the C reference's `sofab_ret_t` codes; `Incomplete` is the
128
+ * finish-less INCOMPLETE decode outcome (MESSAGE_SPEC §7), a distinct,
129
+ * more-bytes-could-complete-it signal split out from `InvalidMsg`.
130
+ */
131
+ declare const SofabErrorCode: {
132
+ /** A caller argument was invalid (e.g. id out of range, empty array). */
133
+ readonly Argument: "ARGUMENT";
134
+ /** The API was used incorrectly (e.g. unbalanced sequence end). */
135
+ readonly Usage: "USAGE";
136
+ /** The output buffer is full and no flush sink was provided. */
137
+ readonly BufferFull: "BUFFER_FULL";
138
+ /** The input being decoded is malformed regardless of what follows (`INVALID`). */
139
+ readonly InvalidMsg: "INVALID_MSG";
140
+ /**
141
+ * The input being decoded ends inside a field (`INCOMPLETE`, MESSAGE_SPEC §7):
142
+ * an unterminated varint, a payload shorter than its declared length, an array
143
+ * that runs off the end, or a nested sequence never closed. Not a malformed
144
+ * message — more bytes could complete it, and the caller owns end-of-input.
145
+ */
146
+ readonly Incomplete: "INCOMPLETE";
147
+ /**
148
+ * A receiver-configured decode limit was exceeded — a dynamic array, string or
149
+ * blob on the wire claims more elements / bytes than the caller's
150
+ * {@link DecodeLimits} (`maxArrayCount` / `maxStringLen` / `maxBlobLen`)
151
+ * allows. Deliberately distinct from {@link SofabErrorCode.InvalidMsg}:
152
+ * exceeding a limit is *policy*, not wire malformation — the identical bytes
153
+ * decode fine under a looser limit — so differential fuzzing must not read it
154
+ * as a conformance divergence. The decoder never clamps or truncates; it
155
+ * rejects, before the offending field is materialized.
156
+ */
157
+ readonly LimitExceeded: "LIMIT_EXCEEDED";
158
+ };
159
+ /** A {@link SofabError}'s cause: one of the {@link SofabErrorCode} values. */
160
+ type SofabErrorCode = (typeof SofabErrorCode)[keyof typeof SofabErrorCode];
161
+ /** The single error type thrown by the encoder and decoder. */
162
+ declare class SofabError extends Error {
163
+ /** The machine-readable cause. */
164
+ readonly code: SofabErrorCode;
165
+ constructor(code: SofabErrorCode, message: string);
166
+ }
167
+
168
+ /**
169
+ * A 64-bit integer value carried as two unsigned 32-bit halves.
170
+ *
171
+ * SofaBuffers accepts `bigint` at every 64-bit surface for ergonomics, but a
172
+ * `bigint` in the encode/decode hot path is expensive — especially on
173
+ * JavaScriptCore (Bun), where profiling showed the `bigint` split/materialise
174
+ * and zig-zag operations dominating the 64-bit array codecs. `Long` lets a
175
+ * caller (and the generated code) stay entirely on `number` arithmetic: the
176
+ * codec reads `.low`/`.high` directly, and any `bigint`↔`Long` conversion
177
+ * happens once at the API boundary rather than once per encode.
178
+ *
179
+ * Values are stored as raw two's-complement bits; the same `Long` serves an
180
+ * unsigned or signed field — {@link Long.toBigInt} takes the signedness.
181
+ */
182
+ declare class Long {
183
+ /** Low 32 bits (unsigned). */
184
+ readonly low: number;
185
+ /** High 32 bits (unsigned). */
186
+ readonly high: number;
187
+ constructor(low: number, high: number);
188
+ /**
189
+ * The 64-bit zero. `Long` is immutable (readonly halves), so this single shared
190
+ * instance is safe to reuse anywhere a zero default is needed — generated code
191
+ * uses it for fixed-count array defaults and pad-fill instead of
192
+ * `Long.fromValue(0)`, which would run `bigint` arithmetic per call on the hot
193
+ * decode/encode path.
194
+ */
195
+ static readonly ZERO: Long;
196
+ /** Construct from raw 32-bit halves. */
197
+ static fromBits(low: number, high: number): Long;
198
+ /** Split a `bigint` into its low/high 32-bit halves (two's complement). */
199
+ static fromBigInt(value: bigint): Long;
200
+ /** From an integer `number` (`|n| < 2^53`); sign handled via `bigint` once. */
201
+ static fromNumber(n: number): Long;
202
+ /** Accept a `Long` as-is, or convert a `bigint` / `number` once. */
203
+ static fromValue(v: Long | bigint | number): Long;
204
+ /** Materialise as a `bigint`. `signed` reads the high bit as two's complement. */
205
+ toBigInt(signed?: boolean): bigint;
206
+ /** Decimal string (`signed` interprets the high bit as two's complement). */
207
+ toString(signed?: boolean): string;
208
+ }
209
+
210
+ /**
211
+ * The output drain used for streaming encodes.
212
+ *
213
+ * When an {@link OStream} is given a `FlushSink`, it writes into a small caller
214
+ * buffer and hands each filled region to the sink, so a message can be far
215
+ * larger than the buffer — larger than RAM, even. The chunk passed to the sink
216
+ * is only valid for the duration of the call; copy it if you need to retain it.
217
+ */
218
+ type FlushSink = (chunk: Uint8Array) => void;
219
+
220
+ /**
221
+ * The SofaBuffers encoder.
222
+ *
223
+ * `OStream` writes fields into a byte buffer. Two modes:
224
+ *
225
+ * - **In-memory** (`new OStream()`): an auto-growing buffer; call
226
+ * {@link OStream.bytes} for the finished message.
227
+ * - **Streaming** (`new OStream(buffer, offset?, flush?)`): writes into a
228
+ * caller-provided buffer and, when it fills, hands the produced bytes to the
229
+ * `flush` sink and continues — so the buffer can be much smaller than the
230
+ * message. `offset` reserves room at the front for a lower-layer header.
231
+ *
232
+ * Generated code typically writes one field per message field; the methods map
233
+ * one-to-one onto the wire types. Problems throw {@link SofabError}.
234
+ */
235
+
236
+ /**
237
+ * Encoder for the SofaBuffers wire format. Each `write*` method appends one
238
+ * field and maps one-to-one onto a wire type. Construct it in-memory (an
239
+ * auto-growing buffer, read back with {@link OStream.bytes}) or in streaming
240
+ * mode over a caller-provided buffer that drains to a {@link FlushSink} as it
241
+ * fills, so the message can outgrow the buffer. Invalid arguments and a full
242
+ * buffer with no sink throw {@link SofabError}.
243
+ */
244
+ declare class OStream {
245
+ private buf;
246
+ private pos;
247
+ private start;
248
+ private readonly flushSink;
249
+ private readonly canGrow;
250
+ private depth;
251
+ private kernel;
252
+ /** In-memory encoder backed by an auto-growing buffer. */
253
+ constructor();
254
+ /** Streaming encoder over a caller buffer, optionally draining to `flush`. */
255
+ constructor(buffer: Uint8Array, offset?: number, flush?: FlushSink);
256
+ /** Bytes currently held in the buffer (since construction or the last flush). */
257
+ get bytesUsed(): number;
258
+ /**
259
+ * The encoded message so far, as a view into the working buffer.
260
+ * Meaningful for the in-memory mode; in streaming mode it is only the
261
+ * not-yet-flushed tail. The view is valid until the next write.
262
+ */
263
+ bytes(): Uint8Array;
264
+ /** Drain buffered bytes to the flush sink (no-op without one). */
265
+ flush(): void;
266
+ /**
267
+ * Install a fresh output buffer to write into, mid-stream. Intended for the
268
+ * streaming (flush-sink) mode: call it from inside your flush callback to hand
269
+ * the encoder a new buffer for the next batch of bytes, so encoding continues
270
+ * without interruption. `offset` reserves space at the front of the new
271
+ * buffer. Any not-yet-flushed bytes in the old buffer are dropped, so
272
+ * {@link flush} first (the flush callback fires before you swap).
273
+ */
274
+ setBuffer(buffer: Uint8Array, offset?: number): void;
275
+ /**
276
+ * Rewind the encoder to empty, reusing the existing buffer. Lets a caller pool
277
+ * one OStream across many messages instead of allocating a fresh buffer per
278
+ * encode. Any view previously returned by {@link bytes} is invalidated.
279
+ */
280
+ reset(): void;
281
+ /** Write an unsigned integer field. */
282
+ writeUnsigned(id: number, value: number | bigint): void;
283
+ /** Write a signed integer field (zig-zag encoded). */
284
+ writeSigned(id: number, value: number | bigint): void;
285
+ /** Write a boolean field (encoded as the unsigned value 0 or 1). */
286
+ writeBoolean(id: number, value: boolean): void;
287
+ /** Write an IEEE-754 32-bit float field. */
288
+ writeFp32(id: number, value: number): void;
289
+ /** Write an IEEE-754 64-bit double field. */
290
+ writeFp64(id: number, value: number): void;
291
+ /** Write a UTF-8 string field. */
292
+ writeString(id: number, text: string): void;
293
+ /** Write a blob (arbitrary bytes) field. */
294
+ writeBlob(id: number, data: Uint8Array): void;
295
+ /** Write a fixed-length field of the given subtype from raw bytes. */
296
+ writeFixlen(id: number, data: Uint8Array, subtype: FixlenSubtype): void;
297
+ /** Write an array of unsigned integers (each a varint). */
298
+ writeUnsignedArray(id: number, values: ArrayLike<number | bigint>): void;
299
+ /** Write an array of signed integers (each zig-zag + varint). */
300
+ writeSignedArray(id: number, values: ArrayLike<number | bigint>): void;
301
+ /**
302
+ * Write an unsigned 64-bit array from {@link Long}[] — the `bigint`-free path.
303
+ * Produces the identical wire to {@link writeUnsignedArray}; reads each Long's
304
+ * 32-bit halves directly, so no `bigint` is created per element.
305
+ */
306
+ writeUnsignedArrayLong(id: number, values: readonly Long[]): void;
307
+ /**
308
+ * Write a signed 64-bit array (zig-zag) from {@link Long}[] — the `bigint`-free
309
+ * path. Zig-zag `(n << 1) ^ (n >> 63)` is computed on the lo/hi pair.
310
+ */
311
+ writeSignedArrayLong(id: number, values: readonly Long[]): void;
312
+ /** Write an array of IEEE-754 32-bit floats. */
313
+ writeFp32Array(id: number, values: ArrayLike<number>): void;
314
+ /** Write an array of IEEE-754 64-bit doubles. */
315
+ writeFp64Array(id: number, values: ArrayLike<number>): void;
316
+ /** Open a nested sequence (a fresh id scope). */
317
+ writeSequenceBegin(id: number): void;
318
+ /** Close the current sequence. */
319
+ writeSequenceEnd(): void;
320
+ /** Ensure exactly `value`'s varint size, then write it (bigint path). */
321
+ private putVarint;
322
+ /** Ensure exactly `value`'s varint size, then write it (number fast path). */
323
+ private putVarintNum;
324
+ private header;
325
+ private fixlenHead;
326
+ private arrayHead;
327
+ /** Copy `data` out, flushing/growing as needed (large payloads stay chunked). */
328
+ private writeRaw;
329
+ /** Ensure `n` contiguous bytes are free at `pos`; returns `pos` for chaining. */
330
+ private ensure;
331
+ /** Ensure *some* room (up to `want`); returns how many bytes are available. */
332
+ private ensureSome;
333
+ private growTo;
334
+ }
335
+
336
+ /**
337
+ * Opt-in decode resource limits (corelib-ts#38).
338
+ *
339
+ * SofaBuffers' `count` (arrays) and `maxlen` (strings / blobs) schema bounds are
340
+ * optional; when a schema omits them the decoder accepts whatever count / length
341
+ * the received message claims, with no upper bound. That leaves a receiver no way
342
+ * to cap memory against a hostile, oversized field. {@link DecodeLimits} is that
343
+ * cap: an optional options object accepted by every decode entry point
344
+ * ({@link decode}, the {@link IStream} constructor, and the {@link Cursor}
345
+ * constructor).
346
+ *
347
+ * The limits are a **receiver-side policy**, not part of the wire format or the
348
+ * message schema. The normative source of the values is the sofabgen config
349
+ * (see sofa-buffers/generator#102): the generator bakes them into generated code
350
+ * as constants and passes them here at decoder construction. This corelib only
351
+ * provides the mechanism — **an omitted limit means no cap (today's behavior);
352
+ * there is no corelib-side default.**
353
+ *
354
+ * Enforcement happens at header time — where the count / length is first decoded,
355
+ * before any array is sized or any payload is accepted or streamed — so a claimed
356
+ * oversize is rejected even if the payload never arrives. Exceeding a limit is
357
+ * never clamped or truncated: it throws a {@link SofabError} with code
358
+ * {@link SofabErrorCode.LimitExceeded}, which is deliberately distinct from
359
+ * `InvalidMsg` (policy, not malformation).
360
+ */
361
+ interface DecodeLimits {
362
+ /**
363
+ * Reject a dynamic (`u*` / `i*`, `fp32` / `fp64`) array whose element `count`
364
+ * exceeds this, before the array is materialized. Omit for no cap.
365
+ */
366
+ maxArrayCount?: number;
367
+ /**
368
+ * Reject a UTF-8 string whose declared byte length exceeds this, before the
369
+ * payload is decoded or streamed. Omit for no cap.
370
+ */
371
+ maxStringLen?: number;
372
+ /**
373
+ * Reject a blob whose declared byte length exceeds this, before the payload is
374
+ * accepted or streamed. Omit for no cap.
375
+ */
376
+ maxBlobLen?: number;
377
+ }
378
+
379
+ /**
380
+ * The SofaBuffers decoder.
381
+ *
382
+ * `IStream` is a push parser: feed it bytes with {@link IStream.feed} and it
383
+ * drives a {@link Visitor}, calling one method per decoded field. It is a
384
+ * resumable state machine, so the chunks you feed can be any size — a whole
385
+ * message, a network packet, or a single byte — and a field that straddles a
386
+ * chunk boundary is picked up seamlessly on the next call.
387
+ *
388
+ * Nesting is hierarchical: {@link Visitor.sequenceBegin} may return a child
389
+ * visitor, and the decoder routes the nested fields to it until the matching
390
+ * end. Generated message classes use this directly — a class implements
391
+ * `Visitor`, and a nested-message field returns the child instance.
392
+ *
393
+ * There is no finish / finalize step (MESSAGE_SPEC §7): {@link IStream.feed}
394
+ * throws only for a *malformed* message ({@link SofabErrorCode.InvalidMsg}); a
395
+ * message that merely ends inside a field is reported — never thrown — by
396
+ * {@link IStream.end}, which returns {@link DecodeStatus.Incomplete} rather than
397
+ * {@link DecodeStatus.Complete}. The caller owns end-of-input and decides
398
+ * whether a trailing `Incomplete` is a truncation error.
399
+ */
400
+
401
+ /**
402
+ * Receives decoded fields from an {@link IStream}. Every method is optional and
403
+ * defaults to a no-op, so a visitor implements only the fields it cares about
404
+ * and silently skips the rest.
405
+ *
406
+ * String and blob payloads arrive as one or more `chunk`s, each tagged with the
407
+ * field's `total` length and the `offset` of the chunk within the field, so a
408
+ * large payload never has to be held in one piece. Array elements arrive one at
409
+ * a time between {@link Visitor.arrayBegin} and {@link Visitor.arrayEnd}.
410
+ */
411
+ interface Visitor {
412
+ /**
413
+ * An unsigned integer field. Number-first: `value` is a `number` when it fits
414
+ * exactly (`≤ 2^53-1`, covering ids, u8..u32 and small u64s) and a `bigint`
415
+ * only beyond that, so the common case avoids a per-value bigint allocation.
416
+ */
417
+ unsigned?(id: number, value: number | bigint): void;
418
+ /** A signed integer field. Number-first like {@link unsigned} (`|value| ≤ 2^53-1` ⇒ `number`). */
419
+ signed?(id: number, value: number | bigint): void;
420
+ /** An IEEE-754 32-bit float field. */
421
+ fp32?(id: number, value: number): void;
422
+ /** An IEEE-754 64-bit double field. */
423
+ fp64?(id: number, value: number): void;
424
+ /** A chunk of a UTF-8 string field. */
425
+ string?(id: number, total: number, offset: number, chunk: Uint8Array): void;
426
+ /** A chunk of a blob field. */
427
+ blob?(id: number, total: number, offset: number, chunk: Uint8Array): void;
428
+ /** Start of an array; `count` elements of `kind` follow. */
429
+ arrayBegin?(id: number, kind: ArrayKind, count: number): void;
430
+ /** One unsigned array element. Number-first like {@link unsigned}. */
431
+ arrayUnsigned?(id: number, index: number, value: number | bigint): void;
432
+ /** One signed array element. Number-first like {@link signed}. */
433
+ 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. */
437
+ arrayFp64?(id: number, index: number, value: number): void;
438
+ /** End of an array. */
439
+ arrayEnd?(id: number): void;
440
+ /**
441
+ * Start of a nested sequence. Return a {@link Visitor} to route the nested
442
+ * fields to it (its {@link Visitor.sequenceEnd} fires at the matching end);
443
+ * return nothing to keep using the current visitor.
444
+ */
445
+ sequenceBegin?(id: number): Visitor | void;
446
+ /** End of the nested sequence this visitor was handling. */
447
+ sequenceEnd?(): void;
448
+ }
449
+ /**
450
+ * Push parser for the SofaBuffers wire format. Feed it bytes in chunks of any
451
+ * size with {@link IStream.feed} and it drives a {@link Visitor}, one call per
452
+ * decoded field, resuming cleanly across chunk boundaries. Call
453
+ * {@link IStream.end} after the final chunk to read whether the message finished
454
+ * on a field boundary. When the whole message is already in one buffer, prefer
455
+ * the faster {@link decode}.
456
+ */
457
+ declare class IStream {
458
+ private readonly state;
459
+ /**
460
+ * @param limits Optional opt-in decode caps ({@link DecodeLimits}). An
461
+ * over-limit array count or string / blob length throws {@link SofabError}
462
+ * (`LIMIT_EXCEEDED`) from {@link feed}, at the offending field's header and
463
+ * before any of its payload is streamed to the visitor. Omit for no caps.
464
+ */
465
+ constructor(limits?: DecodeLimits);
466
+ /**
467
+ * Feed a chunk of bytes, dispatching decoded fields to `visitor`. Throws
468
+ * {@link SofabError} (`INVALID_MSG`) only if the bytes are *malformed*;
469
+ * running out of bytes mid-field is not an error — it simply suspends until
470
+ * the next chunk (see {@link end}).
471
+ */
472
+ feed(chunk: Uint8Array, visitor: Visitor): void;
473
+ /**
474
+ * Report whether the stream ended exactly at a field boundary. Call after the
475
+ * final {@link feed}: returns {@link DecodeStatus.Complete} at a clean field
476
+ * boundary, or {@link DecodeStatus.Incomplete} if the last chunk ended inside
477
+ * a field (a partial varint, an unfinished payload / array, or a still-open
478
+ * nested sequence).
479
+ *
480
+ * Per the finish-less spec (MESSAGE_SPEC §7) this is a pure accessor: it never
481
+ * throws and never promotes an incomplete decode to an error — the caller owns
482
+ * end-of-input and decides whether a trailing `Incomplete` is a truncation
483
+ * error. (A *malformed* message has already thrown from {@link feed}.)
484
+ */
485
+ end(): DecodeStatus;
486
+ }
487
+ /**
488
+ * Decode a complete message held in one contiguous buffer, in a single call.
489
+ *
490
+ * This is the non-streaming convenience — and the fast path: with the whole
491
+ * message in hand it advances one cursor over the buffer instead of running the
492
+ * resumable per-byte state machine, so it is markedly faster than feeding the
493
+ * same bytes through
494
+ * {@link IStream}. Use {@link IStream} when the message arrives in chunks; use
495
+ * this when you already have it whole.
496
+ *
497
+ * The whole buffer *is* the end of input, so the two failure outcomes both
498
+ * throw a {@link SofabError} the caller tells apart by `code` (MESSAGE_SPEC §7):
499
+ * malformed input throws `INVALID_MSG`, while input that ends inside a field —
500
+ * truncation or an unclosed sequence — throws `INCOMPLETE`. A complete message
501
+ * returns normally.
502
+ *
503
+ * Pass `limits` ({@link DecodeLimits}) to cap array counts and string / blob
504
+ * lengths; an over-limit field throws `LIMIT_EXCEEDED` at its header, before it
505
+ * is materialized. Omit for no caps (the default).
506
+ */
507
+ declare function decode(bytes: Uint8Array, visitor: Visitor, limits?: DecodeLimits): void;
508
+
509
+ /**
510
+ * The pull / cursor decoder: a monomorphic companion to {@link "./fast"}.
511
+ *
512
+ * {@link "./fast"}'s {@link decodeContiguous} is a *push* decoder — it drives the
513
+ * buffer and calls a {@link Visitor} method per field. That is the right shape
514
+ * for streaming and skip-subtree callers, but the visitor call sites go
515
+ * megamorphic once a single decode routes through several differently-shaped
516
+ * visitor objects (one per nested message type), which a JIT cannot inline.
517
+ *
518
+ * {@link Cursor} inverts control: it keeps one read cursor over the contiguous
519
+ * {@link Uint8Array} and exposes *pull* primitives — {@link Cursor.readHeader}
520
+ * plus a typed `read*` per wire type — so **generated code drives the loop** with
521
+ * a single `switch (cursor.id)` that reads straight into its own fields. Every
522
+ * call site is then monomorphic (the generated per-type decoder is the only
523
+ * caller), which is what lets V8 inline the whole decode into a flat loop — the
524
+ * same technique protobuf's generated `decode(reader)` uses.
525
+ *
526
+ * It shares {@link "./fast"}'s number-first varint core verbatim: each varint is
527
+ * accumulated into two 32-bit JS *numbers* (`lo`/`hi`) and a `bigint` is
528
+ * materialised only for a 64-bit *value* that does not fit in `2^53-1` (never for
529
+ * ids, lengths or counts). String / blob payloads are returned as a single
530
+ * zero-copy `subarray` view. It reports the same three-valued outcome as the
531
+ * push path (MESSAGE_SPEC §7): malformed input throws a {@link SofabError} with
532
+ * code `INVALID_MSG`, and a read that runs off the end of the buffer mid-field
533
+ * throws `INCOMPLETE`.
534
+ */
535
+
536
+ /**
537
+ * A pull decoder over a complete message held in one contiguous buffer.
538
+ *
539
+ * Usage from generated code: loop on {@link readHeader}; for each field switch on
540
+ * {@link id} and call the matching `read*` (which consumes that field's value and
541
+ * advances the cursor); recurse into a child type's decoder on a nested sequence;
542
+ * fall through to {@link skip} for an unknown id. See {@link readHeader}.
543
+ *
544
+ * Pass {@link DecodeLimits} to cap array counts and string / blob lengths; an
545
+ * over-limit field throws {@link SofabError} (`LIMIT_EXCEEDED`) at its header,
546
+ * before it is materialized. Omit for no caps (the default).
547
+ */
548
+ declare class Cursor {
549
+ /** Field id of the header last accepted by {@link readHeader}. */
550
+ id: number;
551
+ /** Wire type of the header last accepted by {@link readHeader}. */
552
+ wire: number;
553
+ /**
554
+ * Fixlen subtype of the header last accepted by {@link readHeader} — one of
555
+ * {@link FixlenSubtype} — when its {@link wire} is {@link WireType.Fixlen} or
556
+ * {@link WireType.ArrayFixlen}; `-1` otherwise (a non-fixlen field, or a
557
+ * fixlen field whose subtype word is truncated away).
558
+ *
559
+ * The four fixlen subtypes (`fp32`, `fp64`, `string`, `blob`) all share one
560
+ * {@link wire} type, so {@link wire} alone cannot separate them. This is the
561
+ * companion accessor that can: a generated guard reads it right after
562
+ * {@link readHeader} to skip a field whose delivered subtype contradicts the
563
+ * schema (MESSAGE_SPEC §7.3), exactly as it already does on {@link wire} for
564
+ * the other kinds:
565
+ *
566
+ * ```ts
567
+ * case 9: if (c.wire !== WireType.Fixlen || c.fixSub !== FixlenSubtype.Fp64) {
568
+ * c.skip(c.wire); break;
569
+ * } o.somefp64 = c.readFp64(); break;
570
+ * ```
571
+ *
572
+ * It is *peeked* — the subtype word is not consumed — so the matching typed
573
+ * reader (or {@link skip}) still reads and validates it, and a malformed or
574
+ * truncated word surfaces `INVALID` / `INCOMPLETE` there as before.
575
+ */
576
+ fixSub: number;
577
+ private readonly buf;
578
+ private readonly view;
579
+ private readonly n;
580
+ private p;
581
+ private readonly maxArrayCount;
582
+ private readonly maxStringLen;
583
+ private readonly maxBlobLen;
584
+ private lo;
585
+ private hi;
586
+ private depth;
587
+ constructor(buf: Uint8Array, limits?: DecodeLimits);
588
+ /**
589
+ * Advance to the next field header. Returns `true` and sets {@link id} /
590
+ * {@link wire} when a field follows; returns `false` — consuming the marker —
591
+ * at the end of the buffer *or* at the sequence-end that closes the sequence
592
+ * this decoder is reading. So a generated per-type decoder loops uniformly:
593
+ *
594
+ * ```ts
595
+ * while (c.readHeader()) {
596
+ * switch (c.id) {
597
+ * case 4: this.u32 = Number(c.readUnsigned()); break;
598
+ * case 10: this.child = Child.decodeFrom(c); break; // nested sequence
599
+ * default: c.skip(c.wire); break; // unknown field
600
+ * }
601
+ * }
602
+ * ```
603
+ *
604
+ * At the root the loop ends at end-of-buffer; inside a nested sequence it ends
605
+ * at the matching {@link WireType.SequenceEnd} (which is consumed). A field
606
+ * whose id is out of range throws {@link SofabError} (`INVALID_MSG`).
607
+ */
608
+ readHeader(): boolean;
609
+ /** Read an unsigned scalar (wire {@link WireType.Unsigned}), number-first. */
610
+ readUnsigned(): number | bigint;
611
+ /** Read a signed scalar (wire {@link WireType.Signed}), zig-zag, number-first. */
612
+ readSigned(): number | bigint;
613
+ /** Read a 32-bit float scalar (wire {@link WireType.Fixlen}, subtype fp32). */
614
+ readFp32(): number;
615
+ /** Read a 64-bit float scalar (wire {@link WireType.Fixlen}, subtype fp64). */
616
+ readFp64(): number;
617
+ /** Read a UTF-8 string scalar (wire {@link WireType.Fixlen}, subtype string). */
618
+ readString(): string;
619
+ /**
620
+ * Read a blob scalar (wire {@link WireType.Fixlen}, subtype blob) as a
621
+ * zero-copy {@link Uint8Array} view into the source buffer.
622
+ */
623
+ readBlob(): Uint8Array;
624
+ /** Read an unsigned array (wire {@link WireType.ArrayUnsigned}), number-first per element. */
625
+ readUnsignedArray(): (number | bigint)[];
626
+ /** Read a signed array (wire {@link WireType.ArraySigned}), zig-zag, number-first per element. */
627
+ readSignedArray(): (number | bigint)[];
628
+ /**
629
+ * Read an unsigned 64-bit array into {@link Long}[] — the `bigint`-free path.
630
+ * Each element keeps the raw lo/hi halves; call {@link Long.toBigInt} to
631
+ * materialise only the values the caller actually needs.
632
+ */
633
+ readUnsignedArrayLong(): Long[];
634
+ /** Read a signed 64-bit array (zig-zag) into {@link Long}[] — the `bigint`-free path. */
635
+ readSignedArrayLong(): Long[];
636
+ /** Read an fp32 array (wire {@link WireType.ArrayFixlen}, element subtype fp32). */
637
+ readFp32Array(): number[];
638
+ /** Read an fp64 array (wire {@link WireType.ArrayFixlen}, element subtype fp64). */
639
+ readFp64Array(): number[];
640
+ /**
641
+ * Consume the value of the field whose header {@link readHeader} just accepted,
642
+ * discarding it — for a `default:` branch that keeps the cursor in sync on an
643
+ * unknown id. Pass {@link wire}. A {@link WireType.SequenceStart} skips the
644
+ * whole nested sequence.
645
+ */
646
+ skip(wire: number): void;
647
+ private skipValue;
648
+ private skipSequence;
649
+ /**
650
+ * Peek the delivered fixlen subtype of the field {@link readHeader} just
651
+ * accepted, **without advancing the cursor** — the readers / {@link skip}
652
+ * still re-read and validate the word. Returns one of {@link FixlenSubtype}
653
+ * (0..3), a reserved value (4..7), or `-1` when the wire is not a fixlen kind
654
+ * or the subtype word is truncated away.
655
+ *
656
+ * The subtype is the low 3 bits of the fixlen sub-header word, and the low
657
+ * bits of a LEB128 word live entirely in its **first** byte — so this only
658
+ * reads one byte, it never decodes a varint.
659
+ */
660
+ private peekFixSub;
661
+ /** Read and validate an array count word (0..ARRAY_MAX; §4.7/§4.8). */
662
+ private arrayCount;
663
+ /** Read a scalar fixlen sub-header, asserting subtype and exact byte length (floats). */
664
+ private fixlenHeader;
665
+ /** Read a scalar fixlen sub-header for a string/blob, asserting subtype; returns byte length. */
666
+ private fixlenLen;
667
+ /** Read an array fixlen element header (count + element type); returns the count. */
668
+ private arrayFixlenHeader;
669
+ /** Hand back a zero-copy view of the next `len` bytes, advancing the cursor. */
670
+ private take;
671
+ private rawFp32;
672
+ private rawFp64;
673
+ /**
674
+ * The last varint's full value as a `bigint` (64-bit fidelity). Only ever
675
+ * called from {@link unsignedValue} / {@link signedValue} on the `hi` overflow
676
+ * path (`this.hi >>> 0 > 0x1fffff`), so `hi` is always non-zero here.
677
+ */
678
+ private big;
679
+ /**
680
+ * The last varint as an unsigned value, number-first: a `number` when it fits
681
+ * exactly (`≤ 2^53-1`), a `bigint` only beyond that.
682
+ */
683
+ private unsignedValue;
684
+ /** The last zig-zag varint as a signed value, number-first. */
685
+ private signedValue;
686
+ /** The last varint's value as a JS number — exact for ids/lengths/counts. */
687
+ private num;
688
+ /** The last varint with its low 3 tag bits stripped (`value >> 3`). */
689
+ private upper;
690
+ /**
691
+ * Decode one LEB128 varint at the cursor into {@link lo} / {@link hi} (each an
692
+ * unsigned 32-bit half), advancing {@link p}. Throws on truncation or a value
693
+ * spilling past 64 bits (>10 bytes). Unrolled, number-only — no `bigint`.
694
+ */
695
+ private readVarint;
696
+ private set;
697
+ }
698
+
699
+ /**
700
+ * The acceleration seam.
701
+ *
702
+ * The encoder's bulk array paths run through a {@link Kernel} — a small set of
703
+ * self-contained, buffer-oriented transforms over a region the caller has
704
+ * already sized. The default {@link "./js"} kernel is pure TypeScript and works
705
+ * everywhere; a C++ (N-API) or WebAssembly build can implement the same
706
+ * interface and be swapped in with {@link setKernel} for a speed-up, with **no
707
+ * change to the public API**. The boundary is deliberately *bulk* (a whole
708
+ * array per call, into guaranteed capacity) so the cost of crossing into native
709
+ * code is amortised — never one call per element.
710
+ */
711
+ /**
712
+ * Bulk, capacity-guaranteed transforms used on the encoder's fast path.
713
+ *
714
+ * Every method writes into `out` starting at `pos`, assuming the caller has
715
+ * already ensured enough room, and returns the position just past the last byte
716
+ * written. Headers, counts, flushing and validation stay in the stream classes;
717
+ * a kernel only moves bytes.
718
+ */
719
+ interface Kernel {
720
+ /** A short identifier, surfaced in diagnostics and the parity tests. */
721
+ readonly name: string;
722
+ /** Encode each element as an unsigned varint. */
723
+ encodeUnsignedVarints(values: ArrayLike<number | bigint>, out: Uint8Array, pos: number): number;
724
+ /** Zig-zag then varint-encode each element. */
725
+ encodeSignedVarints(values: ArrayLike<number | bigint>, out: Uint8Array, pos: number): number;
726
+ /** Pack each element as a little-endian fp32 (4 bytes). */
727
+ packFp32Array(values: ArrayLike<number>, out: Uint8Array, pos: number): number;
728
+ /** Pack each element as a little-endian fp64 (8 bytes). */
729
+ packFp64Array(values: ArrayLike<number>, out: Uint8Array, pos: number): number;
730
+ }
731
+ /** Install `kernel` as the active acceleration backend. */
732
+ declare function setKernel(kernel: Kernel): void;
733
+ /** The currently active kernel (the JS kernel until something replaces it). */
734
+ declare function getKernel(): Kernel;
735
+
736
+ /**
737
+ * The default, pure-TypeScript {@link Kernel}.
738
+ *
739
+ * Always available — no native dependency, no WebAssembly — so SofaBuffers runs
740
+ * unchanged in Node.js, browsers, Electron and bundled builds. Importing this
741
+ * module registers it as the default kernel (idempotently); a native or WASM
742
+ * kernel can later override it via {@link setKernel}.
743
+ */
744
+
745
+ /**
746
+ * The default, pure-TypeScript {@link Kernel} — active until {@link setKernel}
747
+ * installs another. It implements the bulk encoder transforms with no native or
748
+ * WebAssembly dependency, so it works in every environment.
749
+ */
750
+ declare const jsKernel: Kernel;
751
+
752
+ /**
753
+ * Attempt to load and install the native kernel.
754
+ *
755
+ * @returns `true` if a valid native kernel was installed, `false` if it could
756
+ * not be loaded (not on Node, addon not installed, or wrong shape). Never
757
+ * throws for a missing addon — the JS kernel remains the fallback.
758
+ */
759
+ declare function loadNativeKernel(): Promise<boolean>;
760
+
761
+ /**
762
+ * Optional WebAssembly acceleration loader.
763
+ *
764
+ * Unlike the native addon, a WASM kernel runs in the browser too. This module
765
+ * does nothing at import time; call {@link loadWasmKernel} with the compiled
766
+ * module's bytes (or a streaming source) to instantiate it and install it as
767
+ * the active {@link Kernel}. A real WASM build is shipped separately; this
768
+ * loader is the stable entry point the rest of the library is wired through.
769
+ */
770
+
771
+ /** A factory the WASM glue exposes: given the instance exports, build a Kernel. */
772
+ type WasmKernelFactory = (exports: WebAssembly.Exports) => Kernel;
773
+ /**
774
+ * Instantiate a WASM module and install the kernel it produces.
775
+ *
776
+ * @param source compiled-module bytes, a `Response`/stream, or a ready module.
777
+ * @param factory wraps the instance exports into a {@link Kernel}.
778
+ * @returns `true` once installed; throws only if instantiation itself fails.
779
+ */
780
+ declare function loadWasmKernel(source: BufferSource | Response | PromiseLike<Response> | WebAssembly.Module, factory: WasmKernelFactory, imports?: WebAssembly.Imports): Promise<boolean>;
781
+
782
+ /**
783
+ * The complete public surface of the library, re-exported by {@link "./index"}
784
+ * both as flat named exports and, aggregated, under the `sofab` namespace.
785
+ */
786
+
787
+ declare const _public_API_VERSION: typeof API_VERSION;
788
+ declare const _public_ARRAY_MAX: typeof ARRAY_MAX;
789
+ type _public_ArrayKind = ArrayKind;
790
+ type _public_Cursor = Cursor;
791
+ declare const _public_Cursor: typeof Cursor;
792
+ type _public_DecodeLimits = DecodeLimits;
793
+ type _public_DecodeStatus = DecodeStatus;
794
+ declare const _public_FIXLEN_MAX: typeof FIXLEN_MAX;
795
+ type _public_FixlenSubtype = FixlenSubtype;
796
+ type _public_FlushSink = FlushSink;
797
+ declare const _public_I64_MAX: typeof I64_MAX;
798
+ declare const _public_I64_MIN: typeof I64_MIN;
799
+ declare const _public_ID_MAX: typeof ID_MAX;
800
+ type _public_IStream = IStream;
801
+ declare const _public_IStream: typeof IStream;
802
+ type _public_Kernel = Kernel;
803
+ type _public_Long = Long;
804
+ declare const _public_Long: typeof Long;
805
+ declare const _public_MAX_DEPTH: typeof MAX_DEPTH;
806
+ type _public_OStream = OStream;
807
+ declare const _public_OStream: typeof OStream;
808
+ type _public_SofabError = SofabError;
809
+ declare const _public_SofabError: typeof SofabError;
810
+ type _public_SofabErrorCode = SofabErrorCode;
811
+ declare const _public_U64_MAX: typeof U64_MAX;
812
+ type _public_Visitor = Visitor;
813
+ type _public_WasmKernelFactory = WasmKernelFactory;
814
+ type _public_WireType = WireType;
815
+ declare const _public_decode: typeof decode;
816
+ declare const _public_getKernel: typeof getKernel;
817
+ declare const _public_jsKernel: typeof jsKernel;
818
+ declare const _public_loadNativeKernel: typeof loadNativeKernel;
819
+ declare const _public_loadWasmKernel: typeof loadWasmKernel;
820
+ declare const _public_setKernel: typeof setKernel;
821
+ declare namespace _public {
822
+ export { _public_API_VERSION as API_VERSION, _public_ARRAY_MAX as ARRAY_MAX, type _public_ArrayKind as ArrayKind, _public_Cursor as Cursor, type _public_DecodeLimits as DecodeLimits, type _public_DecodeStatus as DecodeStatus, _public_FIXLEN_MAX as FIXLEN_MAX, type _public_FixlenSubtype as FixlenSubtype, type _public_FlushSink as FlushSink, _public_I64_MAX as I64_MAX, _public_I64_MIN as I64_MIN, _public_ID_MAX as ID_MAX, _public_IStream as IStream, type _public_Kernel as Kernel, _public_Long as Long, _public_MAX_DEPTH as MAX_DEPTH, _public_OStream as OStream, _public_SofabError as SofabError, type _public_SofabErrorCode as SofabErrorCode, _public_U64_MAX as U64_MAX, type _public_Visitor as Visitor, type _public_WasmKernelFactory as WasmKernelFactory, type _public_WireType as WireType, _public_decode as decode, _public_getKernel as getKernel, _public_jsKernel as jsKernel, _public_loadNativeKernel as loadNativeKernel, _public_loadWasmKernel as loadWasmKernel, _public_setKernel as setKernel };
823
+ }
824
+
825
+ export { API_VERSION, ARRAY_MAX, ArrayKind, Cursor, type DecodeLimits, DecodeStatus, FIXLEN_MAX, FixlenSubtype, type FlushSink, I64_MAX, I64_MIN, ID_MAX, IStream, type Kernel, Long, MAX_DEPTH, OStream, SofabError, SofabErrorCode, U64_MAX, type Visitor, type WasmKernelFactory, WireType, decode, getKernel, jsKernel, loadNativeKernel, loadWasmKernel, setKernel, _public as sofab };