cesr-ts 0.4.0 → 0.6.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.
Files changed (204) hide show
  1. package/esm/src/adapters/async-iterable.js +8 -0
  2. package/esm/src/adapters/effection.js +6 -0
  3. package/esm/src/annotate/annotator.js +12 -0
  4. package/esm/src/annotate/cli.js +12 -0
  5. package/esm/src/annotate/comments.js +5 -1
  6. package/esm/src/annotate/denot.js +6 -0
  7. package/esm/src/annotate/render.js +15 -3
  8. package/esm/src/core/bytes.js +9 -0
  9. package/esm/src/core/errors.js +3 -0
  10. package/esm/src/core/parser-engine.js +1 -1
  11. package/esm/src/core/parser-frame-parser.js +22 -18
  12. package/esm/src/core/vocabulary.js +47 -0
  13. package/esm/src/index.js +5 -0
  14. package/esm/src/parser/attachment-parser.js +4 -0
  15. package/esm/src/parser/cold-start.js +6 -0
  16. package/esm/src/parser/group-dispatch.js +2 -2
  17. package/esm/src/primitives/aggor.js +374 -30
  18. package/esm/src/primitives/bexter.js +2 -3
  19. package/esm/src/primitives/blinder.js +6 -0
  20. package/esm/src/primitives/byte-like.js +19 -0
  21. package/esm/src/primitives/cigar.js +17 -3
  22. package/esm/src/primitives/cipher.js +91 -6
  23. package/esm/src/primitives/codex.js +39 -6
  24. package/esm/src/primitives/compactor.js +307 -3
  25. package/esm/src/primitives/counter.js +6 -2
  26. package/esm/src/primitives/dater.js +1 -2
  27. package/esm/src/primitives/decimer.js +7 -11
  28. package/esm/src/primitives/decrypter.js +123 -7
  29. package/esm/src/primitives/diger.js +57 -3
  30. package/esm/src/primitives/disclosure.js +300 -0
  31. package/esm/src/primitives/encrypter.js +130 -7
  32. package/esm/src/primitives/hydrate.js +49 -0
  33. package/esm/src/primitives/ilker.js +2 -3
  34. package/esm/src/primitives/indexer.js +21 -4
  35. package/esm/src/primitives/labeler.js +1 -2
  36. package/esm/src/primitives/mapper.js +612 -35
  37. package/esm/src/primitives/matter.js +133 -3
  38. package/esm/src/primitives/mediar.js +6 -0
  39. package/esm/src/primitives/noncer.js +2 -3
  40. package/esm/src/primitives/number.js +1 -2
  41. package/esm/src/primitives/pather.js +102 -5
  42. package/esm/src/primitives/prefixer.js +1 -2
  43. package/esm/src/primitives/primitive.js +14 -0
  44. package/esm/src/primitives/registry.js +2 -0
  45. package/esm/src/primitives/saider.js +80 -3
  46. package/esm/src/primitives/salter.js +96 -2
  47. package/esm/src/primitives/sealed-box.js +41 -0
  48. package/esm/src/primitives/seqner.js +1 -2
  49. package/esm/src/primitives/siger.js +58 -3
  50. package/esm/src/primitives/signer.js +243 -9
  51. package/esm/src/primitives/streamer.js +28 -0
  52. package/esm/src/primitives/structing.js +727 -0
  53. package/esm/src/primitives/structor.js +20 -0
  54. package/esm/src/primitives/tagger.js +1 -2
  55. package/esm/src/primitives/texter.js +1 -2
  56. package/esm/src/primitives/tholder.js +552 -10
  57. package/esm/src/primitives/traitor.js +2 -3
  58. package/esm/src/primitives/verfer.js +50 -6
  59. package/esm/src/primitives/verser.js +16 -6
  60. package/esm/src/router/router-stub.js +1 -0
  61. package/esm/src/serder/native.js +1326 -0
  62. package/esm/src/serder/serder.js +1558 -59
  63. package/esm/src/serder/serdery.js +92 -7
  64. package/esm/src/serder/smell.js +26 -1
  65. package/esm/src/tables/codex-utils.js +2 -0
  66. package/esm/src/tables/counter-codex.js +2 -0
  67. package/esm/src/tables/counter-groups.js +6 -0
  68. package/esm/src/tables/versions.js +4 -0
  69. package/esm/src/version.js +5 -2
  70. package/package.json +5 -2
  71. package/types/src/adapters/async-iterable.d.ts +8 -0
  72. package/types/src/adapters/async-iterable.d.ts.map +1 -1
  73. package/types/src/adapters/effection.d.ts +7 -0
  74. package/types/src/adapters/effection.d.ts.map +1 -1
  75. package/types/src/annotate/annotator.d.ts +12 -0
  76. package/types/src/annotate/annotator.d.ts.map +1 -1
  77. package/types/src/annotate/cli.d.ts +13 -0
  78. package/types/src/annotate/cli.d.ts.map +1 -1
  79. package/types/src/annotate/comments.d.ts +4 -0
  80. package/types/src/annotate/comments.d.ts.map +1 -1
  81. package/types/src/annotate/denot.d.ts +6 -0
  82. package/types/src/annotate/denot.d.ts.map +1 -1
  83. package/types/src/annotate/render.d.ts.map +1 -1
  84. package/types/src/annotate/types.d.ts +2 -0
  85. package/types/src/annotate/types.d.ts.map +1 -1
  86. package/types/src/bench/parser-benchmark.d.ts +3 -0
  87. package/types/src/bench/parser-benchmark.d.ts.map +1 -1
  88. package/types/src/core/bytes.d.ts +9 -0
  89. package/types/src/core/bytes.d.ts.map +1 -1
  90. package/types/src/core/errors.d.ts +3 -0
  91. package/types/src/core/errors.d.ts.map +1 -1
  92. package/types/src/core/parser-engine.d.ts +4 -0
  93. package/types/src/core/parser-engine.d.ts.map +1 -1
  94. package/types/src/core/parser-frame-parser.d.ts.map +1 -1
  95. package/types/src/core/types.d.ts +2 -0
  96. package/types/src/core/types.d.ts.map +1 -1
  97. package/types/src/core/vocabulary.d.ts +48 -0
  98. package/types/src/core/vocabulary.d.ts.map +1 -0
  99. package/types/src/index.d.ts +5 -0
  100. package/types/src/index.d.ts.map +1 -1
  101. package/types/src/parser/attachment-parser.d.ts +4 -0
  102. package/types/src/parser/attachment-parser.d.ts.map +1 -1
  103. package/types/src/parser/cold-start.d.ts +6 -0
  104. package/types/src/parser/cold-start.d.ts.map +1 -1
  105. package/types/src/parser/group-dispatch.d.ts.map +1 -1
  106. package/types/src/primitives/aggor.d.ts +100 -13
  107. package/types/src/primitives/aggor.d.ts.map +1 -1
  108. package/types/src/primitives/bexter.d.ts.map +1 -1
  109. package/types/src/primitives/blinder.d.ts +6 -0
  110. package/types/src/primitives/blinder.d.ts.map +1 -1
  111. package/types/src/primitives/byte-like.d.ts +7 -0
  112. package/types/src/primitives/byte-like.d.ts.map +1 -0
  113. package/types/src/primitives/cigar.d.ts +11 -1
  114. package/types/src/primitives/cigar.d.ts.map +1 -1
  115. package/types/src/primitives/cipher.d.ts +73 -2
  116. package/types/src/primitives/cipher.d.ts.map +1 -1
  117. package/types/src/primitives/codex.d.ts +34 -4
  118. package/types/src/primitives/codex.d.ts.map +1 -1
  119. package/types/src/primitives/compactor.d.ts +94 -2
  120. package/types/src/primitives/compactor.d.ts.map +1 -1
  121. package/types/src/primitives/counter.d.ts.map +1 -1
  122. package/types/src/primitives/dater.d.ts.map +1 -1
  123. package/types/src/primitives/decimer.d.ts.map +1 -1
  124. package/types/src/primitives/decrypter.d.ts +64 -3
  125. package/types/src/primitives/decrypter.d.ts.map +1 -1
  126. package/types/src/primitives/diger.d.ts +16 -0
  127. package/types/src/primitives/diger.d.ts.map +1 -1
  128. package/types/src/primitives/disclosure.d.ts +201 -0
  129. package/types/src/primitives/disclosure.d.ts.map +1 -0
  130. package/types/src/primitives/encrypter.d.ts +66 -4
  131. package/types/src/primitives/encrypter.d.ts.map +1 -1
  132. package/types/src/primitives/hydrate.d.ts +20 -0
  133. package/types/src/primitives/hydrate.d.ts.map +1 -0
  134. package/types/src/primitives/ilker.d.ts.map +1 -1
  135. package/types/src/primitives/indexer.d.ts.map +1 -1
  136. package/types/src/primitives/labeler.d.ts.map +1 -1
  137. package/types/src/primitives/mapper.d.ts +132 -11
  138. package/types/src/primitives/mapper.d.ts.map +1 -1
  139. package/types/src/primitives/matter.d.ts +33 -1
  140. package/types/src/primitives/matter.d.ts.map +1 -1
  141. package/types/src/primitives/mediar.d.ts +6 -0
  142. package/types/src/primitives/mediar.d.ts.map +1 -1
  143. package/types/src/primitives/noncer.d.ts.map +1 -1
  144. package/types/src/primitives/number.d.ts.map +1 -1
  145. package/types/src/primitives/pather.d.ts +21 -0
  146. package/types/src/primitives/pather.d.ts.map +1 -1
  147. package/types/src/primitives/prefixer.d.ts.map +1 -1
  148. package/types/src/primitives/primitive.d.ts +26 -0
  149. package/types/src/primitives/primitive.d.ts.map +1 -1
  150. package/types/src/primitives/registry.d.ts +3 -0
  151. package/types/src/primitives/registry.d.ts.map +1 -1
  152. package/types/src/primitives/saider.d.ts +41 -0
  153. package/types/src/primitives/saider.d.ts.map +1 -1
  154. package/types/src/primitives/salter.d.ts +56 -1
  155. package/types/src/primitives/salter.d.ts.map +1 -1
  156. package/types/src/primitives/sealed-box.d.ts +29 -0
  157. package/types/src/primitives/sealed-box.d.ts.map +1 -0
  158. package/types/src/primitives/seqner.d.ts.map +1 -1
  159. package/types/src/primitives/siger.d.ts +14 -0
  160. package/types/src/primitives/siger.d.ts.map +1 -1
  161. package/types/src/primitives/signer.d.ts +80 -4
  162. package/types/src/primitives/signer.d.ts.map +1 -1
  163. package/types/src/primitives/streamer.d.ts +31 -0
  164. package/types/src/primitives/streamer.d.ts.map +1 -0
  165. package/types/src/primitives/structing.d.ts +4114 -0
  166. package/types/src/primitives/structing.d.ts.map +1 -0
  167. package/types/src/primitives/structor.d.ts +6 -0
  168. package/types/src/primitives/structor.d.ts.map +1 -1
  169. package/types/src/primitives/tagger.d.ts.map +1 -1
  170. package/types/src/primitives/texter.d.ts.map +1 -1
  171. package/types/src/primitives/tholder.d.ts +150 -5
  172. package/types/src/primitives/tholder.d.ts.map +1 -1
  173. package/types/src/primitives/traitor.d.ts.map +1 -1
  174. package/types/src/primitives/unknown.d.ts +1 -0
  175. package/types/src/primitives/unknown.d.ts.map +1 -1
  176. package/types/src/primitives/verfer.d.ts +12 -0
  177. package/types/src/primitives/verfer.d.ts.map +1 -1
  178. package/types/src/primitives/verser.d.ts +14 -3
  179. package/types/src/primitives/verser.d.ts.map +1 -1
  180. package/types/src/router/router-stub.d.ts +3 -0
  181. package/types/src/router/router-stub.d.ts.map +1 -1
  182. package/types/src/serder/native.d.ts +115 -0
  183. package/types/src/serder/native.d.ts.map +1 -0
  184. package/types/src/serder/serder.d.ts +187 -9
  185. package/types/src/serder/serder.d.ts.map +1 -1
  186. package/types/src/serder/serdery.d.ts +42 -0
  187. package/types/src/serder/serdery.d.ts.map +1 -1
  188. package/types/src/serder/smell.d.ts +22 -0
  189. package/types/src/serder/smell.d.ts.map +1 -1
  190. package/types/src/tables/codex-utils.d.ts +3 -0
  191. package/types/src/tables/codex-utils.d.ts.map +1 -1
  192. package/types/src/tables/counter-codex.d.ts +4 -0
  193. package/types/src/tables/counter-codex.d.ts.map +1 -1
  194. package/types/src/tables/counter-groups.d.ts +6 -0
  195. package/types/src/tables/counter-groups.d.ts.map +1 -1
  196. package/types/src/tables/counter.tables.generated.d.ts.map +1 -1
  197. package/types/src/tables/indexer.codex.generated.d.ts.map +1 -1
  198. package/types/src/tables/matter.codex.generated.d.ts.map +1 -1
  199. package/types/src/tables/table-types.d.ts +6 -0
  200. package/types/src/tables/table-types.d.ts.map +1 -1
  201. package/types/src/tables/versions.d.ts +6 -0
  202. package/types/src/tables/versions.d.ts.map +1 -1
  203. package/types/src/version.d.ts +5 -2
  204. package/types/src/version.d.ts.map +1 -1
