@cbortech/cbor 0.27.0 → 0.27.2

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 (67) hide show
  1. package/README.ja.md +100 -0
  2. package/README.md +103 -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 +314 -9
  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 +26 -4
  14. package/dist/ast/CborTextString.d.ts +2 -2
  15. package/dist/ast/CborUint.d.ts +1 -1
  16. package/dist/ast/CborUnresolvedAppExt.d.ts +9 -0
  17. package/dist/ast/index.cjs +1 -1
  18. package/dist/ast/index.js +2 -2
  19. package/dist/cbor/tagLabels.d.ts +26 -0
  20. package/dist/cddl/eRefScope.d.ts +241 -0
  21. package/dist/cddl/eref.d.ts +64 -0
  22. package/dist/cddl/implicitTags.d.ts +28 -0
  23. package/dist/cddl/index.cjs +2 -2
  24. package/dist/cddl/index.cjs.map +1 -1
  25. package/dist/cddl/index.d.ts +2 -0
  26. package/dist/cddl/index.js +26 -25
  27. package/dist/cddl/index.js.map +1 -1
  28. package/dist/cddl/validator.d.ts +98 -2
  29. package/dist/cdn/index.cjs +1 -1
  30. package/dist/cdn/index.js +1 -1
  31. package/dist/cdn/serialize-utils.d.ts +50 -15
  32. package/dist/extensions/cri.d.ts +2 -2
  33. package/dist/extensions/dt.d.ts +4 -4
  34. package/dist/extensions/eref.d.ts +135 -0
  35. package/dist/extensions/ip.d.ts +3 -3
  36. package/dist/extensions/types.d.ts +40 -1
  37. package/dist/index.cjs +7 -7
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.ts +2 -1
  40. package/dist/index.js +190 -81
  41. package/dist/index.js.map +1 -1
  42. package/dist/js/fromJS.d.ts +23 -1
  43. package/dist/mapEntries-8zshEr-t.js +7085 -0
  44. package/dist/mapEntries-8zshEr-t.js.map +1 -0
  45. package/dist/mapEntries-DwHPAzCN.cjs +68 -0
  46. package/dist/mapEntries-DwHPAzCN.cjs.map +1 -0
  47. package/dist/schema-7r2xjpMK.js +345 -0
  48. package/dist/schema-7r2xjpMK.js.map +1 -0
  49. package/dist/schema-YiigSWjR.cjs +8 -0
  50. package/dist/schema-YiigSWjR.cjs.map +1 -0
  51. package/dist/{serialize-utils-BuIZPaUc.js → serialize-utils-BQtutOo6.js} +70 -52
  52. package/dist/serialize-utils-BQtutOo6.js.map +1 -0
  53. package/dist/{serialize-utils-CjTqQivB.cjs → serialize-utils-DQ8T3Mzw.cjs} +9 -9
  54. package/dist/serialize-utils-DQ8T3Mzw.cjs.map +1 -0
  55. package/dist/types.d.ts +426 -4
  56. package/dist/utils/hexfloat.d.ts +10 -2
  57. package/package.json +13 -14
  58. package/dist/mapEntries-C1f7G0AM.cjs +0 -13
  59. package/dist/mapEntries-C1f7G0AM.cjs.map +0 -1
  60. package/dist/mapEntries-CvLdiN0h.js +0 -3946
  61. package/dist/mapEntries-CvLdiN0h.js.map +0 -1
  62. package/dist/schema-CNfrVRYp.js +0 -1977
  63. package/dist/schema-CNfrVRYp.js.map +0 -1
  64. package/dist/schema-iXpYtKQl.cjs +0 -63
  65. package/dist/schema-iXpYtKQl.cjs.map +0 -1
  66. package/dist/serialize-utils-BuIZPaUc.js.map +0 -1
  67. package/dist/serialize-utils-CjTqQivB.cjs.map +0 -1
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CborExtension } from './extensions/types';
2
+ import { CborItem } from './ast/CborItem';
2
3
  import { CddlSchema } from './cddl/schema';
3
4
  import { ValidateOptions as CddlValidateOptions } from './cddl/validator';
4
5
  import { CddlValidationError, CddlValidationWarning } from './cddl/errors';
@@ -14,6 +15,114 @@ import { CddlValidationError, CddlValidationWarning } from './cddl/errors';
14
15
  */
15
16
  export declare const CBOR_OMIT: unique symbol;
16
17
  export type { CborExtension } from './extensions/types';
