@bcts/known-values 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.
@@ -0,0 +1,425 @@
1
+ /**
2
+ * A value in a namespace of unsigned integers that represents a stand-alone
3
+ * ontological concept.
4
+ *
5
+ * Known Values provide a compact, deterministic way to represent commonly used
6
+ * ontological concepts such as relationships between entities, classes of
7
+ * entities, properties, or enumerated values. They are particularly useful as
8
+ * predicates in Gordian Envelope assertions, offering a more compact and
9
+ * deterministic alternative to URIs. However, known values are not exclusive
10
+ * to Gordian Envelopes and can be used in any context where a compact, unique
11
+ * identifier for a concept is needed.
12
+ *
13
+ * A Known Value is represented as a 64-bit unsigned integer with an optional
14
+ * human-readable name. This approach ensures:
15
+ *
16
+ * - **Compact binary representation** - Each Known Value requires only 1-9
17
+ * bytes depending on value range
18
+ * - **Deterministic encoding** - Every concept has exactly one valid binary
19
+ * representation
20
+ * - **Enhanced security** - Eliminates URI manipulation vulnerabilities
21
+ * - **Standardized semantics** - Values are registered in a central registry
22
+ *
23
+ * While Known Values are most commonly used as predicates in assertions, they
24
+ * can appear in any position in an Envelope (subject, predicate, or object).
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { KnownValue } from '@bcts/known-values';
29
+ *
30
+ * // Create a Known Value with a numeric value
31
+ * const knownValue = new KnownValue(42);
32
+ * console.log(knownValue.value()); // 42
33
+ *
34
+ * // Create a Known Value with a name
35
+ * const namedValue = new KnownValue(1, 'isA');
36
+ * console.log(namedValue.value()); // 1
37
+ * console.log(namedValue.name()); // "isA"
38
+ *
39
+ * // CBOR encoding
40
+ * const cbor = namedValue.taggedCbor();
41
+ * const bytes = namedValue.toCborData();
42
+ *
43
+ * // CBOR decoding
44
+ * const decoded = KnownValue.fromTaggedCbor(cbor);
45
+ * const decodedFromBytes = KnownValue.fromCborData(bytes);
46
+ *
47
+ * // Use a pre-defined Known Value from the registry
48
+ * import { IS_A } from '@bcts/known-values';
49
+ * console.log(IS_A.value()); // 1
50
+ * console.log(IS_A.name()); // "isA"
51
+ * ```
52
+ *
53
+ * @specification
54
+ *
55
+ * Known Values are defined in
56
+ * [BCR-2023-002](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2023-002-known-value.md)
57
+ * and implemented as an Envelope extension in
58
+ * [BCR-2023-003](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2023-003-envelope-known-value.md).
59
+ */
60
+
61
+ import {
62
+ type Cbor,
63
+ type Tag,
64
+ type CborTaggedEncodable,
65
+ type CborTaggedDecodable,
66
+ cbor,
67
+ cborData,
68
+ decodeCbor,
69
+ MajorType,
70
+ } from "@bcts/dcbor";
71
+ import { KNOWN_VALUE } from "@bcts/components";
72
+
73
+ /**
74
+ * The numeric value for the CBOR tag used for Known Values.
75
+ * This is Tag 40000 as defined in the Blockchain Commons registry.
76
+ */
77
+ export const TAG_KNOWN_VALUE = KNOWN_VALUE.value;
78
+
79
+ /**
80
+ * The CBOR tag used for Known Values.
81
+ * This is Tag 40000 as defined in the Blockchain Commons registry.
82
+ */
83
+ export const KNOWN_VALUE_TAG: Tag = KNOWN_VALUE;
84
+
85
+ /**
86
+ * Type for values that can be used to create a KnownValue.
87
+ * Supports both number (for values up to 2^53-1) and bigint (for full 64-bit range).
88
+ */
89
+ export type KnownValueInput = number | bigint;
90
+
91
+ export class KnownValue implements CborTaggedEncodable, CborTaggedDecodable<KnownValue> {
92
+ private readonly _value: bigint;
93
+ private readonly _assignedName: string | undefined;
94
+
95
+ /**
96
+ * Creates a new KnownValue with the given numeric value and optional name.
97
+ *
98
+ * @param value - The numeric value (number or bigint). Numbers are converted to bigint internally.
99
+ * @param assignedName - Optional human-readable name for the value
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const knownValue = new KnownValue(42);
104
+ * console.log(knownValue.value()); // 42
105
+ *
106
+ * const namedValue = new KnownValue(1, 'isA');
107
+ * console.log(namedValue.name()); // "isA"
108
+ *
109
+ * // Using bigint for large values
110
+ * const largeValue = new KnownValue(9007199254740993n);
111
+ * ```
112
+ */
113
+ constructor(value: KnownValueInput, assignedName?: string) {
114
+ this._value = typeof value === "bigint" ? value : BigInt(value);
115
+ this._assignedName = assignedName;
116
+ }
117
+
118
+ // ===========================================================================
119
+ // Value Accessors (backward compatible API)
120
+ // ===========================================================================
121
+
122
+ /**
123
+ * Returns the numeric value of the KnownValue.
124
+ *
125
+ * This is the raw unsigned integer that identifies the concept.
126
+ * Returns a number for backward compatibility. For values > MAX_SAFE_INTEGER,
127
+ * use `valueBigInt()`.
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * import { IS_A, NOTE } from '@bcts/known-values';
132
+ * console.log(IS_A.value()); // 1
133
+ * console.log(NOTE.value()); // 4
134
+ * ```
135
+ */
136
+ value(): number {
137
+ if (this._value > BigInt(Number.MAX_SAFE_INTEGER)) {
138
+ throw new RangeError(
139
+ `KnownValue ${this._value} exceeds MAX_SAFE_INTEGER. Use valueBigInt() instead.`,
140
+ );
141
+ }
142
+ return Number(this._value);
143
+ }
144
+
145
+ /**
146
+ * Returns the numeric value of the KnownValue as a bigint.
147
+ *
148
+ * Use this for values that may exceed Number.MAX_SAFE_INTEGER (2^53-1).
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * const largeValue = new KnownValue(9007199254740993n);
153
+ * console.log(largeValue.valueBigInt()); // 9007199254740993n
154
+ * ```
155
+ */
156
+ valueBigInt(): bigint {
157
+ return this._value;
158
+ }
159
+
160
+ /**
161
+ * Returns the assigned name of the KnownValue, if one exists.
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * const namedValue = new KnownValue(1, 'isA');
166
+ * console.log(namedValue.assignedName()); // "isA"
167
+ *
168
+ * const unnamedValue = new KnownValue(42);
169
+ * console.log(unnamedValue.assignedName()); // undefined
170
+ * ```
171
+ */
172
+ assignedName(): string | undefined {
173
+ return this._assignedName;
174
+ }
175
+
176
+ /**
177
+ * Returns a human-readable name for the KnownValue.
178
+ *
179
+ * If the KnownValue has an assigned name, that name is returned.
180
+ * Otherwise, the string representation of the numeric value is returned.
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * const namedValue = new KnownValue(1, 'isA');
185
+ * console.log(namedValue.name()); // "isA"
186
+ *
187
+ * const unnamedValue = new KnownValue(42);
188
+ * console.log(unnamedValue.name()); // "42"
189
+ * ```
190
+ */
191
+ name(): string {
192
+ return this._assignedName ?? this._value.toString();
193
+ }
194
+
195
+ // ===========================================================================
196
+ // Equality and Hashing
197
+ // ===========================================================================
198
+
199
+ /**
200
+ * Compares this KnownValue with another for equality.
201
+ * Equality is based solely on the numeric value, ignoring the name.
202
+ *
203
+ * @param other - The KnownValue to compare with
204
+ * @returns true if the values are equal
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * const kv1 = new KnownValue(1, 'isA');
209
+ * const kv2 = new KnownValue(1, 'different');
210
+ * console.log(kv1.equals(kv2)); // true (same value, different name)
211
+ * ```
212
+ */
213
+ equals(other: KnownValue): boolean {
214
+ return this._value === other._value;
215
+ }
216
+
217
+ /**
218
+ * Hash code based on the numeric value.
219
+ * Useful for using KnownValue in hash-based collections.
220
+ */
221
+ hashCode(): number {
222
+ // Convert bigint to a 32-bit hash
223
+ return Number(this._value & BigInt(0xffffffff));
224
+ }
225
+
226
+ /**
227
+ * String representation of the KnownValue.
228
+ *
229
+ * If a name is assigned, the name is displayed. Otherwise, the numeric value
230
+ * is displayed.
231
+ */
232
+ toString(): string {
233
+ return this.name();
234
+ }
235
+
236
+ // ===========================================================================
237
+ // CBOR Encoding (CborTaggedEncodable interface)
238
+ // ===========================================================================
239
+
240
+ /**
241
+ * Returns the CBOR tags associated with KnownValue.
242
+ *
243
+ * The primary tag is TAG_KNOWN_VALUE (201).
244
+ *
245
+ * @returns Array containing the KnownValue tag
246
+ */
247
+ cborTags(): Tag[] {
248
+ return [KNOWN_VALUE_TAG];
249
+ }
250
+
251
+ /**
252
+ * Returns the untagged CBOR encoding of this KnownValue.
253
+ *
254
+ * The untagged representation is simply the unsigned integer value.
255
+ *
256
+ * @returns CBOR representation of the value (unsigned integer)
257
+ */
258
+ untaggedCbor(): Cbor {
259
+ return cbor(this._value);
260
+ }
261
+
262
+ /**
263
+ * Returns the tagged CBOR encoding of this KnownValue.
264
+ *
265
+ * This wraps the unsigned integer value with tag 201.
266
+ *
267
+ * @returns Tagged CBOR representation
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * const kv = new KnownValue(1, 'isA');
272
+ * const tagged = kv.taggedCbor();
273
+ * console.log(tagged.toHex()); // "d8c901" (tag 201, value 1)
274
+ * ```
275
+ */
276
+ taggedCbor(): Cbor {
277
+ return cbor({
278
+ tag: TAG_KNOWN_VALUE,
279
+ value: this._value,
280
+ });
281
+ }
282
+
283
+ /**
284
+ * Returns the tagged CBOR encoding as binary data.
285
+ *
286
+ * @returns Binary CBOR representation
287
+ *
288
+ * @example
289
+ * ```typescript
290
+ * const kv = new KnownValue(1, 'isA');
291
+ * const bytes = kv.toCborData();
292
+ * // bytes is Uint8Array containing the CBOR encoding
293
+ * ```
294
+ */
295
+ toCborData(): Uint8Array {
296
+ return cborData(this.taggedCbor());
297
+ }
298
+
299
+ /**
300
+ * Alias for `toCborData()` to match the dcbor interface.
301
+ */
302
+ taggedCborData(): Uint8Array {
303
+ return this.toCborData();
304
+ }
305
+
306
+ // ===========================================================================
307
+ // CBOR Decoding (CborTaggedDecodable interface)
308
+ // ===========================================================================
309
+
310
+ /**
311
+ * Creates a KnownValue from untagged CBOR (an unsigned integer).
312
+ * Instance method for interface compliance.
313
+ *
314
+ * @param cborValue - The CBOR value (must be an unsigned integer)
315
+ * @returns A new KnownValue
316
+ * @throws {Error} If the CBOR is not an unsigned integer
317
+ */
318
+ fromUntaggedCbor(cborValue: Cbor): KnownValue {
319
+ return KnownValue.fromUntaggedCbor(cborValue);
320
+ }
321
+
322
+ /**
323
+ * Creates a KnownValue from tagged CBOR (tag 201).
324
+ * Instance method for interface compliance.
325
+ *
326
+ * @param cborValue - The tagged CBOR value
327
+ * @returns A new KnownValue
328
+ * @throws {Error} If the CBOR is not properly tagged or contains invalid data
329
+ */
330
+ fromTaggedCbor(cborValue: Cbor): KnownValue {
331
+ return KnownValue.fromTaggedCbor(cborValue);
332
+ }
333
+
334
+ // ===========================================================================
335
+ // Static Factory Methods
336
+ // ===========================================================================
337
+
338
+ /**
339
+ * Creates a KnownValue from untagged CBOR (an unsigned integer).
340
+ *
341
+ * @param cborValue - The CBOR value (must be an unsigned integer)
342
+ * @returns A new KnownValue
343
+ * @throws {Error} If the CBOR is not an unsigned integer
344
+ *
345
+ * @example
346
+ * ```typescript
347
+ * const cborValue = cbor(42);
348
+ * const kv = KnownValue.fromUntaggedCbor(cborValue);
349
+ * console.log(kv.value()); // 42
350
+ * ```
351
+ */
352
+ static fromUntaggedCbor(cborValue: Cbor): KnownValue {
353
+ if (cborValue.type !== MajorType.Unsigned) {
354
+ throw new Error(`Expected unsigned integer for KnownValue, got major type ${cborValue.type}`);
355
+ }
356
+ const numValue = cborValue.value as number | bigint;
357
+ return new KnownValue(typeof numValue === "bigint" ? numValue : BigInt(numValue));
358
+ }
359
+
360
+ /**
361
+ * Creates a KnownValue from tagged CBOR (tag 201).
362
+ *
363
+ * @param cborValue - The tagged CBOR value
364
+ * @returns A new KnownValue
365
+ * @throws {Error} If the CBOR is not properly tagged or contains invalid data
366
+ *
367
+ * @example
368
+ * ```typescript
369
+ * const kv = KnownValue.fromTaggedCbor(taggedCborValue);
370
+ * ```
371
+ */
372
+ static fromTaggedCbor(cborValue: Cbor): KnownValue {
373
+ if (cborValue.type !== MajorType.Tagged) {
374
+ throw new Error(`Expected tagged CBOR for KnownValue, got major type ${cborValue.type}`);
375
+ }
376
+
377
+ const tag = cborValue.tag;
378
+ if (tag !== BigInt(TAG_KNOWN_VALUE) && tag !== TAG_KNOWN_VALUE) {
379
+ throw new Error(`Expected tag ${TAG_KNOWN_VALUE} for KnownValue, got ${tag}`);
380
+ }
381
+
382
+ return KnownValue.fromUntaggedCbor(cborValue.value);
383
+ }
384
+
385
+ /**
386
+ * Creates a KnownValue from binary CBOR data.
387
+ *
388
+ * @param data - Binary CBOR data (must be a tagged KnownValue)
389
+ * @returns A new KnownValue
390
+ * @throws {Error} If the data cannot be decoded or is not a valid KnownValue
391
+ *
392
+ * @example
393
+ * ```typescript
394
+ * const bytes = new Uint8Array([0xd8, 0xc9, 0x01]); // tag 201, value 1
395
+ * const kv = KnownValue.fromCborData(bytes);
396
+ * console.log(kv.value()); // 1
397
+ * ```
398
+ */
399
+ static fromCborData(data: Uint8Array): KnownValue {
400
+ const cborValue = decodeCbor(data);
401
+ return KnownValue.fromTaggedCbor(cborValue);
402
+ }
403
+
404
+ /**
405
+ * Creates a KnownValue from a CBOR value, automatically detecting
406
+ * whether it's tagged or untagged.
407
+ *
408
+ * @param cborValue - The CBOR value (tagged or untagged)
409
+ * @returns A new KnownValue
410
+ * @throws {Error} If the CBOR cannot be converted to a KnownValue
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * // Works with both tagged and untagged
415
+ * const kv1 = KnownValue.fromCbor(cbor(42));
416
+ * const kv2 = KnownValue.fromCbor(taggedCborValue);
417
+ * ```
418
+ */
419
+ static fromCbor(cborValue: Cbor): KnownValue {
420
+ if (cborValue.type === MajorType.Tagged) {
421
+ return KnownValue.fromTaggedCbor(cborValue);
422
+ }
423
+ return KnownValue.fromUntaggedCbor(cborValue);
424
+ }
425
+ }