microcbor 0.2.0 → 0.2.1
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 +42 -57
- package/lib/encode.d.ts +3 -4
- package/lib/encode.js +39 -18
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -1
- package/package.json +1 -1
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
|
|
7
|
+
microcbor is a minimal JavaScript [CBOR](https://cbor.io/) implementation featuring
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- a small footprint,
|
|
10
|
+
- fast performance, and
|
|
11
|
+
- an async iterable streaming API
|
|
10
12
|
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
|
@@ -132,19 +112,19 @@ declare class UnsafeIntegerError extends RangeError {
|
|
|
132
112
|
|
|
133
113
|
## Value mapping
|
|
134
114
|
|
|
135
|
-
| CBOR major type | JavaScript
|
|
136
|
-
| ---------------------------- |
|
|
137
|
-
| `0` (non-negative integer) | `number`
|
|
138
|
-
| `1` (negative integer) | `number`
|
|
139
|
-
| `2` (byte string) | `Uint8Array`
|
|
140
|
-
| `3` (UTF-8 string) | `string`
|
|
141
|
-
| `4` (array) | `Array`
|
|
142
|
-
| `5` (map) | `Object`
|
|
143
|
-
| `6` (tagged item) | Unsupported
|
|
144
|
-
| `7` (floating-point numbers) | `number`
|
|
145
|
-
| `7` (booleans) | `boolean`
|
|
146
|
-
| `7` (null) | `null`
|
|
147
|
-
| `7` (undefined) | `undefined`
|
|
115
|
+
| CBOR major type | JavaScript | notes |
|
|
116
|
+
| ---------------------------- | --------------- | -------------------------------------------------------- |
|
|
117
|
+
| `0` (non-negative integer) | `number` | decoding throws an `UnsafeIntegerError` on unsafe values |
|
|
118
|
+
| `1` (negative integer) | `number` | decoding throws an `UnsafeIntegerError` on unsafe values |
|
|
119
|
+
| `2` (byte string) | `Uint8Array` | |
|
|
120
|
+
| `3` (UTF-8 string) | `string` | |
|
|
121
|
+
| `4` (array) | `Array` | |
|
|
122
|
+
| `5` (map) | `Object` | decoding throws an error on non-string keys |
|
|
123
|
+
| `6` (tagged item) | **Unsupported** | |
|
|
124
|
+
| `7` (floating-point numbers) | `number` | |
|
|
125
|
+
| `7` (booleans) | `boolean` | |
|
|
126
|
+
| `7` (null) | `null` | |
|
|
127
|
+
| `7` (undefined) | `undefined` | |
|
|
148
128
|
|
|
149
129
|
## Testing
|
|
150
130
|
|
|
@@ -154,9 +134,12 @@ Tests use [AVA](https://github.com/avajs/ava) and live in the [test](./test/) di
|
|
|
154
134
|
npm run test
|
|
155
135
|
```
|
|
156
136
|
|
|
157
|
-
##
|
|
137
|
+
## Comparison to node-cbor
|
|
158
138
|
|
|
159
|
-
|
|
139
|
+
- microcbor runs isomorphically on the web, in Node, and in Deno. node-cbor ships a separate cbor-web package.
|
|
140
|
+
- 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).
|
|
141
|
+
- microcbor uses async iterables for its streaming API. node-cbor uses NodeJS streams.
|
|
142
|
+
- microcbor is about **2x faster** than node-cbor at encoding and about **1.5x faster** than node-cbor at decoding.
|
|
160
143
|
|
|
161
144
|
```
|
|
162
145
|
microcbor % npm run test -- test/benchmarks.test.js
|
|
@@ -165,12 +148,14 @@ microcbor % npm run test -- test/benchmarks.test.js
|
|
|
165
148
|
> ava
|
|
166
149
|
|
|
167
150
|
|
|
168
|
-
✔ time encode() (
|
|
169
|
-
ℹ microcbor:
|
|
170
|
-
ℹ node-cbor:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
ℹ
|
|
151
|
+
✔ time encode() (390ms)
|
|
152
|
+
ℹ microcbor: 66.47262525558472 (ms)
|
|
153
|
+
ℹ node-cbor: 155.0249171257019 (ms)
|
|
154
|
+
ℹ JSON.stringify: 5.56374979019165 (ms)
|
|
155
|
+
✔ time decode() (161ms)
|
|
156
|
+
ℹ microcbor: 64.23729228973389 (ms)
|
|
157
|
+
ℹ node-cbor: 91.34658432006836 (ms)
|
|
158
|
+
ℹ JSON.parse: 2.7592921257019043 (ms)
|
|
174
159
|
─
|
|
175
160
|
|
|
176
161
|
2 tests passed
|
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.
|
|
5
|
-
this.
|
|
6
|
-
this.
|
|
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.
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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 =
|
|
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,4 +1,5 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export type { CBORValue, CBORMap, CBORArray } from "./types.js";
|
|
2
|
+
export { encode, Encoder } from "./encode.js";
|
|
2
3
|
export { decode } from "./decode.js";
|
|
3
4
|
export { encodeStream } from "./encodeStream.js";
|
|
4
5
|
export { decodeStream } from "./decodeStream.js";
|
package/lib/index.js
CHANGED