18
+ /**
19
+ * Context passed to an `itemOptions` callback (see `ToJSOptions.itemOptions`)
20
+ * describing where the node being visited sits in the tree.
21
+ */
22
+ export interface ItemContext {
23
+ /**
24
+ * The direct parent AST node. `undefined` for the root value being
25
+ * converted. A tag or app-sequence wrapper is its content's `parent` —
26
+ * see `path` for how wrappers affect addressing.
27
+ */
28
+ parent?: CborItem;
29
+ /**
30
+ * The path segment identifying this node: an array index (`number`), or a
31
+ * map key's JS value (any type — CBOR keys are not limited to strings).
32
+ * Always equal to the last element of `path`, except `undefined` for the
33
+ * root value and while converting a map key itself (`isMapKey: true`),
34
+ * since a key has no path of its own — it names its sibling value's
35
+ * segment instead. A tag or app-sequence wrapper's content inherits the
36
+ * wrapper's own `key` (and `path`) unchanged, since the wrapper adds no
37
+ * segment of its own — see `path`.
38
+ */
39
+ key?: unknown;
40
+ /**
41
+ * The map key's own AST node, present when this node is a map entry's key
42
+ * or value — but *not* inherited into a tag/app-sequence wrapper's
43
+ * content the way `key`/`path` are, so it is only reliable at the map
44
+ * entry itself. Lets a callback inspect a non-scalar key structurally
45
+ * (e.g. via `keyNode._toCDN()`) instead of relying only on `key`'s
46
+ * computed JS value.
47
+ */
48
+ keyNode?: CborItem;
49
+ /**
50
+ * `true` when this invocation is for converting a map key itself, rather
51
+ * than one of that key's sibling value's descendants.
52
+ */
53
+ isMapKey?: boolean;
54
+ /**
55
+ * Path from the root to this node, as a sequence of array indices and map
56
+ * key JS values. Empty for the root value. A tag or app-sequence wrapper
57
+ * does not add a segment of its own — its content shares the wrapper's own
58
+ * path.
59
+ */
60
+ readonly path: readonly unknown[];
61
+ /**
62
+ * The options in effect for this node going into this call — i.e. the
63
+ * root options merged with whatever overrides its ancestors already
64
+ * returned, *before* this callback's own return value is merged on top.
65
+ * Read from this to build on the current value of an option (e.g. add an
66
+ * extension to whatever list is already in effect) instead of overriding
67
+ * it outright, or to make a decision based on an option's current value.
68
+ *
69
+ * A mutable-looking field here (currently just `extensions`) is a fresh
70
+ * copy, not the live array in effect elsewhere — mutating it in place
71
+ * (e.g. `ctx.options.extensions.push(ext)`) has no effect on this node,
72
+ * its siblings, or the caller's own options; return an override instead.
73
+ *
74
+ * Has no `reviver` — its type is `ReadonlyToJSNodeOptions`, not
75
+ * `ToJSOptions` — for the same reason this callback's own return value
76
+ * can't set one:
77
+ * since this callback may run more than once for the same node (see
78
+ * `ToJSOptions.itemOptions`), always reflecting only the reviver-
79
+ * independent options keeps what it reads consistent with what it can
80
+ * write, and keeps a callback that only inspects `options` pure across
81
+ * every one of those calls.
82
+ *
83
+ * @example
84
+ * // Add `dt_as_Date` to whatever extensions are already configured,
85
+ * // rather than replacing them.
86
+ * itemOptions: (_node, ctx) => ({
87
+ * extensions: [...(ctx.options.extensions ?? []), dt_as_Date],
88
+ * })
89
+ */
90
+ readonly options: ReadonlyToJSNodeOptions;
91
+ }
92
+ /**
93
+ * `ToJSOptions` with `reviver` removed. Used wherever an API only ever
94
+ * needs to see the reviver-*independent* part of the options in effect for
95
+ * a node — an `itemOptions` callback's return value, and the `options`
96
+ * parameter of `CborExtension.toJS()` — because reviving the value a node
97
+ * converts to always happens afterwards, exactly once per visit, in the
98
+ * container holding it (see `CborArray`/`CborMap`); it is never something
99
+ * the conversion of the node itself should (or, for `CborExtension.toJS()`,
100
+ * even can) branch on. Keeping `reviver` out of both signatures — rather
101
+ * than merely documenting that it should be ignored — means neither can
102
+ * observe whether one is present, which is what makes it safe for a
103
+ * `reviver`-driven `CborArray`/`CborMap` to convert a child more than once
104
+ * (see `ToJSOptions.itemOptions`) without either one's result depending on
105
+ * *which* of those conversions it was called for.
106
+ */
107
+ export type ToJSNodeOptions = Omit<ToJSOptions, 'reviver'>;
108
+ /**
109
+ * `ToJSNodeOptions`, as handed to code that must not mutate it in place:
110
+ * `ItemContext.options` and the `options` parameter of
111
+ * `CborExtension.toJS()`. Both describe options actually in effect
112
+ * elsewhere in the tree, so mutating what's handed out here must not be
113
+ * able to reach that: reassigning a top-level property is a type error
114
+ * (`Readonly<...>`), and `extensions`, the one field on `ToJSOptions`
115
+ * that's an ordinary mutable array, is narrowed to a readonly array so an
116
+ * in-place mutation like `.push()` is one too. This only guards against
117
+ * *accidental* mutation via the type system — the implementation
118
+ * additionally hands out a fresh snapshot, with its own copy of
119
+ * `extensions`, on every call, so even a deliberate cast past this type
120
+ * can't corrupt what another node, sibling, hook, or pass sees (see
121
+ * `_toJSChild`'s `toReadonlyNodeOptions`).
122
+ */
123
+ export type ReadonlyToJSNodeOptions = Readonly<Omit<ToJSNodeOptions, 'extensions'>> & {
124
+ readonly extensions?: readonly CborExtension[];
125
+ };
17
126
  export interface ToHexDumpOptions {
18
127
  /**
19
128
  * Indentation per nesting level.
@@ -37,11 +146,19 @@ export interface ToJSOptions {
37
146
  integerAs?: 'auto' | 'number' | 'bigint';
38
147
  /**
39
148
  * How to represent CBOR map values when converting to JavaScript.
40
- * - `'auto'`: text-string-only keys → `Record<string, unknown>`,
41
- * other key types `Map<unknown, unknown>`.
42
- * Duplicate keys are silently overwritten (last value wins).
149
+ * - `'auto'`: `Record<string, unknown>` when every key is a text string,
150
+ * or (with a CDDL `cddl` schema and `eRefKeys` not `false`) an e-ref
151
+ * annotated integer key that resolves to its name string — `Map<unknown,
152
+ * unknown>` for any other key type. Duplicate *text-string* keys are
153
+ * silently overwritten (last value wins), same as `'object'` below —
154
+ * but if a text-string key and an e-ref name key resolve to the same
155
+ * property string (e.g. `&(title: -1)` alongside a literal `"title"`
156
+ * key in the same map), `'auto'` falls back to `MapEntries` instead of
157
+ * silently dropping one of them.
43
158
  * - `'object'`: always `Record<string, unknown>` — non-string keys are
44
- * converted via `String()`. Duplicate keys are overwritten (last wins).
159
+ * converted via `String()`. Duplicate keys (including a text/e-ref
160
+ * collision as above) are overwritten (last wins) — unlike `'auto'`,
161
+ * `'object'` never falls back, since it was explicitly requested.
45
162
  * - `'entries'`: always `MapEntries` (a typed `Array` subclass) — preserves all
46
163
  * entries including duplicate keys (§2.4.2 of draft-ietf-cbor-edn-literals-27).
47
164
  * `fromJS()` recognises `MapEntries` instances and converts them back to `CborMap`.
@@ -58,6 +175,30 @@ export interface ToJSOptions {
58
175
  * @default false
59
176
  */
60
177
  stripTags?: boolean;
178
+ /**
179
+ * Whether a tag the CDDL schema itself implies is left off the JavaScript
180
+ * value — e.g. with `{ t: time }` (prelude `time = #6.1(number)`), the
181
+ * CBOR `{"t": 1(-14159024)}` (CDN `{"t": DT'1969-07-21T02:56:16Z'}`)
182
+ * converts to `{ t: -14159024 }` (a plain `number`) rather than a
183
+ * `Number` object carrying `Tag.symbol`.
184
+ *
185
+ * A tag counts as implied only where `FromJSOptions.implicitTags` would
186
+ * add it back when converting the result with the same schema, so
187
+ * `toJS()` → `fromJS()` still round-trips: it must be matched by a
188
+ * `#6.N(type)` with a literal tag number, and the untagged content must
189
+ * not be acceptable at that position on its own — under `time / number`
190
+ * a tagged `1(5)` keeps its tag, since plain `5` would stay untagged.
191
+ * Tags the schema doesn't require (`any`, `#6.<uint>(…)`, …) are always
192
+ * kept.
193
+ *
194
+ * Only takes effect for an item validated via `FromCBOROptions.cddl` /
195
+ * `FromCDNOptions.cddl` / `FromJSOptions.cddl`; without a schema, every
196
+ * tag is kept as before. Pass `false` to keep every tag even with one.
197
+ * `stripTags: true` removes all tags regardless.
198
+ *
199
+ * @default true
200
+ */
201
+ implicitTags?: boolean;
61
202
  /**
62
203
  * Post-conversion reviver function, applied bottom-up after the CBOR value
63
204
  * has been converted to JavaScript.
@@ -80,6 +221,110 @@ export interface ToJSOptions {
80
221
  * @default false
81
222
  */
82
223
  undefinedOmits?: boolean;
224
+ /**
225
+ * Extension plugins consulted during `toJS()`, tried in order for every
226
+ * node before its own default conversion. An extension takes part by
227
+ * implementing `CborExtension.toJS()`; the first one to return a result
228
+ * for a given node wins and that node's own `_toJS()` is not called.
229
+ *
230
+ * Unlike the `extensions` option on `FromCDNOptions`/`FromCBOROptions`,
231
+ * this does not affect parsing — it only lets `toJS()` reinterpret nodes
232
+ * that a *different* extension configuration already produced. For
233
+ * example, a tree parsed with the plain `dt` extension (`DT'...'` →
234
+ * `number`) can still be converted with `dt_as_Date` selected here
235
+ * (`DT'...'` → `Date`), and vice versa — see `itemOptions` below to apply
236
+ * this to only part of a tree.
237
+ *
238
+ * There is no `builtinExtensions` equivalent for `toJS()`: leaving this
239
+ * unset (the default) keeps every node's own built-in conversion
240
+ * behavior, so no bundled extension needs to be "re-added" here just to
241
+ * get default output.
242
+ */
243
+ extensions?: CborExtension[];
244
+ /**
245
+ * Called for every node during `toJS()`, before that node is converted,
246
+ * to override the options used for its subtree. Return a partial options
247
+ * object to merge over the options in effect for this node (they apply to
248
+ * this node and are inherited by its descendants, who may override them
249
+ * again); return `undefined` to make no change.
250
+ *
251
+ * This is the mechanism for applying an option — including `extensions`,
252
+ * to select a different conversion for one node without affecting its
253
+ * siblings — to only part of a document, keyed off `ctx.path` or the
254
+ * node's own shape (`item instanceof ...`).
255
+ *
256
+ * **May be called more than once for the same AST node.** With a
257
+ * `reviver` present, `CborArray`/`CborMap` convert each child at least
258
+ * twice: once (with `reviver` itself withheld from the options this
259
+ * callback sees, though its own return value is honoured as normal) to
260
+ * build a holder a `reviver` call can inspect via `this[j]` for a
261
+ * not-yet-processed sibling `j`, and once more, for real, to compute the
262
+ * value the container actually keeps — and a node several levels down
263
+ * can in principle be offered to this callback more than twice, if a
264
+ * container at some intermediate level runs a pre-population pass of its
265
+ * own while itself sitting inside an *outer* container's. Each call is
266
+ * independent and reflects only the state relevant to *that* particular
267
+ * conversion (e.g. `ctx.path` for a value under a composite map key
268
+ * shows whatever that key currently converts to, which can differ
269
+ * between an earlier, not-yet-revived call and a later one after the
270
+ * key's own `reviver` has run). Write this callback as a pure function of
271
+ * `item`/`ctx` — it must not assume, or count, how many times it runs.
272
+ *
273
+ * @example
274
+ * // Convert only `date1` with `dt_as_Date`, leaving other DT values as
275
+ * // the epoch numbers the tree was originally parsed with.
276
+ * item.toJS({
277
+ * itemOptions: (node, ctx) =>
278
+ * ctx.path.length === 1 && ctx.path[0] === 'date1'
279
+ * ? { extensions: [dt_as_Date] }
280
+ * : undefined,
281
+ * });
282
+ */
283
+ itemOptions?: (item: CborItem, ctx: ItemContext) => Partial<ToJSNodeOptions> | undefined;
284
+ /**
285
+ * Whether a map key annotated by CDDL `e'...'` external-reference
286
+ * resolution (draft-ietf-cbor-edn-e-ref) — an integer key the `cddl`
287
+ * schema names via a `&(name: value)` group entry at a map-member-key
288
+ * position, e.g. `? &(title: -1) => oltext` — converts to a plain-object
289
+ * key spelled as that name (`"title"`) instead of the integer itself. A
290
+ * general CDDL constant rule (`title = -1`) is not enough on its own to
291
+ * annotate a key this way — see `cddl/eref.ts`'s module doc — only
292
+ * `e'title'` written explicitly in CDN source resolves via a plain
293
+ * constant rule.
294
+ *
295
+ * Defaults to `true`: such a key exists at all only because some earlier
296
+ * `cddl`-validated decode/parse (or an `eRefKeys`-driven `fromJS()`, see
297
+ * `FromJSOptions.eRefKeys`) already named it, so leaving this option unset
298
+ * uses that name — pass `false` explicitly to fall back to the plain
299
+ * integer key instead (e.g. for code that reads `obj[-1]` and doesn't
300
+ * want it silently renamed to `obj.title`). A map with no CDDL-named keys
301
+ * at all is entirely unaffected either way — `mapAs: 'auto'`'s own choice
302
+ * between a plain object and `MapEntries` only ever treats a key as
303
+ * object-eligible via this option when the key is actually annotated;
304
+ * this is not a general "coerce integer keys to strings" switch.
305
+ *
306
+ * Only takes effect for a key CDDL validation has already annotated (see
307
+ * `FromCBOROptions.cddl` / `FromCDNOptions.cddl`); without a schema, or for
308
+ * a key the schema does not name this way, the key converts as usual.
309
+ * Annotation is unambiguous by construction (a name mapped to more than
310
+ * one value, or a value named more than once, in the same schema is
311
+ * excluded from annotation entirely — and a name ambiguous in either
312
+ * direction is excluded from *both*), so this never introduces a name
313
+ * collision on its own — though a name can still collide with an
314
+ * unrelated map's own genuine text-string key of the same spelling
315
+ * (e.g. `&(title: -1)` alongside a literal `"title"` key in the same
316
+ * map). `mapAs: 'auto'` treats that case differently from a plain
317
+ * duplicate text-string key: it detects the collision and falls back to
318
+ * `MapEntries` rather than silently dropping one side — see
319
+ * `ToJSOptions.mapAs`. An explicit `mapAs: 'object'` still overwrites
320
+ * (last value wins), the same as it does for any other non-injective key
321
+ * conversion. Only integer-valued names are annotated (see
322
+ * `cddl/eref.ts`'s Scope note) — a float, text, or byte-string map key is
323
+ * never affected.
324
+ *
325
+ * @default true
326
+ */
327
+ eRefKeys?: boolean;
83
328
  }