@@ -1,22 +1,49 @@
1
- import { DeserializeError, SemanticInterpretationError, ShortageError, SyntaxParseError, UnknownCodeError, } from "../core/errors.js";
1
+ import { decode as decodeMsgpack, encode as encodeMsgpack } from "@msgpack/msgpack";
2
+ import { b, codeB2ToB64, codeB64ToB2, t } from "../core/bytes.js";
3
+ import { decodeKeriCbor, encodeKeriCbor } from "../core/cbor.js";
4
+ import { DeserializeError, SemanticInterpretationError, SerializeError, ShortageError, SyntaxParseError, UnknownCodeError, } from "../core/errors.js";
2
5
  import { parseAttachmentDispatch } from "../parser/group-dispatch.js";
6
+ import { CtrDexV2 } from "../tables/counter-codex.js";
3
7
  import { AGGOR_LIST_CODES, AGGOR_MAP_CODES } from "../tables/counter-groups.js";
4
- import { parseCounter } from "./counter.js";
8
+ import { MATTER_SIZES } from "../tables/matter.tables.generated.js";
9
+ import { Bexter } from "./bexter.js";
10
+ import { BEXTER_CODES, DECIMAL_CODES, ESCAPE_CODES, LabelDex, LABELER_CODES, MtrDex } from "./codex.js";
11
+ import { Counter, parseCounter } from "./counter.js";
12
+ import { Decimer } from "./decimer.js";
13
+ import { Diger } from "./diger.js";
5
14
  import { parseLabeler } from "./labeler.js";
