@blue-quickjs/dv 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 @blue-labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # dv
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Building
6
+
7
+ Run `nx build dv` to build the library.
8
+
9
+ ## Running unit tests
10
+
11
+ Run `nx test dv` to execute the unit tests via [Vitest](https://vitest.dev/).
@@ -0,0 +1,2 @@
1
+ export * from './lib/dv.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './lib/dv.js';
@@ -0,0 +1,31 @@
1
+ export type DV = DVPrimitive | DVArray | DVObject;
2
+ export type DVPrimitive = null | boolean | number | string;
3
+ export type DVArray = DV[];
4
+ export type DVObject = {
5
+ [key: string]: DV;
6
+ };
7
+ export interface DvLimits {
8
+ maxDepth: number;
9
+ maxEncodedBytes: number;
10
+ maxStringBytes: number;
11
+ maxArrayLength: number;
12
+ maxMapLength: number;
13
+ }
14
+ export declare const DV_LIMIT_DEFAULTS: Readonly<DvLimits>;
15
+ type PartialLimits = Partial<DvLimits>;
16
+ export interface DvEncodeOptions {
17
+ limits?: PartialLimits;
18
+ }
19
+ export type DvDecodeOptions = DvEncodeOptions;
20
+ export type DvValidateOptions = DvEncodeOptions;
21
+ export type DvErrorCode = 'UNSUPPORTED_TYPE' | 'NAN_OR_INF' | 'INTEGER_OUT_OF_RANGE' | 'INVALID_STRING' | 'STRING_TOO_LONG' | 'ARRAY_TOO_LONG' | 'MAP_TOO_LONG' | 'DEPTH_EXCEEDED' | 'ENCODED_TOO_LARGE' | 'NON_CANONICAL_INTEGER' | 'NON_CANONICAL_FLOAT' | 'NON_CANONICAL_LENGTH' | 'INVALID_UTF8' | 'DUPLICATE_KEY' | 'KEY_ORDER' | 'UNSUPPORTED_CBOR' | 'TRUNCATED' | 'TRAILING_BYTES';
22
+ export declare class DvError extends Error {
23
+ readonly code: DvErrorCode;
24
+ constructor(code: DvErrorCode, message: string);
25
+ }
26
+ export declare function encodeDv(value: unknown, options?: DvEncodeOptions): Uint8Array;
27
+ export declare function decodeDv(input: ArrayBufferView | ArrayBuffer | Uint8Array, options?: DvDecodeOptions): DV;
28
+ export declare function validateDv(value: unknown, options?: DvValidateOptions): asserts value is DV;
29
+ export declare function isDv(value: unknown, options?: DvValidateOptions): value is DV;
30
+ export {};
31
+ //# sourceMappingURL=dv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dv.d.ts","sourceRoot":"","sources":["../../src/lib/dv.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,EAAE,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAC;AAClD,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAC3D,MAAM,MAAM,OAAO,GAAG,EAAE,EAAE,CAAC;AAC3B,MAAM,MAAM,QAAQ,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,EAAE,CAAA;CAAE,CAAC;AAE7C,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,QAAQ,CAMhD,CAAC;AAEF,KAAK,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvC,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,MAAM,eAAe,GAAG,eAAe,CAAC;AAC9C,MAAM,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAEhD,MAAM,MAAM,WAAW,GACnB,kBAAkB,GAClB,YAAY,GACZ,sBAAsB,GACtB,gBAAgB,GAChB,iBAAiB,GACjB,gBAAgB,GAChB,cAAc,GACd,gBAAgB,GAChB,mBAAmB,GACnB,uBAAuB,GACvB,qBAAqB,GACrB,sBAAsB,GACtB,cAAc,GACd,eAAe,GACf,WAAW,GACX,kBAAkB,GAClB,WAAW,GACX,gBAAgB,CAAC;AAErB,qBAAa,OAAQ,SAAQ,KAAK;aAEd,IAAI,EAAE,WAAW;gBAAjB,IAAI,EAAE,WAAW,EACjC,OAAO,EAAE,MAAM;CAKlB;AAED,wBAAgB,QAAQ,CACtB,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,eAAe,GACxB,UAAU,CAKZ;AAED,wBAAgB,QAAQ,CACtB,KAAK,EAAE,eAAe,GAAG,WAAW,GAAG,UAAU,EACjD,OAAO,CAAC,EAAE,eAAe,GACxB,EAAE,CAwBJ;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,KAAK,IAAI,EAAE,CAGrB;AAED,wBAAgB,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,KAAK,IAAI,EAAE,CAO7E"}
package/dist/lib/dv.js ADDED
@@ -0,0 +1,565 @@
1
+ const textEncoder = new TextEncoder();
2
+ const textDecoder = new TextDecoder('utf-8', { fatal: true });
3
+ const MAX_SAFE_INT = Number.MAX_SAFE_INTEGER;
4
+ const MIN_SAFE_INT = Number.MIN_SAFE_INTEGER;
5
+ const CBOR_MAJOR_UINT = 0;
6
+ const CBOR_MAJOR_NINT = 1;
7
+ const CBOR_MAJOR_TEXT = 3;
8
+ const CBOR_MAJOR_ARRAY = 4;
9
+ const CBOR_MAJOR_MAP = 5;
10
+ const CBOR_MAJOR_SIMPLE = 7;
11
+ export const DV_LIMIT_DEFAULTS = {
12
+ maxDepth: 64,
13
+ maxEncodedBytes: 1_048_576,
14
+ maxStringBytes: 262_144,
15
+ maxArrayLength: 65_535,
16
+ maxMapLength: 65_535,
17
+ };
18
+ export class DvError extends Error {
19
+ code;
20
+ constructor(code, message) {
21
+ super(message);
22
+ this.code = code;
23
+ this.name = 'DvError';
24
+ }
25
+ }
26
+ export function encodeDv(value, options) {
27
+ const limits = normalizeLimits(options?.limits);
28
+ const builder = new ByteBuilder(limits.maxEncodedBytes);
29
+ encodeValue(value, builder, limits, 0);
30
+ return builder.toUint8Array();
31
+ }
32
+ export function decodeDv(input, options) {
33
+ const limits = normalizeLimits(options?.limits);
34
+ const bytes = input instanceof Uint8Array
35
+ ? input
36
+ : input instanceof ArrayBuffer
37
+ ? new Uint8Array(input)
38
+ : new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
39
+ if (bytes.length > limits.maxEncodedBytes) {
40
+ throw dvError('ENCODED_TOO_LARGE', `encoded DV exceeds maxEncodedBytes (${bytes.length} > ${limits.maxEncodedBytes})`);
41
+ }
42
+ const reader = new CborReader(bytes);
43
+ const value = readValue(reader, limits, 0);
44
+ if (!reader.isEOF()) {
45
+ throw dvError('TRAILING_BYTES', 'unexpected trailing bytes after DV value');
46
+ }
47
+ return value;
48
+ }
49
+ export function validateDv(value, options) {
50
+ // Encoding performs full validation, including size/limit checks.
51
+ encodeDv(value, options);
52
+ }
53
+ export function isDv(value, options) {
54
+ try {
55
+ validateDv(value, options);
56
+ return true;
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+ function normalizeLimits(limits) {
63
+ return {
64
+ maxDepth: limits?.maxDepth ?? DV_LIMIT_DEFAULTS.maxDepth,
65
+ maxEncodedBytes: limits?.maxEncodedBytes ?? DV_LIMIT_DEFAULTS.maxEncodedBytes,
66
+ maxStringBytes: limits?.maxStringBytes ?? DV_LIMIT_DEFAULTS.maxStringBytes,
67
+ maxArrayLength: limits?.maxArrayLength ?? DV_LIMIT_DEFAULTS.maxArrayLength,
68
+ maxMapLength: limits?.maxMapLength ?? DV_LIMIT_DEFAULTS.maxMapLength,
69
+ };
70
+ }
71
+ class ByteBuilder {
72
+ maxBytes;
73
+ chunks = [];
74
+ size = 0;
75
+ constructor(maxBytes) {
76
+ this.maxBytes = maxBytes;
77
+ }
78
+ pushByte(byte) {
79
+ this.ensure(1);
80
+ this.chunks.push(byte & 0xff);
81
+ this.size += 1;
82
+ }
83
+ pushBytes(bytes) {
84
+ this.ensure(bytes.length);
85
+ for (const b of bytes) {
86
+ this.chunks.push(b);
87
+ }
88
+ this.size += bytes.length;
89
+ }
90
+ pushUint(value, width) {
91
+ this.ensure(width);
92
+ const view = new DataView(new ArrayBuffer(width));
93
+ if (width === 1) {
94
+ view.setUint8(0, Number(value));
95
+ }
96
+ else if (width === 2) {
97
+ view.setUint16(0, Number(value), false);
98
+ }
99
+ else if (width === 4) {
100
+ view.setUint32(0, Number(value), false);
101
+ }
102
+ else {
103
+ view.setBigUint64(0, BigInt(value), false);
104
+ }
105
+ this.pushBytes(new Uint8Array(view.buffer));
106
+ }
107
+ pushFloat64(value) {
108
+ this.ensure(8);
109
+ const view = new DataView(new ArrayBuffer(8));
110
+ view.setFloat64(0, value, false);
111
+ this.pushBytes(new Uint8Array(view.buffer));
112
+ }
113
+ toUint8Array() {
114
+ return Uint8Array.from(this.chunks);
115
+ }
116
+ ensure(additional) {
117
+ if (this.size + additional > this.maxBytes) {
118
+ throw dvError('ENCODED_TOO_LARGE', `encoded DV exceeds maxEncodedBytes (${this.size + additional} > ${this.maxBytes})`);
119
+ }
120
+ }
121
+ }
122
+ function dvError(code, message) {
123
+ return new DvError(code, message);
124
+ }
125
+ function encodeValue(value, builder, limits, depth) {
126
+ if (value === null) {
127
+ builder.pushByte(0xf6);
128
+ return;
129
+ }
130
+ const type = typeof value;
131
+ if (type === 'boolean') {
132
+ builder.pushByte(value ? 0xf5 : 0xf4);
133
+ return;
134
+ }
135
+ if (type === 'number') {
136
+ encodeNumber(value, builder);
137
+ return;
138
+ }
139
+ if (type === 'string') {
140
+ encodeString(value, builder, limits);
141
+ return;
142
+ }
143
+ if (Array.isArray(value)) {
144
+ encodeArray(value, builder, limits, depth);
145
+ return;
146
+ }
147
+ if (isPlainObject(value)) {
148
+ encodeMap(value, builder, limits, depth);
149
+ return;
150
+ }
151
+ throw dvError('UNSUPPORTED_TYPE', `unsupported DV type: ${type}`);
152
+ }
153
+ function encodeNumber(value, builder) {
154
+ if (!Number.isFinite(value)) {
155
+ throw dvError('NAN_OR_INF', 'DV numbers must be finite');
156
+ }
157
+ if (Object.is(value, -0)) {
158
+ value = 0;
159
+ }
160
+ if (Number.isInteger(value)) {
161
+ if (value > MAX_SAFE_INT || value < MIN_SAFE_INT) {
162
+ throw dvError('INTEGER_OUT_OF_RANGE', `integer is outside safe range (${value} not in [${MIN_SAFE_INT}, ${MAX_SAFE_INT}])`);
163
+ }
164
+ encodeInteger(value, builder);
165
+ return;
166
+ }
167
+ builder.pushByte(0xfb);
168
+ builder.pushFloat64(value);
169
+ }
170
+ function encodeInteger(value, builder) {
171
+ if (value >= 0) {
172
+ encodeTypeAndLength(builder, CBOR_MAJOR_UINT, value);
173
+ }
174
+ else {
175
+ encodeTypeAndLength(builder, CBOR_MAJOR_NINT, -1 - value);
176
+ }
177
+ }
178
+ function encodeString(value, builder, limits) {
179
+ if (!isWellFormedString(value)) {
180
+ throw dvError('INVALID_STRING', 'string contains lone surrogate code points');
181
+ }
182
+ const bytes = textEncoder.encode(value);
183
+ if (bytes.length > limits.maxStringBytes) {
184
+ throw dvError('STRING_TOO_LONG', `string exceeds maxStringBytes (${bytes.length} > ${limits.maxStringBytes})`);
185
+ }
186
+ encodeTypeAndLength(builder, CBOR_MAJOR_TEXT, bytes.length);
187
+ builder.pushBytes(bytes);
188
+ }
189
+ function encodeArray(value, builder, limits, depth) {
190
+ const nextDepth = depth + 1;
191
+ if (nextDepth > limits.maxDepth) {
192
+ throw dvError('DEPTH_EXCEEDED', `maxDepth ${limits.maxDepth} exceeded`);
193
+ }
194
+ if (value.length > limits.maxArrayLength) {
195
+ throw dvError('ARRAY_TOO_LONG', `array length exceeds maxArrayLength (${value.length} > ${limits.maxArrayLength})`);
196
+ }
197
+ encodeTypeAndLength(builder, CBOR_MAJOR_ARRAY, value.length);
198
+ for (const element of value) {
199
+ encodeValue(element, builder, limits, nextDepth);
200
+ }
201
+ }
202
+ function encodeMap(value, builder, limits, depth) {
203
+ const keys = Object.keys(value);
204
+ const nextDepth = depth + 1;
205
+ if (nextDepth > limits.maxDepth) {
206
+ throw dvError('DEPTH_EXCEEDED', `maxDepth ${limits.maxDepth} exceeded`);
207
+ }
208
+ if (keys.length > limits.maxMapLength) {
209
+ throw dvError('MAP_TOO_LONG', `map entries exceed maxMapLength (${keys.length} > ${limits.maxMapLength})`);
210
+ }
211
+ const encodedKeys = keys.map((key) => {
212
+ const keyBytes = encodeStringBytes(key, limits);
213
+ const header = typeAndLengthBytes(CBOR_MAJOR_TEXT, keyBytes.length);
214
+ const full = concat(header, keyBytes);
215
+ return { key, encoded: full };
216
+ });
217
+ encodedKeys.sort((a, b) => compareCanonicalKeys(a.encoded, b.encoded));
218
+ for (let i = 1; i < encodedKeys.length; i += 1) {
219
+ if (compareCanonicalKeys(encodedKeys[i - 1].encoded, encodedKeys[i].encoded) === 0) {
220
+ throw dvError('DUPLICATE_KEY', `map contains duplicate key "${encodedKeys[i].key}"`);
221
+ }
222
+ }
223
+ encodeTypeAndLength(builder, CBOR_MAJOR_MAP, encodedKeys.length);
224
+ for (const entry of encodedKeys) {
225
+ builder.pushBytes(entry.encoded);
226
+ encodeValue(value[entry.key], builder, limits, nextDepth);
227
+ }
228
+ }
229
+ function encodeStringBytes(value, limits) {
230
+ if (!isWellFormedString(value)) {
231
+ throw dvError('INVALID_STRING', 'string contains lone surrogate code points');
232
+ }
233
+ const bytes = textEncoder.encode(value);
234
+ if (bytes.length > limits.maxStringBytes) {
235
+ throw dvError('STRING_TOO_LONG', `string exceeds maxStringBytes (${bytes.length} > ${limits.maxStringBytes})`);
236
+ }
237
+ return bytes;
238
+ }
239
+ function typeAndLengthBytes(major, length) {
240
+ const builder = new ByteBuilder(Number.MAX_SAFE_INTEGER);
241
+ encodeTypeAndLength(builder, major, length);
242
+ return builder.toUint8Array();
243
+ }
244
+ function encodeTypeAndLength(builder, major, length) {
245
+ if (typeof length === 'number' && (!Number.isInteger(length) || length < 0)) {
246
+ throw dvError('NON_CANONICAL_LENGTH', `length must be a non-negative integer: ${length}`);
247
+ }
248
+ const value = typeof length === 'bigint' ? length : BigInt(length);
249
+ if (value <= 23n) {
250
+ builder.pushByte((major << 5) | Number(value));
251
+ }
252
+ else if (value <= 0xffn) {
253
+ builder.pushByte((major << 5) | 24);
254
+ builder.pushUint(value, 1);
255
+ }
256
+ else if (value <= 0xffffn) {
257
+ builder.pushByte((major << 5) | 25);
258
+ builder.pushUint(value, 2);
259
+ }
260
+ else if (value <= 0xffffffffn) {
261
+ builder.pushByte((major << 5) | 26);
262
+ builder.pushUint(value, 4);
263
+ }
264
+ else {
265
+ builder.pushByte((major << 5) | 27);
266
+ builder.pushUint(value, 8);
267
+ }
268
+ }
269
+ function isPlainObject(value) {
270
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
271
+ return false;
272
+ }
273
+ const proto = Object.getPrototypeOf(value);
274
+ return proto === Object.prototype || proto === null;
275
+ }
276
+ function isWellFormedString(value) {
277
+ for (let i = 0; i < value.length; i += 1) {
278
+ const code = value.charCodeAt(i);
279
+ if (code >= 0xd800 && code <= 0xdbff) {
280
+ if (i + 1 >= value.length) {
281
+ return false;
282
+ }
283
+ const next = value.charCodeAt(i + 1);
284
+ if (next < 0xdc00 || next > 0xdfff) {
285
+ return false;
286
+ }
287
+ i += 1;
288
+ }
289
+ else if (code >= 0xdc00 && code <= 0xdfff) {
290
+ return false;
291
+ }
292
+ }
293
+ return true;
294
+ }
295
+ class CborReader {
296
+ bytes;
297
+ offset = 0;
298
+ constructor(bytes) {
299
+ this.bytes = bytes;
300
+ }
301
+ readByte() {
302
+ if (this.offset >= this.bytes.length) {
303
+ throw dvError('TRUNCATED', 'unexpected end of buffer');
304
+ }
305
+ const value = this.bytes[this.offset];
306
+ this.offset += 1;
307
+ return value;
308
+ }
309
+ readUint8() {
310
+ return this.readByte();
311
+ }
312
+ readUint16() {
313
+ if (this.offset + 2 > this.bytes.length) {
314
+ throw dvError('TRUNCATED', 'unexpected end of buffer');
315
+ }
316
+ const view = new DataView(this.bytes.buffer, this.bytes.byteOffset + this.offset, 2);
317
+ const value = view.getUint16(0, false);
318
+ this.offset += 2;
319
+ return value;
320
+ }
321
+ readUint32() {
322
+ if (this.offset + 4 > this.bytes.length) {
323
+ throw dvError('TRUNCATED', 'unexpected end of buffer');
324
+ }
325
+ const view = new DataView(this.bytes.buffer, this.bytes.byteOffset + this.offset, 4);
326
+ const value = view.getUint32(0, false);
327
+ this.offset += 4;
328
+ return value;
329
+ }
330
+ readUint64() {
331
+ if (this.offset + 8 > this.bytes.length) {
332
+ throw dvError('TRUNCATED', 'unexpected end of buffer');
333
+ }
334
+ const view = new DataView(this.bytes.buffer, this.bytes.byteOffset + this.offset, 8);
335
+ const value = view.getBigUint64(0, false);
336
+ this.offset += 8;
337
+ return value;
338
+ }
339
+ readFloat64() {
340
+ if (this.offset + 8 > this.bytes.length) {
341
+ throw dvError('TRUNCATED', 'unexpected end of buffer');
342
+ }
343
+ const view = new DataView(this.bytes.buffer, this.bytes.byteOffset + this.offset, 8);
344
+ const value = view.getFloat64(0, false);
345
+ this.offset += 8;
346
+ return value;
347
+ }
348
+ takeSlice(start, end) {
349
+ return this.bytes.slice(start, end);
350
+ }
351
+ position() {
352
+ return this.offset;
353
+ }
354
+ isEOF() {
355
+ return this.offset === this.bytes.length;
356
+ }
357
+ }
358
+ function readValue(reader, limits, depth) {
359
+ const initial = reader.readByte();
360
+ const major = initial >> 5;
361
+ const additional = initial & 0x1f;
362
+ switch (major) {
363
+ case CBOR_MAJOR_UINT:
364
+ return readUnsigned(additional, reader);
365
+ case CBOR_MAJOR_NINT:
366
+ return readNegative(additional, reader);
367
+ case CBOR_MAJOR_TEXT:
368
+ return readText(additional, reader, limits);
369
+ case CBOR_MAJOR_ARRAY:
370
+ return readArray(additional, reader, limits, depth);
371
+ case CBOR_MAJOR_MAP:
372
+ return readMap(additional, reader, limits, depth);
373
+ case CBOR_MAJOR_SIMPLE:
374
+ return readSimpleOrFloat(additional, reader);
375
+ default:
376
+ throw dvError('UNSUPPORTED_CBOR', `unsupported CBOR major type ${major}`);
377
+ }
378
+ }
379
+ function readUnsigned(additional, reader) {
380
+ const value = readLengthValue(additional, reader);
381
+ if (value > MAX_SAFE_INT) {
382
+ throw dvError('INTEGER_OUT_OF_RANGE', `integer is outside safe range (${value} > ${MAX_SAFE_INT})`);
383
+ }
384
+ return Number(value);
385
+ }
386
+ function readNegative(additional, reader) {
387
+ const value = readLengthValue(additional, reader);
388
+ if (value >= BigInt(MAX_SAFE_INT)) {
389
+ throw dvError('INTEGER_OUT_OF_RANGE', `integer is outside safe range (-1 - ${value} < ${MIN_SAFE_INT})`);
390
+ }
391
+ return -1 - Number(value);
392
+ }
393
+ function readText(additional, reader, limits) {
394
+ const length = readLength(additional, reader);
395
+ if (length > limits.maxStringBytes) {
396
+ throw dvError('STRING_TOO_LONG', `string exceeds maxStringBytes (${length} > ${limits.maxStringBytes})`);
397
+ }
398
+ const start = reader.position();
399
+ for (let i = 0; i < length; i += 1) {
400
+ reader.readByte();
401
+ }
402
+ const bytes = reader.takeSlice(start, start + length);
403
+ let decoded;
404
+ try {
405
+ decoded = textDecoder.decode(bytes);
406
+ }
407
+ catch {
408
+ throw dvError('INVALID_UTF8', 'invalid UTF-8 in string');
409
+ }
410
+ const roundTrip = textEncoder.encode(decoded);
411
+ if (!bytesEqual(bytes, roundTrip)) {
412
+ throw dvError('INVALID_UTF8', 'invalid UTF-8 in string');
413
+ }
414
+ return decoded;
415
+ }
416
+ function readArray(additional, reader, limits, depth) {
417
+ const length = readLength(additional, reader);
418
+ const nextDepth = depth + 1;
419
+ if (nextDepth > limits.maxDepth) {
420
+ throw dvError('DEPTH_EXCEEDED', `maxDepth ${limits.maxDepth} exceeded`);
421
+ }
422
+ if (length > limits.maxArrayLength) {
423
+ throw dvError('ARRAY_TOO_LONG', `array length exceeds maxArrayLength (${length} > ${limits.maxArrayLength})`);
424
+ }
425
+ const result = [];
426
+ for (let i = 0; i < length; i += 1) {
427
+ result.push(readValue(reader, limits, nextDepth));
428
+ }
429
+ return result;
430
+ }
431
+ function readMap(additional, reader, limits, depth) {
432
+ const length = readLength(additional, reader);
433
+ const nextDepth = depth + 1;
434
+ if (nextDepth > limits.maxDepth) {
435
+ throw dvError('DEPTH_EXCEEDED', `maxDepth ${limits.maxDepth} exceeded`);
436
+ }
437
+ if (length > limits.maxMapLength) {
438
+ throw dvError('MAP_TOO_LONG', `map entries exceed maxMapLength (${length} > ${limits.maxMapLength})`);
439
+ }
440
+ const result = Object.create(null);
441
+ let previousKey;
442
+ for (let i = 0; i < length; i += 1) {
443
+ const keyStart = reader.position();
444
+ const keyInitial = reader.readByte();
445
+ const keyMajor = keyInitial >> 5;
446
+ if (keyMajor !== CBOR_MAJOR_TEXT) {
447
+ throw dvError('UNSUPPORTED_CBOR', 'map keys must be text strings');
448
+ }
449
+ const key = readText(keyInitial & 0x1f, reader, limits);
450
+ const keyEnd = reader.position();
451
+ const encodedKey = reader.takeSlice(keyStart, keyEnd);
452
+ if (previousKey) {
453
+ const ordering = compareCanonicalKeys(previousKey, encodedKey);
454
+ if (ordering === 0) {
455
+ throw dvError('DUPLICATE_KEY', `map contains duplicate key "${key}"`);
456
+ }
457
+ if (ordering > 0) {
458
+ throw dvError('KEY_ORDER', 'map keys are not in canonical order');
459
+ }
460
+ }
461
+ previousKey = encodedKey;
462
+ result[key] = readValue(reader, limits, nextDepth);
463
+ }
464
+ return result;
465
+ }
466
+ function readSimpleOrFloat(additional, reader) {
467
+ if (additional === 20) {
468
+ return false;
469
+ }
470
+ if (additional === 21) {
471
+ return true;
472
+ }
473
+ if (additional === 22) {
474
+ return null;
475
+ }
476
+ if (additional === 27) {
477
+ const value = reader.readFloat64();
478
+ if (!Number.isFinite(value)) {
479
+ throw dvError('NAN_OR_INF', 'DV numbers must be finite');
480
+ }
481
+ if (Number.isInteger(value)) {
482
+ throw dvError('NON_CANONICAL_FLOAT', 'integers must use CBOR integer encoding');
483
+ }
484
+ return Object.is(value, -0) ? 0 : value;
485
+ }
486
+ if (additional === 31) {
487
+ throw dvError('NON_CANONICAL_LENGTH', 'indefinite lengths are not allowed');
488
+ }
489
+ if (additional === 24 || additional === 25 || additional === 26) {
490
+ throw dvError('NON_CANONICAL_FLOAT', 'only float64 is allowed');
491
+ }
492
+ throw dvError('UNSUPPORTED_CBOR', `unsupported simple value ${additional}`);
493
+ }
494
+ function readLength(additional, reader) {
495
+ const value = readLengthValue(additional, reader);
496
+ if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
497
+ throw dvError('NON_CANONICAL_LENGTH', 'length exceeds supported range');
498
+ }
499
+ return Number(value);
500
+ }
501
+ function readLengthValue(additional, reader) {
502
+ if (additional <= 23) {
503
+ return BigInt(additional);
504
+ }
505
+ if (additional === 24) {
506
+ const value = reader.readUint8();
507
+ if (value < 24) {
508
+ throw dvError('NON_CANONICAL_LENGTH', 'length not using shortest encoding');
509
+ }
510
+ return BigInt(value);
511
+ }
512
+ if (additional === 25) {
513
+ const value = reader.readUint16();
514
+ if (value <= 0xff) {
515
+ throw dvError('NON_CANONICAL_LENGTH', 'length not using shortest encoding');
516
+ }
517
+ return BigInt(value);
518
+ }
519
+ if (additional === 26) {
520
+ const value = reader.readUint32();
521
+ if (value <= 0xffff) {
522
+ throw dvError('NON_CANONICAL_LENGTH', 'length not using shortest encoding');
523
+ }
524
+ return BigInt(value);
525
+ }
526
+ if (additional === 27) {
527
+ const value = reader.readUint64();
528
+ if (value <= 0xffffffffn) {
529
+ throw dvError('NON_CANONICAL_LENGTH', 'length not using shortest encoding');
530
+ }
531
+ return value;
532
+ }
533
+ if (additional === 31) {
534
+ throw dvError('NON_CANONICAL_LENGTH', 'indefinite lengths are not allowed');
535
+ }
536
+ throw dvError('UNSUPPORTED_CBOR', `unsupported additional info ${additional}`);
537
+ }
538
+ function compareCanonicalKeys(a, b) {
539
+ if (a.length !== b.length) {
540
+ return a.length - b.length;
541
+ }
542
+ for (let i = 0; i < a.length; i += 1) {
543
+ if (a[i] !== b[i]) {
544
+ return a[i] - b[i];
545
+ }
546
+ }
547
+ return 0;
548
+ }
549
+ function concat(a, b) {
550
+ const out = new Uint8Array(a.length + b.length);
551
+ out.set(a, 0);
552
+ out.set(b, a.length);
553
+ return out;
554
+ }
555
+ function bytesEqual(a, b) {
556
+ if (a.length !== b.length) {
557
+ return false;
558
+ }
559
+ for (let i = 0; i < a.length; i += 1) {
560
+ if (a[i] !== b[i]) {
561
+ return false;
562
+ }
563
+ }
564
+ return true;
565
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@blue-quickjs/dv",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ "./package.json": "./package.json",
10
+ ".": {
11
+ "@blue-quickjs/source": "./src/index.ts",
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "!**/*.tsbuildinfo"
20
+ ],
21
+ "nx": {
22
+ "name": "dv"
23
+ },
24
+ "dependencies": {
25
+ "tslib": "^2.3.0"
26
+ },
27
+ "license": "MIT",
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/bluecontract/blue-quickjs.git"
34
+ }
35
+ }