@cbortech/cbor 0.27.0 → 0.27.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 (48) hide show
  1. package/README.ja.md +73 -0
  2. package/README.md +75 -0
  3. package/dist/ast/CborAppSeqResult.d.ts +37 -4
  4. package/dist/ast/CborArray.d.ts +2 -2
  5. package/dist/ast/CborByteString.d.ts +2 -2
  6. package/dist/ast/CborEmbeddedCBOR.d.ts +1 -1
  7. package/dist/ast/CborFloat.d.ts +1 -1
  8. package/dist/ast/CborIndefiniteByteString.d.ts +1 -1
  9. package/dist/ast/CborIndefiniteTextString.d.ts +1 -1
  10. package/dist/ast/CborItem.d.ts +297 -7
  11. package/dist/ast/CborMap.d.ts +2 -2
  12. package/dist/ast/CborNint.d.ts +1 -1
  13. package/dist/ast/CborTag.d.ts +4 -4
  14. package/dist/ast/CborTextString.d.ts +2 -2
  15. package/dist/ast/CborUint.d.ts +1 -1
  16. package/dist/ast/index.cjs +1 -1
  17. package/dist/ast/index.js +1 -1
  18. package/dist/cddl/index.cjs +1 -1
  19. package/dist/cddl/index.js +1 -1
  20. package/dist/cdn/index.cjs +1 -1
  21. package/dist/cdn/index.js +1 -1
  22. package/dist/cdn/serialize-utils.d.ts +30 -2
  23. package/dist/extensions/cri.d.ts +2 -2
  24. package/dist/extensions/dt.d.ts +4 -4
  25. package/dist/extensions/ip.d.ts +3 -3
  26. package/dist/extensions/types.d.ts +40 -1
  27. package/dist/index.cjs +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +3 -3
  30. package/dist/{mapEntries-CvLdiN0h.js → mapEntries-CCLaJSaJ.js} +1114 -933
  31. package/dist/mapEntries-CCLaJSaJ.js.map +1 -0
  32. package/dist/mapEntries-CZZJScaj.cjs +13 -0
  33. package/dist/mapEntries-CZZJScaj.cjs.map +1 -0
  34. package/dist/{schema-CNfrVRYp.js → schema-DN9inJny.js} +3 -3
  35. package/dist/{schema-CNfrVRYp.js.map → schema-DN9inJny.js.map} +1 -1
  36. package/dist/{schema-iXpYtKQl.cjs → schema-zsg5yCPK.cjs} +2 -2
  37. package/dist/{schema-iXpYtKQl.cjs.map → schema-zsg5yCPK.cjs.map} +1 -1
  38. package/dist/{serialize-utils-CjTqQivB.cjs → serialize-utils-DhlW61ZX.cjs} +6 -6
  39. package/dist/serialize-utils-DhlW61ZX.cjs.map +1 -0
  40. package/dist/{serialize-utils-BuIZPaUc.js → serialize-utils-h-CVB9rg.js} +57 -47
  41. package/dist/{serialize-utils-BuIZPaUc.js.map → serialize-utils-h-CVB9rg.js.map} +1 -1
  42. package/dist/types.d.ts +273 -0
  43. package/dist/utils/hexfloat.d.ts +10 -2
  44. package/package.json +7 -8
  45. package/dist/mapEntries-C1f7G0AM.cjs +0 -13
  46. package/dist/mapEntries-C1f7G0AM.cjs.map +0 -1
  47. package/dist/mapEntries-CvLdiN0h.js.map +0 -1
  48. package/dist/serialize-utils-CjTqQivB.cjs.map +0 -1
package/README.ja.md CHANGED
@@ -708,6 +708,79 @@ console.log(text);
708
708
  // DT'2026-05-06T00:00:00Z'
