@bcts/dcbor 1.0.0-alpha.10

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 (45) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +13 -0
  3. package/dist/index.cjs +9151 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +3107 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.mts +3107 -0
  8. package/dist/index.d.mts.map +1 -0
  9. package/dist/index.iife.js +9155 -0
  10. package/dist/index.iife.js.map +1 -0
  11. package/dist/index.mjs +9027 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/package.json +80 -0
  14. package/src/.claude-flow/metrics/agent-metrics.json +1 -0
  15. package/src/.claude-flow/metrics/performance.json +87 -0
  16. package/src/.claude-flow/metrics/task-metrics.json +10 -0
  17. package/src/byte-string.ts +300 -0
  18. package/src/cbor-codable.ts +170 -0
  19. package/src/cbor-tagged-codable.ts +72 -0
  20. package/src/cbor-tagged-decodable.ts +184 -0
  21. package/src/cbor-tagged-encodable.ts +138 -0
  22. package/src/cbor-tagged.ts +104 -0
  23. package/src/cbor.ts +869 -0
  24. package/src/conveniences.ts +840 -0
  25. package/src/date.ts +553 -0
  26. package/src/decode.ts +276 -0
  27. package/src/diag.ts +462 -0
  28. package/src/dump.ts +277 -0
  29. package/src/error.ts +259 -0
  30. package/src/exact.ts +714 -0
  31. package/src/float.ts +279 -0
  32. package/src/global.d.ts +34 -0
  33. package/src/globals.d.ts +0 -0
  34. package/src/index.ts +180 -0
  35. package/src/map.ts +308 -0
  36. package/src/prelude.ts +70 -0
  37. package/src/set.ts +515 -0
  38. package/src/simple.ts +153 -0
  39. package/src/stdlib.ts +55 -0
  40. package/src/string-util.ts +55 -0
  41. package/src/tag.ts +53 -0
  42. package/src/tags-store.ts +294 -0
  43. package/src/tags.ts +231 -0
  44. package/src/varint.ts +124 -0
  45. package/src/walk.ts +516 -0
