microcbor 0.3.0 → 1.0.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 +173 -56
- package/lib/CBORDecoderStream.d.ts +5 -0
- package/lib/CBORDecoderStream.js +37 -0
- package/lib/CBOREncoderStream.d.ts +9 -0
- package/lib/CBOREncoderStream.js +24 -0
- package/lib/{decode.d.ts → Decoder.d.ts} +1 -0
- package/lib/{decode.js → Decoder.js} +1 -0
- package/lib/Encoder.d.ts +65 -0
- package/lib/Encoder.js +291 -0
- package/lib/decodeAsyncIterable.d.ts +37 -0
- package/lib/{decodeStream.js → decodeAsyncIterable.js} +11 -4
- package/lib/decodeIterable.d.ts +3 -0
- package/lib/decodeIterable.js +206 -0
- package/lib/encodeAsyncIterable.d.ts +4 -0
- package/lib/encodeAsyncIterable.js +9 -0
- package/lib/encodeIterable.d.ts +4 -0
- package/lib/encodeIterable.js +9 -0
- package/lib/encodingLength.d.ts +5 -1
- package/lib/encodingLength.js +8 -66
- package/lib/index.d.ts +8 -4
- package/lib/index.js +8 -4
- package/lib/utils.d.ts +7 -0
- package/lib/utils.js +72 -0
- package/package.json +16 -9
- package/lib/decodeStream.d.ts +0 -2
- package/lib/encode.d.ts +0 -35
- package/lib/encode.js +0 -228
- package/lib/encodeStream.d.ts +0 -4
- package/lib/encodeStream.js +0 -8
package/README.md
CHANGED
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
# microcbor
|
|
2
2
|
|
|
3
|
-
[](https://github.com/RichardLitt/standard-readme) [](https://opensource.org/licenses/MIT) [](https://www.npmjs.com/package/microcbor) 
|
|
3
|
+
[](https://github.com/RichardLitt/standard-readme) [](https://opensource.org/licenses/MIT) [](https://www.npmjs.com/package/microcbor) 
|
|
4
4
|
|
|
5
5
|
Encode JavaScript values as canonical CBOR.
|
|
6
6
|
|
|
7
7
|
microcbor is a minimal JavaScript [CBOR](https://cbor.io/) implementation featuring
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
- fast performance
|
|
11
|
-
-
|
|
9
|
+
- small footprint
|
|
10
|
+
- fast performance
|
|
11
|
+
- `Iterable` and `AsyncIterable` streaming APIs with "chunk recycling" encoding option
|
|
12
|
+
- [Web Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API)-compatible [TransformStream](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) classes
|
|
12
13
|
|
|
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
|
+
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 utf-8 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
|
|
|
15
|
-
This library is TypeScript-native, ESM-only, and has just one dependency [joeltg/fp16](https://github.com/joeltg/fp16) for half-precision floats.
|
|
16
|
+
This library is TypeScript-native, ESM-only, and has just **one dependency** [joeltg/fp16](https://github.com/joeltg/fp16) for half-precision floats.
|
|
16
17
|
|
|
17
18
|
## Table of Contents
|
|
18
19
|
|
|
19
20
|
- [Install](#install)
|
|
20
21
|
- [Usage](#usage)
|
|
21
22
|
- [API](#api)
|
|
23
|
+
- [CBOR Values](#cbor-values)
|
|
24
|
+
- [Encoding](#encoding)
|
|
25
|
+
- [`EncodeOptions`](#encodeoptions)
|
|
26
|
+
- [`encodingLength`](#encodinglength)
|
|
27
|
+
- [`encode`](#encode)
|
|
28
|
+
- [`encodeIterable`](#encodeiterable)
|
|
29
|
+
- [`encodeAsyncIterable`](#encodeasynciterable)
|
|
30
|
+
- [`CBOREncoderStream`](#cborencoderstream)
|
|
31
|
+
- [Decoding](#decoding)
|
|
32
|
+
- [`decode`](#decode)
|
|
33
|
+
- [`decodeIterable`](#decodeiterable)
|
|
34
|
+
- [`decodeAsyncIterable`](#decodeasynciterable)
|
|
35
|
+
- [`CBORDecoderStream`](#cbordecoderstream)
|
|
22
36
|
- [Value mapping](#value-mapping)
|
|
23
37
|
- [Testing](#testing)
|
|
24
38
|
- [Benchmarks](#benchmarks)
|
|
@@ -31,12 +45,6 @@ This library is TypeScript-native, ESM-only, and has just one dependency [joeltg
|
|
|
31
45
|
npm i microcbor
|
|
32
46
|
```
|
|
33
47
|
|
|
34
|
-
Or in Deno:
|
|
35
|
-
|
|
36
|
-
```typescript
|
|
37
|
-
import { encode, decode } from "https://cdn.skypack.dev/microcbor"
|
|
38
|
-
```
|
|
39
|
-
|
|
40
48
|
## Usage
|
|
41
49
|
|
|
42
50
|
```typescript
|
|
@@ -57,47 +65,134 @@ console.log(decode(data))
|
|
|
57
65
|
|
|
58
66
|
## API
|
|
59
67
|
|
|
68
|
+
### CBOR Values
|
|
69
|
+
|
|
60
70
|
```ts
|
|
61
|
-
declare type CBORValue =
|
|
62
|
-
| undefined
|
|
63
|
-
| null
|
|
64
|
-
| boolean
|
|
65
|
-
| number
|
|
66
|
-
| string
|
|
67
|
-
| Uint8Array
|
|
68
|
-
| CBORArray
|
|
69
|
-
| CBORMap
|
|
71
|
+
declare type CBORValue = undefined | null | boolean | number | string | Uint8Array | CBORArray | CBORMap
|
|
70
72
|
|
|
71
73
|
interface CBORArray extends Array<CBORValue> {}
|
|
72
74
|
interface CBORMap {
|
|
73
|
-
|
|
75
|
+
[key: string]: CBORValue
|
|
74
76
|
}
|
|
77
|
+
```
|
|
75
78
|
|
|
76
|
-
|
|
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.
|
|
80
|
-
declare function encode(
|
|
81
|
-
value: CBORValue,
|
|
82
|
-
options?: { chunkSize?: number }
|
|
83
|
-
): Uint8Array
|
|
79
|
+
### Encoding
|
|
84
80
|
|
|
85
|
-
|
|
86
|
-
source: AsyncIterable<CBORValue>,
|
|
87
|
-
options?: { chunkSize?: number }
|
|
88
|
-
): AsyncIterable<Uint8Array>
|
|
81
|
+
#### `EncodeOptions`
|
|
89
82
|
|
|
90
|
-
|
|
83
|
+
```ts
|
|
84
|
+
export interface EncodeOptions {
|
|
85
|
+
/**
|
|
86
|
+
* Re-use the same underlying ArrayBuffer for all yielded chunks.
|
|
87
|
+
* If this is enabled, the consumer must copy each chunk content
|
|
88
|
+
* themselves to a new buffer if they wish to keep it.
|
|
89
|
+
* This mode is useful for efficiently hashing objects without
|
|
90
|
+
* ever allocating memory for the entire encoded result.
|
|
91
|
+
* @default false
|
|
92
|
+
*/
|
|
93
|
+
chunkRecycling?: boolean
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Maximum chunk size.
|
|
97
|
+
* @default 4096
|
|
98
|
+
*/
|
|
99
|
+
chunkSize?: number
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Minimum bitsize for floating-point numbers: 16, 32, or 64.
|
|
103
|
+
* @default 16
|
|
104
|
+
*/
|
|
105
|
+
minFloatSize?: (typeof FloatSize)[keyof typeof FloatSize]
|
|
106
|
+
}
|
|
107
|
+
```
|
|
91
108
|
|
|
92
|
-
|
|
93
|
-
source: AsyncIterable<Uint8Array>
|
|
94
|
-
): AsyncIterable<CBORValue>
|
|
109
|
+
#### `encodingLength`
|
|
95
110
|
|
|
96
|
-
|
|
97
|
-
|
|
111
|
+
```ts
|
|
112
|
+
/**
|
|
113
|
+
* Calculate the byte length that a value will encode into
|
|
114
|
+
* without actually allocating anything.
|
|
115
|
+
*/
|
|
98
116
|
declare function encodingLength(value: CBORValue): number
|
|
99
117
|
```
|
|
100
118
|
|
|
119
|
+
#### `encode`
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
/**
|
|
123
|
+
* Encode a single CBOR value.
|
|
124
|
+
* options.chunkRecycling has no effect here.
|
|
125
|
+
*/
|
|
126
|
+
export function encode(value: CBORValue, options: EncodeOptions = {}): Uint8Array
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### `encodeIterable`
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
/** Encode an iterable of CBOR values into an iterable of Uint8Array chunks */
|
|
133
|
+
export function* encodeIterable(
|
|
134
|
+
source: Iterable<CBORValue>,
|
|
135
|
+
options: EncodeOptions = {},
|
|
136
|
+
): IterableIterator<Uint8Array>
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### `encodeAsyncIterable`
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
/** Encode an async iterable of CBOR values into an async iterable of Uint8Array chunks */
|
|
144
|
+
export async function* encodeAsyncIterable(
|
|
145
|
+
source: AsyncIterable<CBORValue>,
|
|
146
|
+
options: EncodeOptions = {},
|
|
147
|
+
): AsyncIterableIterator<Uint8Array>
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### `CBOREncoderStream`
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
/**
|
|
155
|
+
* Encode a Web Streams API ReadableStream.
|
|
156
|
+
* options.chunkRecycling has no effect here.
|
|
157
|
+
*/
|
|
158
|
+
export class CBOREncoderStream extends TransformStream<CBORValue, Uint8Array> {
|
|
159
|
+
public constructor(options: EncodeOptions = {})
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Decoding
|
|
164
|
+
|
|
165
|
+
#### `decode`
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
/** Decode a single CBOR value. */
|
|
169
|
+
export function decode(data: Uint8Array): CBORValue
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### `decodeIterable`
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
/** Decode an iterable of Uint8Array chunks into an iterable of CBOR values */
|
|
176
|
+
export function* decodeIterable(source: Iterable<Uint8Array>): IterableIterator<CBORValue>
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### `decodeAsyncIterable`
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
/** Decode an async iterable of Uint8Array chunks into an async iterable of CBOR values */
|
|
184
|
+
export async function* decodeAsyncIterable(source: AsyncIterable<Uint8Array>): AsyncIterable<CBORValue>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### `CBORDecoderStream`
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
/** Decode a Web Streams API ReadableStream. */
|
|
191
|
+
export class CBORDecoderStream extends TransformStream<Uint8Array, CBORValue> {
|
|
192
|
+
public constructor()
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
101
196
|
## Unsafe integer handling
|
|
102
197
|
|
|
103
198
|
- JavaScript integers below `Number.MIN_SAFE_INTEGER` or greater than `Number.MAX_SAFE_INTEGER` will encode as CBOR floating-point numbers, as per the [suggestion in the CBOR spec](https://www.rfc-editor.org/rfc/rfc8949.html#name-converting-from-json-to-cbo).
|
|
@@ -105,7 +200,7 @@ declare function encodingLength(value: CBORValue): number
|
|
|
105
200
|
|
|
106
201
|
```typescript
|
|
107
202
|
declare class UnsafeIntegerError extends RangeError {
|
|
108
|
-
|
|
203
|
+
readonly value: bigint
|
|
109
204
|
}
|
|
110
205
|
```
|
|
111
206
|
|
|
@@ -138,23 +233,45 @@ npm run test
|
|
|
138
233
|
- microcbor runs isomorphically on the web, in Node, and in Deno. node-cbor ships a separate cbor-web package.
|
|
139
234
|
- 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
235
|
- microcbor uses async iterables for its streaming API. node-cbor uses NodeJS streams.
|
|
141
|
-
- microcbor is about **
|
|
236
|
+
- microcbor is about **4x faster** than node-cbor at canonical encoding, ~2x faster than node-cbor's default non-canonical encoding, and ~1.5x faster than node-cbor at decoding.
|
|
142
237
|
|
|
143
238
|
```
|
|
144
|
-
microcbor % npm run test -- test/benchmarks.test.
|
|
145
|
-
|
|
146
|
-
> microcbor@0.
|
|
147
|
-
> ava
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
✔ time encode() (
|
|
151
|
-
ℹ microcbor:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
ℹ
|
|
156
|
-
|
|
157
|
-
|
|
239
|
+
microcbor % npm run test -- test/benchmarks.test.ts
|
|
240
|
+
|
|
241
|
+
> microcbor@0.4.0 test
|
|
242
|
+
> ava test/benchmarks.test.ts
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
✔ time encode() (237ms)
|
|
246
|
+
ℹ microcbor: {
|
|
247
|
+
avg: 0.2836770999999993,
|
|
248
|
+
std: 0.1553461595001637,
|
|
249
|
+
} (ms)
|
|
250
|
+
ℹ node-cbor: {
|
|
251
|
+
avg: 0.47247252999999945,
|
|
252
|
+
std: 0.6099837601508338,
|
|
253
|
+
} (ms)
|
|
254
|
+
ℹ node-cbor (canonical): {
|
|
255
|
+
avg: 0.9973837600000031,
|
|
256
|
+
std: 1.203792591464195,
|
|
257
|
+
} (ms)
|
|
258
|
+
ℹ JSON.stringify: {
|
|
259
|
+
avg: 0.009709539999999493,
|
|
260
|
+
std: 0.0014329558361671918,
|
|
261
|
+
} (ms)
|
|
262
|
+
✔ time decode()
|
|
263
|
+
ℹ microcbor: {
|
|
264
|
+
avg: 0.19635871000000235,
|
|
265
|
+
std: 0.35634472331099276,
|
|
266
|
+
} (ms)
|
|
267
|
+
ℹ node-cbor: {
|
|
268
|
+
avg: 0.35364794999999843,
|
|
269
|
+
std: 0.31256985912702206,
|
|
270
|
+
} (ms)
|
|
271
|
+
ℹ JSON.parse: {
|
|
272
|
+
avg: 0.018565019999997504,
|
|
273
|
+
std: 0.004339636959421219,
|
|
274
|
+
} (ms)
|
|
158
275
|
─
|
|
159
276
|
|
|
160
277
|
2 tests passed
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Decoder } from "./decodeAsyncIterable.js";
|
|
2
|
+
/** Decode a Web Streams API ReadableStream */
|
|
3
|
+
export class CBORDecoderStream extends TransformStream {
|
|
4
|
+
constructor() {
|
|
5
|
+
let readableController;
|
|
6
|
+
const readable = new ReadableStream({
|
|
7
|
+
start(controller) {
|
|
8
|
+
readableController = controller;
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
// We need to track whick chunks have been "processed" and only resolve each
|
|
12
|
+
// .transform() promise once all data from each chunk has been enqueued.
|
|
13
|
+
const chunks = new WeakMap();
|
|
14
|
+
async function pipe(controller) {
|
|
15
|
+
const decoder = new Decoder(readable.values(), {
|
|
16
|
+
onFree: (chunk) => chunks.get(chunk)?.resolve(),
|
|
17
|
+
});
|
|
18
|
+
for await (const value of decoder) {
|
|
19
|
+
controller.enqueue(value);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
super({
|
|
23
|
+
start(controller) {
|
|
24
|
+
pipe(controller).catch((err) => controller.error(err));
|
|
25
|
+
},
|
|
26
|
+
transform(chunk) {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
chunks.set(chunk, { resolve });
|
|
29
|
+
readableController.enqueue(chunk);
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
flush() {
|
|
33
|
+
readableController.close();
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CBORValue } from "./types.js";
|
|
2
|
+
import { EncodeOptions } from "./Encoder.js";
|
|
3
|
+
/**
|
|
4
|
+
* Encode a Web Streams API ReadableStream.
|
|
5
|
+
* options.chunkRecycling has no effect here.
|
|
6
|
+
*/
|
|
7
|
+
export declare class CBOREncoderStream extends TransformStream<CBORValue, Uint8Array> {
|
|
8
|
+
constructor(options?: EncodeOptions);
|
|
9
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Encoder } from "./Encoder.js";
|
|
2
|
+
/**
|
|
3
|
+
* Encode a Web Streams API ReadableStream.
|
|
4
|
+
* options.chunkRecycling has no effect here.
|
|
5
|
+
*/
|
|
6
|
+
export class CBOREncoderStream extends TransformStream {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
const encoder = new Encoder({ ...options, chunkRecycling: false });
|
|
9
|
+
super({
|
|
10
|
+
transform(value, controller) {
|
|
11
|
+
// Encode the incoming value and push all resulting chunks
|
|
12
|
+
for (const chunk of encoder.encodeValue(value)) {
|
|
13
|
+
controller.enqueue(chunk);
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
flush(controller) {
|
|
17
|
+
// Push any remaining chunks when the stream is closing
|
|
18
|
+
for (const chunk of encoder.flush()) {
|
|
19
|
+
controller.enqueue(chunk);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
package/lib/Encoder.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { CBORValue } from "./types.js";
|
|
2
|
+
export declare const FloatSize: {
|
|
3
|
+
f16: number;
|
|
4
|
+
f32: number;
|
|
5
|
+
f64: number;
|
|
6
|
+
};
|
|
7
|
+
export interface EncodeOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Re-use the same underlying ArrayBuffer for all yielded chunks.
|
|
10
|
+
* If this is enabled, the consumer must copy each chunk content
|
|
11
|
+
* themselves to a new buffer if they wish to keep it.
|
|
12
|
+
* This mode is useful for efficiently hashing objects without
|
|
13
|
+
* ever allocating memory for the entire encoded result.
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
16
|
+
chunkRecycling?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Maximum chunk size
|
|
19
|
+
* @default 4096
|
|
20
|
+
*/
|
|
21
|
+
chunkSize?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Minimum bitsize for floating-point numbers: 16, 32, or 64
|
|
24
|
+
* @default 16
|
|
25
|
+
*/
|
|
26
|
+
minFloatSize?: (typeof FloatSize)[keyof typeof FloatSize];
|
|
27
|
+
}
|
|
28
|
+
export declare class Encoder {
|
|
29
|
+
#private;
|
|
30
|
+
static defaultChunkSize: number;
|
|
31
|
+
readonly chunkRecycling: boolean;
|
|
32
|
+
readonly chunkSize: number;
|
|
33
|
+
readonly minFloatSize: (typeof FloatSize)[keyof typeof FloatSize];
|
|
34
|
+
private readonly encoder;
|
|
35
|
+
private readonly buffer;
|
|
36
|
+
private readonly view;
|
|
37
|
+
private readonly array;
|
|
38
|
+
private offset;
|
|
39
|
+
constructor(options?: EncodeOptions);
|
|
40
|
+
get closed(): boolean;
|
|
41
|
+
private allocate;
|
|
42
|
+
private float16;
|
|
43
|
+
private float32;
|
|
44
|
+
private float64;
|
|
45
|
+
private uint8;
|
|
46
|
+
private uint16;
|
|
47
|
+
private uint32;
|
|
48
|
+
private uint64;
|
|
49
|
+
private encodeTypeAndArgument;
|
|
50
|
+
private encodeNumber;
|
|
51
|
+
private encodeInteger;
|
|
52
|
+
private encodeFloat;
|
|
53
|
+
private encodeString;
|
|
54
|
+
private encodeBytes;
|
|
55
|
+
private writeBytes;
|
|
56
|
+
encodeValue(value: CBORValue): Iterable<Uint8Array>;
|
|
57
|
+
flush(): Iterable<Uint8Array>;
|
|
58
|
+
private static compareEntries;
|
|
59
|
+
private static getAdditionalInformation;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Encode a single CBOR value.
|
|
63
|
+
* options.chunkRecycling has no effect here.
|
|
64
|
+
*/
|
|
65
|
+
export declare function encode(value: CBORValue, options?: EncodeOptions): Uint8Array;
|