709
709
  ```
710
710
 
711
+ ### 項目ごとのオプション上書き
712
+
713
+ `toJS()` の `itemOptions` は、各ノードが変換される直前にすべてのノードに対して呼ばれるため、
714
+ ドキュメントの一部だけを他と異なる方法で変換できます。そのノードとその子孫に適用する
715
+ オプションを部分的なオプションオブジェクトとして返すか、変更しない場合は `undefined` を
716
+ 返します。`ctx.path` は配列インデックスとマップキーからなる列でノードを識別し、
717
+ ルートでは空になります。
718
+
719
+ 同じノードに対して複数回呼ばれることがあります —— `reviver` も併用している場合、配列や
720
+ object-mode の map は各子要素を少なくとも2回変換します。1回は、先行する兄弟要素自身の
721
+ `reviver` 呼び出しから見える値を構築するため、もう1回は実際に保持される値を計算するため
722
+ です。このコールバックは `node`/`ctx` のみに依存する純粋関数として書き、呼ばれる回数には
723
+ 依存しないようにしてください。
724
+
725
+ ```ts
726
+ import { CBOR } from '@cbortech/cbor';
727
+
728
+ const item = CBOR.fromCDN(
729
+ `{"date1": DT'2026-08-23T00:00:00Z', "date2": DT'2026-08-23T00:00:00Z'}`
730
+ );
731
+
732
+ const value = item.toJS({
733
+ stripTags: true,
734
+ itemOptions: (_node, ctx) =>
735
+ ctx.path.length === 1 && ctx.path[0] === 'date1'
736
+ ? { extensions: [CBOR.dt_as_Date] }
737
+ : undefined,
738
+ });
739
+
740
+ console.log(value);
741
+ // { date1: Date(...), date2: 1787443200 }
742
+ ```
743
+
744
+ `ToJSOptions` の `extensions` は `itemOptions` なしでも単体で機能し、ツリー全体を
745
+ 再解釈できます —— デフォルトの `dt` extension でパースされた値も、`toJS()` に
746
+ `extensions` を渡すことで `dt_as_Date`(またはその逆)に変換し直せます。
747
+
748
+ `ctx.options` には、そのノードにおいて現在有効なオプション —— ルートのオプションに
749
+ 祖先の `itemOptions` がすでに返した上書きをマージしたもの —— が渡されます。これにより、
750
+ コールバックはオプションを丸ごと置き換えるのではなく、現在の値を土台にして組み立てる
751
+ ことができます。
752
+
753
+ ```ts
754
+ itemOptions: (_node, ctx) => ({
755
+ extensions: [...(ctx.options.extensions ?? []), CBOR.dt_as_Date],
756
+ });
757
+ ```
758
+
759
+ `toCDN()` にも同じ `itemOptions` オプションがあり、ドキュメントの一部だけを
760
+ 他と異なる方法でフォーマットできます。
761
+
762
+ ```ts
763
+ import { CBOR } from '@cbortech/cbor';
764
+
765
+ const item = CBOR.fromCDN('{"raw": 255, "count": 255}');
766
+
767
+ const text = item.toCDN({
768
+ itemOptions: (_node, ctx) =>
769
+ ctx.path.length === 1 && ctx.path[0] === 'raw'
770
+ ? { intFormat: 'hex' }
771
+ : undefined,
772
+ });
773
+
774
+ console.log(text);
775
+ // {"raw":0xff,"count":255}
776
+ ```
777
+
778
+ `toCDN()` には `reviver` に相当するものがないため、それを理由に複数回呼ばれる
779
+ ことはありません —— ただし `toCDN()` 自体のレイアウト判定(`inlineLeafContainers`
780
+ の1行収まるかの判定、あるいはタグ/app-sequence 値自身の複数語判定)が実際の
781
+ 出力より前にエントリを再描画してその答えを得ることがあるため、その場合には
782
+ やはり複数回呼ばれ得ます。こちらも純粋な関数として書いてください。
783
+
711
784
  ## 文字列連結と不定長文字列
712
785
 
713
786
  draft-ietf-cbor-edn-literals-27(§3.5 / §3.6)の app-extension
package/README.md CHANGED
@@ -719,6 +719,81 @@ console.log(text);
719
719
  // DT'2026-05-06T00:00:00Z'
720
720
  ```
721
721
 
722
+ ### Per-item option overrides
723
+
724
+ `itemOptions` on `toJS()` is called for every node before it is converted, so
725
+ one part of a document can be converted differently from the rest. Return a
726
+ partial options object to override options for that node and its descendants,
727
+ or `undefined` to leave them unchanged. `ctx.path` identifies the node as a
728
+ sequence of array indices and map keys, empty at the root.
729
+
730
+ It can be called more than once for the same node — when a `reviver` is also
731
+ present, arrays and object-mode maps convert each child at least twice: once
732
+ to build a value visible to an earlier sibling's own `reviver` call, and once
733
+ more for the value that's actually kept. Write it as a pure function of
734
+ `node`/`ctx`, not relying on how many times it runs.
735
+
736
+ ```ts
737
+ import { CBOR } from '@cbortech/cbor';
738
+
739
+ const item = CBOR.fromCDN(
740
+ `{"date1": DT'2026-08-23T00:00:00Z', "date2": DT'2026-08-23T00:00:00Z'}`
741
+ );
742
+
743
+ const value = item.toJS({
744
+ stripTags: true,
745
+ itemOptions: (_node, ctx) =>
746
+ ctx.path.length === 1 && ctx.path[0] === 'date1'
747
+ ? { extensions: [CBOR.dt_as_Date] }
748
+ : undefined,
749
+ });
750
+
751
+ console.log(value);
752
+ // { date1: Date(...), date2: 1787443200 }
753
+ ```
754
+
755
+ `extensions` on `ToJSOptions` also works on its own, without `itemOptions`, to
756
+ reinterpret an entire tree: a value parsed with the default `dt` extension can
757
+ still be converted with `dt_as_Date` (or vice versa) by passing `extensions`
758
+ directly to `toJS()`.
759
+
760
+ `ctx.options` carries the options already in effect for the node — the root
761
+ options merged with whatever an ancestor's `itemOptions` already returned —
762
+ so a callback can build on the current value of an option instead of
763
+ overriding it outright:
764
+
765
+ ```ts
766
+ itemOptions: (_node, ctx) => ({
767
+ extensions: [...(ctx.options.extensions ?? []), CBOR.dt_as_Date],
768
+ });
769
+ ```
770
+
771
+ `toCDN()` takes the same `itemOptions` option, formatting one part of a
772
+ document differently from the rest:
773
+
774
+ ```ts
775
+ import { CBOR } from '@cbortech/cbor';
776
+
777
+ const item = CBOR.fromCDN('{"raw": 255, "count": 255}');
778
+
779
+ const text = item.toCDN({
780
+ itemOptions: (_node, ctx) =>
781
+ ctx.path.length === 1 && ctx.path[0] === 'raw'
782
+ ? { intFormat: 'hex' }
783
+ : undefined,
784
+ });
785
+
786
+ console.log(text);
787
+ // {"raw":0xff,"count":255}
788
+ ```
789
+
790
+ There is no `reviver` for `toCDN()`, so it can't be called more than once for
791
+ that reason — but it can still be called more than once for a node that's
792
+ also a `toCDN()` layout decision (`inlineLeafContainers`'s one-line collapse
793
+ check, or a tag/app-sequence value's own multi-word check re-render an entry
794
+ purely to answer that question before the real render). Write it as a pure
795
+ function here too.
796
+
722
797
  ## String Concatenation and Indefinite-Length Strings
