microcbor 0.2.0 → 0.3.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/README.md CHANGED
@@ -4,22 +4,22 @@
4
4
 
5
5
  Encode JavaScript values as canonical CBOR.
6
6
 
7
- microcbor is a minimal JavaScript [CBOR](https://cbor.io/) implementation. You can use microcbor to serialize JavaScript values to CBOR, and to deserialize them back into JavaScript values again. **microcbor doesn't support tags, bigints, typed arrays, non-string keys, or indefinite-length collections.**
7
+ microcbor is a minimal JavaScript [CBOR](https://cbor.io/) implementation featuring
8
8
 
9
- microcbor follows the [deterministic CBOR encoding requirements](https://www.rfc-editor.org/rfc/rfc8949.html#core-det) - all floating-point numbers are serialized in the smallest possible size without losing precision, and object entries are always sorted by key in byte-wise lexicographic order. `NaN` is always serialized as `0xf97e00`.
9
+ - a small footprint,
10
+ - fast performance, and
11
+ - an async iterable streaming API
10
12
 
11
- This library is TypeScript-native, ESM-only, and has just one dependency ([joeltg/fp16](https://github.com/joeltg/fp16) for half-precision floats). It works in Node, the browser, and Deno.
13
+ microcbor follows the [deterministic CBOR encoding requirements](https://www.rfc-editor.org/rfc/rfc8949.html#core-det) - all floating-point numbers are serialized in the smallest possible size without losing precision, and object entries are always sorted by key in byte-wise lexicographic order. `NaN` is always serialized as `0xf97e00`. **microcbor doesn't support tags, bigints, typed arrays, non-string keys, or indefinite-length collections.**
14
+
15
+ This library is TypeScript-native, ESM-only, and has just one dependency [joeltg/fp16](https://github.com/joeltg/fp16) for half-precision floats. It works in Node, the browser, and Deno.
12
16
 
13
17
  ## Table of Contents
14
18
 
15
19
  - [Install](#install)
16
20
  - [Usage](#usage)
17
21
  - [API](#api)
18
- - [Value types](#value-types)
19
- - [Encoding](#encoding)
20
- - [Decoding](#decoding)
21
- - [Encoding length](#encoding-length)
22
- - [Support](#support)
22
+ - [Value mapping](#value-mapping)
23
23
  - [Testing](#testing)
24
24
  - [Benchmarks](#benchmarks)
25
25
  - [Contributing](#contributing)
@@ -34,15 +34,7 @@ npm i microcbor
34
34
  Or in Deno:
35
35
 
36
36
  ```typescript
37
- import {
38
- encode,
39
- decode,
40
- encodeStream,
41
- decodeStream,
42
- encodingLength,
43
- CBORValue,
44
- UnsafeIntegerError,
45
- } from "https://cdn.skypack.dev/microcbor"
37
+ import { encode, decode } from "https://cdn.skypack.dev/microcbor"
46
38
  ```
47
39
 
48
40
  ## Usage
@@ -65,8 +57,6 @@ console.log(decode(data))
65
57
 
66
58
  ## API
67
59
 
68
- ### Value types
69
-
70
60
  ```ts
71
61
  declare type CBORValue =
72
62
  | undefined
@@ -82,39 +72,29 @@ interface CBORArray extends Array<CBORValue> {}
82
72
  interface CBORMap {
83
73
  [key: string]: CBORValue
84
74
  }
85
- ```
86
-
87
- ### Encoding
88
75
 
89
- ```typescript
76
+ // If not provided, chunkSize defaults to 512 bytes.
77
+ // It's only a guideline; `encodeStream` won't break up
78
+ // individual CBOR values like strings or byte arrays
79
+ // that are larger than the provided chunk size.
90
80
  declare function encode(
91
81
  value: CBORValue,
92
- options: { chunkSize?: number } = {}
82
+ options?: { chunkSize?: number }
93
83
  ): Uint8Array
94
84
 
95
85
  declare function encodeStream(
96
86
  source: AsyncIterable<CBORValue>,
97
87
  options?: { chunkSize?: number }
98
88
  ): AsyncIterable<Uint8Array>
99
- ```
100
89
 
101
- If not provided, `chunkSize` defaults to 512 bytes. It's only a guideline; `encodeStream` won't break up individual CBOR values like strings or byte arrays that are larger than the provided chunk size.
102
-
103
- ### Decoding
104
-
105
- ```typescript
106
90
  declare function decode(data: Uint8Array): CBORValue
107
91
 
108
92
  declare function decodeStream(
109
93
  source: AsyncIterable<Uint8Array>
110
94
  ): AsyncIterable<CBORValue>
111
- ```
112
95
 
113
- ### Encoding length
114
-
115
- You can measure the byte length that a given value will serialize to without actually allocating anything.
116
-
117
- ```ts
96
+ // You can measure the byte length that a given value will
97
+ // serialize to without actually allocating anything.
118
98
  declare function encodingLength(value: CBORValue): number
119
99
  ```
120
100
 
@@ -126,25 +106,24 @@ declare function encodingLength(value: CBORValue): number
126
106
  ```typescript
127
107
  declare class UnsafeIntegerError extends RangeError {
128
108
  readonly value: bigint
129
- constructor(message: string, value: bigint)
130
109
  }
131
110
  ```
132
111
 
133
112
  ## Value mapping
134
113
 
135
- | CBOR major type | JavaScript | notes |
136
- | ---------------------------- | -------------- | -------------------------------------------------------- |
137
- | `0` (non-negative integer) | `number` | decoding throws an `UnsafeIntegerError` on unsafe values |
138
- | `1` (negative integer) | `number` | decoding throws an `UnsafeIntegerError` on unsafe values |
139
- | `2` (byte string) | `Uint8Array` | |
140
- | `3` (UTF-8 string) | `string` | |
141
- | `4` (array) | `Array` | |
142
- | `5` (map) | `Object` | decoding throws an error on non-string keys |
143
- | `6` (tagged item) | Unsupported | decoding throws an error on non-string keys |
144
- | `7` (floating-point numbers) | `number` | |
145
- | `7` (booleans) | `boolean` | |
146
- | `7` (null) | `null` | |
147
- | `7` (undefined) | `undefined` | |
114
+ | CBOR major type | JavaScript | notes |
115
+ | ---------------------------- | --------------- | -------------------------------------------------------- |
116
+ | `0` (non-negative integer) | `number` | decoding throws an `UnsafeIntegerError` on unsafe values |
117
+ | `1` (negative integer) | `number` | decoding throws an `UnsafeIntegerError` on unsafe values |
118
+ | `2` (byte string) | `Uint8Array` | |
119
+ | `3` (UTF-8 string) | `string` | |
120
+ | `4` (array) | `Array` | |
121
+ | `5` (map) | `Object` | decoding throws an error on non-string keys |
122
+ | `6` (tagged item) | **Unsupported** | |
123
+ | `7` (floating-point numbers) | `number` | |
124
+ | `7` (booleans) | `boolean` | |
125
+ | `7` (null) | `null` | |
126
+ | `7` (undefined) | `undefined` | |
148
127
 
149
128
  ## Testing
150
129
 
@@ -154,9 +133,12 @@ Tests use [AVA](https://github.com/avajs/ava) and live in the [test](./test/) di
154
133
  npm run test
155
134
  ```
156
135
 
157
- ## Benchmarks
136
+ ## Comparison to node-cbor
158
137
 
159
- Basic testing in [src/benchmarks.test.js](src/benchmarks.test.js) indicate that microcbor is about **2x as fast** as node-cbor at encoding and about **1.5x as fast** as node-cbor at decoding.
138
+ - microcbor runs isomorphically on the web, in Node, and in Deno. node-cbor ships a separate cbor-web package.
139
+ - microcbor encodes `Uint8Array` values as CBOR byte strings (major type 2). node-cbor encodes `Uint8Array` values as tagged type arrays (major type 6 / RFC 8746), and encodes NodeJS `Buffer` values as CBOR byte strings (major type 2).
140
+ - microcbor uses async iterables for its streaming API. node-cbor uses NodeJS streams.
141
+ - microcbor is about **2x faster** than node-cbor at encoding and about **1.5x faster** than node-cbor at decoding.
160
142
 
161
143
  ```
162
144
  microcbor % npm run test -- test/benchmarks.test.js
@@ -165,12 +147,14 @@ microcbor % npm run test -- test/benchmarks.test.js
165
147
  > ava
166
148
 
167
149
 
168
- ✔ time encode() (382ms)
169
- ℹ microcbor: 63.44141721725464 (ms)
170
- ℹ node-cbor: 152.31466674804688 (ms)
171
- time decode() (164ms)
172
- microcbor: 72.13012504577637 (ms)
173
- node-cbor: 87.16287469863892 (ms)
150
+ ✔ time encode() (390ms)
151
+ ℹ microcbor: 66.47262525558472 (ms)
152
+ ℹ node-cbor: 155.0249171257019 (ms)
153
+ JSON.stringify: 5.56374979019165 (ms)
154
+ time decode() (161ms)
155
+ microcbor: 64.23729228973389 (ms)
156
+ ℹ node-cbor: 91.34658432006836 (ms)
157
+ ℹ JSON.parse: 2.7592921257019043 (ms)
174
158
 
175
159
 
176
160
  2 tests passed
package/lib/decode.d.ts CHANGED
@@ -1,2 +1,20 @@
1
1
  import type { CBORValue } from "./types.js";
2
+ export declare class Decoder {
3
+ #private;
4
+ private readonly data;
5
+ constructor(data: Uint8Array);
6
+ getOffset(): number;
7
+ private constant;
8
+ private float16;
9
+ private float32;
10
+ private float64;
11
+ private uint8;
12
+ private uint16;
13
+ private uint32;
14
+ private uint64;
15
+ private decodeBytes;
16
+ private decodeString;
17
+ private getArgument;
18
+ decodeValue(): CBORValue;
19
+ }
2
20
  export declare function decode(data: Uint8Array): CBORValue;
package/lib/decode.js CHANGED
@@ -1,32 +1,49 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _Decoder_offset, _Decoder_view;
1
13
  import { getFloat16 } from "fp16";
2
14
  import { UnsafeIntegerError, maxSafeInteger, minSafeInteger } from "./utils.js";
3
- class Decoder {
15
+ export class Decoder {
4
16
  constructor(data) {
5
17
  this.data = data;
18
+ _Decoder_offset.set(this, void 0);
19
+ _Decoder_view.set(this, void 0);
6
20
  this.constant = (size, f) => () => {
7
21
  const value = f();
8
- this.offset += size;
22
+ __classPrivateFieldSet(this, _Decoder_offset, __classPrivateFieldGet(this, _Decoder_offset, "f") + size, "f");
9
23
  return value;
10
24
  };
11
- this.float16 = this.constant(2, () => getFloat16(this.view, this.offset));
12
- this.float32 = this.constant(4, () => this.view.getFloat32(this.offset));
13
- this.float64 = this.constant(8, () => this.view.getFloat64(this.offset));
14
- this.uint8 = this.constant(1, () => this.view.getUint8(this.offset));
15
- this.uint16 = this.constant(2, () => this.view.getUint16(this.offset));
16
- this.uint32 = this.constant(4, () => this.view.getUint32(this.offset));
17
- this.uint64 = this.constant(8, () => this.view.getBigUint64(this.offset));
18
- this.offset = 0;
19
- this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
25
+ this.float16 = this.constant(2, () => getFloat16(__classPrivateFieldGet(this, _Decoder_view, "f"), __classPrivateFieldGet(this, _Decoder_offset, "f")));
26
+ this.float32 = this.constant(4, () => __classPrivateFieldGet(this, _Decoder_view, "f").getFloat32(__classPrivateFieldGet(this, _Decoder_offset, "f")));
27
+ this.float64 = this.constant(8, () => __classPrivateFieldGet(this, _Decoder_view, "f").getFloat64(__classPrivateFieldGet(this, _Decoder_offset, "f")));
28
+ this.uint8 = this.constant(1, () => __classPrivateFieldGet(this, _Decoder_view, "f").getUint8(__classPrivateFieldGet(this, _Decoder_offset, "f")));
29
+ this.uint16 = this.constant(2, () => __classPrivateFieldGet(this, _Decoder_view, "f").getUint16(__classPrivateFieldGet(this, _Decoder_offset, "f")));
30
+ this.uint32 = this.constant(4, () => __classPrivateFieldGet(this, _Decoder_view, "f").getUint32(__classPrivateFieldGet(this, _Decoder_offset, "f")));
31
+ this.uint64 = this.constant(8, () => __classPrivateFieldGet(this, _Decoder_view, "f").getBigUint64(__classPrivateFieldGet(this, _Decoder_offset, "f")));
32
+ __classPrivateFieldSet(this, _Decoder_offset, 0, "f");
33
+ __classPrivateFieldSet(this, _Decoder_view, new DataView(data.buffer, data.byteOffset, data.byteLength), "f");
34
+ }
35
+ getOffset() {
36
+ return __classPrivateFieldGet(this, _Decoder_offset, "f");
20
37
  }
21
38
  decodeBytes(length) {
22
39
  const value = new Uint8Array(length);
23
- value.set(this.data.subarray(this.offset, this.offset + length), 0);
24
- this.offset += length;
40
+ value.set(this.data.subarray(__classPrivateFieldGet(this, _Decoder_offset, "f"), __classPrivateFieldGet(this, _Decoder_offset, "f") + length), 0);
41
+ __classPrivateFieldSet(this, _Decoder_offset, __classPrivateFieldGet(this, _Decoder_offset, "f") + length, "f");
25
42
  return value;
26
43
  }
27
44
  decodeString(length) {
28
- const value = new TextDecoder().decode(this.data.subarray(this.offset, this.offset + length));
29
- this.offset += length;
45
+ const value = new TextDecoder().decode(this.data.subarray(__classPrivateFieldGet(this, _Decoder_offset, "f"), __classPrivateFieldGet(this, _Decoder_offset, "f") + length));
46
+ __classPrivateFieldSet(this, _Decoder_offset, __classPrivateFieldGet(this, _Decoder_offset, "f") + length, "f");
30
47
  return value;
31
48
  }
32
49
  getArgument(additionalInformation) {
@@ -136,6 +153,7 @@ class Decoder {
136
153
  }
137
154
  }
138
155
  }
156
+ _Decoder_offset = new WeakMap(), _Decoder_view = new WeakMap();
139
157
  export function decode(data) {
140
158
  return new Decoder(data).decodeValue();
141
159
  }
package/lib/encode.d.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  import type { CBORValue } from "./types.js";
2
2
  export declare class Encoder {
3
- readonly options: {
4
- chunkSize?: number;
5
- };
6
3
  static defaultChunkSize: number;
7
4
  closed: boolean;
8
5
  private buffer;
9
6
  private view;
10
7
  private offset;
8
+ private readonly encoder;
9
+ private readonly chunkSize;
11
10
  constructor(options?: {
12
11
  chunkSize?: number;
12
+ noCopy?: boolean;
13
13
  });
14
14
  private allocate;
15
15
  private float16;
@@ -19,7 +19,6 @@ export declare class Encoder {
19
19
  private uint16;
20
20
  private uint32;
21
21
  private uint64;
22
- private constant;
23
22
  private encodeTypeAndArgument;
24
23
  private encodeNumber;
25
24
  private encodeInteger;
package/lib/encode.js CHANGED
@@ -1,15 +1,9 @@
1
1
  import { getFloat16Precision, getFloat32Precision, setFloat16, Precision, } from "fp16";
2
2
  export class Encoder {
3
3
  constructor(options = {}) {
4
- this.options = options;
5
- this.float16 = this.constant(2, (value) => setFloat16(this.view, this.offset, value));
6
- this.float32 = this.constant(4, (value) => this.view.setFloat32(this.offset, value));
7
- this.float64 = this.constant(8, (value) => this.view.setFloat64(this.offset, value));
8
- this.uint8 = this.constant(1, (value) => this.view.setUint8(this.offset, value));
9
- this.uint16 = this.constant(2, (value) => this.view.setUint16(this.offset, value));
10
- this.uint32 = this.constant(4, (value) => this.view.setUint32(this.offset, value));
11
- this.uint64 = this.constant(8, (value) => this.view.setBigUint64(this.offset, BigInt(value)));
12
- this.buffer = new ArrayBuffer(options.chunkSize || Encoder.defaultChunkSize);
4
+ this.encoder = new TextEncoder();
5
+ this.chunkSize = options.chunkSize || Encoder.defaultChunkSize;
6
+ this.buffer = new ArrayBuffer(this.chunkSize);
13
7
  this.view = new DataView(this.buffer);
14
8
  this.offset = 0;
15
9
  this.closed = false;
@@ -17,19 +11,46 @@ export class Encoder {
17
11
  *allocate(size) {
18
12
  if (this.buffer.byteLength < this.offset + size) {
19
13
  yield new Uint8Array(this.buffer, 0, this.offset);
20
- const byteLength = Math.max(size, this.options.chunkSize || Encoder.defaultChunkSize);
14
+ const byteLength = Math.max(size, this.chunkSize);
21
15
  this.buffer = new ArrayBuffer(byteLength);
22
16
  this.view = new DataView(this.buffer);
23
17
  this.offset = 0;
24
18
  }
25
19
  }
26
- constant(size, f) {
27
- const g = function* (value) {
28
- yield* this.allocate(size);
29
- f(value);
30
- this.offset += size;
31
- };
32
- return g.bind(this);
20
+ *float16(value) {
21
+ yield* this.allocate(2);
22
+ setFloat16(this.view, this.offset, value);
23
+ this.offset += 2;
24
+ }
25
+ *float32(value) {
26
+ yield* this.allocate(4);
27
+ this.view.setFloat32(this.offset, value);
28
+ this.offset += 4;
29
+ }
30
+ *float64(value) {
31
+ yield* this.allocate(8);
32
+ this.view.setFloat64(this.offset, value);
33
+ this.offset += 8;
34
+ }
35
+ *uint8(value) {
36
+ yield* this.allocate(1);
37
+ this.view.setUint8(this.offset, value);
38
+ this.offset += 1;
39
+ }
40
+ *uint16(value) {
41
+ yield* this.allocate(2);
42
+ this.view.setUint16(this.offset, value);
43
+ this.offset += 2;
44
+ }
45
+ *uint32(value) {
46
+ yield* this.allocate(4);
47
+ this.view.setUint32(this.offset, value);
48
+ this.offset += 4;
49
+ }
50
+ *uint64(value) {
51
+ yield* this.allocate(8);
52
+ this.view.setBigUint64(this.offset, BigInt(value));
53
+ this.offset += 8;
33
54
  }
34
55
  *encodeTypeAndArgument(type, argument) {
35
56
  const additionalInformation = Encoder.getAdditionalInformation(argument);
@@ -84,7 +105,7 @@ export class Encoder {
84
105
  }
85
106
  }
86
107
  *encodeString(value) {
87
- const data = new TextEncoder().encode(value);
108
+ const data = this.encoder.encode(value);
88
109
  yield* this.encodeTypeAndArgument(3, data.byteLength);
89
110
  yield* this.allocate(data.byteLength);
90
111
  new Uint8Array(this.buffer, this.offset).set(data);
package/lib/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export { encode } from "./encode.js";
2
- export { decode } from "./decode.js";
1
+ export type { CBORValue, CBORMap, CBORArray } from "./types.js";
2
+ export { encode, Encoder } from "./encode.js";
3
+ export { decode, Decoder } from "./decode.js";
3
4
  export { encodeStream } from "./encodeStream.js";
4
5
  export { decodeStream } from "./decodeStream.js";
5
6
  export { encodingLength } from "./encodingLength.js";
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { encode } from "./encode.js";
2
- export { decode } from "./decode.js";
1
+ export { encode, Encoder } from "./encode.js";
2
+ export { decode, Decoder } from "./decode.js";
3
3
  export { encodeStream } from "./encodeStream.js";
4
4
  export { decodeStream } from "./decodeStream.js";
5
5
  export { encodingLength } from "./encodingLength.js";
package/lib/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare type CBORValue = undefined | null | boolean | number | string | Uint8Array | CBORArray | CBORMap;
1
+ export type CBORValue = undefined | null | boolean | number | string | Uint8Array | CBORArray | CBORMap;
2
2
  export interface CBORArray extends Array<CBORValue> {
3
3
  }
4
4
  export interface CBORMap {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "microcbor",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Encode JavaScript values as canonical CBOR",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -27,9 +27,9 @@
27
27
  },
28
28
  "homepage": "https://github.com/joeltg/microcbor#readme",
29
29
  "devDependencies": {
30
- "ava": "^4.3.1",
31
- "cbor": "^8.0.2",
32
- "typescript": "^4.8.2"
30
+ "ava": "^5.3.1",
31
+ "cbor": "^8.1.0",
32
+ "typescript": "^5.1.6"
33
33
  },
34
34
  "dependencies": {
35
35
  "fp16": "^0.2.0"