@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/diag.ts ADDED
@@ -0,0 +1,462 @@
1
+ /**
2
+ * Enhanced diagnostic formatting for CBOR values.
3
+ *
4
+ * Provides multiple formatting options including
5
+ * - Annotated diagnostics with tag names
6
+ * - Summarized values using custom summarizers
7
+ * - Flat (single-line) vs. pretty (multi-line) formatting
8
+ * - Configurable tag store usage
9
+ *
10
+ * @module diag
11
+ */
12
+
13
+ import { type Cbor, MajorType, type Simple } from "./cbor";
14
+ import { bytesToHex } from "./dump";
15
+ import type { CborMap } from "./map";
16
+ import { getGlobalTagsStore, type TagsStore } from "./tags-store";
17
+ import type { Tag } from "./tag";
18
+ import type { WalkElement } from "./walk";
19
+
20
+ /**
21
+ * Options for diagnostic formatting.
22
+ */
23
+ export interface DiagFormatOpts {
24
+ /**
25
+ * Add tag names as annotations.
26
+ * When true, tagged values are displayed as "tagName(content)" instead of "tagValue(content)".
27
+ *
28
+ * @default false
29
+ */
30
+ annotate?: boolean;
31
+
32
+ /**
33
+ * Use custom summarizers for tagged values.
34
+ * When true, calls registered summarizers for tagged values.
35
+ *
36
+ * @default false
37
+ */
38
+ summarize?: boolean;
39
+
40
+ /**
41
+ * Single-line (flat) output.
42
+ * When true, arrays and maps are formatted without line breaks.
43
+ *
44
+ * @default false
45
+ */
46
+ flat?: boolean;
47
+
48
+ /**
49
+ * Tag store to use for tag name resolution.
50
+ * - TagsStore instance: Use specific store
51
+ * - 'global': Use global singleton store
52
+ * - 'none': Don't use any store (show tag numbers)
53
+ *
54
+ * @default 'global'
55
+ */
56
+ tags?: TagsStore | "global" | "none";
57
+
58
+ /**
59
+ * Current indentation level (internal use for recursion).
60
+ * @internal
61
+ */
62
+ indent?: number;
63
+
64
+ /**
65
+ * Indentation string (spaces per level).
66
+ * @internal
67
+ */
68
+ indentString?: string;
69
+ }
70
+
71
+ /**
72
+ * Default formatting options.
73
+ */
74
+ const DEFAULT_OPTS = {
75
+ annotate: false,
76
+ summarize: false,
77
+ flat: false,
78
+ tags: "global" as const,
79
+ indent: 0,
80
+ indentString: " ", // 4 spaces to match Rust
81
+ } as const satisfies DiagFormatOpts;
82
+
83
+ /**
84
+ * Format CBOR value as diagnostic notation with options.
85
+ *
86
+ * @param cbor - CBOR value to format
87
+ * @param opts - Formatting options
88
+ * @returns Diagnostic string
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const value = cbor({ name: 'Alice', age: 30 });
93
+ * console.log(diagnosticOpt(value, { flat: true }));
94
+ * // {\"name\": \"Alice\", \"age\": 30}
95
+ *
96
+ * const tagged = createTaggedCbor({ ... });
97
+ * console.log(diagnosticOpt(tagged, { annotate: true }));
98
+ * // date(1234567890)
99
+ * ```
100
+ */
101
+ export function diagnosticOpt(cbor: Cbor, opts?: DiagFormatOpts): string {
102
+ const options = { ...DEFAULT_OPTS, ...opts };
103
+ return formatDiagnostic(cbor, options);
104
+ }
105
+
106
+ /**
107
+ * Format CBOR value as standard diagnostic notation.
108
+ *
109
+ * @param cbor - CBOR value to format
110
+ * @returns Diagnostic string (pretty-printed with multiple lines for complex structures)
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const value = cbor([1, 2, 3]);
115
+ * console.log(diagnostic(value));
116
+ * // For simple arrays: "[1, 2, 3]"
117
+ * // For nested structures: multi-line formatted output
118
+ * ```
119
+ */
120
+ export function diagnostic(cbor: Cbor): string {
121
+ return diagnosticOpt(cbor);
122
+ }
123
+
124
+ /**
125
+ * Format CBOR value with tag name annotations.
126
+ *
127
+ * Tagged values are displayed with their registered names instead of numeric tags.
128
+ *
129
+ * @param cbor - CBOR value to format
130
+ * @returns Annotated diagnostic string (pretty-printed format)
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * const date = CborDate.now().taggedCbor();
135
+ * console.log(diagnosticAnnotated(date));
136
+ * // date(1234567890) instead of 1(1234567890)
137
+ * ```
138
+ */
139
+ export function diagnosticAnnotated(cbor: Cbor): string {
140
+ return diagnosticOpt(cbor, { annotate: true });
141
+ }
142
+
143
+ /**
144
+ * Format CBOR value as flat (single-line) diagnostic notation.
145
+ *
146
+ * Arrays and maps are formatted without line breaks.
147
+ *
148
+ * @param cbor - CBOR value to format
149
+ * @returns Flat diagnostic string
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * const nested = cbor([[1, 2], [3, 4]]);
154
+ * console.log(diagnosticFlat(nested));
155
+ * // "[[1, 2], [3, 4]]"
156
+ * ```
157
+ */
158
+ export function diagnosticFlat(cbor: Cbor): string;
159
+ // eslint-disable-next-line no-redeclare
160
+ export function diagnosticFlat(element: WalkElement): string;
161
+ // eslint-disable-next-line no-redeclare
162
+ export function diagnosticFlat(input: Cbor | WalkElement): string {
163
+ // Check if it's a WalkElement by checking for 'type' property
164
+ if (
165
+ typeof input === "object" &&
166
+ input !== null &&
167
+ "type" in input &&
168
+ (input.type === "single" || input.type === "keyvalue")
169
+ ) {
170
+ const element = input as WalkElement;
171
+ if (element.type === "single") {
172
+ return diagnosticOpt(element.cbor, { flat: true });
173
+ } else {
174
+ return `${diagnosticOpt(element.key, { flat: true })}: ${diagnosticOpt(element.value, { flat: true })}`;
175
+ }
176
+ }
177
+ // Otherwise treat as Cbor
178
+ return diagnosticOpt(input as Cbor, { flat: true });
179
+ }
180
+
181
+ /**
182
+ * Format CBOR value using custom summarizers for tagged values.
183
+ *
184
+ * If a summarizer is registered for a tagged value, uses that instead of
185
+ * showing the full content.
186
+ *
187
+ * @param cbor - CBOR value to format
188
+ * @returns Summarized diagnostic string
189
+ *
190
+ * @example
191
+ * ```typescript
192
+ * // If a summarizer is registered for tag 123:
193
+ * const tagged = cbor({ type: MajorType.Tagged, tag: 123, value: ... });
194
+ * console.log(summary(tagged));
195
+ * // "custom-summary" (instead of full content)
196
+ * ```
197
+ */
198
+ export function summary(cbor: Cbor): string {
199
+ return diagnosticOpt(cbor, { summarize: true, flat: true });
200
+ }
201
+
202
+ /**
203
+ * Internal recursive formatter.
204
+ *
205
+ * @internal
206
+ */
207
+ function formatDiagnostic(cbor: Cbor, opts: DiagFormatOpts): string {
208
+ switch (cbor.type) {
209
+ case MajorType.Unsigned:
210
+ return formatUnsigned(cbor.value);
211
+
212
+ case MajorType.Negative:
213
+ return formatNegative(cbor.value);
214
+
215
+ case MajorType.ByteString:
216
+ return formatBytes(cbor.value);
217
+
218
+ case MajorType.Text:
219
+ return formatText(cbor.value);
220
+
221
+ case MajorType.Array:
222
+ return formatArray(cbor.value, opts);
223
+
224
+ case MajorType.Map:
225
+ return formatMap(cbor.value, opts);
226
+
227
+ case MajorType.Tagged:
228
+ return formatTagged(cbor.tag, cbor.value, opts);
229
+
230
+ case MajorType.Simple:
231
+ return formatSimple(cbor.value);
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Format unsigned integer.
237
+ */
238
+ function formatUnsigned(value: number | bigint): string {
239
+ return String(value);
240
+ }
241
+
242
+ /**
243
+ * Format negative integer.
244
+ */
245
+ function formatNegative(value: number | bigint): string {
246
+ // Value is stored as magnitude, convert to actual negative value for display
247
+ if (typeof value === "bigint") {
248
+ return String(-value - 1n);
249
+ } else {
250
+ return String(-value - 1);
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Format byte string.
256
+ */
257
+ function formatBytes(value: Uint8Array): string {
258
+ return `h'${bytesToHex(value)}'`;
259
+ }
260
+
261
+ /**
262
+ * Format text string.
263
+ */
264
+ function formatText(value: string): string {
265
+ // Escape special characters
266
+ const escaped = value
267
+ .replace(/\\/g, "\\\\")
268
+ .replace(/"/g, '\\"')
269
+ .replace(/\n/g, "\\n")
270
+ .replace(/\r/g, "\\r")
271
+ .replace(/\t/g, "\\t");
272
+ return `"${escaped}"`;
273
+ }
274
+
275
+ /**
276
+ * Format array.
277
+ */
278
+ function formatArray(items: readonly Cbor[], opts: DiagFormatOpts): string {
279
+ if (items.length === 0) {
280
+ return "[]";
281
+ }
282
+
283
+ // Format items first to check their lengths
284
+ const formatted = items.map((item) => formatDiagnostic(item, opts));
285
+
286
+ // Decide between single-line and multi-line based on complexity
287
+ const shouldUseMultiLine =
288
+ opts.flat !== true &&
289
+ (containsComplexStructure(items) ||
290
+ formatted.join(", ").length > 20 ||
291
+ formatted.some((s) => s.length > 20));
292
+
293
+ if (shouldUseMultiLine) {
294
+ // Multi-line formatting
295
+ const indent = opts.indent ?? 0;
296
+ const indentStr = (opts.indentString ?? " ").repeat(indent);
297
+ const itemIndentStr = (opts.indentString ?? " ").repeat(indent + 1);
298
+
299
+ const formattedWithIndent = items.map((item) => {
300
+ const childOpts = { ...opts, indent: indent + 1 };
301
+ const itemStr = formatDiagnostic(item, childOpts);
302
+ return `${itemIndentStr}${itemStr}`;
303
+ });
304
+
305
+ return `[\n${formattedWithIndent.join(",\n")}\n${indentStr}]`;
306
+ } else {
307
+ // Single-line formatting
308
+ return `[${formatted.join(", ")}]`;
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Check if items contain complex structures (arrays or maps).
314
+ */
315
+ function containsComplexStructure(items: readonly Cbor[]): boolean {
316
+ return items.some((item) => item.type === MajorType.Array || item.type === MajorType.Map);
317
+ }
318
+
319
+ /**
320
+ * Format map.
321
+ */
322
+ function formatMap(map: CborMap, opts: DiagFormatOpts): string {
323
+ // Extract entries from CborMap or use empty array
324
+ const entries = map?.entriesArray ?? [];
325
+
326
+ if (entries.length === 0) {
327
+ return "{}";
328
+ }
329
+
330
+ interface FormattedPair {
331
+ key: string;
332
+ value: string;
333
+ }
334
+
335
+ // Format each key-value pair
336
+ const formattedPairs: FormattedPair[] = entries.map((entry: { key: Cbor; value: Cbor }) => ({
337
+ key: formatDiagnostic(entry.key, opts),
338
+ value: formatDiagnostic(entry.value, opts),
339
+ }));
340
+
341
+ // Decide between single-line and multi-line based on complexity
342
+ const totalLength = formattedPairs.reduce(
343
+ (sum: number, pair: FormattedPair) => sum + pair.key.length + pair.value.length + 2,
344
+ 0,
345
+ ); // +2 for ": "
346
+
347
+ const shouldUseMultiLine =
348
+ opts.flat !== true &&
349
+ (entries.some(
350
+ (e: { key: Cbor; value: Cbor }) =>
351
+ e.key.type === MajorType.Array ||
352
+ e.key.type === MajorType.Map ||
353
+ e.value.type === MajorType.Array ||
354
+ e.value.type === MajorType.Map,
355
+ ) ||
356
+ totalLength > 40 ||
357
+ entries.length > 3);
358
+
359
+ if (shouldUseMultiLine) {
360
+ // Multi-line formatting
361
+ const indent = opts.indent ?? 0;
362
+ const indentStr = (opts.indentString ?? " ").repeat(indent);
363
+ const itemIndentStr = (opts.indentString ?? " ").repeat(indent + 1);
364
+
365
+ const formattedEntries = formattedPairs.map((pair: FormattedPair) => {
366
+ return `${itemIndentStr}${pair.key}:\n${itemIndentStr}${pair.value}`;
367
+ });
368
+
369
+ return `{\n${formattedEntries.join(",\n")}\n${indentStr}}`;
370
+ } else {
371
+ // Single-line formatting
372
+ const pairs = formattedPairs.map((pair: FormattedPair) => `${pair.key}: ${pair.value}`);
373
+ return `{${pairs.join(", ")}}`;
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Format tagged value.
379
+ */
380
+ function formatTagged(tag: number | bigint, content: Cbor, opts: DiagFormatOpts): string {
381
+ // Check for summarizer first
382
+ if (opts.summarize === true) {
383
+ const store = resolveTagsStore(opts.tags);
384
+ const summarizer = store?.summarizer(tag);
385
+ if (summarizer !== undefined) {
386
+ return summarizer(content, opts.flat ?? false);
387
+ }
388
+ }
389
+
390
+ // Get tag name as comment if annotation is enabled
391
+ let comment: string | undefined;
392
+ if (opts.annotate === true) {
393
+ const store = resolveTagsStore(opts.tags);
394
+ const tagObj: Tag = { value: tag };
395
+ const assignedName = store?.assignedNameForTag(tagObj);
396
+ if (assignedName !== undefined) {
397
+ comment = assignedName;
398
+ }
399
+ }
400
+
401
+ // Always use tag number (not name) in the output
402
+ const tagStr = String(tag);
403
+
404
+ // Format content
405
+ const contentStr = formatDiagnostic(content, opts);
406
+
407
+ // Add comment if present
408
+ const result = `${tagStr}(${contentStr})`;
409
+ if (comment !== undefined) {
410
+ return `${result} / ${comment} /`;
411
+ }
412
+ return result;
413
+ }
414
+
415
+ /**
416
+ * Format simple value.
417
+ */
418
+ function formatSimple(value: Simple): string {
419
+ // Handle discriminated union
420
+ switch (value.type) {
421
+ case "True":
422
+ return "true";
423
+ case "False":
424
+ return "false";
425
+ case "Null":
426
+ return "null";
427
+ case "Float":
428
+ return formatFloat(value.value);
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Format float value.
434
+ */
435
+ function formatFloat(value: number): string {
436
+ if (isNaN(value)) {
437
+ return "NaN";
438
+ } else if (!isFinite(value)) {
439
+ return value > 0 ? "Infinity" : "-Infinity";
440
+ } else {
441
+ // Show decimal point for clarity, unless already in scientific notation
442
+ const str = String(value);
443
+ // Scientific notation (contains 'e') or already has decimal point
444
+ if (str.includes(".") || str.includes("e")) {
445
+ return str;
446
+ }
447
+ return `${str}.0`;
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Resolve tags store from option.
453
+ */
454
+ function resolveTagsStore(tags?: TagsStore | "global" | "none"): TagsStore | undefined {
455
+ if (tags === "none") {
456
+ return undefined;
457
+ } else if (tags === "global" || tags === undefined) {
458
+ return getGlobalTagsStore();
459
+ } else {
460
+ return tags;
461
+ }
462
+ }