package/src/dump.ts ADDED
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Hex dump utilities for CBOR data.
3
+ *
4
+ * Affordances for viewing the encoded binary representation of CBOR as hexadecimal.
5
+ * Optionally annotates the output, breaking it up into semantically meaningful lines,
6
+ * formatting dates, and adding names of known tags.
7
+ *
8
+ * @module dump
9
+ */
10
+
11
+ import { type Cbor, MajorType, cborData } from "./cbor";
12
+ import { encodeVarInt } from "./varint";
13
+ import { flanked, sanitized } from "./string-util";
14
+ import type { TagsStore } from "./tags-store";
15
+ import { getGlobalTagsStore } from "./tags-store";
16
+ import { createTag } from "./tag";
17
+ import { CborError } from "./error";
18
+
19
+ /**
20
+ * Options for hex formatting.
21
+ */
22
+ export interface HexFormatOpts {
23
+ /** Whether to annotate the hex dump with semantic information */
24
+ annotate?: boolean;
25
+ /** Optional tags store for resolving tag names */
26
+ tagsStore?: TagsStore;
27
+ }
28
+
29
+ /**
30
+ * Convert bytes to hex string.
31
+ */
32
+ export const bytesToHex = (bytes: Uint8Array): string => {
33
+ return Array.from(bytes)
34
+ .map((b) => b.toString(16).padStart(2, "0"))
35
+ .join("");
36
+ };
37
+
38
+ /**
39
+ * Convert hex string to bytes.
40
+ */
41
+ export const hexToBytes = (hexString: string): Uint8Array => {
42
+ const hex = hexString.replace(/\s/g, "");
43
+ const bytes = new Uint8Array(hex.length / 2);
44
+ for (let i = 0; i < hex.length; i += 2) {
45
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
46
+ }
47
+ return bytes;
48
+ };
49
+
50
+ /**
51
+ * Returns the encoded hexadecimal representation of CBOR.
52
+ *
53
+ * @param cbor - CBOR value to convert
54
+ * @returns Hex string
55
+ */
56
+ export const hex = (cbor: Cbor): string => bytesToHex(cborData(cbor));
57
+
58
+ /**
59
+ * Returns the encoded hexadecimal representation of CBOR with options.
60
+ *
61
+ * Optionally annotates the output, e.g., breaking the output up into
62
+ * semantically meaningful lines, formatting dates, and adding names of
63
+ * known tags.
64
+ *
65
+ * @param cbor - CBOR value to convert
66
+ * @param opts - Formatting options
67
+ * @returns Hex string (possibly annotated)
68
+ */
69
+ export const hexOpt = (cbor: Cbor, opts: HexFormatOpts = {}): string => {
70
+ if (opts.annotate !== true) {
71
+ return hex(cbor);
72
+ }
73
+
74
+ const items = dumpItems(cbor, 0, opts);
75
+ const noteColumn = items.reduce((largest, item) => {
76
+ return Math.max(largest, item.formatFirstColumn().length);
77
+ }, 0);
78
+
79
+ // Round up to nearest multiple of 4
80
+ const roundedNoteColumn = ((noteColumn + 4) & ~3) - 1;
81
+
82
+ const lines = items.map((item) => item.format(roundedNoteColumn));
83
+ return lines.join("\n");
84
+ };
85
+
86
+ /**
87
+ * Returns the encoded hexadecimal representation of CBOR, with annotations.
88
+ *
89
+ * @param cbor - CBOR value to convert
90
+ * @param tagsStore - Optional tags store for tag name resolution
91
+ * @returns Annotated hex string
92
+ */
93
+ export const hexAnnotated = (cbor: Cbor, tagsStore?: TagsStore): string => {
94
+ // Use global tags store if not provided
95
+ tagsStore ??= getGlobalTagsStore();
96
+ return hexOpt(cbor, { annotate: true, tagsStore });
97
+ };
98
+
99
+ /**
100
+ * Internal structure for dump items.
101
+ */
102
+ class DumpItem {
103
+ constructor(
104
+ public level: number,
105
+ public data: Uint8Array[],
106
+ public note?: string,
107
+ ) {}
108
+
109
+ format(noteColumn: number): string {
110
+ const column1 = this.formatFirstColumn();
111
+ let column2 = "";
112
+ let padding = "";
113
+
114
+ if (this.note !== undefined) {
115
+ const paddingCount = Math.max(1, Math.min(39, noteColumn) - column1.length + 1);
116
+ padding = " ".repeat(paddingCount);
117
+ column2 = `# ${this.note}`;
118
+ }
119
+
120
+ return column1 + padding + column2;
121
+ }
122
+
123
+ formatFirstColumn(): string {
124
+ const indent = " ".repeat(this.level * 4);
125
+ const hexParts = this.data.map(bytesToHex).filter((x) => x.length > 0);
126
+ const hexStr = hexParts.join(" ");
127
+ return indent + hexStr;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Generate dump items for a CBOR value (recursive).
133
+ */
134
+ function dumpItems(cbor: Cbor, level: number, opts: HexFormatOpts): DumpItem[] {
135
+ const items: DumpItem[] = [];
136
+
137
+ switch (cbor.type) {
138
+ case MajorType.Unsigned: {
139
+ const data = cborData(cbor);
140
+ items.push(new DumpItem(level, [data], `unsigned(${cbor.value})`));
141
+ break;
142
+ }
143
+
144
+ case MajorType.Negative: {
145
+ const data = cborData(cbor);
146
+ const actualValue = typeof cbor.value === "bigint" ? -1n - cbor.value : -1 - cbor.value;
147
+ items.push(new DumpItem(level, [data], `negative(${actualValue})`));
148
+ break;
149
+ }
150
+
151
+ case MajorType.ByteString: {
152
+ const header = encodeVarInt(cbor.value.length, MajorType.ByteString);
153
+ items.push(new DumpItem(level, [header], `bytes(${cbor.value.length})`));
154
+
155
+ if (cbor.value.length > 0) {
156
+ let note: string | undefined = undefined;
157
+ // Try to decode as UTF-8 string for annotation
158
+ try {
159
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(cbor.value);
160
+ const sanitizedText = sanitized(text);
161
+ if (sanitizedText !== undefined && sanitizedText !== "") {
162
+ note = flanked(sanitizedText, '"', '"');
163
+ }
164
+ } catch {
165
+ // Not valid UTF-8, no annotation
166
+ }
167
+
168
+ items.push(new DumpItem(level + 1, [cbor.value], note));
169
+ }
170
+ break;
171
+ }
172
+
173
+ case MajorType.Text: {
174
+ const utf8Data = new TextEncoder().encode(cbor.value);
175
+ const header = encodeVarInt(utf8Data.length, MajorType.Text);
176
+ const firstByte = header[0];
177
+ if (firstByte === undefined) {
178
+ throw new CborError({ type: "Custom", message: "Invalid varint encoding" });
179
+ }
180
+ const headerData = [new Uint8Array([firstByte]), header.slice(1)];
181
+
182
+ items.push(new DumpItem(level, headerData, `text(${utf8Data.length})`));
183
+
184
+ items.push(new DumpItem(level + 1, [utf8Data], flanked(cbor.value, '"', '"')));
185
+ break;
186
+ }
187
+
188
+ case MajorType.Array: {
189
+ const header = encodeVarInt(cbor.value.length, MajorType.Array);
190
+ const firstByte = header[0];
191
+ if (firstByte === undefined) {
192
+ throw new CborError({ type: "Custom", message: "Invalid varint encoding" });
193
+ }
194
+ const headerData = [new Uint8Array([firstByte]), header.slice(1)];
195
+
196
+ items.push(new DumpItem(level, headerData, `array(${cbor.value.length})`));
197
+
198
+ for (const item of cbor.value) {
199
+ items.push(...dumpItems(item, level + 1, opts));
200
+ }
201
+ break;
202
+ }
203
+
204
+ case MajorType.Map: {
205
+ const header = encodeVarInt(cbor.value.size, MajorType.Map);
206
+ const firstByte = header[0];
207
+ if (firstByte === undefined) {
208
+ throw new CborError({ type: "Custom", message: "Invalid varint encoding" });
209
+ }
210
+ const headerData = [new Uint8Array([firstByte]), header.slice(1)];
211
+
212
+ items.push(new DumpItem(level, headerData, `map(${cbor.value.size})`));
213
+
214
+ for (const entry of cbor.value.entriesArray) {
215
+ items.push(...dumpItems(entry.key, level + 1, opts));
216
+ items.push(...dumpItems(entry.value, level + 1, opts));
217
+ }
218
+ break;
219
+ }
220
+
221
+ case MajorType.Tagged: {
222
+ const tagValue = cbor.tag;
223
+ if (tagValue === undefined) {
224
+ throw new CborError({ type: "Custom", message: "Tagged CBOR value must have a tag" });
225
+ }
226
+ const header = encodeVarInt(
227
+ typeof tagValue === "bigint" ? Number(tagValue) : tagValue,
228
+ MajorType.Tagged,
229
+ );
230
+ const firstByte = header[0];
231
+ if (firstByte === undefined) {
232
+ throw new CborError({ type: "Custom", message: "Invalid varint encoding" });
233
+ }
234
+ const headerData = [new Uint8Array([firstByte]), header.slice(1)];
235
+
236
+ const noteComponents: string[] = [`tag(${tagValue})`];
237
+
238
+ // Add tag name if tags store is provided
239
+ const numericTagValue = typeof tagValue === "bigint" ? Number(tagValue) : tagValue;
240
+ const tag = createTag(numericTagValue);
241
+ const tagName = opts.tagsStore?.assignedNameForTag(tag);
242
+ if (tagName !== undefined) {
243
+ noteComponents.push(tagName);
244
+ }
245
+
246
+ const tagNote = noteComponents.join(" ");
247
+
248
+ items.push(new DumpItem(level, headerData, tagNote));
249
+
250
+ items.push(...dumpItems(cbor.value, level + 1, opts));
251
+ break;
252
+ }
253
+
254
+ case MajorType.Simple: {
255
+ const data = cborData(cbor);
256
+ const simple = cbor.value;
257
+ let note: string;
258
+
259
+ if (simple.type === "True") {
260
+ note = "true";
261
+ } else if (simple.type === "False") {
262
+ note = "false";
263
+ } else if (simple.type === "Null") {
264
+ note = "null";
265
+ } else if (simple.type === "Float") {
266
+ note = `${simple.value}`;
267
+ } else {
268
+ note = "simple";
269
+ }
270
+
271
+ items.push(new DumpItem(level, [data], note));
272
+ break;
273
+ }
274
+ }
275
+
276
+ return items;
277
+ }
package/src/error.ts ADDED
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Error types for CBOR encoding and decoding.
3
+ *
4
+ * @module error
5
+ */
6
+
7
+ import type { Tag } from "./tag";
8
+ import { tagToString } from "./tag";
9
+
10
+ /**
11
+ * A comprehensive set of errors that can occur during CBOR encoding and
12
+ * decoding operations with special focus on enforcing the deterministic
13
+ * encoding rules specified in the dCBOR specification.
14
+ *
15
+ * The dCBOR implementation validates all encoded CBOR against the
16
+ * deterministic encoding requirements of RFC 8949 §4.2.1, plus additional
17
+ * constraints defined in the dCBOR application profile. These errors represent
18
+ * all the possible validation failures and decoding issues that can arise.
19
+ */
20
+ export type Error =
21
+ /**
22
+ * The CBOR data ended prematurely during decoding, before a complete CBOR
23
+ * item could be decoded. This typically happens when a CBOR item's
24
+ * structure indicates more data than is actually present.
25
+ */
26
+ | { readonly type: "Underrun" }
27
+ /**
28
+ * An unsupported or invalid value was encountered in a CBOR header byte.
29
+ * The parameter contains the unsupported header byte value.
30
+ * This can occur when decoding CBOR that uses unsupported features or is
31
+ * malformed.
32
+ */
33
+ | { readonly type: "UnsupportedHeaderValue"; readonly value: number }
34
+ /**
35
+ * A CBOR numeric value was encoded in a non-canonical form, violating the
36
+ * deterministic encoding requirement of dCBOR (per Section 2.3 of the
37
+ * dCBOR specification).
38
+ *
39
+ * This error is triggered when:
40
+ * - An integer is not encoded in its shortest possible form
41
+ * - A floating point value that could be represented as an integer was not
42
+ * reduced
43
+ * - A NaN value was not encoded in its canonical form (`f97e00`)
44
+ */
45
+ | { readonly type: "NonCanonicalNumeric" }
46
+ /**
47
+ * An invalid CBOR simple value was encountered during decoding.
48
+ *
49
+ * Per Section 2.4 of the dCBOR specification, only `false`, `true`,
50
+ * `null`, and floating point values are valid simple values in dCBOR.
51
+ * All other major type 7 values are invalid.
52
+ */
53
+ | { readonly type: "InvalidSimpleValue" }
54
+ /**
55
+ * A CBOR text string was not valid UTF-8. The parameter contains the
56
+ * specific error message.
57
+ *
58
+ * All CBOR text strings (major type 3) must be valid UTF-8 per RFC 8949.
59
+ */
60
+ | { readonly type: "InvalidString"; readonly message: string }
61
+ /**
62
+ * A CBOR text string was not encoded in Unicode Canonical Normalization
63
+ * Form C (NFC).
64
+ *
65
+ * Per Section 2.5 of the dCBOR specification, all text strings must be in
66
+ * NFC form, and decoders must reject any encoded text strings that are
67
+ * not in NFC.
68
+ */
69
+ | { readonly type: "NonCanonicalString" }
70
+ /**
71
+ * The decoded CBOR item didn't consume all input data.
72
+ * The parameter contains the number of unused bytes.
73
+ *
74
+ * This error occurs when decoding functions expect exactly one CBOR item
75
+ * but the input contains additional data after a valid CBOR item.
76
+ */
77
+ | { readonly type: "UnusedData"; readonly count: number }
78
+ /**
79
+ * The keys in a decoded CBOR map were not in the canonical lexicographic order
80
+ * of their encoding.
81
+ *
82
+ * Per the CDE specification and Section 2.1 of dCBOR, map keys must be in
83
+ * ascending lexicographic order of their encoded representation for
84
+ * deterministic encoding.
85
+ */
86
+ | { readonly type: "MisorderedMapKey" }
87
+ /**
88
+ * A decoded CBOR map contains duplicate keys, which is invalid.
89
+ *
90
+ * Per Section 2.2 of the dCBOR specification, CBOR maps must not contain
91
+ * duplicate keys, and decoders must reject encoded maps with duplicate
92
+ * keys.
93
+ */
94
+ | { readonly type: "DuplicateMapKey" }
95
+ /**
96
+ * A requested key was not found in a CBOR map during data extraction.
97
+ */
98
+ | { readonly type: "MissingMapKey" }
99
+ /**
100
+ * A CBOR numeric value could not be represented in the specified target
101
+ * numeric type.
102
+ *
103
+ * This occurs when attempting to convert a CBOR number to a numeric
104
+ * type that is too small to represent the value without loss of
105
+ * precision.
106
+ */
107
+ | { readonly type: "OutOfRange" }
108
+ /**
109
+ * The CBOR value is not of the expected type for a conversion or
110
+ * operation.
111
+ *
112
+ * This occurs when attempting to convert a CBOR value to a type that
113
+ * doesn't match the actual CBOR item's type (e.g., trying to convert a
114
+ * string to an integer).
115
+ */
116
+ | { readonly type: "WrongType" }
117
+ /**
118
+ * The CBOR tagged value had a different tag than expected.
119
+ * Contains the expected tag and the actual tag found.
120
+ */
121
+ | { readonly type: "WrongTag"; readonly expected: Tag; readonly actual: Tag }
122
+ /**
123
+ * Invalid UTF‑8 in a text string.
124
+ */
125
+ | { readonly type: "InvalidUtf8"; readonly message: string }
126
+ /**
127
+ * Invalid ISO 8601 date format.
128
+ */
129
+ | { readonly type: "InvalidDate"; readonly message: string }
130
+ /**
131
+ * Custom error message.
132
+ */
133
+ | { readonly type: "Custom"; readonly message: string };
134
+
135
+ /**
136
+ * Create a custom error with a message.
137
+ *
138
+ * Matches Rust's `Error::msg()` method.
139
+ */
140
+ export const errorMsg = (message: string): Error => ({
141
+ type: "Custom",
142
+ message,
143
+ });
144
+
145
+ /**
146
+ * Convert an Error to a display string.
147
+ *
148
+ * Matches Rust's `Display` trait / `to_string()` method.
149
+ */
150
+ export const errorToString = (error: Error): string => {
151
+ switch (error.type) {
152
+ case "Underrun":
153
+ return "early end of CBOR data";
154
+ case "UnsupportedHeaderValue":
155
+ return "unsupported value in CBOR header";
156
+ case "NonCanonicalNumeric":
157
+ return "a CBOR numeric value was encoded in non-canonical form";
158
+ case "InvalidSimpleValue":
159
+ return "an invalid CBOR simple value was encountered";
160
+ case "InvalidString":
161
+ return `an invalidly-encoded UTF-8 string was encountered in the CBOR (${error.message})`;
162
+ case "NonCanonicalString":
163
+ return "a CBOR string was not encoded in Unicode Canonical Normalization Form C";
164
+ case "UnusedData":
165
+ return `the decoded CBOR had ${error.count} extra bytes at the end`;
166
+ case "MisorderedMapKey":
167
+ return "the decoded CBOR map has keys that are not in canonical order";
168
+ case "DuplicateMapKey":
169
+ return "the decoded CBOR map has a duplicate key";
170
+ case "MissingMapKey":
171
+ return "missing CBOR map key";
172
+ case "OutOfRange":
173
+ return "the CBOR numeric value could not be represented in the specified numeric type";
174
+ case "WrongType":
175
+ return "the decoded CBOR value was not the expected type";
176
+ case "WrongTag":
177
+ return `expected CBOR tag ${tagToString(error.expected)}, but got ${tagToString(error.actual)}`;
178
+ case "InvalidUtf8":
179
+ return `invalid UTF‑8 string: ${error.message}`;
180
+ case "InvalidDate":
181
+ return `invalid ISO 8601 date string: ${error.message}`;
182
+ case "Custom":
183
+ return error.message;
184
+ }
185
+ };
186
+
187
+ /**
188
+ * Result type matching Rust's `Result<T, Error>`.
189
+ *
190
+ * In TypeScript, we use a discriminated union for Result instead of
191
+ * try/catch for better type safety and Rust compatibility.
192
+ */
193
+ export type Result<T> = { ok: true; value: T } | { ok: false; error: Error };
194
+
195
+ /**
196
+ * Create a successful Result.
197
+ */
198
+ export const Ok = <T>(value: T): Result<T> => ({
199
+ ok: true,
200
+ value,
201
+ });
202
+
203
+ /**
204
+ * Create a failed Result.
205
+ */
206
+ export const Err = <T>(error: Error): Result<T> => ({
207
+ ok: false,
208
+ error,
209
+ });
210
+
211
+ /**
212
+ * Typed error class for all CBOR-related errors.
213
+ *
214
+ * Wraps the discriminated union Error type in a JavaScript Error object
215
+ * for proper error handling with stack traces.
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * throw new CborError({ type: 'Underrun' });
220
+ * throw new CborError({ type: 'WrongTag', expected: tag1, actual: tag2 });
221
+ * ```
222
+ */
223
+ export class CborError extends Error {
224
+ /**
225
+ * The structured error information.
226
+ */
227
+ public readonly errorType: Error;
228
+
229
+ /**
230
+ * Create a new CborError.
231
+ *
232
+ * @param errorType - The discriminated union error type
233
+ * @param message - Optional custom message (defaults to errorToString(errorType))
234
+ */
235
+ constructor(errorType: Error, message?: string) {
236
+ super(message ?? errorToString(errorType));
237
+ this.name = "CborError";
238
+ this.errorType = errorType;
239
+
240
+ // Maintains proper stack trace for where error was thrown (V8 only)
241
+ if ("captureStackTrace" in Error) {
242
+ (
243
+ Error as {
244
+ captureStackTrace(target: object, constructor: typeof CborError): void;
245
+ }
246
+ ).captureStackTrace(this, CborError);
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Check if an error is a CborError.
252
+ *
253
+ * @param error - Error to check
254
+ * @returns True if error is a CborError
255
+ */
256
+ static isCborError(error: unknown): error is CborError {
257
+ return error instanceof CborError;
258
+ }
259
+ }