cesr-ts 0.2.3 → 0.3.1

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.
Files changed (91) hide show
  1. package/README.md +36 -3
  2. package/esm/src/adapters/async-iterable.js +9 -9
  3. package/esm/src/annotate/annotator.js +18 -10
  4. package/esm/src/annotate/comments.js +3 -6
  5. package/esm/src/annotate/render.js +123 -24
  6. package/esm/src/bench/parser-benchmark.js +134 -0
  7. package/esm/src/core/bytes.js +6 -0
  8. package/esm/src/core/errors.js +24 -0
  9. package/esm/src/core/parser-attachment-collector.js +154 -0
  10. package/esm/src/core/parser-constants.js +74 -0
  11. package/esm/src/core/parser-deferred-frames.js +73 -0
  12. package/esm/src/core/parser-engine.js +128 -505
  13. package/esm/src/core/parser-frame-parser.js +643 -0
  14. package/esm/src/core/parser-policy.js +137 -0
  15. package/esm/src/core/parser-stream-state.js +62 -0
  16. package/esm/src/core/recovery-diagnostics.js +25 -0
  17. package/esm/src/index.js +4 -0
  18. package/esm/src/parser/attachment-fallback-policy.js +142 -0
  19. package/esm/src/parser/group-dispatch.js +547 -233
  20. package/esm/src/primitives/counter.js +4 -5
  21. package/esm/src/primitives/mapper.js +126 -45
  22. package/esm/src/primitives/matter.js +1 -1
  23. package/esm/src/router/router-stub.js +6 -6
  24. package/esm/src/serder/serder.js +44 -7
  25. package/esm/src/serder/smell.js +2 -1
  26. package/esm/src/tables/counter-version-registry.js +201 -0
  27. package/esm/src/version.js +2 -2
  28. package/package.json +3 -1
  29. package/types/_dnt.polyfills.d.ts +5 -0
  30. package/types/_dnt.polyfills.d.ts.map +1 -1
  31. package/types/src/adapters/async-iterable.d.ts +2 -2
  32. package/types/src/adapters/async-iterable.d.ts.map +1 -1
  33. package/types/src/adapters/effection.d.ts +2 -2
  34. package/types/src/adapters/effection.d.ts.map +1 -1
  35. package/types/src/annotate/annotator.d.ts.map +1 -1
  36. package/types/src/annotate/comments.d.ts.map +1 -1
  37. package/types/src/annotate/render.d.ts +8 -2
  38. package/types/src/annotate/render.d.ts.map +1 -1
  39. package/types/src/annotate/types.d.ts +2 -2
  40. package/types/src/annotate/types.d.ts.map +1 -1
  41. package/types/src/bench/parser-benchmark.d.ts +70 -0
  42. package/types/src/bench/parser-benchmark.d.ts.map +1 -0
  43. package/types/src/core/bytes.d.ts +6 -0
  44. package/types/src/core/bytes.d.ts.map +1 -1
  45. package/types/src/core/errors.d.ts +10 -0
  46. package/types/src/core/errors.d.ts.map +1 -1
  47. package/types/src/core/parser-attachment-collector.d.ts +51 -0
  48. package/types/src/core/parser-attachment-collector.d.ts.map +1 -0
  49. package/types/src/core/parser-constants.d.ts +30 -0
  50. package/types/src/core/parser-constants.d.ts.map +1 -0
  51. package/types/src/core/parser-deferred-frames.d.ts +38 -0
  52. package/types/src/core/parser-deferred-frames.d.ts.map +1 -0
  53. package/types/src/core/parser-engine.d.ts +53 -44
  54. package/types/src/core/parser-engine.d.ts.map +1 -1
  55. package/types/src/core/parser-frame-parser.d.ts +89 -0
  56. package/types/src/core/parser-frame-parser.d.ts.map +1 -0
  57. package/types/src/core/parser-policy.d.ts +27 -0
  58. package/types/src/core/parser-policy.d.ts.map +1 -0
  59. package/types/src/core/parser-stream-state.d.ts +30 -0
  60. package/types/src/core/parser-stream-state.d.ts.map +1 -0
  61. package/types/src/core/recovery-diagnostics.d.ts +59 -0
  62. package/types/src/core/recovery-diagnostics.d.ts.map +1 -0
  63. package/types/src/core/types.d.ts +61 -7
  64. package/types/src/core/types.d.ts.map +1 -1
  65. package/types/src/index.d.ts +4 -0
  66. package/types/src/index.d.ts.map +1 -1
  67. package/types/src/parser/attachment-fallback-policy.d.ts +78 -0
  68. package/types/src/parser/attachment-fallback-policy.d.ts.map +1 -0
  69. package/types/src/parser/group-dispatch.d.ts +85 -15
  70. package/types/src/parser/group-dispatch.d.ts.map +1 -1
  71. package/types/src/primitives/aggor.d.ts +2 -2
  72. package/types/src/primitives/aggor.d.ts.map +1 -1
  73. package/types/src/primitives/blinder.d.ts +2 -2
  74. package/types/src/primitives/blinder.d.ts.map +1 -1
  75. package/types/src/primitives/counter.d.ts.map +1 -1
  76. package/types/src/primitives/mapper.d.ts +44 -1
  77. package/types/src/primitives/mapper.d.ts.map +1 -1
  78. package/types/src/primitives/mediar.d.ts +2 -2
  79. package/types/src/primitives/mediar.d.ts.map +1 -1
  80. package/types/src/primitives/sealer.d.ts +2 -2
  81. package/types/src/primitives/sealer.d.ts.map +1 -1
  82. package/types/src/router/router-stub.d.ts +5 -5
  83. package/types/src/router/router-stub.d.ts.map +1 -1
  84. package/types/src/serder/serder.d.ts +2 -2
  85. package/types/src/serder/serder.d.ts.map +1 -1
  86. package/types/src/serder/serdery.d.ts +2 -2
  87. package/types/src/serder/serdery.d.ts.map +1 -1
  88. package/types/src/serder/smell.d.ts.map +1 -1
  89. package/types/src/tables/counter-version-registry.d.ts +90 -0
  90. package/types/src/tables/counter-version-registry.d.ts.map +1 -0
  91. package/types/src/version.d.ts +2 -2
