@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.
- package/LICENSE +48 -0
- package/README.md +13 -0
- package/dist/index.cjs +9151 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3107 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +3107 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +9155 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +9027 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +80 -0
- package/src/.claude-flow/metrics/agent-metrics.json +1 -0
- package/src/.claude-flow/metrics/performance.json +87 -0
- package/src/.claude-flow/metrics/task-metrics.json +10 -0
- package/src/byte-string.ts +300 -0
- package/src/cbor-codable.ts +170 -0
- package/src/cbor-tagged-codable.ts +72 -0
- package/src/cbor-tagged-decodable.ts +184 -0
- package/src/cbor-tagged-encodable.ts +138 -0
- package/src/cbor-tagged.ts +104 -0
- package/src/cbor.ts +869 -0
- package/src/conveniences.ts +840 -0
- package/src/date.ts +553 -0
- package/src/decode.ts +276 -0
- package/src/diag.ts +462 -0
- package/src/dump.ts +277 -0
- package/src/error.ts +259 -0
- package/src/exact.ts +714 -0
- package/src/float.ts +279 -0
- package/src/global.d.ts +34 -0
- package/src/globals.d.ts +0 -0
- package/src/index.ts +180 -0
- package/src/map.ts +308 -0
- package/src/prelude.ts +70 -0
- package/src/set.ts +515 -0
- package/src/simple.ts +153 -0
- package/src/stdlib.ts +55 -0
- package/src/string-util.ts +55 -0
- package/src/tag.ts +53 -0
- package/src/tags-store.ts +294 -0
- package/src/tags.ts +231 -0
- package/src/varint.ts +124 -0
- package/src/walk.ts +516 -0
package/src/set.ts
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Set data structure for CBOR with tag(258) encoding.
|
|
3
|
+
*
|
|
4
|
+
* A Set is encoded as an array with no duplicate elements,
|
|
5
|
+
* tagged with tag(258) to indicate set semantics.
|
|
6
|
+
*
|
|
7
|
+
* @module set
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { type Cbor, MajorType, type CborInput } from "./cbor";
|
|
11
|
+
import { cbor, cborData } from "./cbor";
|
|
12
|
+
import { CborMap } from "./map";
|
|
13
|
+
import { createTag, type Tag } from "./tag";
|
|
14
|
+
import { TAG_SET } from "./tags";
|
|
15
|
+
import {
|
|
16
|
+
type CborTaggedEncodable,
|
|
17
|
+
type CborTaggedDecodable,
|
|
18
|
+
createTaggedCbor,
|
|
19
|
+
validateTag,
|
|
20
|
+
extractTaggedContent,
|
|
21
|
+
} from "./cbor-tagged";
|
|
22
|
+
import { extractCbor } from "./conveniences";
|
|
23
|
+
import { CborError } from "./error";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* CBOR Set type with tag(258) encoding.
|
|
27
|
+
*
|
|
28
|
+
* Internally uses a CborMap to ensure unique elements with deterministic ordering.
|
|
29
|
+
* Elements are ordered by their CBOR encoding (lexicographic byte order).
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* // Create set
|
|
34
|
+
* const set = CborSet.fromArray([1, 2, 3]);
|
|
35
|
+
* const set2 = CborSet.fromSet(new Set([1, 2, 3]));
|
|
36
|
+
*
|
|
37
|
+
* // Add elements
|
|
38
|
+
* set.insert(4);
|
|
39
|
+
* set.insert(2); // Duplicate, no effect
|
|
40
|
+
*
|
|
41
|
+
* // Check membership
|
|
42
|
+
* console.log(set.contains(2)); // true
|
|
43
|
+
* console.log(set.contains(99)); // false
|
|
44
|
+
*
|
|
45
|
+
* // Encode to CBOR
|
|
46
|
+
* const tagged = set.taggedCbor();
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export class CborSet implements CborTaggedEncodable, CborTaggedDecodable<CborSet> {
|
|
50
|
+
readonly #map: CborMap;
|
|
51
|
+
|
|
52
|
+
constructor() {
|
|
53
|
+
this.#map = new CborMap();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// =========================================================================
|
|
57
|
+
// Factory Methods
|
|
58
|
+
// =========================================================================
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create CborSet from array.
|
|
62
|
+
*
|
|
63
|
+
* Duplicates are automatically removed.
|
|
64
|
+
*
|
|
65
|
+
* @param items - Array of items to add to the set
|
|
66
|
+
* @returns New CborSet instance
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* const set = CborSet.fromArray([1, 2, 3, 2, 1]);
|
|
71
|
+
* console.log(set.size); // 3
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
static fromArray<T extends CborInput>(items: T[]): CborSet {
|
|
75
|
+
const set = new CborSet();
|
|
76
|
+
for (const item of items) {
|
|
77
|
+
set.insert(item);
|
|
78
|
+
}
|
|
79
|
+
return set;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create CborSet from JavaScript Set.
|
|
84
|
+
*
|
|
85
|
+
* @param items - JavaScript Set of items
|
|
86
|
+
* @returns New CborSet instance
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const jsSet = new Set([1, 2, 3]);
|
|
91
|
+
* const cborSet = CborSet.fromSet(jsSet);
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
static fromSet<T extends CborInput>(items: Set<T>): CborSet {
|
|
95
|
+
return CborSet.fromArray(Array.from(items));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create CborSet from iterable.
|
|
100
|
+
*
|
|
101
|
+
* @param items - Iterable of items
|
|
102
|
+
* @returns New CborSet instance
|
|
103
|
+
*/
|
|
104
|
+
static fromIterable<T extends CborInput>(items: Iterable<T>): CborSet {
|
|
105
|
+
return CborSet.fromArray(Array.from(items));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// =========================================================================
|
|
109
|
+
// Core Methods
|
|
110
|
+
// =========================================================================
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Insert an element into the set.
|
|
114
|
+
*
|
|
115
|
+
* If the element already exists, has no effect.
|
|
116
|
+
*
|
|
117
|
+
* @param value - Value to insert
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```typescript
|
|
121
|
+
* const set = new CborSet();
|
|
122
|
+
* set.insert(1);
|
|
123
|
+
* set.insert(2);
|
|
124
|
+
* set.insert(1); // No effect, already exists
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
insert(value: CborInput): void {
|
|
128
|
+
const cborValue = encodeCborValue(value);
|
|
129
|
+
// In a set, key and value are the same
|
|
130
|
+
this.#map.set(cborValue, cborValue);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Check if set contains an element.
|
|
135
|
+
*
|
|
136
|
+
* @param value - Value to check
|
|
137
|
+
* @returns true if element is in the set
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* const set = CborSet.fromArray([1, 2, 3]);
|
|
142
|
+
* console.log(set.contains(2)); // true
|
|
143
|
+
* console.log(set.contains(99)); // false
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
contains(value: CborInput): boolean {
|
|
147
|
+
const cborValue = encodeCborValue(value);
|
|
148
|
+
return this.#map.has(cborValue);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Remove an element from the set.
|
|
153
|
+
*
|
|
154
|
+
* @param value - Value to remove
|
|
155
|
+
* @returns true if element was removed, false if not found
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* const set = CborSet.fromArray([1, 2, 3]);
|
|
160
|
+
* set.delete(2); // Returns true
|
|
161
|
+
* set.delete(99); // Returns false
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
delete(value: CborInput): boolean {
|
|
165
|
+
const cborValue = encodeCborValue(value);
|
|
166
|
+
return this.#map.delete(cborValue);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Remove all elements from the set.
|
|
171
|
+
*/
|
|
172
|
+
clear(): void {
|
|
173
|
+
this.#map.clear();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get the number of elements in the set.
|
|
178
|
+
*
|
|
179
|
+
* @returns Number of elements
|
|
180
|
+
*/
|
|
181
|
+
get size(): number {
|
|
182
|
+
return this.#map.size;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check if the set is empty.
|
|
187
|
+
*
|
|
188
|
+
* @returns true if set has no elements
|
|
189
|
+
*/
|
|
190
|
+
isEmpty(): boolean {
|
|
191
|
+
return this.#map.size === 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// =========================================================================
|
|
195
|
+
// Set Operations
|
|
196
|
+
// =========================================================================
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Create a new set containing elements in this set or the other set.
|
|
200
|
+
*
|
|
201
|
+
* @param other - Other set
|
|
202
|
+
* @returns New set with union of elements
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```typescript
|
|
206
|
+
* const set1 = CborSet.fromArray([1, 2, 3]);
|
|
207
|
+
* const set2 = CborSet.fromArray([3, 4, 5]);
|
|
208
|
+
* const union = set1.union(set2);
|
|
209
|
+
* // union contains [1, 2, 3, 4, 5]
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
union(other: CborSet): CborSet {
|
|
213
|
+
const result = new CborSet();
|
|
214
|
+
for (const value of this) {
|
|
215
|
+
result.insert(extractCbor(value) as CborInput);
|
|
216
|
+
}
|
|
217
|
+
for (const value of other) {
|
|
218
|
+
result.insert(extractCbor(value) as CborInput);
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Create a new set containing elements in both this set and the other set.
|
|
225
|
+
*
|
|
226
|
+
* @param other - Other set
|
|
227
|
+
* @returns New set with intersection of elements
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* const set1 = CborSet.fromArray([1, 2, 3]);
|
|
232
|
+
* const set2 = CborSet.fromArray([2, 3, 4]);
|
|
233
|
+
* const intersection = set1.intersection(set2);
|
|
234
|
+
* // intersection contains [2, 3]
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
intersection(other: CborSet): CborSet {
|
|
238
|
+
const result = new CborSet();
|
|
239
|
+
for (const value of this) {
|
|
240
|
+
const extracted = extractCbor(value) as CborInput;
|
|
241
|
+
if (other.contains(extracted)) {
|
|
242
|
+
result.insert(extracted);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Create a new set containing elements in this set but not in the other set.
|
|
250
|
+
*
|
|
251
|
+
* @param other - Other set
|
|
252
|
+
* @returns New set with difference of elements
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```typescript
|
|
256
|
+
* const set1 = CborSet.fromArray([1, 2, 3]);
|
|
257
|
+
* const set2 = CborSet.fromArray([2, 3, 4]);
|
|
258
|
+
* const diff = set1.difference(set2);
|
|
259
|
+
* // diff contains [1]
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
difference(other: CborSet): CborSet {
|
|
263
|
+
const result = new CborSet();
|
|
264
|
+
for (const value of this) {
|
|
265
|
+
const extracted = extractCbor(value) as CborInput;
|
|
266
|
+
if (!other.contains(extracted)) {
|
|
267
|
+
result.insert(extracted);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Check if this set is a subset of another set.
|
|
275
|
+
*
|
|
276
|
+
* @param other - Other set
|
|
277
|
+
* @returns true if all elements of this set are in the other set
|
|
278
|
+
*/
|
|
279
|
+
isSubsetOf(other: CborSet): boolean {
|
|
280
|
+
for (const value of this) {
|
|
281
|
+
if (!other.contains(extractCbor(value) as CborInput)) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Check if this set is a superset of another set.
|
|
290
|
+
*
|
|
291
|
+
* @param other - Other set
|
|
292
|
+
* @returns true if all elements of the other set are in this set
|
|
293
|
+
*/
|
|
294
|
+
isSupersetOf(other: CborSet): boolean {
|
|
295
|
+
return other.isSubsetOf(this);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// =========================================================================
|
|
299
|
+
// Iteration
|
|
300
|
+
// =========================================================================
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Iterate over elements in the set.
|
|
304
|
+
*
|
|
305
|
+
* Elements are returned in deterministic order (by CBOR encoding).
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```typescript
|
|
309
|
+
* const set = CborSet.fromArray([3, 1, 2]);
|
|
310
|
+
* for (const value of set) {
|
|
311
|
+
* console.log(extractCbor(value));
|
|
312
|
+
* }
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
*[Symbol.iterator](): Iterator<Cbor> {
|
|
316
|
+
for (const [_, value] of this.#map) {
|
|
317
|
+
yield value;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Get all values as an array.
|
|
323
|
+
*
|
|
324
|
+
* @returns Array of CBOR values in deterministic order
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```typescript
|
|
328
|
+
* const set = CborSet.fromArray([3, 1, 2]);
|
|
329
|
+
* const values = set.values();
|
|
330
|
+
* // Values in deterministic order
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
values(): Cbor[] {
|
|
334
|
+
return Array.from(this);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Execute a function for each element.
|
|
339
|
+
*
|
|
340
|
+
* @param callback - Function to call for each element
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```typescript
|
|
344
|
+
* set.forEach(value => {
|
|
345
|
+
* console.log(extractCbor(value));
|
|
346
|
+
* });
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
349
|
+
forEach(callback: (value: Cbor) => void): void {
|
|
350
|
+
for (const value of this) {
|
|
351
|
+
callback(value);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// =========================================================================
|
|
356
|
+
// CborTagged Implementation
|
|
357
|
+
// =========================================================================
|
|
358
|
+
|
|
359
|
+
cborTags(): Tag[] {
|
|
360
|
+
return [createTag(TAG_SET, "set")];
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
untaggedCbor(): Cbor {
|
|
364
|
+
// Encode as an array of values
|
|
365
|
+
const values = this.values();
|
|
366
|
+
return cbor(values);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
taggedCbor(): Cbor {
|
|
370
|
+
return createTaggedCbor(this);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
fromUntaggedCbor(c: Cbor): CborSet {
|
|
374
|
+
if (c.type !== MajorType.Array) {
|
|
375
|
+
throw new CborError({ type: "WrongType" });
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
this.clear();
|
|
379
|
+
for (const value of c.value) {
|
|
380
|
+
this.insert(extractCbor(value) as CborInput);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return this;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
fromTaggedCbor(c: Cbor): CborSet {
|
|
387
|
+
const expectedTags = this.cborTags();
|
|
388
|
+
validateTag(c, expectedTags);
|
|
389
|
+
const content = extractTaggedContent(c);
|
|
390
|
+
return this.fromUntaggedCbor(content);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Decode a CborSet from tagged CBOR (static method).
|
|
395
|
+
*
|
|
396
|
+
* @param cbor - Tagged CBOR value with tag(258)
|
|
397
|
+
* @returns Decoded CborSet instance
|
|
398
|
+
*/
|
|
399
|
+
static fromTaggedCborStatic(cbor: Cbor): CborSet {
|
|
400
|
+
return new CborSet().fromTaggedCbor(cbor);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// =========================================================================
|
|
404
|
+
// Conversion
|
|
405
|
+
// =========================================================================
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Convert to CBOR array (untagged).
|
|
409
|
+
*
|
|
410
|
+
* @returns CBOR array
|
|
411
|
+
*/
|
|
412
|
+
toCbor(): Cbor {
|
|
413
|
+
return this.untaggedCbor();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Convert to CBOR bytes (tagged).
|
|
418
|
+
*
|
|
419
|
+
* @returns Encoded CBOR bytes
|
|
420
|
+
*/
|
|
421
|
+
toBytes(): Uint8Array {
|
|
422
|
+
return cborData(this.taggedCbor());
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Convert to JavaScript Set.
|
|
427
|
+
*
|
|
428
|
+
* @returns JavaScript Set with extracted values
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* ```typescript
|
|
432
|
+
* const cborSet = CborSet.fromArray([1, 2, 3]);
|
|
433
|
+
* const jsSet = cborSet.toSet();
|
|
434
|
+
* console.log(jsSet.has(1)); // true
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
toSet(): Set<unknown> {
|
|
438
|
+
const result = new Set();
|
|
439
|
+
for (const value of this) {
|
|
440
|
+
result.add(extractCbor(value));
|
|
441
|
+
}
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Convert to JavaScript Array.
|
|
447
|
+
*
|
|
448
|
+
* @returns Array with extracted values
|
|
449
|
+
*/
|
|
450
|
+
toArray(): unknown[] {
|
|
451
|
+
return Array.from(this.toSet());
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// =========================================================================
|
|
455
|
+
// Display
|
|
456
|
+
// =========================================================================
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Get diagnostic notation for the set.
|
|
460
|
+
*
|
|
461
|
+
* @returns String representation
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```typescript
|
|
465
|
+
* const set = CborSet.fromArray([1, 2, 3]);
|
|
466
|
+
* console.log(set.diagnostic); // "[1, 2, 3]"
|
|
467
|
+
* ```
|
|
468
|
+
*/
|
|
469
|
+
get diagnostic(): string {
|
|
470
|
+
const items = this.values()
|
|
471
|
+
.map((v) => {
|
|
472
|
+
const extracted = extractCbor(v);
|
|
473
|
+
if (typeof extracted === "string") {
|
|
474
|
+
return `"${extracted}"`;
|
|
475
|
+
}
|
|
476
|
+
return String(extracted);
|
|
477
|
+
})
|
|
478
|
+
.join(", ");
|
|
479
|
+
return `[${items}]`;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Convert to string (same as diagnostic).
|
|
484
|
+
*
|
|
485
|
+
* @returns String representation
|
|
486
|
+
*/
|
|
487
|
+
toString(): string {
|
|
488
|
+
return this.diagnostic;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Convert to JSON (returns array of values).
|
|
493
|
+
*
|
|
494
|
+
* @returns Array for JSON serialization
|
|
495
|
+
*/
|
|
496
|
+
toJSON(): unknown[] {
|
|
497
|
+
return this.toArray();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ============================================================================
|
|
502
|
+
// Helper Functions
|
|
503
|
+
// ============================================================================
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Convert a value to CBOR for use in set operations.
|
|
507
|
+
*
|
|
508
|
+
* @internal
|
|
509
|
+
*/
|
|
510
|
+
function encodeCborValue(value: CborInput): Cbor {
|
|
511
|
+
if (typeof value === "object" && value !== null && "isCbor" in value && value.isCbor === true) {
|
|
512
|
+
return value as Cbor;
|
|
513
|
+
}
|
|
514
|
+
return cbor(value);
|
|
515
|
+
}
|
package/src/simple.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Simple Values (Major Type 7).
|
|
3
|
+
*
|
|
4
|
+
* @module simple
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { MajorType } from "./cbor";
|
|
8
|
+
import { encodeVarInt } from "./varint";
|
|
9
|
+
import { f64CborData } from "./float";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Represents CBOR simple values (major type 7).
|
|
13
|
+
*
|
|
14
|
+
* In CBOR, simple values are a special category that includes booleans (`true`
|
|
15
|
+
* and `false`), `null`, and floating point numbers.
|
|
16
|
+
*
|
|
17
|
+
* Per Section 2.4 of the dCBOR specification, only these specific simple
|
|
18
|
+
* values are valid in dCBOR. All other major type 7 values (such as undefined
|
|
19
|
+
* or other simple values) are invalid and will be rejected by dCBOR decoders.
|
|
20
|
+
*
|
|
21
|
+
* When encoding floating point values, dCBOR follows specific numeric
|
|
22
|
+
* reduction rules detailed in Section 2.3 of the dCBOR specification,
|
|
23
|
+
* including
|
|
24
|
+
* - Integral floating point values must be reduced to integers when possible
|
|
25
|
+
* - NaN values must be normalized to the canonical form `f97e00`
|
|
26
|
+
*/
|
|
27
|
+
export type Simple =
|
|
28
|
+
| { readonly type: "False" }
|
|
29
|
+
| { readonly type: "True" }
|
|
30
|
+
| { readonly type: "Null" }
|
|
31
|
+
| { readonly type: "Float"; readonly value: number };
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Returns the standard name of the simple value as a string.
|
|
35
|
+
*
|
|
36
|
+
* For `False`, `True`, and `Null`, this returns their lowercase string
|
|
37
|
+
* representation. For `Float` values, it returns their numeric representation.
|
|
38
|
+
*/
|
|
39
|
+
export const simpleName = (simple: Simple): string => {
|
|
40
|
+
switch (simple.type) {
|
|
41
|
+
case "False":
|
|
42
|
+
return "false";
|
|
43
|
+
case "True":
|
|
44
|
+
return "true";
|
|
45
|
+
case "Null":
|
|
46
|
+
return "null";
|
|
47
|
+
case "Float": {
|
|
48
|
+
const v = simple.value;
|
|
49
|
+
if (Number.isNaN(v)) {
|
|
50
|
+
return "NaN";
|
|
51
|
+
} else if (!Number.isFinite(v)) {
|
|
52
|
+
return v > 0 ? "Infinity" : "-Infinity";
|
|
53
|
+
} else {
|
|
54
|
+
return String(v);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Checks if the simple value is a floating point number.
|
|
62
|
+
*/
|
|
63
|
+
export const isFloat = (simple: Simple): simple is { type: "Float"; value: number } =>
|
|
64
|
+
simple.type === "Float";
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Checks if the simple value is the NaN (Not a Number) representation.
|
|
68
|
+
*/
|
|
69
|
+
export const isNaN = (simple: Simple): boolean =>
|
|
70
|
+
simple.type === "Float" && Number.isNaN(simple.value);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Encodes the simple value to its raw CBOR byte representation.
|
|
74
|
+
*
|
|
75
|
+
* Returns the CBOR bytes that represent this simple value according to the
|
|
76
|
+
* dCBOR deterministic encoding rules:
|
|
77
|
+
* - `False` encodes as `0xf4`
|
|
78
|
+
* - `True` encodes as `0xf5`
|
|
79
|
+
* - `Null` encodes as `0xf6`
|
|
80
|
+
* - `Float` values encode according to the IEEE 754 floating point rules,
|
|
81
|
+
* using the shortest representation that preserves precision.
|
|
82
|
+
*/
|
|
83
|
+
export const simpleCborData = (simple: Simple): Uint8Array => {
|
|
84
|
+
switch (simple.type) {
|
|
85
|
+
case "False":
|
|
86
|
+
return encodeVarInt(20, MajorType.Simple);
|
|
87
|
+
case "True":
|
|
88
|
+
return encodeVarInt(21, MajorType.Simple);
|
|
89
|
+
case "Null":
|
|
90
|
+
return encodeVarInt(22, MajorType.Simple);
|
|
91
|
+
case "Float":
|
|
92
|
+
return f64CborData(simple.value);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Compare two Simple values for equality.
|
|
98
|
+
*
|
|
99
|
+
* Two `Simple` values are equal if they're the same variant. For `Float`
|
|
100
|
+
* variants, the contained floating point values are compared for equality,
|
|
101
|
+
* with NaN values considered equal to each other.
|
|
102
|
+
*/
|
|
103
|
+
export const simpleEquals = (a: Simple, b: Simple): boolean => {
|
|
104
|
+
if (a.type !== b.type) return false;
|
|
105
|
+
|
|
106
|
+
switch (a.type) {
|
|
107
|
+
case "False":
|
|
108
|
+
case "True":
|
|
109
|
+
case "Null":
|
|
110
|
+
return true;
|
|
111
|
+
case "Float": {
|
|
112
|
+
const bFloat = b as { type: "Float"; value: number };
|
|
113
|
+
const v1 = a.value;
|
|
114
|
+
const v2 = bFloat.value;
|
|
115
|
+
return v1 === v2 || (Number.isNaN(v1) && Number.isNaN(v2));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Hash a Simple value.
|
|
122
|
+
*
|
|
123
|
+
* Matches Rust's Hash trait implementation.
|
|
124
|
+
*/
|
|
125
|
+
export const simpleHash = (simple: Simple): number => {
|
|
126
|
+
// Simple FNV-1a hash
|
|
127
|
+
let hash = 2166136261;
|
|
128
|
+
|
|
129
|
+
switch (simple.type) {
|
|
130
|
+
case "False":
|
|
131
|
+
hash ^= 0;
|
|
132
|
+
break;
|
|
133
|
+
case "True":
|
|
134
|
+
hash ^= 1;
|
|
135
|
+
break;
|
|
136
|
+
case "Null":
|
|
137
|
+
hash ^= 2;
|
|
138
|
+
break;
|
|
139
|
+
case "Float": {
|
|
140
|
+
// Hash the bit representation of the float
|
|
141
|
+
const buffer = new ArrayBuffer(8);
|
|
142
|
+
const view = new DataView(buffer);
|
|
143
|
+
view.setFloat64(0, simple.value, true);
|
|
144
|
+
for (let i = 0; i < 8; i++) {
|
|
145
|
+
hash ^= view.getUint8(i);
|
|
146
|
+
hash = Math.imul(hash, 16777619);
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return hash >>> 0;
|
|
153
|
+
};
|
package/src/stdlib.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard library re-exports and compatibility layer.
|
|
3
|
+
*
|
|
4
|
+
* In Rust, this handles std/no_std feature flags.
|
|
5
|
+
* In TypeScript, this is primarily documentation.
|
|
6
|
+
*
|
|
7
|
+
* @module stdlib
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { CborError } from "./error";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Concatenate multiple byte arrays into one.
|
|
14
|
+
*/
|
|
15
|
+
export const concatBytes = (arrays: Uint8Array[]): Uint8Array => {
|
|
16
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
17
|
+
const result = new Uint8Array(totalLength);
|
|
18
|
+
let offset = 0;
|
|
19
|
+
for (const arr of arrays) {
|
|
20
|
+
result.set(arr, offset);
|
|
21
|
+
offset += arr.length;
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if two byte arrays are equal.
|
|
28
|
+
*/
|
|
29
|
+
export const areBytesEqual = (a: Uint8Array, b: Uint8Array): boolean => {
|
|
30
|
+
if (a.length !== b.length) return false;
|
|
31
|
+
for (let i = 0; i < a.length; i++) {
|
|
32
|
+
if (a[i] !== b[i]) return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Lexicographically compare two byte arrays.
|
|
39
|
+
* Returns: -1 if a < b, 0 if a == b, 1 if a > b
|
|
40
|
+
*/
|
|
41
|
+
export const lexicographicallyCompareBytes = (a: Uint8Array, b: Uint8Array): number => {
|
|
42
|
+
const minLen = Math.min(a.length, b.length);
|
|
43
|
+
for (let i = 0; i < minLen; i++) {
|
|
44
|
+
const aVal = a[i];
|
|
45
|
+
const bVal = b[i];
|
|
46
|
+
if (aVal === undefined || bVal === undefined) {
|
|
47
|
+
throw new CborError({ type: "Custom", message: "Unexpected undefined byte in array" });
|
|
48
|
+
}
|
|
49
|
+
if (aVal < bVal) return -1;
|
|
50
|
+
if (aVal > bVal) return 1;
|
|
51
|
+
}
|
|
52
|
+
if (a.length < b.length) return -1;
|
|
53
|
+
if (a.length > b.length) return 1;
|
|
54
|
+
return 0;
|
|
55
|
+
};
|