84
329
  export interface ToCBOROptions {
85
330
  }
@@ -525,7 +770,151 @@ export interface FromJSOptions {
525
770
  * Mirrors `FromCBOROptions.cddlValidationOptions`.
526
771
  */
527
772
  cddlValidationOptions?: CddlValidateOptions;
773
+ /**
774
+ * When not `false`, a plain object's property name that the `cddl` schema
775
+ * names — via a `&(name: value)` group entry at a map-member-key
776
+ * position (e.g. `? &(title: -1) => oltext`), or a bare reference to a
777
+ * constant rule at that same position — converts to that integer key
778
+ * instead of a text-string key, so `fromJS({ title: "oops" }, { cddl })`
779
+ * produces the same map `toJS()` on that map would read back as
780
+ * `{ title: "oops" }` (both options default to on, so neither needs
781
+ * spelling out explicitly to get a round-trip). The resulting key
782
+ * round-trips through `toCDN()` as `e'title'` the same way a
783
+ * schema-annotated key produced by `fromCBOR()`/`fromCDN()` does — only
784
+ * an unambiguous, integer-valued name is ever used this way (see
785
+ * `cddl/eref.ts`'s module doc).
786
+ *
787
+ * `ToJSOptions.eRefKeys` only ever renames an integer key some earlier
788
+ * step already labeled (`annotateERefKeys()`, run automatically after a
789
+ * successful `cddl`-validated decode/parse — or `e'name'` written
790
+ * explicitly in CDN source); this direction is different, since it
791
+ * *constructs* the key before any validation happens — so which names are
792
+ * even eligible is resolved **positionally**: only a name bound at a member-key position
793
+ * within the CDDL type actually governing *this* property's own position
794
+ * (starting from the schema's root rule, or `cddlValidationOptions.rule`
795
+ * when set, and descending through nested map types the same way this
796
+ * object's own nesting does) is used, never a name that merely exists
797
+ * *somewhere* in the schema. A property named `title` is therefore left
798
+ * as an ordinary text-string key unless the schema specifically names
799
+ * `title` at the exact position this object is being converted for —
800
+ * even if some unrelated, structurally unreachable rule elsewhere in the
801
+ * same schema happens to name `title` too (see `cddl/eRefScope.ts`'s own
802
+ * module doc, including its Scope note on what "governing type" this can
803
+ * and can't resolve).
804
+ *
805
+ * Only applies to a plain object's own properties (`Object.entries()`), not
806
+ * to a `MapEntries` key, which is preserved exactly as given (a native
807
+ * `Map` is not currently recognized by `fromJS()` at all — see its own
808
+ * doc — independent of `eRefKeys`) — a `MapEntries` entry's *value* still
809
+ * gets a nested scope when its own key is a string naming a known map
810
+ * property, the same way an ordinary property's value does, so a nested
811
+ * object reached this way still converts correctly; an array element
812
+ * never does, regardless. Without a schema, or for a property name the
813
+ * schema does not name this way at this exact position, the key converts as usual (a
814
+ * text-string key). Pass `false` explicitly to keep every property as a
815
+ * plain text-string key even when the schema would otherwise name one —
816
+ * e.g. for code that writes `{ title: "oops" }` and wants that spelled
817
+ * literally rather than resolved to `-1`. Requires `cddl` — has no effect
818
+ * on its own.
819
+ *
820
+ * @default true
821
+ */
822
+ eRefKeys?: boolean;
823
+ /**
824
+ * Whether a tag the CDDL schema requires is added to a value that lacks
825
+ * it — e.g. with `{ t: time }` (prelude `time = #6.1(number)`),
826
+ * `{ t: -14159024 }` (a plain `number`, no `Tag.symbol`) converts to
827
+ * `{"t": 1(-14159024)}` (CDN `{"t": DT'1969-07-21T02:56:16Z'}`). The
828
+ * mirror image of `ToJSOptions.implicitTags`.
829
+ *
830
+ * Inference is a fallback, never a rewrite of valid data: nothing is
831
+ * added when the converted value already matches the schema, and at
832
+ * every choice an alternative the value matches as-is wins — with
833
+ * `time / number`, a plain `5` stays `5`. Only a `#6.N(type)` with a
834
+ * literal tag number is inferred (the tag is created the same way a
835
+ * `Tag.symbol`-annotated value would be, so tag 1 becomes a `DT'…'`
836
+ * node); a value already carrying `Tag.symbol` is used as given. When
837
+ * even inference cannot make the value match, it is left unchanged and
838
+ * the usual `CddlMismatchError` is thrown.
839
+ *
840
+ * Requires `cddl` — has no effect on its own. Pass `false` to require
841
+ * every tag to be spelled out via `Tag.symbol`, as without a schema.
842
+ *
843
+ * @default true
844
+ */
845
+ implicitTags?: boolean;
528
846
  }