723
798
 
724
799
  The `t1` / `b1` / `ilbs` / `ilts` app-extensions from
@@ -1,5 +1,5 @@
1
1
  import { ToCDNOptions, ToCBOROptions, ToJSOptions } from '../types';
2
- import { CborItem } from './CborItem';
2
+ import { CborItem, Occurrence } from './CborItem';
3
3
  import { CborWriter } from '../cbor/encode';
4
4
  /**
5
5
  * Wraps a resolved app-sequence result and preserves the original EDN source
@@ -13,6 +13,39 @@ import { CborWriter } from '../cbor/encode';
13
13
  * lines also delegates to the inner item, since it cannot be re-emitted
14
14
  * without breaking the single-line guarantee.
15
15
  *
16
+ * When `toCDN()`'s `itemOptions` is configured (see `needsCdnItemDispatch`),
17
+ * the verbatim `ednSource` fast path alone can't be trusted: it was produced
18
+ * without ever visiting `this.inner`'s own descendants, so an override
19
+ * targeting one of them (e.g. one chunk of an `ilbs<<...>>`) would otherwise
20
+ * never even be offered the chance to apply. But merely *having* `itemOptions`
21
+ * configured must not by itself change the output — many callers set it
22
+ * without it ever matching anything in this particular subtree, and
23
+ * `itemOptions` returning `undefined` everywhere is documented to mean "no
24
+ * change" (see `CdnItemContext`). So this renders through the normal
25
+ * recursive path while tracking, via `CDN_OVERRIDE_TRACKER` (an out-of-band
26
+ * symbol key, *not* a wrapped `itemOptions` — wrapping it would make
27
+ * `ctx.options.itemOptions` a different function for every descendant here
28
+ * than everywhere else in the tree, observable even to a pure callback),
29
+ * whether any call in the subtree actually returned an override, and only
30
+ * swaps in that freshly rendered text when one did; otherwise the exact
31
+ * preserved `ednSource` is still returned.
32
+ *
33
+ * Each `_toCDN()` call installs its *own fresh* tracker box before
34
+ * recursing, rather than reusing whatever tracker it was handed — reusing
35
+ * one directly would let an override applied by some unrelated part of the
36
+ * tree that merely happens to share the same ambient `options` object (a
37
+ * later sibling reached through the same `itemOptions`, say) falsely mark
38
+ * *this* wrapper's own verbatim source as stale. But a genuinely *nested*
39
+ * app-sequence (e.g. a custom `wrap<<ilbs<<h'61'>>>>` extension wrapping an
40
+ * `ilbs<<...>>` result) must still have its own outer verbatim source
41
+ * abandoned when the inner wrapper's own tracked render found something to
42
+ * apply — an inner override changes what the outer source would need to
43
+ * embed too. So once this call's own local tracker comes back `applied`,
44
+ * that fact is additionally bubbled up to whichever ancestor tracker (if
45
+ * any) this call itself received via `options` — the exact wrapper this
46
+ * call is nested inside, never a sibling reached only through shared
47
+ * top-level `options`.
48
+ *
16
49
  * CBOR encoding and JS conversion always delegate to the inner item so the
17
50
  * wrapper is fully transparent for those operations.
18
51
  */
@@ -42,8 +75,8 @@ export declare class CborAppSeqResult extends CborItem {
42
75
  * already produced a `\n` in that string, which the caller's own
43
76
  * `s.includes('\n')` check picks up independently either way).
44
77
  */
45
- _isMultiWordText(options: ToCDNOptions | undefined, strict?: boolean): boolean;
78
+ _isMultiWordText(options: ToCDNOptions | undefined, strict?: boolean, path?: readonly unknown[]): boolean;
46
79
  _encodeTo(writer: CborWriter, options?: ToCBOROptions): void;
47
- _toCDN(options: ToCDNOptions | undefined, depth: number): string;
48
- _toJS(options?: ToJSOptions): unknown;
80
+ _toCDN(options: ToCDNOptions | undefined, depth: number, path?: readonly unknown[]): string;
81
+ _toJS(options?: ToJSOptions, path?: readonly unknown[], occurrence?: Occurrence): unknown;
49
82
  }
@@ -12,7 +12,7 @@ export declare class CborArray extends CborItem {
12
12
  });
13
13
  get _containsCdnContainer(): boolean;
14
14
  _encodeTo(writer: CborWriter, options?: ToCBOROptions): void;
15
- _toCDN(options: ToCDNOptions | undefined, depth: number): string;
15
+ _toCDN(options: ToCDNOptions | undefined, depth: number, path?: readonly unknown[]): string;
16
16
  _toHexDump(depth: number, options?: ToCDNOptions): AnnotatedLine[];
17
- _toJS(options?: ToJSOptions): unknown;
17
+ _toJS(options?: ToJSOptions, path?: readonly unknown[], occurrence?: readonly unknown[]): unknown;
18
18
  }