@@ -0,0 +1,643 @@
1
+ import { parseAttachmentGroup } from "../parser/attachment-parser.js";
2
+ import { sniff } from "../parser/cold-start.js";
3
+ import { parseCounter } from "../primitives/counter.js";
4
+ import { parseIlker } from "../primitives/ilker.js";
5
+ import { isLabelerCode, parseLabeler } from "../primitives/labeler.js";
6
+ import { parseMatter } from "../primitives/matter.js";
7
+ import { interpretMapperBodySyntax, parseMapperBodySyntax, } from "../primitives/mapper.js";
8
+ import { parseVerser } from "../primitives/verser.js";
9
+ import { reapSerder } from "../serder/serdery.js";
10
+ import { Kinds, Protocols } from "../tables/versions.js";
11
+ import { b64ToInt, intToB64 } from "./bytes.js";
12
+ import { ColdStartError, DeserializeError, SemanticInterpretationError, ShortageError, SyntaxParseError, UnknownCodeError, } from "./errors.js";
13
+ import { BODY_WITH_ATTACHMENT_GROUP_NAMES, DEFAULT_VERSION, FRAME_START_GROUP_NAMES, GENERIC_GROUP_NAMES, GENUS_VERSION_CODE, isAttachmentDomain, isFrameBoundaryCounter, MAP_BODY_CODES, NATIVE_BODY_GROUP_NAMES, NON_NATIVE_BODY_GROUP_NAMES, quadletUnit, tokenSize, } from "./parser-constants.js";
14
+ /**
15
+ * Parses frame starts and bounded frame payload structures.
16
+ *
17
+ * Responsibilities:
18
+ * - resolve frame-start forms (`msg`, native/body groups, wrappers)
19
+ * - preserve version-scope semantics for stream and nested payloads
20
+ * - emit enclosed GenericGroup siblings through callback for deferred handling
21
+ */
22
+ export class FrameParser {
23
+ constructor(options) {
24
+ Object.defineProperty(this, "frameBoundaryPolicy", {
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ value: void 0
29
+ });
30
+ Object.defineProperty(this, "attachmentVersionFallbackPolicy", {
31
+ enumerable: true,
32
+ configurable: true,
33
+ writable: true,
34
+ value: void 0
35
+ });
36
+ Object.defineProperty(this, "onEnclosedFrames", {
37
+ enumerable: true,
38
+ configurable: true,
39
+ writable: true,
40
+ value: void 0
41
+ });
42
+ Object.defineProperty(this, "recoveryDiagnosticObserver", {
43
+ enumerable: true,
44
+ configurable: true,
45
+ writable: true,
46
+ value: void 0
47
+ });
48
+ this.frameBoundaryPolicy = options.frameBoundaryPolicy;
49
+ this.attachmentVersionFallbackPolicy =
50
+ options.attachmentVersionFallbackPolicy;
51
+ this.onEnclosedFrames = options.onEnclosedFrames;
52
+ this.recoveryDiagnosticObserver = options.recoveryDiagnosticObserver;
53
+ }
54
+ /** Probe whether the next token is a top-level frame boundary counter. */
55
+ isFrameBoundaryAhead(input, version, cold) {
56
+ const peek = this.tryParseCounter(input, version, cold);
57
+ return peek !== null && isFrameBoundaryCounter(peek);
58
+ }
59
+ /**
60
+ * Parse one frame start from the current head.
61
+ * Does not parse trailing top-level attachments.
62
+ */
63
+ parseFrame(input, inheritedVersion = DEFAULT_VERSION) {
64
+ const syntax = this.parseFrameStartSyntax(input, inheritedVersion);
65
+ return this.interpretFrameStartSyntax(input, syntax);
66
+ }
67
+ /**
68
+ * Parse frame-start tokens into a syntax artifact.
69
+ *
70
+ * Non-goal: this is not a global two-pass parser rewrite. It only separates
71
+ * the highest-coupling frame-start syntax extraction from semantic dispatch.
72
+ */
73
+ parseFrameStartSyntax(input, inheritedVersion) {
74
+ let offset = 0;
75
+ let activeVersion = inheritedVersion;
76
+ let cold = sniff(input.slice(offset));
77
+ while (cold === "ano") {
78
+ offset += 1;
79
+ if (input.length <= offset) {
80
+ throw new ShortageError(offset + 1, input.length);
81
+ }
82
+ cold = sniff(input.slice(offset));
83
+ }
84
+ if (cold === "txt" || cold === "bny") {
85
+ const peek = parseCounter(input.slice(offset), activeVersion, cold);
86
+ if (peek.code === GENUS_VERSION_CODE) {
87
+ offset += tokenSize(peek, cold);
88
+ activeVersion = this.decodeVersionCounter(peek);
89
+ if (input.length <= offset) {
90
+ throw new ShortageError(offset + 1, input.length);
91
+ }
92
+ cold = sniff(input.slice(offset));
93
+ }
94
+ }
95
+ if (cold === "msg") {
96
+ return {
97
+ kind: "message",
98
+ offset,
99
+ cold,
100
+ streamVersion: activeVersion,
101
+ };
102
+ }
103
+ if (!isAttachmentDomain(cold)) {
104
+ throw new ColdStartError(`Expected message or CESR body group at frame start but got ${cold}`);
105
+ }
106
+ const { counter, version: frameVersion, } = this.resolveFrameStartCounter(input.slice(offset), activeVersion, cold);
107
+ const headerSize = tokenSize(counter, cold);
108
+ const unit = quadletUnit(cold);
109
+ if (BODY_WITH_ATTACHMENT_GROUP_NAMES.has(counter.name)) {
110
+ return {
111
+ kind: "bodyWithAttachmentGroup",
112
+ offset,
113
+ cold,
114
+ streamVersion: frameVersion,
115
+ counter,
116
+ frameVersion,
117
+ headerSize,
118
+ unit,
119
+ };
120
+ }
121
+ if (NON_NATIVE_BODY_GROUP_NAMES.has(counter.name)) {
122
+ return {
123
+ kind: "nonNativeBodyGroup",
124
+ offset,
125
+ cold,
126
+ streamVersion: frameVersion,
127
+ counter,
128
+ frameVersion,
129
+ headerSize,
130
+ unit,
131
+ };
132
+ }
133
+ if (NATIVE_BODY_GROUP_NAMES.has(counter.name)) {
134
+ return {
135
+ kind: "nativeBodyGroup",
136
+ offset,
137
+ cold,
138
+ streamVersion: frameVersion,
139
+ counter,
140
+ frameVersion,
141
+ headerSize,
142
+ unit,
143
+ };
144
+ }
145
+ if (GENERIC_GROUP_NAMES.has(counter.name)) {
146
+ return {
147
+ kind: "genericGroup",
148
+ offset,
149
+ cold,
150
+ streamVersion: frameVersion,
151
+ counter,
152
+ frameVersion,
153
+ headerSize,
154
+ unit,
155
+ };
156
+ }
157
+ throw new ColdStartError(`Unsupported body-group counter at frame start: ${counter.code}`);
158
+ }
159
+ /** Interpret frame-start syntax artifacts into semantic parse behavior. */
160
+ interpretFrameStartSyntax(input, syntax) {
161
+ const requireGroupMetadata = () => {
162
+ const counter = syntax.counter;
163
+ const frameVersion = syntax.frameVersion;
164
+ const headerSize = syntax.headerSize;
165
+ const unit = syntax.unit;
166
+ if (!counter || !frameVersion || !headerSize || !unit) {
167
+ throw new SemanticInterpretationError("Frame-start syntax artifact missing required group metadata");
168
+ }
169
+ return { counter, frameVersion, headerSize, unit };
170
+ };
171
+ switch (syntax.kind) {
172
+ case "message": {
173
+ const { serder, consumed } = reapSerder(input.slice(syntax.offset));
174
+ return {
175
+ frame: { body: serder, attachments: [] },
176
+ consumed: syntax.offset + consumed,
177
+ version: serder.gvrsn ?? serder.pvrsn,
178
+ streamVersion: serder.gvrsn ?? serder.pvrsn,
179
+ };
180
+ }
181
+ case "bodyWithAttachmentGroup": {
182
+ const { counter, frameVersion, headerSize, unit } = requireGroupMetadata();
183
+ return this.parseBodyWithAttachmentGroup(input, syntax.offset, headerSize, counter.count, unit, frameVersion, syntax.streamVersion);
184
+ }
185
+ case "nonNativeBodyGroup": {
186
+ if (syntax.cold !== "txt" && syntax.cold !== "bny") {
187
+ throw new SemanticInterpretationError(`Expected attachment domain for non-native body but got ${syntax.cold}`);
188
+ }
189
+ const { counter, frameVersion, headerSize, unit } = requireGroupMetadata();
190
+ return this.parseNonNativeBodyGroup(input, syntax.offset, headerSize, counter.count, unit, syntax.cold, frameVersion, syntax.streamVersion);
191
+ }
192
+ case "nativeBodyGroup": {
193
+ if (syntax.cold !== "txt" && syntax.cold !== "bny") {
194
+ throw new SemanticInterpretationError(`Expected attachment domain for native body but got ${syntax.cold}`);
195
+ }
196
+ const { counter, frameVersion, headerSize, unit } = requireGroupMetadata();
197
+ return this.parseNativeBodyGroup(input, syntax.offset, headerSize, counter.count, unit, syntax.cold, frameVersion, counter.code, syntax.streamVersion);
198
+ }
199
+ case "genericGroup": {
200
+ const { counter, frameVersion, headerSize, unit } = requireGroupMetadata();
201
+ return this.parseGenericGroup(input, syntax.offset, headerSize, counter.count, unit, frameVersion, syntax.streamVersion);
202
+ }
203
+ }
204
+ }
205
+ /** Parse nested `BodyWithAttachmentGroup` payload as one complete enclosed frame. */
206
+ parseBodyWithAttachmentGroup(input, offset, headerSize, count, unit, version, streamVersion) {
207
+ const payloadSize = count * unit;
208
+ const total = headerSize + payloadSize;
209
+ if (input.length < offset + total) {
210
+ throw new ShortageError(offset + total, input.length);
211
+ }
212
+ const payload = input.slice(offset + headerSize, offset + total);
213
+ const nested = this.parseCompleteFrame(payload, version, false);
214
+ if (nested.consumed !== payload.length) {
215
+ throw new ColdStartError("BodyWithAttachmentGroup payload did not parse to a complete frame");
216
+ }
217
+ return {
218
+ frame: nested.frame,
219
+ consumed: offset + total,
220
+ version: nested.frame.body.gvrsn ?? nested.frame.body.pvrsn, // fallback to pvrsn is a v1 fallback compatibility heuristic
221
+ streamVersion,
222
+ };
223
+ }
224
+ /**
225
+ * Parse `NonNativeBodyGroup` payload, preserving opaque-body fallback behavior
226
+ * when Serder decode is not possible.
227
+ * Returns only a body object (Serder) with no attachments.
228
+ */
229
+ parseNonNativeBodyGroup(input, offset, headerSize, count, unit, cold, version, streamVersion) {
230
+ const matter = parseMatter(input.slice(offset + headerSize), cold);
231
+ const bodySize = tokenSize(matter, cold);
232
+ const payloadSize = count * unit;
233
+ if (payloadSize !== bodySize) {
234
+ throw new ColdStartError(`NonNativeBodyGroup payload size mismatch: expected=${payloadSize} actual=${bodySize}`);
235
+ }
236
+ try {
237
+ const { serder } = reapSerder(matter.raw);
238
+ return {
239
+ frame: { body: serder, attachments: [] },
240
+ consumed: offset + headerSize + bodySize,
241
+ version: serder.gvrsn ?? serder.pvrsn,
242
+ streamVersion,
243
+ };
244
+ }
245
+ catch (_error) {
246
+ return {
247
+ frame: {
248
+ body: {
249
+ raw: matter.raw,
250
+ ked: null,
251
+ proto: Protocols.keri,
252
+ kind: Kinds.cesr,
253
+ size: matter.raw.length,
254
+ pvrsn: version,
255
+ gvrsn: version,
256
+ ilk: null,
257
+ said: null,
258
+ },
259
+ attachments: [],
260
+ },
261
+ consumed: offset + headerSize + bodySize,
262
+ version,
263
+ streamVersion,
264
+ };
265
+ }
266
+ }
267
+ /** Parse native fixed/map body group and extract metadata/native fields. */
268
+ parseNativeBodyGroup(input, offset, headerSize, count, unit, cold, version, bodyCode, streamVersion) {
269
+ const payloadSize = count * unit;
270
+ const total = headerSize + payloadSize;
271
+ if (input.length < offset + total) {
272
+ throw new ShortageError(offset + total, input.length);
273
+ }
274
+ const raw = input.slice(offset, offset + total);
275
+ const syntax = this.parseNativeBodySyntax(raw, cold, version);
276
+ const metadata = this.interpretNativeMetadataSyntax(syntax.metadata, version);
277
+ const fields = this.interpretNativeFieldSyntax(syntax.fields);
278
+ return {
279
+ frame: {
280
+ body: {
281
+ raw,
282
+ ked: null,
283
+ proto: metadata.proto,
284
+ kind: Kinds.cesr,
285
+ size: raw.length,
286
+ pvrsn: metadata.pvrsn,
287
+ gvrsn: metadata.gvrsn,
288
+ ilk: metadata.ilk,
289
+ said: metadata.said,
290
+ native: {
291
+ bodyCode,
292
+ fields,
293
+ },
294
+ },
295
+ attachments: [],
296
+ },
297
+ consumed: offset + total,
298
+ version,
299
+ streamVersion,
300
+ };
301
+ }
302
+ /**
303
+ * Parse bounded `GenericGroup` payload into enclosed frames.
304
+ * Returns first frame and reports remaining siblings via callback.
305
+ */
306
+ parseGenericGroup(input, offset, headerSize, count, unit, version, streamVersion) {
307
+ const payloadSize = count * unit;
308
+ const total = headerSize + payloadSize;
309
+ if (input.length < offset + total) {
310
+ throw new ShortageError(offset + total, input.length);
311
+ }
312
+ const payload = input.slice(offset + headerSize, offset + total);
313
+ const frames = this.parseFrameSequence(payload, version);
314
+ if (frames.length === 0) {
315
+ throw new ColdStartError("GenericGroup payload contained no enclosed frames");
316
+ }
317
+ const [first, ...rest] = frames;
318
+ if (rest.length > 0) {
319
+ this.onEnclosedFrames(rest);
320
+ }
321
+ return {
322
+ frame: first,
323
+ consumed: offset + total,
324
+ version: first.body.gvrsn ?? first.body.pvrsn,
325
+ streamVersion,
326
+ };
327
+ }
328
+ /** Parse all enclosed frames inside a bounded payload slice, in order. */
329
+ parseFrameSequence(input, inheritedVersion) {
330
+ const out = [];
331
+ let offset = 0;
332
+ let activeVersion = inheritedVersion;
333
+ while (offset < input.length) {
334
+ while (offset < input.length && sniff(input.slice(offset)) === "ano") {
335
+ offset += 1;
336
+ }
337
+ if (offset >= input.length) {
338
+ break;
339
+ }
340
+ const base = this.parseFrame(input.slice(offset), activeVersion);
341
+ offset += base.consumed;
342
+ const frameVersion = base.version;
343
+ const streamVersion = base.streamVersion;
344
+ const attachments = [...base.frame.attachments];
345
+ // greedily parse attachment groups until offset == input.length
346
+ while (offset < input.length) {
347
+ const nextCold = sniff(input.slice(offset));
348
+ if (nextCold === "ano") {
349
+ offset += 1;
350
+ continue;
351
+ }
352
+ if (nextCold === "msg") {
353
+ break;
354
+ }
355
+ if (!isAttachmentDomain(nextCold)) {
356
+ throw new ColdStartError(`Unsupported attachment cold code ${nextCold}`);
357
+ }
358
+ if (this.isFrameBoundaryAhead(input.slice(offset), streamVersion, nextCold)) {
359
+ break;
360
+ }
361
+ const { group, consumed } = parseAttachmentGroup(input.slice(offset), frameVersion, nextCold, {
362
+ versionFallbackPolicy: this.attachmentVersionFallbackPolicy,
363
+ onRecoveryDiagnostic: this.recoveryDiagnosticObserver,
364
+ });
365
+ attachments.push(group);
366
+ offset += consumed;
367
+ }
368
+ out.push({
369
+ body: base.frame.body,
370
+ attachments,
371
+ });
372
+ activeVersion = base.streamVersion;
373
+ }
374
+ return out;
375
+ }
376
+ /** Parse one complete frame from a bounded slice, including trailing attachments. */
377
+ parseCompleteFrame(input, inheritedVersion = DEFAULT_VERSION, stopAtNextMessage = true) {
378
+ const base = this.parseFrame(input, inheritedVersion);
379
+ const version = base.version;
380
+ const attachments = [...base.frame.attachments];
381
+ let offset = base.consumed;
382
+ while (offset < input.length) {
383
+ const nextCold = sniff(input.slice(offset));
384
+ if (nextCold === "ano") {
385
+ offset += 1;
386
+ continue;
387
+ }
388
+ if (nextCold === "msg") {
389
+ if (stopAtNextMessage) {
390
+ break;
391
+ }
392
+ throw new ColdStartError("Enclosed frame payload encountered unexpected nested message start");
393
+ }
394
+ if (!isAttachmentDomain(nextCold)) {
395
+ throw new ColdStartError(`Unsupported attachment cold code ${nextCold}`);
396
+ }
397
+ const { group, consumed } = parseAttachmentGroup(input.slice(offset), version, nextCold, {
398
+ versionFallbackPolicy: this.attachmentVersionFallbackPolicy,
399
+ onRecoveryDiagnostic: this.recoveryDiagnosticObserver,
400
+ });
401
+ attachments.push(group);
402
+ offset += consumed;
403
+ if (this.frameBoundaryPolicy.shouldStopAfterAttachmentGroupCollection()) {
404
+ break;
405
+ }
406
+ }
407
+ return {
408
+ frame: { body: base.frame.body, attachments },
409
+ consumed: offset,
410
+ };
411
+ }
412
+ /** Decode a genus-version counter token into major/minor Versionage values. */
413
+ decodeVersionCounter(counter) {
414
+ const triplet = counter.qb64.length >= 3
415
+ ? counter.qb64.slice(-3)
416
+ : intToB64(counter.count, 3);
417
+ const majorRaw = b64ToInt(triplet[0] ?? "A");
418
+ const minorRaw = b64ToInt(triplet[1] ?? "A");
419
+ const major = majorRaw === 1 ? 1 : 2;
420
+ return {
421
+ major,
422
+ minor: minorRaw,
423
+ };
424
+ }
425
+ /** Build native-body syntax artifacts used by semantic interpretation phase. */
426
+ parseNativeBodySyntax(raw, cold, fallbackVersion) {
427
+ try {
428
+ const bodyCounter = parseCounter(raw, fallbackVersion, cold);
429
+ return {
430
+ metadata: this.parseNativeMetadataSyntax(raw, cold, fallbackVersion, bodyCounter.code),
431
+ fields: this.parseNativeFieldSyntax(raw, cold, fallbackVersion, bodyCounter.code),
432
+ };
433
+ }
434
+ catch (error) {
435
+ if (error instanceof ShortageError ||
436
+ error instanceof UnknownCodeError ||
437
+ error instanceof DeserializeError) {
438
+ throw new SyntaxParseError(`Native body syntax parse failed: ${error.message}`, error);
439
+ }
440
+ throw error;
441
+ }
442
+ }
443
+ /** Parse advisory native metadata tokens without semantic projection. */
444
+ parseNativeMetadataSyntax(raw, cold, fallbackVersion, bodyCode) {
445
+ const out = {};
446
+ let offset = 0;
447
+ try {
448
+ const bodyCounter = parseCounter(raw, fallbackVersion, cold);
449
+ offset += tokenSize(bodyCounter, cold);
450
+ if (MAP_BODY_CODES.has(bodyCode)) {
451
+ offset = this.skipNativeLabelers(raw, offset, cold);
452
+ }
453
+ out.verser = parseVerser(raw.slice(offset), cold);
454
+ offset += tokenSize(out.verser, cold);
455
+ if (MAP_BODY_CODES.has(bodyCode)) {
456
+ offset = this.skipNativeLabelers(raw, offset, cold);
457
+ }
458
+ out.ilker = parseIlker(raw.slice(offset), cold);
459
+ offset += tokenSize(out.ilker, cold);
460
+ if (MAP_BODY_CODES.has(bodyCode)) {
461
+ offset = this.skipNativeLabelers(raw, offset, cold);
462
+ }
463
+ out.saider = parseMatter(raw.slice(offset), cold);
464
+ }
465
+ catch (_error) {
466
+ // metadata parse remains advisory; do not fail native-body parse when absent.
467
+ }
468
+ return out;
469
+ }
470
+ /** Interpret advisory metadata syntax into projected body metadata fields. */
471
+ interpretNativeMetadataSyntax(syntax, fallbackVersion) {
472
+ return {
473
+ proto: syntax.verser?.proto ?? Protocols.keri,
474
+ pvrsn: syntax.verser?.pvrsn ?? fallbackVersion,
475
+ gvrsn: syntax.verser?.gvrsn ?? fallbackVersion,
476
+ ilk: syntax.ilker?.ilk ?? null,
477
+ said: syntax.saider?.code.startsWith("E") ? syntax.saider.qb64 : null,
478
+ };
479
+ }
480
+ /** Parse native field tokens without resolving label/value pairing semantics. */
481
+ parseNativeFieldSyntax(raw, cold, fallbackVersion, bodyCode) {
482
+ if (MAP_BODY_CODES.has(bodyCode)) {
483
+ return {
484
+ kind: "map",
485
+ mapper: parseMapperBodySyntax(raw, fallbackVersion, cold),
486
+ };
487
+ }
488
+ const bodyCounter = parseCounter(raw, fallbackVersion, cold);
489
+ const total = tokenSize(bodyCounter, cold);
490
+ const payloadBytes = bodyCounter.count * quadletUnit(cold);
491
+ const start = total;
492
+ const end = start + payloadBytes;
493
+ const entries = [];
494
+ let offset = start;
495
+ while (offset < end) {
496
+ const at = raw.slice(offset, end);
497
+ const ctr = this.tryParseCounter(at, fallbackVersion, cold);
498
+ if (ctr) {
499
+ const size = tokenSize(ctr, cold);
500
+ entries.push({
501
+ kind: "value",
502
+ code: ctr.code,
503
+ qb64: ctr.qb64,
504
+ });
505
+ offset += size;
506
+ continue;
507
+ }
508
+ const token = parseMatter(at, cold);
509
+ const size = tokenSize(token, cold);
510
+ offset += size;
511
+ if (isLabelerCode(token.code)) {
512
+ entries.push({
513
+ kind: "label",
514
+ label: parseLabeler(at, cold).label,
515
+ code: token.code,
516
+ qb64: token.qb64,
517
+ });
518
+ continue;
519
+ }
520
+ entries.push({
521
+ kind: "value",
522
+ code: token.code,
523
+ qb64: token.qb64,
524
+ });
525
+ }
526
+ if (offset !== end) {
527
+ throw new ShortageError(end, offset);
528
+ }
529
+ return { kind: "fixed", entries };
530
+ }
531
+ /** Interpret native field syntax into semantic fields with label/value pairing. */
532
+ interpretNativeFieldSyntax(syntax) {
533
+ if (syntax.kind === "map") {
534
+ try {
535
+ return interpretMapperBodySyntax(syntax.mapper).map((field) => ({
536
+ label: field.label,
537
+ code: field.code,
538
+ qb64: field.qb64,
539
+ }));
540
+ }
541
+ catch (error) {
542
+ if (error instanceof SemanticInterpretationError) {
543
+ throw new SemanticInterpretationError(`Native body semantic interpretation failed: ${error.message}`, error);
544
+ }
545
+ throw error;
546
+ }
547
+ }
548
+ const out = [];
549
+ let pendingLabel = null;
550
+ for (const entry of syntax.entries) {
551
+ if (entry.kind === "label") {
552
+ pendingLabel = entry.label;
553
+ continue;
554
+ }
555
+ out.push({
556
+ label: pendingLabel,
557
+ code: entry.code,
558
+ qb64: entry.qb64,
559
+ });
560
+ pendingLabel = null;
561
+ }
562
+ if (pendingLabel !== null) {
563
+ throw new SemanticInterpretationError("Dangling native map label without value");
564
+ }
565
+ return out;
566
+ }
567
+ /** Skip consecutive labeler tokens in native map-body parsing context. */
568
+ skipNativeLabelers(raw, offset, cold) {
569
+ let out = offset;
570
+ while (out < raw.length) {
571
+ const item = parseMatter(raw.slice(out), cold);
572
+ if (!isLabelerCode(item.code)) {
573
+ break;
574
+ }
575
+ out += tokenSize(item, cold);
576
+ }
577
+ return out;
578
+ }
579
+ /** Probe parseCounter with ordered major-version fallback. */
580
+ tryParseCounter(input, version, cold) {
581
+ const attempts = [
582
+ version,
583
+ { major: 2, minor: 0 },
584
+ { major: 1, minor: 0 },
585
+ ];
586
+ for (const attempt of attempts) {
587
+ try {
588
+ return parseCounter(input, attempt, cold);
589
+ }
590
+ catch (error) {
591
+ if (error instanceof ShortageError) {
592
+ throw error;
593
+ }
594
+ if (error instanceof UnknownCodeError ||
595
+ error instanceof DeserializeError) {
596
+ continue;
597
+ }
598
+ throw error;
599
+ }
600
+ }
601
+ return null;
602
+ }
603
+ /** Resolve a valid frame-start counter, preferring current stream major version. */
604
+ resolveFrameStartCounter(input, preferredVersion, cold) {
605
+ const alternate = preferredVersion.major >= 2
606
+ ? { major: 1, minor: 0 }
607
+ : { major: 2, minor: 0 };
608
+ const attempts = [preferredVersion, alternate];
609
+ let firstParsed = null;
610
+ let firstError = null;
611
+ for (const attempt of attempts) {
612
+ try {
613
+ const counter = parseCounter(input, attempt, cold);
614
+ if (!firstParsed) {
615
+ firstParsed = { counter, version: attempt };
616
+ }
617
+ if (FRAME_START_GROUP_NAMES.has(counter.name)) {
618
+ return { counter, version: attempt };
619
+ }
620
+ }
621
+ catch (error) {
622
+ if (error instanceof ShortageError) {
623
+ throw error;
624
+ }
625
+ if (error instanceof UnknownCodeError ||
626
+ error instanceof DeserializeError) {
627
+ if (!firstError) {
628
+ firstError = error;
629
+ }
630
+ continue;
631
+ }
632
+ throw error;
633
+ }
634
+ }
635
+ if (firstParsed) {
636
+ return firstParsed;
637
+ }
638
+ if (firstError) {
639
+ throw firstError;
640
+ }
641
+ throw new ColdStartError("Unable to resolve frame-start counter");
642
+ }
643
+ }