847
+ /**
848
+ * Context passed to a `toCDN()` `itemOptions` callback (see
849
+ * `ToCDNOptions.itemOptions`) describing where the node being visited sits
850
+ * in the tree. Structurally the same shape as `ItemContext` (`toJS()`'s
851
+ * own per-item context — see there for the full rationale behind each
852
+ * field), but `path`'s map-key segments are derived the CDN way: a
853
+ * text-string key contributes its string value, any other key contributes
854
+ * its own `toCDN()` rendering — there is no "JS value" for a CDN key to
855
+ * contribute instead, since converting to JS is not what this call is
856
+ * doing.
857
+ *
858
+ * Unlike `ItemContext`, a node's `toCDN()` `itemOptions` callback can run
859
+ * more than once for reasons `toJS()`'s never does: `inlineLeafContainers`
860
+ * (and a tag/app-sequence value's own multi-word check) render an entry a
861
+ * second time, at the *same* depth and options, purely to answer a layout
862
+ * question (does it fit on one line? is it multi-word?) before the real
863
+ * render — an existing, deliberate characteristic of `toCDN()` itself (see
864
+ * `serializeContainer` in `cdn/serialize-utils.ts`), not something
865
+ * `itemOptions` introduces. Write this callback as a pure function of
866
+ * `item`/`ctx`, the same guidance as `ItemContext`'s own, for the same
867
+ * reason: it must not assume, or count, how many times it runs.
868
+ */
869
+ export interface CdnItemContext {
870
+ /** The direct parent AST node. `undefined` for the root value being converted. */
871
+ parent?: CborItem;
872
+ /**
873
+ * The path segment identifying this node: an array index (`number`), or
874
+ * a map key's string value/`toCDN()` rendering (see `CdnItemContext`'s
875
+ * own note). Always equal to the last element of `path`, except
876
+ * `undefined` for the root value and while converting a map key itself
877
+ * (`isMapKey: true`). A tag or app-sequence wrapper's content inherits
878
+ * the wrapper's own `key` (and `path`) unchanged, since the wrapper adds
879
+ * no segment of its own.
880
+ */
881
+ key?: unknown;
882
+ /** The map key's own AST node, present when this node is a map entry's key or value. */
883
+ keyNode?: CborItem;
884
+ /**
885
+ * `true` when this invocation is for converting a map key itself, rather
886
+ * than one of that key's sibling value's descendants.
887
+ */
888
+ isMapKey?: boolean;
889
+ /**
890
+ * Path from the root to this node, as a sequence of array indices and
891
+ * map key identifiers (see `CdnItemContext`'s own note). Empty for the
892
+ * root value. A tag or app-sequence wrapper does not add a segment of
893
+ * its own — its content shares the wrapper's own path.
894
+ */
895
+ readonly path: readonly unknown[];
896
+ /**
897
+ * The options in effect for this node going into this call — the root
898
+ * options merged with whatever overrides its ancestors already
899
+ * returned, *before* this callback's own return value is merged on top.
900
+ * A mutable-looking field here (currently just the deprecated
901
+ * `textStringFormat`) is a fresh copy, not the live array in effect
902
+ * elsewhere — mutating it in place has no effect on this node, its
903
+ * siblings, or the caller's own options; return an override instead.
904
+ */
905
+ readonly options: ReadonlyToCDNOptions;
906
+ }
907
+ /**
908
+ * `ToCDNOptions`, as handed to code that must not mutate it in place —
909
+ * `CdnItemContext.options` — the `toCDN()` counterpart of
910
+ * `ReadonlyToJSNodeOptions`. `textStringFormat` (the one field on
911
+ * `ToCDNOptions` that's an ordinary mutable array, and already deprecated)
912
+ * is narrowed to a readonly array for the same reason `extensions` is
913
+ * there; see `ReadonlyToJSNodeOptions`'s own doc for the full rationale.
914
+ */
915
+ export type ReadonlyToCDNOptions = Readonly<Omit<ToCDNOptions, 'textStringFormat'>> & {
916
+ readonly textStringFormat?: readonly TextStringFormat[];
917
+ };
529
918
  export interface ToCDNOptions {
530
919
  /**
531
920
  * Indentation for pretty-printing.
@@ -1049,6 +1438,39 @@ export interface ToCDNOptions {
1049
1438
  * @default 'auto'
1050
1439
  */
1051
1440
  encodingIndicators?: 'always' | 'auto' | 'never';
1441
+ /**
1442
+ * Called for every node during `toCDN()`, before that node is rendered,
1443
+ * to override the options used for its subtree. Return a partial options
1444
+ * object to merge over the options in effect for this node (they apply to
1445
+ * this node and are inherited by its descendants, who may override them
1446
+ * again); return `undefined` to make no change.
1447
+ *
1448
+ * This is the mechanism for applying an option to only part of a
1449
+ * document — e.g. rendering one array element in hex while the rest stay
1450
+ * decimal — keyed off `ctx.path` or the node's own shape
1451
+ * (`item instanceof ...`).
1452
+ *
1453
+ * **May be called more than once for the same AST node** — see
1454
+ * `CdnItemContext`'s own note on why, and why this callback should be a
1455
+ * pure function of `item`/`ctx` rather than relying on how many times it
1456
+ * runs.
1457
+ *
1458
+ * Not called for keys of an indefinite-length string's chunks or a
1459
+ * `<<...>>` sequence's items, which have no key of their own to convert
1460
+ * (only array elements and map entries/keys have a `path` segment).
1461
+ *
1462
+ * @example
1463
+ * // Render only the value at key "raw" using hex integers, leaving the
1464
+ * // rest of the document in the default decimal format.
1465
+ * item.toCDN({
1466
+ * indent: 2,
1467
+ * itemOptions: (_node, ctx) =>
1468
+ * ctx.path.length === 1 && ctx.path[0] === 'raw'
1469
+ * ? { intFormat: 'hex' }
1470
+ * : undefined,
1471
+ * });
1472
+ */
1473
+ itemOptions?: (item: CborItem, ctx: CdnItemContext) => Partial<ToCDNOptions> | undefined;
1052
1474
  }