@@ -63,8 +63,8 @@ export declare class CborByteString extends CborItem {
63
63
  * needed here at all now — it's accepted purely for interface
64
64
  * consistency with the base class.
65
65
  */
66
- _isMultiWordText(options: ToCDNOptions | undefined, _strict?: boolean): boolean;
66
+ _isMultiWordText(options: ToCDNOptions | undefined, _strict?: boolean, _path?: readonly unknown[]): boolean;
67
67
  _encodeTo(writer: CborWriter, _options?: ToCBOROptions): void;
68
- _toCDN(options: ToCDNOptions | undefined, _depth: number): string;
68
+ _toCDN(options: ToCDNOptions | undefined, _depth: number, _path?: readonly unknown[]): string;
69
69
  _toJS(_options?: ToJSOptions): unknown;
70
70
  }
@@ -21,7 +21,7 @@ export declare class CborEmbeddedCBOR extends CborItem {
21
21
  /** The raw concatenated CBOR bytes of all contained items. */
22
22
  private _content;
23
23
  _encodeTo(writer: CborWriter, options?: ToCBOROptions): void;
24
- _toCDN(options: ToCDNOptions | undefined, depth: number): string;
24
+ _toCDN(options: ToCDNOptions | undefined, depth: number, path?: readonly unknown[]): string;
25
25
  _toHexDump(depth: number, options?: ToCDNOptions): AnnotatedLine[];
26
26
  _toJS(_options?: ToJSOptions): unknown;
27
27
  }
@@ -48,6 +48,6 @@ export declare class CborFloat extends CborItem {
48
48
  literalSource?: string;
49
49
  });
50
50
  _encodeTo(writer: CborWriter, _options?: ToCBOROptions): void;
51
- _toCDN(options: ToCDNOptions | undefined, _depth: number): string;
51
+ _toCDN(options: ToCDNOptions | undefined, _depth: number, _path?: readonly unknown[]): string;
52
52
  _toJS(_options?: ToJSOptions): unknown;
53
53
  }
@@ -32,7 +32,7 @@ export declare class CborIndefiniteByteString extends CborItem {
32
32
  * was removed there too).
33
33
  */
34
34
  _isMultiWordText(options: ToCDNOptions | undefined, _strict?: boolean): boolean;
35
- _toCDN(options: ToCDNOptions | undefined, depth: number): string;
35
+ _toCDN(options: ToCDNOptions | undefined, depth: number, path?: readonly unknown[]): string;
36
36
  _toHexDump(depth: number, options?: ToCDNOptions): AnnotatedLine[];
37
37
  _toJS(_options?: ToJSOptions): unknown;
38
38
  }
@@ -24,7 +24,7 @@ export declare class CborIndefiniteTextString extends CborItem {
24
24
  * depend on it.
25
25
  */
26
26
  _isMultiWordText(options: ToCDNOptions | undefined, _strict?: boolean): boolean;
27
- _toCDN(options: ToCDNOptions | undefined, depth: number): string;
27
+ _toCDN(options: ToCDNOptions | undefined, depth: number, path?: readonly unknown[]): string;
28
28
  _toHexDump(depth: number, options?: ToCDNOptions): AnnotatedLine[];
29
29
  _toJS(_options?: ToJSOptions): unknown;
30
30
  }
@@ -1,4 +1,4 @@
1
- import { CBOROptions, ToCDNOptions, ToJSOptions, ToHexDumpOptions, ToCBOROptions, CborComment, CborComments, DecodeWarning, ParseWarning } from '../types';
1
+ import { CBOROptions, ToCDNOptions, ToJSOptions, ToHexDumpOptions, ToCBOROptions, CborComment, CborComments, DecodeWarning, ParseWarning, ItemContext, ReadonlyToJSNodeOptions, CdnItemContext, ReadonlyToCDNOptions } from '../types';
2
2
  import { CborWriter } from '../cbor/encode';
3
3
  /** @internal One line of an annotated hex dump. */