6
- import { parseMatter } from "./matter.js";
15
+ import { Matter, parseMatter } from "./matter.js";
16
+ import { Texter } from "./texter.js";
17
+ /** Deep-clone helper so semantic constructors never mutate caller-provided SAD input. */
18
+ function cloneValue(value) {
19
+ if (Array.isArray(value)) {
20
+ return value.map((entry) => cloneValue(entry));
21
+ }
22
+ if (value && typeof value === "object") {
23
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, cloneValue(v)]));
24
+ }
25
+ return value;
26
+ }
7
27
  function tokenSize(token, domain) {
8
28
  return domain === "bny" ? token.fullSizeB2 : token.fullSize;
9
29
  }
10
30
  function isMapGroupCode(code) {
11
31
  return AGGOR_MAP_CODES.has(code);
12
32
  }
33
+ /**
34
+ * Normalize qb2 map bytes into the canonical qb64 text form used internally.
35
+ *
36
+ * This follows the broader native-serder rule that qb64 text is the maintainer
37
+ * source of truth even when the original transport was qb2.
38
+ */
39
+ function canonicalizeNativeRaw(input, version) {
40
+ if (input.length > 0 && input[0] === "-".charCodeAt(0)) {
41
+ return input;
42
+ }
43
+ const counter = parseCounter(input, version, "bny");
44
+ return b(codeB2ToB64(input, counter.fullSize + counter.count * 4));
45
+ }
13
46
  function parseCounterProbe(input, version, domain) {
14
- /**
15
- * Syntax probe only: attempt known major versions without committing semantic
16
- * meaning until dispatch selection succeeds.
17
- */
18
- // Probe both active and known majors so map parsing remains robust when
19
- // streams mix legacy and current counters in nested structures.
20
47
  const attempts = [
21
48
  version,
22
49
  { major: 2, minor: 0 },
@@ -40,7 +67,8 @@ function parseCounterProbe(input, version, domain) {
40
67
  return null;
41
68
  }
42
69
  function parseLabelProbe(input, domain) {
43
- /** Label probe only; non-label parse failures are treated as "not a label". */
70
+ // A failed label parse is not automatically an error. In a native map
71
+ // payload, it may simply mean the next token is a value rather than a key.
44
72
  try {
45
73
  return parseLabeler(input, domain);
46
74
  }
@@ -56,12 +84,8 @@ function parseLabelProbe(input, domain) {
56
84
  }
57
85
  }
58
86
  function parseMapperValueSyntax(input, version, domain) {
59
- /**
60
- * Parse a single map value token artifact.
61
- *
62
- * If the value is itself a map-group counter, recurse into
63
- * `parseMapperBodySyntax` and attach nested syntax in `children`.
64
- */
87
+ // Grouped list/map values always start with a counter token, so probe that
88
+ // shape before falling back to plain Matter parsing.
65
89
  const counter = parseCounterProbe(input, version, domain);
66
90
  if (counter) {
67
91
  const dispatch = parseAttachmentDispatch(input, version, domain);
@@ -85,12 +109,8 @@ function parseMapperValueSyntax(input, version, domain) {
85
109
  };
86
110
  }
87
111
  function parseMapPayloadSyntax(input, version, domain, start, end) {
88
- /**
89
- * Tokenize payload bytes into an ordered syntax stream.
90
- *
91
- * This phase does not pair labels with values; pairing is deferred to
92
- * `interpretMapperBodySyntax`.
93
- */
112
+ // This phase is syntax-only: it tokenizes "label" and "value" artifacts
113
+ // without yet enforcing that every label is paired with a value.
94
114
  const entries = [];
95
115
  let offset = start;
96
116
  while (offset < end) {
@@ -177,9 +197,558 @@ export function parseMapperBodySyntax(input, version, domain) {
177
197
  throw error;
178
198
  }
179
199
  }
200
+ function encodeTag(text) {
201
+ const tags = new Map([
202
+ [1, LabelDex.Tag1],
203
+ [2, LabelDex.Tag2],
204
+ [3, LabelDex.Tag3],
205
+ [4, LabelDex.Tag4],
206
+ [5, LabelDex.Tag5],
207
+ [6, LabelDex.Tag6],
208
+ [7, LabelDex.Tag7],
209
+ [8, LabelDex.Tag8],
210
+ [9, LabelDex.Tag9],
211
+ [10, LabelDex.Tag10],
212
+ [11, LabelDex.Tag11],
213
+ ]);
214
+ const code = tags.get(text.length);
215
+ if (!code) {
216
+ throw new SerializeError(`Unsupported mapper tag length=${text.length}`);
217
+ }
218
+ const pad = code === LabelDex.Tag1 || code === LabelDex.Tag5 || code === LabelDex.Tag9
219
+ ? "_"
220
+ : "";
221
+ return `${code}${pad}${text}`;
222
+ }
223
+ /** Encode base64-safe text through the compact StrB64/Bexter families. */
224
+ function encodeBext(text) {
225
+ const rem = text.length % 4;
226
+ const code = rem === 0
227
+ ? LabelDex.StrB64_L0
228
+ : rem === 1
229
+ ? LabelDex.StrB64_L1
230
+ : LabelDex.StrB64_L2;
231
+ const raw = Bexter.rawify(text);
232
+ return new Bexter({ code, raw }).qb64;
233
+ }
234
+ /** Encode arbitrary UTF-8 text through CESR bytes-label families. */
235
+ function encodeBytes(text) {
236
+ const raw = b(text);
237
+ const rem = raw.length % 3;
238
+ const code = rem === 0
239
+ ? LabelDex.Bytes_L0
240
+ : rem === 1
241
+ ? LabelDex.Bytes_L1
242
+ : LabelDex.Bytes_L2;
243
+ return new Texter({ code, raw }).qb64;
244
+ }
180
245
  /**
181
- * Parse map-style native bodies/counters into labeled semantic fields.
182
- * Compatibility wrapper over syntax parsing + semantic interpretation phases.
246
+ * Choose the narrowest native text encoding that can round-trip `text`.
247
+ *
248
+ * Read this as the mapper-side "smallest truthful token" rule:
249
+ * tag first for tiny safe labels/text, then StrB64, then raw bytes.
250
+ */
251
+ function encodeText(text) {
252
+ if (text.length === 0) {
253
+ return LabelDex.Empty;
254
+ }
255
+ if (/^[A-Za-z0-9_-]{1,11}$/.test(text)) {
256
+ return encodeTag(text);
257
+ }
258
+ if (/^[A-Za-z0-9_-]+$/.test(text)) {
259
+ return encodeBext(text);
260
+ }
261
+ return encodeBytes(text);
262
+ }
263
+ function serializeValue(value, strict) {
264
+ // This is the recursive mapper exhale ladder. Ordering matters because a
265
+ // string might be either already-qualified CESR text or ordinary text that
266
+ // needs label-style encoding.
267
+ if (value === null) {
268
+ return new Matter({ code: MtrDex.Null, raw: new Uint8Array() }).qb64;
269
+ }
270
+ if (typeof value === "boolean") {
271
+ return new Matter({
272
+ code: value ? MtrDex.Yes : MtrDex.No,
273
+ raw: new Uint8Array(),
274
+ }).qb64;
275
+ }
276
+ if (typeof value === "number") {
277
+ return new Decimer({ decimal: value }).qb64;
278
+ }
279
+ if (typeof value === "bigint") {
280
+ return new Decimer({ dns: value.toString() }).qb64;
281
+ }
282
+ if (typeof value === "string") {
283
+ try {
284
+ const primitive = parseMatter(b(value), "txt");
285
+ if (primitive.qb64.length === value.length) {
286
+ const escape = ESCAPE_CODES.has(primitive.code)
287
+ ? new Matter({ code: MtrDex.Escape, raw: new Uint8Array() }).qb64
288
+ : "";
289
+ return `${escape}${primitive.qb64}`;
290
+ }
291
+ }
292
+ catch {
293
+ // fall through to text encoding
294
+ }
295
+ return encodeText(value);
296
+ }
297
+ if (value instanceof Uint8Array) {
298
+ return new Texter({
299
+ code: value.length % 3 === 0
300
+ ? LabelDex.Bytes_L0
301
+ : value.length % 3 === 1
302
+ ? LabelDex.Bytes_L1
303
+ : LabelDex.Bytes_L2,
304
+ raw: value,
305
+ }).qb64;
306
+ }
307
+ if (Array.isArray(value)) {
308
+ const payload = value.map((entry) => serializeValue(entry, strict)).join("");
309
+ const code = payload.length / 4 < 64 ** 2
310
+ ? CtrDexV2.GenericListGroup
311
+ : CtrDexV2.BigGenericListGroup;
312
+ return `${new Counter({ code, count: payload.length / 4 }).qb64}${payload}`;
313
+ }
314
+ if (value && typeof value === "object") {
315
+ const payload = Object.entries(value).map(([label, entry]) => `${strict ? encodeText(label) : encodeText(label)}${serializeValue(entry, strict)}`).join("");
316
+ const code = payload.length / 4 < 64 ** 2
317
+ ? CtrDexV2.GenericMapGroup
318
+ : CtrDexV2.BigGenericMapGroup;
319
+ return `${new Counter({ code, count: payload.length / 4 }).qb64}${payload}`;
320
+ }
321
+ throw new SerializeError(`Nonserializable mapper value=${String(value)}`);
322
+ }
323
+ function deserializeValue(raw, strict) {
324
+ // This is the inverse of `serializeValue()`: grouped containers first, then
325
+ // leaf primitives. `nextOffset` tells the caller exactly how much wire space
326
+ // was consumed by this one semantic value.
327
+ const counter = parseCounterProbe(raw, { major: 2, minor: 0 }, "txt");
328
+ if (counter) {
329
+ if (AGGOR_LIST_CODES.has(counter.code)) {
330
+ const total = counter.fullSize + counter.count * 4;
331
+ const payload = raw.slice(counter.fullSize, total);
332
+ const list = [];
333
+ let offset = 0;
334
+ while (offset < payload.length) {
335
+ const parsed = deserializeValue(payload.slice(offset), strict);
336
+ list.push(parsed.value);
337
+ offset += parsed.nextOffset;
338
+ }
339
+ return { value: list, nextOffset: total };
340
+ }
341
+ if (AGGOR_MAP_CODES.has(counter.code)) {
342
+ const total = counter.fullSize + counter.count * 4;
343
+ const payload = raw.slice(counter.fullSize, total);
344
+ const map = {};
345
+ let offset = 0;
346
+ while (offset < payload.length) {
347
+ const labeler = parseLabeler(payload.slice(offset), "txt");
348
+ const label = strict ? labeler.label : labeler.text;
349
+ offset += labeler.fullSize;
350
+ const parsed = deserializeValue(payload.slice(offset), strict);
351
+ map[label] = parsed.value;
352
+ offset += parsed.nextOffset;
353
+ }
354
+ return { value: map, nextOffset: total };
355
+ }
356
+ }
357
+ const matter = parseMatter(raw, "txt");
358
+ let offset = matter.fullSize;
359
+ if (matter.code === MtrDex.Escape) {
360
+ const escaped = parseMatter(raw.slice(offset), "txt");
361
+ return { value: escaped.qb64, nextOffset: offset + escaped.fullSize };
362
+ }
363
+ if (matter.code === MtrDex.Null) {
364
+ return { value: null, nextOffset: offset };
365
+ }
366
+ if (matter.code === MtrDex.Yes) {
367
+ return { value: true, nextOffset: offset };
368
+ }
369
+ if (matter.code === MtrDex.No) {
370
+ return { value: false, nextOffset: offset };
371
+ }
372
+ if (DECIMAL_CODES.has(matter.code)) {
373
+ return { value: new Decimer(matter).decimal, nextOffset: offset };
374
+ }
375
+ if (LABELER_CODES.has(matter.code) || BEXTER_CODES.has(matter.code)) {
376
+ return {
377
+ value: parseLabeler(b(matter.qb64), "txt").text,
378
+ nextOffset: offset,
379
+ };
380
+ }
381
+ return { value: matter.qb64, nextOffset: offset };
382
+ }
383
+ function buildFieldProjection(value, strict) {
384
+ // `fields` is rebuilt from semantic data so the projection stays coherent
385
+ // even when the mapper originated from `Mapper.fromSad(...)` rather than a
386
+ // syntax-parse path.
387
+ return Object.entries(value).map(([label, entry]) => {
388
+ const serialized = serializeValue(entry, strict);
389
+ const primitive = serialized.startsWith("-")
390
+ ? parseCounter(b(serialized), { major: 2, minor: 0 }, "txt")
391
+ : parseMatter(b(serialized), "txt");
392
+ const children = entry && typeof entry === "object" && !Array.isArray(entry)
393
+ ? buildFieldProjection(entry, strict)
394
+ : undefined;
395
+ return {
396
+ label,
397
+ primitive,
398
+ isCounter: AGGOR_LIST_CODES.has(primitive.code)
399
+ || AGGOR_MAP_CODES.has(primitive.code),
400
+ children,
401
+ };
402
+ });
403
+ }
404
+ function verifyMapRoundTrip(mapper) {
405
+ // Verification is explicit on purpose: rebuild a mapper from the semantic
406
+ // view and require byte-for-byte raw equality. If this fails, our semantic
407
+ // projection lost information or our serializer is dishonest.
408
+ const actual = mapper.kind === "CESR"
409
+ ? Mapper.fromSad(mapper.mad, {
410
+ strict: mapper.strict,
411
+ saidive: mapper.saidive,
412
+ saids: mapper.saids,
413
+ kind: mapper.kind,
414
+ makify: false,
415
+ verify: false,
416
+ })
417
+ : Mapper.fromSad(mapper.mad, {
418
+ strict: mapper.strict,
419
+ saidive: mapper.saidive,
420
+ saids: mapper.saids,
421
+ kind: mapper.kind,
422
+ makify: false,
423
+ verify: false,
424
+ });
425
+ if (t(actual.raw) !== t(mapper.raw)) {
426
+ throw new DeserializeError("Invalid mapper round trip against raw.");
427
+ }
428
+ }
429
+ /**
430
+ * Semantic CESR-native map primitive.
431
+ *
432
+ * KERIpy substance: `Mapper` is the reusable native field-map engine beneath
433
+ * compactable ACDC sections. It owns both the semantic map view (`mad`) and the
434
+ * exact enclosed native group serialization (`raw`, `qb64`, `qb2`).
435
+ *
436
+ * Cohesive mental model:
437
+ * 1. `parseMapperBodySyntax()` tokenizes raw native bytes into syntax artifacts.
438
+ * 2. `interpretMapperBodySyntax()` pairs those artifacts into readable fields.
439
+ * 3. `Mapper` owns the full semantic lifecycle: build from `mad`, or inhale
440
+ * from raw, then expose both semantic (`mad`) and wire (`raw`, `qb64`,
441
+ * `qb2`) views.
442
+ *
443
+ * Put differently:
444
+ * syntax helpers are for inspection and parser diagnostics;
445
+ * `Mapper` is for truth.
446
+ *
447
+ * Compare carefully:
448
+ * - use `Mapper` when the disclosed thing is a general map
449
+ * - do not use it for fixed-field blind/bound/media tuples, which have their
450
+ * own explicit schema and workflow layer
451
+ */
452
+ export class Mapper {
453
+ /**
454
+ * Create one semantic mapper from either a field map (`mad`) or raw bytes.
455
+ *
456
+ * Constructor modes:
457
+ * - semantic mode: caller provides `mad`, optional `makify`, optional
458
+ * `saidive`, and the constructor serializes deterministically
459
+ * - native inhale mode: caller provides `raw`/`qb64`/`qb64b`/`qb2` with
460
+ * `kind="CESR"` and the constructor decodes grouped native content
461
+ * - non-native inhale mode: caller provides raw JSON/CBOR/MGPK with
462
+ * `kind!="CESR"` and the constructor decodes via the corresponding codec
463
+ */
464
+ constructor(init = {}) {
465
+ Object.defineProperty(this, "code", {
466
+ enumerable: true,
467
+ configurable: true,
468
+ writable: true,
469
+ value: void 0
470
+ });
471
+ Object.defineProperty(this, "count", {
472
+ enumerable: true,
473
+ configurable: true,
474
+ writable: true,
475
+ value: void 0
476
+ });
477
+ Object.defineProperty(this, "fullSize", {
478
+ enumerable: true,
479
+ configurable: true,
480
+ writable: true,
481
+ value: void 0
482
+ });
483
+ Object.defineProperty(this, "fullSizeB2", {
484
+ enumerable: true,
485
+ configurable: true,
486
+ writable: true,
487
+ value: void 0
488
+ });
489
+ Object.defineProperty(this, "totalSize", {
490
+ enumerable: true,
491
+ configurable: true,
492
+ writable: true,
493
+ value: void 0
494
+ });
495
+ Object.defineProperty(this, "totalSizeB2", {
496
+ enumerable: true,
497
+ configurable: true,
498
+ writable: true,
499
+ value: void 0
500
+ });
501
+ Object.defineProperty(this, "fields", {
502
+ enumerable: true,
503
+ configurable: true,
504
+ writable: true,
505
+ value: void 0
506
+ });
507
+ Object.defineProperty(this, "raw", {
508
+ enumerable: true,
509
+ configurable: true,
510
+ writable: true,
511
+ value: void 0
512
+ });
513
+ Object.defineProperty(this, "strict", {
514
+ enumerable: true,
515
+ configurable: true,
516
+ writable: true,
517
+ value: void 0
518
+ });
519
+ Object.defineProperty(this, "saids", {
520
+ enumerable: true,
521
+ configurable: true,
522
+ writable: true,
523
+ value: void 0
524
+ });
525
+ Object.defineProperty(this, "saidive", {
526
+ enumerable: true,
527
+ configurable: true,
528
+ writable: true,
529
+ value: void 0
530
+ });
531
+ Object.defineProperty(this, "kind", {
532
+ enumerable: true,
533
+ configurable: true,
534
+ writable: true,
535
+ value: void 0
536
+ });
537
+ Object.defineProperty(this, "_mad", {
538
+ enumerable: true,
539
+ configurable: true,
540
+ writable: true,
541
+ value: void 0
542
+ });
543
+ this.strict = init.strict ?? true;
544
+ this.saids = { ...(init.saids ?? { d: "E" }) };
545
+ this.saidive = init.saidive ?? false;
546
+ this.kind = init.kind ?? "CESR";
547
+ if (init.mad || (!init.raw && !init.qb64 && !init.qb64b && !init.qb2)) {
548
+ const mad = cloneValue(init.mad ?? {});
549
+ if (init.makify && this.saidive) {
550
+ // Mapper saidification is intentionally top-level only. Recursive leaf
551
+ // discovery belongs to `Compactor`; `Mapper` just makes one map
552
+ // self-addressing when asked.
553
+ const dummy = cloneValue(mad);
554
+ for (const [label, code] of Object.entries(this.saids)) {
555
+ if (!(label in dummy))
556
+ continue;
557
+ try {
558
+ const valueCode = parseMatter(b(String(dummy[label])), "txt").code;
559
+ if (valueCode) {
560
+ this.saids[label] = valueCode;
561
+ }
562
+ }
563
+ catch {
564
+ // keep configured default
565
+ }
566
+ const sizage = MATTER_SIZES.get(this.saids[label]);
567
+ if (!sizage?.fs) {
568
+ throw new SerializeError(`Unsupported mapper SAID code=${this.saids[label]}`);
569
+ }
570
+ dummy[label] = "#".repeat(sizage.fs);
571
+ }
572
+ const dummiedRaw = this.kind === "CESR"
573
+ ? Mapper.serializeCesrMap(dummy, this.strict, true, this.saids)
574
+ : Mapper.serializeNonNativeMap(dummy, this.kind);
575
+ for (const [label, code] of Object.entries(this.saids)) {
576
+ if (!(label in mad))
577
+ continue;
578
+ mad[label] = new Diger({ code, raw: Diger.digest(dummiedRaw, code) }).qb64;
579
+ }
580
+ }
581
+ if (this.kind === "CESR") {
582
+ // In semantic->native mode we already trust `mad`, so we derive the
583
+ // readable `fields` projection directly from semantic data instead of
584
+ // re-parsing the raw bytes we just emitted.
585
+ this.raw = Mapper.serializeCesrMap(mad, this.strict);
586
+ const counter = parseCounter(this.raw, init.version ?? { major: 2, minor: 0 }, "txt");
587
+ this.code = counter.code;
588
+ this.count = counter.count;
589
+ this.fullSize = counter.fullSize;
590
+ this.fullSizeB2 = counter.fullSizeB2;
591
+ this.totalSize = counter.fullSize + counter.count * 4;
592
+ this.totalSizeB2 = Math.ceil(this.totalSize * 3 / 4);
593
+ this.fields = buildFieldProjection(mad, this.strict);
594
+ }
595
+ else {
596
+ this.raw = Mapper.serializeNonNativeMap(mad, this.kind);
597
+ this.code = CtrDexV2.GenericMapGroup;
598
+ this.count = 0;
599
+ this.fullSize = 0;
600
+ this.fullSizeB2 = 0;
601
+ this.totalSize = this.raw.length;
602
+ this.totalSizeB2 = this.raw.length;
603
+ this.fields = buildFieldProjection(mad, this.strict);
604
+ }
605
+ this._mad = mad;
606
+ if (init.verify ?? true) {
607
+ verifyMapRoundTrip(this);
608
+ }
609
+ return;
610
+ }
611
+ if (this.kind !== "CESR") {
612
+ // Non-native support matters because compact/disclose logic for ACDC has
613
+ // to reason about JSON/CBOR/MGPK section forms too, not just native wire
614
+ // forms.
615
+ this.raw = init.raw ?? init.qb64b ?? b(init.qb64 ?? "");
616
+ this._mad = Mapper.deserializeNonNativeMap(this.raw, this.kind);
617
+ this.code = CtrDexV2.GenericMapGroup;
618
+ this.count = 0;
619
+ this.fullSize = 0;
620
+ this.fullSizeB2 = 0;
621
+ this.totalSize = this.raw.length;
622
+ this.totalSizeB2 = this.raw.length;
623
+ this.fields = buildFieldProjection(this._mad, this.strict);
624
+ if (init.verify ?? true) {
625
+ verifyMapRoundTrip(this);
626
+ }
627
+ return;
628
+ }
629
+ const version = init.version ?? { major: 2, minor: 0 };
630
+ const raw = init.qb2
631
+ ? canonicalizeNativeRaw(init.qb2, version)
632
+ : init.qb64
633
+ ? b(init.qb64)
634
+ : init.qb64b
635
+ ? init.qb64b
636
+ : init.raw;
637
+ // In native inhale mode we *do* walk the wire payload because the whole
638
+ // point is to reconstruct semantic values from the compact map encoding.
639
+ const counter = parseCounter(raw, version, "txt");
640
+ if (!isMapGroupCode(counter.code)) {
641
+ throw new UnknownCodeError(`Expected map-body/group counter, got ${counter.code}`);
642
+ }
643
+ const totalSize = counter.fullSize + counter.count * 4;
644
+ const payload = raw.slice(counter.fullSize, totalSize);
645
+ const mad = {};
646
+ let offset = 0;
647
+ while (offset < payload.length) {
648
+ const labeler = parseLabeler(payload.slice(offset), "txt");
649
+ const label = this.strict ? labeler.label : labeler.text;
650
+ offset += labeler.fullSize;
651
+ const parsed = deserializeValue(payload.slice(offset), this.strict);
652
+ mad[label] = parsed.value;
653
+ offset += parsed.nextOffset;
654
+ }
655
+ this.raw = raw.slice(0, totalSize);
656
+ this.code = counter.code;
657
+ this.count = counter.count;
658
+ this.fullSize = counter.fullSize;
659
+ this.fullSizeB2 = counter.fullSizeB2;
660
+ this.totalSize = totalSize;
661
+ this.totalSizeB2 = Math.ceil(totalSize * 3 / 4);
662
+ this.fields = buildFieldProjection(mad, this.strict);
663
+ this._mad = mad;
664
+ if (init.verify ?? true) {
665
+ verifyMapRoundTrip(this);
666
+ }
667
+ }
668
+ static fromSad(mad, options = {}) {
669
+ return new Mapper({ ...options, mad });
670
+ }
671
+ /**
672
+ * Serialize one semantic map into an enclosed CESR native map-group.
673
+ *
674
+ * The result includes the leading group counter. Nested maps/lists are
675
+ * recursively enclosed in their own grouped payloads.
676
+ */
677
+ static serializeCesrMap(mad, strict = true, dummy = false, saids = {}) {
678
+ const payload = Object.entries(mad).map(([label, value]) => {
679
+ const rendered = dummy && label in saids
680
+ ? (() => {
681
+ const sizage = MATTER_SIZES.get(saids[label]);
682
+ if (!sizage?.fs) {
683
+ throw new SerializeError(`Unsupported mapper SAID code=${saids[label]}`);
684
+ }
685
+ return "#".repeat(sizage.fs);
686
+ })()
687
+ : serializeValue(value, strict);
688
+ return `${encodeText(label)}${rendered}`;
689
+ }).join("");
690
+ const code = payload.length / 4 < 64 ** 2
691
+ ? CtrDexV2.GenericMapGroup
692
+ : CtrDexV2.BigGenericMapGroup;
693
+ return b(`${new Counter({ code, count: payload.length / 4 }).qb64}${payload}`);
694
+ }
695
+ static serializeNonNativeMap(mad, kind) {
696
+ if (kind === "JSON") {
697
+ return b(JSON.stringify(mad));
698
+ }
699
+ if (kind === "CBOR") {
700
+ return encodeKeriCbor(mad);
701
+ }
702
+ if (kind === "MGPK") {
703
+ return encodeMsgpack(mad);
704
+ }
705
+ throw new SerializeError(`Unsupported mapper serialization kind=${kind}`);
706
+ }
707
+ static deserializeNonNativeMap(raw, kind) {
708
+ if (kind === "JSON") {
709
+ return JSON.parse(t(raw));
710
+ }
711
+ if (kind === "CBOR") {
712
+ return decodeKeriCbor(raw);
713
+ }
714
+ if (kind === "MGPK") {
715
+ return decodeMsgpack(raw);
716
+ }
717
+ throw new DeserializeError(`Unsupported mapper deserialization kind=${kind}`);
718
+ }
719
+ get mad() {
720
+ // Clone on read so callers can inspect or tweak the returned object
721
+ // without mutating mapper internals behind the class's back.
722
+ return cloneValue(this._mad);
723
+ }
724
+ get qb64b() {
725
+ return this.raw.slice();
726
+ }
727
+ get qb64() {
728
+ return t(this.raw);
729
+ }
730
+ get qb2() {
731
+ if (this.kind !== "CESR") {
732
+ throw new SerializeError(`Binary domain undefined for non-native kind=${this.kind}`);
733
+ }
734
+ return codeB64ToB2(this.qb64);
735
+ }
736
+ get said() {
737
+ const label = Object.keys(this.saids)[0];
738
+ const value = this._mad[label];
739
+ return typeof value === "string" ? value : null;
740
+ }
741
+ get size() {
742
+ return this.raw.length;
743
+ }
744
+ }
745
+ /**
746
+ * Parse map-style native bodies/counters into a semantic `Mapper`.
747
+ *
748
+ * Compatibility wrapper: existing parser-oriented tests can keep calling this
749
+ * helper, while newer code can instantiate `Mapper` directly from `mad` or
750
+ * raw. This wrapper preserves the older parser-friendly feel while the class
751
+ * itself is the new semantic authority.
183
752
  */
184
753
  export function parseMapperBody(input, version, domain) {
185
754
  let syntax;
@@ -192,9 +761,8 @@ export function parseMapperBody(input, version, domain) {
192
761
  }
193
762
  throw error;
194
763
  }
195
- let fields;
196
764
  try {
197
- fields = interpretMapperBodySyntax(syntax);
765
+ interpretMapperBodySyntax(syntax);
198
766
  }
199
767
  catch (error) {
200
768
  if (error instanceof SemanticInterpretationError) {
@@ -202,15 +770,24 @@ export function parseMapperBody(input, version, domain) {
202
770
  }
203
771
  throw error;
204
772
  }
205
- return {
206
- code: syntax.code,
207
- count: syntax.count,
208
- fullSize: syntax.fullSize,
209
- fullSizeB2: syntax.fullSizeB2,
210
- totalSize: syntax.totalSize,
211
- totalSizeB2: syntax.totalSizeB2,
212
- fields,
213
- };
773
+ const mapper = new Mapper({
774
+ mad: {},
775
+ strict: true,
776
+ kind: "CESR",
777
+ verify: false,
778
+ });
779
+ mapper.code = syntax.code;
780
+ mapper.count = syntax.count;
781
+ mapper.fullSize = syntax.fullSize;
782
+ mapper.fullSizeB2 = syntax.fullSizeB2;
783
+ mapper.totalSize = syntax.totalSize;
784
+ mapper.totalSizeB2 = syntax.totalSizeB2;
785
+ mapper.fields = interpretMapperBodySyntax(syntax);
786
+ mapper.raw = domain === "txt"
787
+ ? input.slice(0, syntax.totalSize)
788
+ : canonicalizeNativeRaw(input, version).slice(0, syntax.totalSize);
789
+ mapper._mad = {};
790
+ return mapper;
214
791
  }
215
792
  /** True for counter codes that represent map/list-native aggregate primitives. */
216
793
  export function isMapperCounterCode(code) {