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 CHANGED
@@ -1,24 +1,38 @@
1
1
  # microcbor
2
2
 
3
- [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg)](https://github.com/RichardLitt/standard-readme) [![license](https://img.shields.io/github/license/joeltg/microcbor)](https://opensource.org/licenses/MIT) [![NPM version](https://img.shields.io/npm/v/microcbor)](https://www.npmjs.com/package/microcbor) ![TypeScript types](https://img.shields.io/npm/types/microcbor) ![lines of code](https://img.shields.io/tokei/lines/github/joeltg/microcbor)
3
+ [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg)](https://github.com/RichardLitt/standard-readme) [![license](https://img.shields.io/github/license/joeltg/microcbor)](https://opensource.org/licenses/MIT) [![NPM version](https://img.shields.io/npm/v/microcbor)](https://www.npmjs.com/package/microcbor) ![TypeScript types](https://img.shields.io/npm/types/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
- - a small footprint,
10
- - fast performance, and
11
- - an async iterable streaming API
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. It works in Node, the browser, and Deno.
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
- [key: string]: CBORValue
75
+ [key: string]: CBORValue
74
76
  }
77
+ ```
75
78
 
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.
80
- declare function encode(
81
- value: CBORValue,
82
- options?: { chunkSize?: number }
83
- ): Uint8Array
79
+ ### Encoding
84
80
 
85
- declare function encodeStream(
86
- source: AsyncIterable<CBORValue>,
87
- options?: { chunkSize?: number }
88
- ): AsyncIterable<Uint8Array>
81
+ #### `EncodeOptions`
89
82
 
90
- declare function decode(data: Uint8Array): CBORValue
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
- declare function decodeStream(
93
- source: AsyncIterable<Uint8Array>
94
- ): AsyncIterable<CBORValue>
109
+ #### `encodingLength`
95
110
 
96
- // You can measure the byte length that a given value will
97
- // serialize to without actually allocating anything.
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
- readonly value: bigint
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 **2x faster** than node-cbor at encoding and about **1.5x faster** than node-cbor at decoding.
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.js
145
-
146
- > microcbor@0.2.0 test
147
- > ava
148
-
149
-
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)
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,5 @@
1
+ import { CBORValue } from "./types.js";
2
+ /** Decode a Web Streams API ReadableStream */
3
+ export declare class CBORDecoderStream extends TransformStream<Uint8Array, CBORValue> {
4
+ constructor();
5
+ }
@@ -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
+ }
@@ -17,4 +17,5 @@ export declare class Decoder {
17
17
  private getArgument;
18
18
  decodeValue(): CBORValue;
19
19
  }
20
+ /** Decode a single CBOR value */
20
21
  export declare function decode(data: Uint8Array): CBORValue;
@@ -154,6 +154,7 @@ export class Decoder {
154
154
  }
155
155
  }
156
156
  _Decoder_offset = new WeakMap(), _Decoder_view = new WeakMap();
157
+ /** Decode a single CBOR value */
157
158
  export function decode(data) {
158
159
  return new Decoder(data).decodeValue();
159
160
  }
@@ -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;