@bcts/dcbor 1.0.0-alpha.8 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/tags-store.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ *
2
6
  * Tag registry and management system.
3
7
  *
4
8
  * The TagsStore provides a centralized registry for CBOR tags,
@@ -9,17 +13,26 @@
9
13
 
10
14
  import type { Cbor, CborNumber } from "./cbor";
11
15
  import type { Tag } from "./tag";
16
+ import type { Error as CborErrorType } from "./error";
17
+
18
+ /**
19
+ * Result type for summarizer functions, matching Rust's Result<String, Error>.
20
+ */
21
+ export type SummarizerResult =
22
+ | { readonly ok: true; readonly value: string }
23
+ | { readonly ok: false; readonly error: CborErrorType };
12
24
 
13
25
  /**
14
26
  * Function type for custom CBOR value summarizers.
15
27
  *
16
28
  * Summarizers provide custom string representations for tagged values.
29
+ * Returns a Result type matching Rust's `Result<String, Error>`.
17
30
  *
18
31
  * @param cbor - The CBOR value to summarize
19
32
  * @param flat - If true, produce single-line output
20
- * @returns String summary of the value
33
+ * @returns Result with summary string on success, or error on failure
21
34
  */
22
- export type CborSummarizer = (cbor: Cbor, flat: boolean) => string;
35
+ export type CborSummarizer = (cbor: Cbor, flat: boolean) => SummarizerResult;
23
36
 
24
37
  /**
25
38
  * Interface for tag store operations.
@@ -80,9 +93,9 @@ export interface TagsStoreTrait {
80
93
  * Stores tags with their names and optional summarizer functions.
81
94
  */