1053
1475
  export type TextStringFormat = 'newline' | 'cdn' | DeprecatedTextStringFormat;
1054
1476
  /** @deprecated Use `'cdn'` instead. */
@@ -19,8 +19,16 @@ export declare function parseHexFloat(s: string): number;
19
19
  * Convert a JS number to a normalized hex float string compatible with
20
20
  * CDN diagnostic notation.
21
21
  *
22
- * - Normal values: `0x1.[hex fraction]p[+-][exp]` (e.g. `0x1.8p+0` for 1.5)
23
- * - Subnormal values: `0x0.[hex fraction]p-1022`
22
+ * - Every nonzero finite value: `0x1.[hex fraction]p[+-][exp]` (e.g.
23
+ * `0x1.8p+0` for 1.5) including a *subnormal* double, renormalized
24
+ * into this same leading-`1` form rather than spelled out anchored to
25
+ * its own stored field layout (`0x0.[hex fraction]p-1022`, still a
26
+ * correct spelling of the same value, but needlessly longer: e.g.
27
+ * `Number.MIN_VALUE` used to read `0x0.0000000000001p-1022`, equal to
28
+ * but far longer than the normalized `0x1p-1074`. A hex-float literal
29
+ * is just `significand * 2^exponent` — there's no reason its spelling
30
+ * should vary by whether the *double* happened to run out of exponent
31
+ * range, once it's just text.)
24
32
  * - Zero: `0x0p+0` / `-0x0p+0`
25
33
  * - Non-finite values (NaN, ±Infinity) are returned unchanged as EDN tokens.
26
34
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cbortech/cbor",
3
- "version": "0.27.0",
3
+ "version": "0.27.2",
4
4
  "description": "Convert between CBOR, CDN (CBOR-EDN), and JavaScript values with CDDL validation",
5
5
  "keywords": [
6
6
  "cbor",
@@ -93,10 +93,10 @@
93
93
  "start": "node dist/index.js",
94
94
  "dev": "npm run build && npm start",
95
95
  "clean": "node -e \"['dist','coverage'].forEach(d=>require('fs').rmSync(d,{recursive:true,force:true}))\"",
96
- "test": "vitest run --exclude src/cdn/edn-test-vectors.test.ts --exclude src/cdn/edn-abnf-vectors.test.ts --exclude src/cddl/cddl-corpus.test.ts --exclude src/cddl/cddl-validation-vectors.test.ts --exclude site/src/playground.browser.test.ts",
96
+ "test": "vitest run --exclude src/cdn/edn-test-vectors.test.ts --exclude src/cdn/edn-abnf-vectors.test.ts --exclude src/cddl/cddl-corpus.test.ts --exclude src/cddl/cddl-validation-vectors.test.ts --exclude 'site/src/**/*.browser.test.ts'",
97
97
  "test:exports": "npm run build && node scripts/check-package-exports.mjs",
98
98
  "fetch-test-vectors": "node scripts/fetch-test-vectors.mjs",
99
- "test:node": "vitest run --exclude src/cdn/edn-test-vectors.test.ts --exclude src/cdn/edn-abnf-vectors.test.ts --exclude src/cddl/cddl-corpus.test.ts --exclude src/cddl/cddl-validation-vectors.test.ts --exclude site/src/playground.browser.test.ts",
99
+ "test:node": "vitest run --exclude src/cdn/edn-test-vectors.test.ts --exclude src/cdn/edn-abnf-vectors.test.ts --exclude src/cddl/cddl-corpus.test.ts --exclude src/cddl/cddl-validation-vectors.test.ts --exclude 'site/src/**/*.browser.test.ts'",
100
100
  "test:edn-vectors": "vitest run src/cdn/edn-test-vectors.test.ts",
101
101
  "test:edn-abnf": "vitest run src/cdn/edn-abnf-vectors.test.ts",
102
102
  "test:cddl-corpus": "vitest run src/cddl/cddl-corpus.test.ts",
@@ -118,19 +118,18 @@
118
118
  "site:test": "npm run build && npm run --workspace site test"
119
119
  },
120
120
  "devDependencies": {
121
- "@cbortech/hash-extension": "^0.2.3",
122
- "@types/node": "^24.13.3",
121
+ "@types/node": "^24.13.6",
123
122
  "@typescript/typescript6": "^6.0.2",
124
- "@vitest/browser": "^4.1.10",
125
- "@vitest/browser-playwright": "^4.1.10",
126
- "@vitest/coverage-v8": "^4.1.10",
127
- "@vitest/ui": "^4.1.10",
123
+ "@vitest/browser": "^4.1.11",
124
+ "@vitest/browser-playwright": "^4.1.11",
125
+ "@vitest/coverage-v8": "^4.1.11",
126
+ "@vitest/ui": "^4.1.11",
128
127
  "cross-env": "^10.1.0",
129
- "playwright": "^1.62.1",
130
- "prettier": "^3.9.6",
128
+ "playwright": "^1.63.0",
129
+ "prettier": "^3.9.8",
131
130
  "typescript": "^7.0.2",
132
- "vite": "^8.2.1",
133
- "vite-plugin-dts": "^5.0.3",
134
- "vitest": "^4.1.10"
131
+ "vite": "^8.3.0",
132
+ "vite-plugin-dts": "^5.1.1",
133
+ "vitest": "^4.1.11"
135
134
  }
136
135
  }