4
4
  export interface AnnotatedLine {
@@ -27,6 +27,194 @@ export interface AppSeqSourceFeatures {
27
27
  rawString?: boolean;
28
28
  concatenation?: boolean;
29
29
  }
30
+ /**
31
+ * @internal
32
+ * Cheap upfront check for whether per-node option/extension resolution is
33
+ * needed at all for a given `toJS()` options object. Container nodes use
34
+ * this to choose between the plain `child._toJS(options)` recursion (when
35
+ * `false`, identical cost to before `itemOptions`/`extensions` existed) and
36
+ * `child._toJSChild(options, path, ctx)` (when `true`).
37
+ */
38
+ export declare function needsItemDispatch(options: ToJSOptions | undefined): boolean;
39
+ /**
40
+ * @internal
41
+ * Options for a reviver-driven container's "raw" structural pre-population
42
+ * pass (see `CborArray`/`CborMap.toObject`) — `reviver` removed, everything
43
+ * else (including `itemOptions`/`extensions`) left as-is.
44
+ *
45
+ * This pass's output is never returned to the caller as the final result —
46
+ * every position it computes is unconditionally overwritten by the *real*
47
+ * (revived) pass's own result before `toJS()` returns — but it is not
48
+ * write-only scaffolding either: a `reviver` reads it directly, as `this[j]`
49
+ * for a not-yet-processed sibling `j`, while deciding how to revive an
50
+ * *earlier* sibling (matching `JSON.parse`'s own reviver contract, which
51
+ * this replicates). That value must reflect `itemOptions`/`extensions`
52
+ * exactly as the real pass would — e.g. an `integerAs: 'bigint'` override
53
+ * for position `j` — or a reviver reading `this[j]` would see the wrong
54
+ * type. So `itemOptions`/`extensions` stay active here, unlike `reviver`
55
+ * itself.
56
+ *
57
+ * Running dispatch during this pass is safe only because callers building
58
+ * a child's occurrence for it — `CborArray`/`CborMap.toObject` — insert
59
+ * `RAW_PASS_MARKER` into that occurrence (see `Occurrence`), so its
60
+ * resolutions are filed under a different `DispatchCache` key than the
61
+ * real pass's and can never be reused *by* the real pass, however many
62
+ * ancestor levels apart the two passes are. That distinction matters for
63
+ * more than consistency — a composite (non-scalar) map key is itself
64
+ * revived differently between the two passes (this pass leaves its own
65
+ * nested content entirely unrevived, matching `this[j]`'s contract above;
66
+ * the real pass revives it normally), so a value node whose
67
+ * `ItemContext.path` is built from that key's converted JS value would
68
+ * otherwise see a stale, pre-revival path if the real pass reused this
69
+ * pass's cached decision.
70
+ */
71
+ export declare function withoutReviver(options: ToJSOptions | undefined): ToJSOptions | undefined;
72
+ /**
73
+ * @internal
74
+ * Build the `reviver`-free, `extensions`-copied view of `options` handed
75
+ * to code that must not be able to mutate options actually in effect
76
+ * elsewhere in the tree — `ItemContext.options` and the `options`
77
+ * parameter of `CborExtension.toJS()` — see `ReadonlyToJSNodeOptions`.
78
+ *
79
+ * A plain `{ ...options, reviver: undefined }` spread (as `withoutReviver`
80
+ * does) is not enough here: `extensions`, if present, would still be the
81
+ * *same array* `options.extensions` is, so `.push()`/`.splice()`/etc. on
82
+ * the copy would still mutate the original in place — corrupting it for
83
+ * this node's own later use, its siblings, and the caller. `extensions` is
84
+ * the only field on `ToJSOptions` that's an ordinary mutable container, so
85
+ * it's the only one that needs its own copy here.
86
+ */
87
+ export declare function toReadonlyNodeOptions(options: ToJSOptions): ReadonlyToJSNodeOptions;
88
+ /**
89
+ * @internal
90
+ * Stable, allocation-light identifier for "this occurrence of a node" —
91
+ * used only to key `DispatchCache` lookups, never exposed via
92
+ * `ItemContext`. Built exactly the way `ItemContext.path` is (accumulated
93
+ * from the root, one element per container level, unchanged through a
94
+ * tag/app-sequence wrapper), but from *structural* container positions —
95
+ * an array index, or a map entry's ordinal paired with a `'k'`/`'v'` role
96
+ * tag (see `CborArray`/`CborMap`) — rather than from the JS values `path`
97
+ * is built from.
98
+ *
99
+ * That distinction matters in two ways `path` alone (or even `path` plus
100
+ * only the *immediate* container's identity) does not cover:
101
+ *
102
+ * - A composite (non-scalar) map key re-converts to a *new* array/object
103
+ * every time its JS value is asked for, so two visits to the very same
104
+ * logical position produce `path`s that are structurally equal but not
105
+ * reference-equal at that segment — unusable for matching.
106
+ * - Two different positions can legitimately produce the very same `path`,
107
+ * when duplicate map entries carry equal keys (e.g. two `"a"` entries).
108
+ * - A *container* (not just a leaf) can itself be a shared node instance
109
+ * reused at two positions (e.g. the same `CborArray` placed at two
110
+ * different indices of an outer array). Matching only on `{immediate
111
+ * parent identity, local slot}` — as an earlier version of this type
112
+ * did — correctly distinguishes the *container's own* two occurrences,
113
+ * but not its *descendants'*: each of the shared container's children
114
+ * would resolve `{parent: <the shared container>, slot: <local index>}`
115
+ * identically regardless of which of the container's own two
116
+ * occurrences was being converted, silently conflating them. Chaining
117
+ * the full occurrence from the root — rather than just one level —
118
+ * avoids this: the shared container's own two occurrences differ
119
+ * earlier in the chain, so appending the same local slot to each still
120
+ * yields two different full chains for its children.
121
+ *
122
+ * A local slot only needs to be unique among its own container's direct
123
+ * children — `CborArray` uses the element index (`number`) directly;
124
+ * `CborMap` prefixes a map entry's ordinal with `'k'`/`'v'`
125
+ * (`` `k${i}` ``/`` `v${i}` ``) so a key and its own value, which share an
126
+ * ordinal, don't collide.
127
+ *
128
+ * `RAW_PASS_MARKER` (see below) is the one non-structural element this
129
+ * chain can contain: `CborArray`/`CborMap.toObject` insert it — once,
130
+ * immediately before that level's own local slot — only when building a
131
+ * child's occurrence for their raw structural pre-population pass (see
132
+ * `withoutReviver`), never for the real, revived pass. That's what lets
133
+ * `_toJSChild`'s cache correctly tell apart the *distinct* raw-pass
134
+ * resolutions a single occurrence can otherwise receive when more than one
135
+ * ancestor level forks — e.g. the very same value node visited once
136
+ * through an outer container's own raw pass (before *any* ancestor has
137
+ * revived anything) and once more through a different, inner container's
138
+ * own raw pass nested inside the outer's real pass (after some ancestor,
139
+ * such as a composite map key, *has* already been revived) — two visits
140
+ * that share every structural coordinate yet must not share a cached
141
+ * decision, since what a reviver-sensitive value like that key converts to
142
+ * differs between them (see `DispatchCacheEntry`). A container that isn't
143
+ * itself forking (no `reviver` in its own options, so it takes the plain,
144
+ * unsplit path) never inserts the marker — it simply passes the
145
+ * occurrence chain it was given through unchanged before extending it with
146
+ * its own children's local slots, the same way it always did.
147
+ */
148
+ export type Occurrence = readonly unknown[];
149
+ /**
150
+ * @internal
151
+ * Sentinel inserted into an `Occurrence` chain by `CborArray`/
152
+ * `CborMap.toObject` when building a child's occurrence for their own raw
153
+ * structural pre-population pass — see `Occurrence`'s own note. A
154
+ * dedicated symbol rather than a string/number so it can never collide
155
+ * with an ordinary local slot value (an array index or a `` `k${i}` ``/
156
+ * `` `v${i}` `` map-entry tag).
157
+ */
158
+ export declare const RAW_PASS_MARKER: unique symbol;
159
+ /**
160
+ * @internal
161
+ * The root value's occurrence, for `toJS()`'s own top-level `_toJSChild()`
162
+ * call — empty, the same way `ItemContext.path` is empty at the root. Also
163
+ * used by `CborTag`/`CborAppSeqResult` as a defensive fallback if `_toJS()`
164
+ * is ever invoked without an `occurrence` (only possible via a direct,
165
+ * non-`toJS()` call to `_toJS()`, which the dispatch cache never sees).
166
+ */
167
+ export declare const ROOT_OCCURRENCE: Occurrence;
168
+ /**
169
+ * @internal
170
+ * Cheap upfront check for whether per-node option resolution is needed at
171
+ * all for a given `toCDN()` options object — the `toCDN()` analogue of
172
+ * `needsItemDispatch`.
173
+ */
174
+ export declare function needsCdnItemDispatch(options: ToCDNOptions | undefined): boolean;
175
+ /**
176
+ * @internal
177
+ * Build the copied-`textStringFormat` view of `options` handed to
178
+ * `CdnItemContext.options` — the `toCDN()` analogue of
179
+ * `toReadonlyNodeOptions`; see `ReadonlyToCDNOptions`. Also strips
180
+ * `CDN_OVERRIDE_TRACKER` (see there) — `ctx.options` is documented as
181
+ * reflecting the current *effective options*, so it should never expose an
182
+ * internal, out-of-band bookkeeping key that isn't part of `ToCDNOptions` at
183
+ * all, even though it isn't otherwise observable through any named field.
184
+ */
185
+ export declare function toReadonlyCdnOptions(options: ToCDNOptions): ReadonlyToCDNOptions;
186
+ /**
187
+ * @internal
188
+ * Out-of-band symbol key that lets a caller (currently only
189
+ * `CborAppSeqResult._toCDN`) track, across an entire `_resolveCdnOptions()`
190
+ * subtree, whether `itemOptions` ever actually returned an override —
191
+ * without touching `options.itemOptions` itself.
192
+ *
193
+ * `CborAppSeqResult` needs to know whether its preserved verbatim source is
194
+ * still exactly right after offering `itemOptions` a chance to override one
195
+ * of its descendants (see its own doc). Wrapping `options.itemOptions` in a
196
+ * tracking function was tried first, but that function becomes
197
+ * `ctx.options.itemOptions` for every descendant (`_resolveCdnOptions`
198
+ * builds `ctx.options` from the very `options` it was given) — a pure
199
+ * `itemOptions` callback could tell it apart from the caller's own function
200
+ * by identity, observing a difference between an ordinary subtree and one
201
+ * reached through an app-sequence wrapper that `ctx.options`'s "current
202
+ * effective options" contract never promises.
203
+ *
204
+ * A symbol-keyed property on `options` instead survives every
205
+ * `{...options, ...override}` merge `_resolveCdnOptions` performs exactly
206
+ * the same way `itemOptions` itself does (object spread copies symbol keys
207
+ * too, and by reference — the same tracker box is shared, never cloned, by
208
+ * every node in the subtree), while never being part of the public
209
+ * `ToCDNOptions` shape or observable through any named field — see
210
+ * `toReadonlyCdnOptions`, which explicitly strips it before building
211
+ * `ctx.options`.
212
+ */
213
+ export declare const CDN_OVERRIDE_TRACKER: unique symbol;
214
+ /** @internal Mutable box referenced (never copied) via `CDN_OVERRIDE_TRACKER`. */
215
+ export interface CdnOverrideTrackerBox {
216
+ applied: boolean;
217
+ }
30
218
  /**
31
219
  * Abstract base class for all CBOR AST nodes.
32
220
  *
@@ -178,8 +366,19 @@ export declare abstract class CborItem {
178
366
  * string's, or byte string's own sqstr-text, word count is unaffected by
179
367
  * it either way) — only `CborTag` (and, transitively, `CborAppSeqResult`
180
368
  * delegating to its inner value) actually consult it.
369
+ *
370
+ * `path` is this entry's own full path (see `CdnItemContext.path`),
371
+ * passed down by the same caller for the same reason `renderEntry`
372
+ * receives it: `CborTag`/`CborAppSeqResult` re-render `this` here (see
373
+ * their own overrides) purely to answer this method's question, and that
374
+ * re-render must resolve any of *its own* descendants' `itemOptions`
375
+ * against the entry's real path — not an empty one — or a descendant
376
+ * several levels inside a tag-wrapped entry could see a different
377
+ * `ctx.path` here than the real render further down gives it. Only
378
+ * `CborTag`/`CborAppSeqResult` consult it; every other override ignores
379
+ * it, the same as `strict`.
181
380
  */
182
- _isMultiWordText(_options: ToCDNOptions | undefined, _strict?: boolean): boolean;
381
+ _isMultiWordText(_options: ToCDNOptions | undefined, _strict?: boolean, _path?: readonly unknown[]): boolean;
183
382
  /** Serialize this node to CBOR binary. */
184
383
  toCBOR(options?: ToCBOROptions): Uint8Array;
185
384
  /** Serialize this node to a CDN text string. */
@@ -246,17 +445,108 @@ export declare abstract class CborItem {
246
445
  * @internal
247
446
  * Depth-aware CDN serialization.
248
447
  * Leaf nodes receive `depth` but may ignore it.
249
- * Container nodes use `depth` for indentation and call
250
- * `child._toCDN(options, depth + 1)` when recursing.
448
+ * Container nodes use `depth` for indentation and, when recursing, must
449
+ * resolve each child's options via `child._resolveCdnOptions()` first
450
+ * (rather than passing `options` straight through) so `itemOptions` is
451
+ * honored for every node, not just the root — see `_resolveCdnOptions`.
452
+ * `path` is this node's own full path from the root (see
453
+ * `CdnItemContext.path`); only meaningful when `needsCdnItemDispatch()`
454
+ * is `true` for the options in effect — leaf implementations that don't
455
+ * recurse can ignore it, as can any implementation when dispatch isn't
456
+ * in play.
457
+ */
458
+ abstract _toCDN(options: ToCDNOptions | undefined, depth: number, path?: readonly unknown[]): string;
459
+ /**
460
+ * @internal
461
+ * Resolve `options.itemOptions` for this node (if any), returning the
462
+ * options a caller should use for both this node's own `_toCDN()` call
463
+ * and (via `_isMultiWordText()`) any layout probe of it — see
464
+ * `CdnItemContext`. Unlike `toJS()`'s `_toJSChild()`, this never caches:
465
+ * see the "toCDN() per-item dispatch" note above `needsCdnItemDispatch`
466
+ * for why that's both unnecessary and, per this codebase's own prior
467
+ * experience with `CborTag._isMultiWordText`, unsafe here specifically.
468
+ *
469
+ * `ctx` follows the same "no `key`, derived from `path`'s last element"
470
+ * convention `_toJSChild()` uses (see there) — a caller passes
471
+ * `parent`/`keyNode`/`isMapKey` only; `key` is filled in here.
251
472
  */
252
- abstract _toCDN(options: ToCDNOptions | undefined, depth: number): string;
473
+ _resolveCdnOptions(options: ToCDNOptions | undefined, path: readonly unknown[], ctx: Omit<CdnItemContext, 'path' | 'key' | 'options'>): ToCDNOptions | undefined;
253
474
  /**
254
475
  * @internal
255
476
  * Core conversion logic implemented by each subclass.
256
- * Container nodes apply `options.reviver` to their direct children.
477
+ * Container nodes apply `options.reviver` to their direct children, and
478
+ * must recurse via `child._toJSChild()` (never `child._toJS()` directly)
479
+ * so that `options.itemOptions`/`options.extensions` are honored for
480
+ * every node, not just the root — see `_toJSChild`.
481
+ * `path` is this node's own full path from the root (see `ItemContext.path`);
482
+ * only meaningful, and only ever non-empty, when `needsItemDispatch()` is
483
+ * `true` for the options in effect — leaf implementations that don't
484
+ * recurse can ignore it. `occurrence` is this node's own cache-matching
485
+ * identity (see `Occurrence`) — `CborArray`/`CborMap` build on it to
486
+ * derive their children's own occurrences, and `CborTag`/`CborAppSeqResult`
487
+ * pass it through unchanged to their content's `_toJSChild()` call; leaf
488
+ * implementations that don't recurse can ignore it.
257
489
  * Do not call this directly — use `toJS()` instead.
258
490
  */
259
- abstract _toJS(options?: ToJSOptions): unknown;
491
+ abstract _toJS(options?: ToJSOptions, path?: readonly unknown[], occurrence?: Occurrence): unknown;
492
+ /**
493
+ * @internal
494
+ * Entry point container nodes must use when recursing into a child during
495
+ * `toJS()`, instead of calling `child._toJS(options)` directly, so that
496
+ * `options.itemOptions` and `options.extensions` (toJS hooks) are honored
497
+ * for every node in the tree, not just the root.
498
+ *
499
+ * Guarded by `needsItemDispatch()` at each call site rather than
500
+ * internally, so that containers can skip straight to the cheap
501
+ * `child._toJS(options)` call — matching pre-`itemOptions` behavior
502
+ * exactly, with no extra allocation — whenever neither option is in play
503
+ * for the whole conversion.
504
+ *
505
+ * `path` is this child's own full path from the root, already computed by
506
+ * the caller: `[...parentPath, key]` for an array element or map
507
+ * entry/key, or the parent's own `path` unchanged for a transparent
508
+ * wrapper's content (`CborTag`, `CborAppSeqResult`) — see
509
+ * `ItemContext.path`. `ctx.key` is *not* supplied by the caller — it's
510
+ * derived here from `path`'s own last element (or left `undefined` for
511
+ * the root and for `isMapKey` visits), which is what keeps a wrapper's
512
+ * content correctly reporting its outer key: since `CborTag`/
513
+ * `CborAppSeqResult` pass their own unmodified `path` straight through,
514
+ * that derivation naturally recovers the tag's own key for its content
515
+ * too, matching `ItemContext.key`'s documented invariant of always
516
+ * equalling `path`'s last element outside those two exceptions.
517
+ *
518
+ * `occurrence` identifies this child's position for cache-matching
519
+ * purposes only (see `Occurrence`) — deliberately separate from `path`,
520
+ * which is built from *converted JS values* and so is unreliable for
521
+ * that: matching on `path` alone either double-resolves the same position
522
+ * (a composite map key converts to a fresh, non-`===` array/object every
523
+ * time) or wrongly conflates two different positions that happen to
524
+ * convert to equal `path`s (duplicate map entries with equal keys).
525
+ * `occurrence` avoids both by construction — see `Occurrence`.
526
+ *
527
+ * A single node can be visited more than once *at the same occurrence* in
528
+ * one `toJS()` call — a `reviver`-driven `CborArray`/`CborMap` converts
529
+ * each child once to pre-populate an unrevived holder and once more to
530
+ * compute the revived value (see their own `_toJS()`), and either pass
531
+ * may itself recurse through a `CborTag`/`CborAppSeqResult` wrapper that
532
+ * adds no occurrence of its own (content shares the wrapper's). The raw
533
+ * pre-population pass's own occurrence for a child is distinguished from
534
+ * the real pass's by `RAW_PASS_MARKER` (see `Occurrence`), so that even
535
+ * distinct, *nested* raw passes reaching the same node — one from an
536
+ * outer container's own split, one from a different, inner container's
537
+ * own split nested inside the outer's real pass — resolve independently
538
+ * rather than colliding with each other. Neither `options.itemOptions`
539
+ * nor an `extensions` `toJS()` hook may run more than once for the same
540
+ * occurrence despite all that, since either may be stateful — so the
541
+ * *decision* (not the reviver-dependent application of an `itemOptions`
542
+ * override — see `DispatchOutcome`) is cached in `options`'s dispatch
543
+ * cache, when one is present, and reused on a later visit to that exact
544
+ * occurrence. A node *reused* at more than one occurrence (the same
545
+ * `CborItem` instance placed at two positions in a hand-built tree)
546
+ * still resolves once per occurrence, never conflating two different
547
+ * ones.
548
+ */
549
+ _toJSChild(options: ToJSOptions | undefined, path: readonly unknown[], occurrence: Occurrence, ctx: Omit<ItemContext, 'path' | 'key' | 'options'>): unknown;
260
550
  /**
261
551
  * @internal
262
552
  * Collect annotated-hex lines for this node.
@@ -12,7 +12,7 @@ export declare class CborMap extends CborItem {
12
12
  });
13
13
  get _containsCdnContainer(): boolean;
14
14
  _encodeTo(writer: CborWriter, options?: ToCBOROptions): void;
15
- _toCDN(options: ToCDNOptions | undefined, depth: number): string;
15
+ _toCDN(options: ToCDNOptions | undefined, depth: number, path?: readonly unknown[]): string;
16
16
  _toHexDump(depth: number, options?: ToCDNOptions): AnnotatedLine[];
17
- _toJS(options?: ToJSOptions): unknown;
17
+ _toJS(options?: ToJSOptions, path?: readonly unknown[], occurrence?: readonly unknown[]): unknown;
18
18
  }
@@ -31,6 +31,6 @@ export declare class CborNint extends CborItem {
31
31
  /** The actual decoded negative value (−1 − argument). */
32
32
  get value(): bigint;
33
33
  _encodeTo(writer: CborWriter, _options?: ToCBOROptions): void;
34
- _toCDN(options: ToCDNOptions | undefined, _depth: number): string;
34
+ _toCDN(options: ToCDNOptions | undefined, _depth: number, _path?: readonly unknown[]): string;
35
35
  _toJS(options?: ToJSOptions): unknown;
36
36
  }