82
95
  export class TagsStore implements TagsStoreTrait {
83
- readonly #tagsByValue = new Map<string, Tag>();
84
- readonly #tagsByName = new Map<string, Tag>();
85
- readonly #summarizers = new Map<string, CborSummarizer>();
96
+ private readonly _tagsByValue = new Map<string, Tag>();
97
+ private readonly _tagsByName = new Map<string, Tag>();
98
+ private readonly _summarizers = new Map<string, CborSummarizer>();
86
99
 
87
100
  constructor() {
88
101
  // Start with empty store, matching Rust's Default implementation
@@ -92,7 +105,13 @@ export class TagsStore implements TagsStoreTrait {
92
105
  /**
93
106
  * Insert a tag into the registry.
94
107
  *
95
- * @param tag - The tag to register
108
+ * Matches Rust's TagsStore::insert() behavior:
109
+ * - Throws if the tag name is undefined or empty
110
+ * - Throws if a tag with the same value exists with a different name
111
+ * - Allows re-registering the same tag value with the same name
112
+ *
113
+ * @param tag - The tag to register (must have a non-empty name)
114
+ * @throws Error if tag has no name, empty name, or conflicts with existing registration
96
115
  *
97
116
  * @example
98
117
  * ```typescript
@@ -101,11 +120,25 @@ export class TagsStore implements TagsStoreTrait {
101
120
  * ```
102
121
  */
103
122
  insert(tag: Tag): void {
104
- const key = this.#valueKey(tag.value);
105
- this.#tagsByValue.set(key, tag);
106
- if (tag.name !== undefined) {
107
- this.#tagsByName.set(tag.name, tag);
123
+ const name = tag.name;
124
+
125
+ // Rust: let name = tag.name().unwrap(); assert!(!name.is_empty());
126
+ if (name === undefined || name === "") {
127
+ throw new Error(`Tag ${tag.value} must have a non-empty name`);
108
128
  }
129
+
130
+ const key = this._valueKey(tag.value);
131
+ const existing = this._tagsByValue.get(key);
132
+
133
+ // Rust: if old_name != name { panic!(...) }
134
+ if (existing?.name !== undefined && existing.name !== name) {
135
+ throw new Error(
136
+ `Attempt to register tag: ${tag.value} '${existing.name}' with different name: '${name}'`,
137
+ );
138
+ }
139
+
140
+ this._tagsByValue.set(key, tag);
141
+ this._tagsByName.set(name, tag);
109
142
  }
110
143
 
111
144
  /**
@@ -144,34 +177,13 @@ export class TagsStore implements TagsStoreTrait {
144
177
  * ```
145
178
  */
146
179
  setSummarizer(tagValue: CborNumber, summarizer: CborSummarizer): void {
147
- const key = this.#valueKey(tagValue);
148
- this.#summarizers.set(key, summarizer);
149
- }
150
-
151
- /**
152
- * Remove a tag from the registry.
153
- *
154
- * @param tagValue - The numeric tag value to remove
155
- * @returns true if a tag was removed, false otherwise
156
- */
157
- remove(tagValue: CborNumber): boolean {
158
- const key = this.#valueKey(tagValue);
159
- const tag = this.#tagsByValue.get(key);
160
- if (tag === undefined) {
161
- return false;
162
- }
163
-
164
- this.#tagsByValue.delete(key);
165
- if (tag.name !== undefined) {
166
- this.#tagsByName.delete(tag.name);
167
- }
168
- this.#summarizers.delete(key);
169
- return true;
180
+ const key = this._valueKey(tagValue);
181
+ this._summarizers.set(key, summarizer);
170
182
  }
171
183
 
172
184
  assignedNameForTag(tag: Tag): string | undefined {
173
- const key = this.#valueKey(tag.value);
174
- const stored = this.#tagsByValue.get(key);
185
+ const key = this._valueKey(tag.value);
186
+ const stored = this._tagsByValue.get(key);
175
187
  return stored?.name;
176
188
  }
177
189
 
@@ -180,12 +192,12 @@ export class TagsStore implements TagsStoreTrait {
180
192
  }
181
193
 
182
194
  tagForValue(value: CborNumber): Tag | undefined {
183
- const key = this.#valueKey(value);
184
- return this.#tagsByValue.get(key);
195
+ const key = this._valueKey(value);
196
+ return this._tagsByValue.get(key);
185
197
  }
186
198
 
187
199
  tagForName(name: string): Tag | undefined {
188
- return this.#tagsByName.get(name);
200
+ return this._tagsByName.get(name);
189
201
  }
190
202
 
191
203
  nameForValue(value: CborNumber): string {
@@ -194,35 +206,8 @@ export class TagsStore implements TagsStoreTrait {
194
206
  }
195
207
 
196
208
  summarizer(tag: CborNumber): CborSummarizer | undefined {
197
- const key = this.#valueKey(tag);
198
- return this.#summarizers.get(key);
199
- }
200
-
201
- /**
202
- * Get all registered tags.
203
- *
204
- * @returns Array of all registered tags
205
- */
206
- getAllTags(): Tag[] {
207
- return Array.from(this.#tagsByValue.values());
208
- }
209
-
210
- /**
211
- * Clear all registered tags and summarizers.
212
- */
213
- clear(): void {
214
- this.#tagsByValue.clear();
215
- this.#tagsByName.clear();
216
- this.#summarizers.clear();
217
- }
218
-
219
- /**
220
- * Get the number of registered tags.
221
- *
222
- * @returns Number of tags in the registry
223
- */
224
- get size(): number {
225
- return this.#tagsByValue.size;
209
+ const key = this._valueKey(tag);
210
+ return this._summarizers.get(key);
226
211
  }
227
212
 
228
213
  /**
@@ -231,7 +216,7 @@ export class TagsStore implements TagsStoreTrait {
231
216
  *
232
217
  * @private
233
218
  */
234
- #valueKey(value: CborNumber): string {
219
+ private _valueKey(value: CborNumber): string {
235
220
  return value.toString();
236
221
  }
237
222
  }
package/src/tags.ts CHANGED
@@ -1,6 +1,21 @@
1
1
  /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ *
2
6
  * Standard CBOR tag definitions from the IANA registry.
3
7
  *
8
+ * **Cross-package note (parity with Rust).** `bc-dcbor-rust/src/tags.rs`
9
+ * intentionally only defines the three tags dcbor itself needs:
10
+ * `TAG_DATE` (1), `TAG_POSITIVE_BIGNUM` (2), `TAG_NEGATIVE_BIGNUM` (3).
11
+ * Every other Blockchain Commons tag — UR types, IANA semantic tags, the
12
+ * Envelope/XID family — lives in the dedicated `bc-tags-rust` crate
13
+ * (mirrored as `@bcts/tags` in TypeScript). This file additionally
14
+ * re-exports many of those constants for ergonomic in-package use; they
15
+ * are duplicate convenience definitions, not authoritative. Prefer
16
+ * importing from `@bcts/tags` in new code; treat the extras here as
17
+ * deprecated aliases that may be relocated in a future release.
18
+ *
4
19
  * @module tags
5
20
  * @see https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
6
21
  */
@@ -40,6 +55,18 @@ export const TAG_POSITIVE_BIGNUM = 2;
40
55
  */
41
56
  export const TAG_NEGATIVE_BIGNUM = 3;
42
57
 
58
+ /**
59
+ * Name for tag 2 (positive bignum).
60
+ * Matches Rust's `TAG_NAME_POSITIVE_BIGNUM`.
61
+ */
62
+ export const TAG_NAME_POSITIVE_BIGNUM = "positive-bignum";
63
+
64
+ /**
65
+ * Name for tag 3 (negative bignum).
66
+ * Matches Rust's `TAG_NAME_NEGATIVE_BIGNUM`.
67
+ */
68
+ export const TAG_NAME_NEGATIVE_BIGNUM = "negative-bignum";
69
+
43
70
  /**
44
71
  * Tag 4: Decimal fraction [exponent, mantissa]
45
72
  */
@@ -148,11 +175,11 @@ export const TAG_SELF_DESCRIBE_CBOR = 55799;
148
175
  // Matches Rust's register_tags() functionality
149
176
  // ============================================================================
150
177
 
151
- import type { TagsStore } from "./tags-store";
178
+ import type { TagsStore, SummarizerResult } from "./tags-store";
152
179
  import { getGlobalTagsStore } from "./tags-store";
153
180
  import { CborDate } from "./date";
154
181
  import type { Cbor } from "./cbor";
155
- import { diagnostic } from "./diag";
182
+ import { biguintFromUntaggedCbor, bigintFromNegativeUntaggedCbor } from "./bignum";
156
183
 
157
184
  // Tag constants matching Rust
158
185
  export const TAG_DATE = 1;
@@ -169,13 +196,48 @@ export const registerTagsIn = (tagsStore: TagsStore): void => {
169
196
  tagsStore.insertAll(tags);
170
197
 
171
198
  // Set summarizer for date tag
172
- tagsStore.setSummarizer(TAG_DATE, (untaggedCbor: Cbor, _flat: boolean): string => {
199
+ tagsStore.setSummarizer(TAG_DATE, (untaggedCbor: Cbor, _flat: boolean): SummarizerResult => {
173
200
  try {
174
- return CborDate.fromUntaggedCbor(untaggedCbor).toString();
175
- } catch {
176
- return diagnostic(untaggedCbor);
201
+ return { ok: true, value: CborDate.fromUntaggedCbor(untaggedCbor).toString() };
202
+ } catch (e) {
203
+ // On error, return error result matching Rust's Result::Err
204
+ const message = e instanceof Error ? e.message : String(e);
205
+ return { ok: false, error: { type: "Custom", message } };
177
206
  }
178
207
  });
208
+
209
+ // Register bignum tags (matching Rust's #[cfg(feature = "num-bigint")] block)
210
+ const biguintTag = createTag(TAG_POSITIVE_BIGNUM, TAG_NAME_POSITIVE_BIGNUM);
211
+ const bigintTag = createTag(TAG_NEGATIVE_BIGNUM, TAG_NAME_NEGATIVE_BIGNUM);
212
+ tagsStore.insertAll([biguintTag, bigintTag]);
213
+
214
+ // Summarizer for tag 2 (positive bignum)
215
+ tagsStore.setSummarizer(
216
+ TAG_POSITIVE_BIGNUM,
217
+ (untaggedCbor: Cbor, _flat: boolean): SummarizerResult => {
218
+ try {
219
+ const value = biguintFromUntaggedCbor(untaggedCbor);
220
+ return { ok: true, value: `bignum(${value})` };
221
+ } catch (e) {
222
+ const message = e instanceof Error ? e.message : String(e);
223
+ return { ok: false, error: { type: "Custom", message } };
224
+ }
225
+ },
226
+ );
227
+
228
+ // Summarizer for tag 3 (negative bignum)
229
+ tagsStore.setSummarizer(
230
+ TAG_NEGATIVE_BIGNUM,
231
+ (untaggedCbor: Cbor, _flat: boolean): SummarizerResult => {
232
+ try {
233
+ const value = bigintFromNegativeUntaggedCbor(untaggedCbor);
234
+ return { ok: true, value: `bignum(${value})` };
235
+ } catch (e) {
236
+ const message = e instanceof Error ? e.message : String(e);
237
+ return { ok: false, error: { type: "Custom", message } };
238
+ }
239
+ },
240
+ );
179
241
  };
180
242
 
181
243
  /**
package/src/varint.ts CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ */
6
+
1
7
  import { type CborNumber, isCborNumber, type MajorType } from "./cbor";
2
8
  import { hasFractionalPart } from "./float";
3
9
  import { CborError } from "./error";
@@ -47,21 +53,20 @@ export const encodeVarInt = (value: CborNumber, majorType: MajorType): Uint8Arra
47
53
  return new Uint8Array(buffer);
48
54
  }
49
55
  } else {
50
- value = BigInt(value);
51
- const bitsNeeded = Math.ceil(Math.log2(Number(value)) / 8) * 8;
52
- if (bitsNeeded > 64) {
56
+ // Bigint branch — value is strictly greater than `Number.MAX_SAFE_INTEGER`,
57
+ // therefore strictly greater than `0xffffffff`. The CBOR encoding rule
58
+ // collapses to: 9 bytes total (header `0x1b | type` + 8 big-endian bytes)
59
+ // if the value fits in u64, otherwise `OutOfRange`. We must NOT use
60
+ // `Math.log2(Number(value))` here — `Number(value)` is lossy past 2^53
61
+ // and would mis-pick the encoding length for values near u64::MAX.
62
+ const big = BigInt(value);
63
+ if (big > 0xffffffffffffffffn) {
53
64
  throw new CborError({ type: "OutOfRange" });
54
65
  }
55
- const length = Math.ceil(bitsNeeded / 8) + 1;
56
- const buffer = new ArrayBuffer(length);
66
+ const buffer = new ArrayBuffer(9);
57
67
  const view = new DataView(buffer);
58
- let i = length - 1;
59
- while (value > 0) {
60
- view.setUint8(i, Number(value & 0xffn));
61
- value >>= 8n;
62
- i--;
63
- }
64
68
  view.setUint8(0, 0x1b | type);
69
+ view.setBigUint64(1, big);
65
70
  return new Uint8Array(buffer);
66
71
  }
67
72
  };