@@ -1,5 +1,5 @@
1
1
  import { ToCDNOptions, ToJSOptions, ToCBOROptions } from '../types';
2
- import { CborItem, AnnotatedLine } from './CborItem';
2
+ import { CborItem, AnnotatedLine, Occurrence } from './CborItem';
3
3
  import { CborWriter, EncodingWidth } from '../cbor/encode';
4
4
  /** CBOR Major Type 6 — tagged data item. */
5
5
  export declare class CborTag extends CborItem {
@@ -44,9 +44,9 @@ export declare class CborTag extends CborItem {
44
44
  * entry — an accepted, narrow cost (only tag-wrapped entries reach this
45
45
  * at all).
46
46
  */
47
- _isMultiWordText(options: ToCDNOptions | undefined, strict?: boolean): boolean;
47
+ _isMultiWordText(options: ToCDNOptions | undefined, strict?: boolean, path?: readonly unknown[]): boolean;
48
48
  _encodeTo(writer: CborWriter, options?: ToCBOROptions): void;
49
- _toCDN(options: ToCDNOptions | undefined, depth: number): string;
49
+ _toCDN(options: ToCDNOptions | undefined, depth: number, path?: readonly unknown[]): string;
50
50
  _toHexDump(depth: number, options?: ToCDNOptions): AnnotatedLine[];
51
- _toJS(options?: ToJSOptions): unknown;
51
+ _toJS(options?: ToJSOptions, path?: readonly unknown[], occurrence?: Occurrence): unknown;
52
52
  }