@btoon/js 0.0.0-stage → 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 +21 -0
- package/README.md +328 -2
- package/dist/cjs/constants.d.ts +34 -0
- package/dist/cjs/constants.js +32 -0
- package/dist/cjs/core.d.ts +15 -0
- package/dist/cjs/core.js +275 -0
- package/dist/cjs/decode.d.ts +10 -0
- package/dist/cjs/decode.js +554 -0
- package/dist/cjs/encode.d.ts +2 -0
- package/dist/cjs/encode.js +946 -0
- package/dist/cjs/errors.d.ts +53 -0
- package/dist/cjs/errors.js +45 -0
- package/dist/cjs/index.d.ts +68 -0
- package/dist/cjs/index.js +86 -0
- package/dist/cjs/inflate.d.ts +9 -0
- package/dist/cjs/inflate.js +353 -0
- package/dist/cjs/node.d.ts +64 -0
- package/dist/cjs/node.js +82 -0
- package/dist/cjs/options.d.ts +95 -0
- package/dist/cjs/options.js +128 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/platform-node.d.ts +3 -0
- package/dist/cjs/platform-node.js +132 -0
- package/dist/cjs/platform-portable.d.ts +7 -0
- package/dist/cjs/platform-portable.js +16 -0
- package/dist/cjs/sha256.d.ts +5 -0
- package/dist/cjs/sha256.js +141 -0
- package/dist/cjs/utf8.d.ts +19 -0
- package/dist/cjs/utf8.js +107 -0
- package/dist/cjs/values.d.ts +58 -0
- package/dist/cjs/values.js +129 -0
- package/dist/cjs/web.d.ts +25 -0
- package/dist/cjs/web.js +151 -0
- package/dist/esm/constants.d.ts +34 -0
- package/dist/esm/constants.js +29 -0
- package/dist/esm/core.d.ts +15 -0
- package/dist/esm/core.js +269 -0
- package/dist/esm/decode.d.ts +10 -0
- package/dist/esm/decode.js +549 -0
- package/dist/esm/encode.d.ts +2 -0
- package/dist/esm/encode.js +943 -0
- package/dist/esm/errors.d.ts +53 -0
- package/dist/esm/errors.js +39 -0
- package/dist/esm/index.d.ts +68 -0
- package/dist/esm/index.js +74 -0
- package/dist/esm/inflate.d.ts +9 -0
- package/dist/esm/inflate.js +349 -0
- package/dist/esm/node.d.ts +64 -0
- package/dist/esm/node.js +70 -0
- package/dist/esm/options.d.ts +95 -0
- package/dist/esm/options.js +124 -0
- package/dist/esm/platform-node.d.ts +3 -0
- package/dist/esm/platform-node.js +96 -0
- package/dist/esm/platform-portable.d.ts +7 -0
- package/dist/esm/platform-portable.js +13 -0
- package/dist/esm/sha256.d.ts +5 -0
- package/dist/esm/sha256.js +136 -0
- package/dist/esm/utf8.d.ts +19 -0
- package/dist/esm/utf8.js +99 -0
- package/dist/esm/values.d.ts +58 -0
- package/dist/esm/values.js +120 -0
- package/dist/esm/web.d.ts +25 -0
- package/dist/esm/web.js +143 -0
- package/package.json +85 -4
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SHA-256 (FIPS 180-4) and HMAC-SHA256 (RFC 2104) for runtimes without a synchronous
|
|
3
|
+
// native implementation. Used only for the signed envelope (SPEC §8).
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.sha256 = sha256;
|
|
6
|
+
exports.hmacSha256 = hmacSha256;
|
|
7
|
+
exports.timingSafeEqual = timingSafeEqual;
|
|
8
|
+
const K = new Int32Array([
|
|
9
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98,
|
|
10
|
+
0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
|
|
11
|
+
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8,
|
|
12
|
+
0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
|
13
|
+
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
|
|
14
|
+
0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
|
|
15
|
+
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
|
|
16
|
+
0xc67178f2,
|
|
17
|
+
]);
|
|
18
|
+
const IV = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
|
|
19
|
+
class Sha256 {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.h = new Int32Array(IV);
|
|
22
|
+
this.w = new Int32Array(64);
|
|
23
|
+
this.block = new Uint8Array(64);
|
|
24
|
+
this.blockLen = 0;
|
|
25
|
+
this.total = 0;
|
|
26
|
+
}
|
|
27
|
+
update(data) {
|
|
28
|
+
let i = 0;
|
|
29
|
+
const n = data.length;
|
|
30
|
+
this.total += n;
|
|
31
|
+
if (this.blockLen > 0) {
|
|
32
|
+
const take = Math.min(64 - this.blockLen, n);
|
|
33
|
+
this.block.set(data.subarray(0, take), this.blockLen);
|
|
34
|
+
this.blockLen += take;
|
|
35
|
+
i = take;
|
|
36
|
+
if (this.blockLen < 64)
|
|
37
|
+
return this;
|
|
38
|
+
this.compress(this.block, 0);
|
|
39
|
+
this.blockLen = 0;
|
|
40
|
+
}
|
|
41
|
+
for (; i + 64 <= n; i += 64)
|
|
42
|
+
this.compress(data, i);
|
|
43
|
+
if (i < n) {
|
|
44
|
+
this.block.set(data.subarray(i), 0);
|
|
45
|
+
this.blockLen = n - i;
|
|
46
|
+
}
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
digest() {
|
|
50
|
+
const bits = this.total * 8;
|
|
51
|
+
const block = this.block;
|
|
52
|
+
block[this.blockLen++] = 0x80;
|
|
53
|
+
if (this.blockLen > 56) {
|
|
54
|
+
block.fill(0, this.blockLen);
|
|
55
|
+
this.compress(block, 0);
|
|
56
|
+
this.blockLen = 0;
|
|
57
|
+
}
|
|
58
|
+
block.fill(0, this.blockLen, 56);
|
|
59
|
+
const view = new DataView(block.buffer);
|
|
60
|
+
view.setUint32(56, Math.floor(bits / 4294967296));
|
|
61
|
+
view.setUint32(60, bits >>> 0);
|
|
62
|
+
this.compress(block, 0);
|
|
63
|
+
const out = new Uint8Array(32);
|
|
64
|
+
const ov = new DataView(out.buffer);
|
|
65
|
+
for (let j = 0; j < 8; j++)
|
|
66
|
+
ov.setInt32(j * 4, this.h[j]);
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
compress(d, p) {
|
|
70
|
+
const w = this.w;
|
|
71
|
+
for (let j = 0; j < 16; j++, p += 4)
|
|
72
|
+
w[j] = (d[p] << 24) | (d[p + 1] << 16) | (d[p + 2] << 8) | d[p + 3];
|
|
73
|
+
for (let j = 16; j < 64; j++) {
|
|
74
|
+
const a = w[j - 15];
|
|
75
|
+
const b = w[j - 2];
|
|
76
|
+
const s0 = ((a >>> 7) | (a << 25)) ^ ((a >>> 18) | (a << 14)) ^ (a >>> 3);
|
|
77
|
+
const s1 = ((b >>> 17) | (b << 15)) ^ ((b >>> 19) | (b << 13)) ^ (b >>> 10);
|
|
78
|
+
w[j] = (w[j - 16] + s0 + w[j - 7] + s1) | 0;
|
|
79
|
+
}
|
|
80
|
+
const h = this.h;
|
|
81
|
+
let a = h[0];
|
|
82
|
+
let b = h[1];
|
|
83
|
+
let c = h[2];
|
|
84
|
+
let e = h[4];
|
|
85
|
+
let f = h[5];
|
|
86
|
+
let g = h[6];
|
|
87
|
+
let d0 = h[3];
|
|
88
|
+
let hh = h[7];
|
|
89
|
+
for (let j = 0; j < 64; j++) {
|
|
90
|
+
const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
|
|
91
|
+
const ch = (e & f) ^ (~e & g);
|
|
92
|
+
const t1 = (hh + S1 + ch + K[j] + w[j]) | 0;
|
|
93
|
+
const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
|
|
94
|
+
const maj = (a & b) ^ (a & c) ^ (b & c);
|
|
95
|
+
const t2 = (S0 + maj) | 0;
|
|
96
|
+
hh = g;
|
|
97
|
+
g = f;
|
|
98
|
+
f = e;
|
|
99
|
+
e = (d0 + t1) | 0;
|
|
100
|
+
d0 = c;
|
|
101
|
+
c = b;
|
|
102
|
+
b = a;
|
|
103
|
+
a = (t1 + t2) | 0;
|
|
104
|
+
}
|
|
105
|
+
h[0] = (h[0] + a) | 0;
|
|
106
|
+
h[1] = (h[1] + b) | 0;
|
|
107
|
+
h[2] = (h[2] + c) | 0;
|
|
108
|
+
h[3] = (h[3] + d0) | 0;
|
|
109
|
+
h[4] = (h[4] + e) | 0;
|
|
110
|
+
h[5] = (h[5] + f) | 0;
|
|
111
|
+
h[6] = (h[6] + g) | 0;
|
|
112
|
+
h[7] = (h[7] + hh) | 0;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function sha256(data) {
|
|
116
|
+
return new Sha256().update(data).digest();
|
|
117
|
+
}
|
|
118
|
+
/** HMAC-SHA256 over the concatenation of `parts` (no copy of the parts is made). */
|
|
119
|
+
function hmacSha256(key, parts) {
|
|
120
|
+
const k = key.length > 64 ? sha256(key) : key;
|
|
121
|
+
const ipad = new Uint8Array(64);
|
|
122
|
+
const opad = new Uint8Array(64);
|
|
123
|
+
for (let i = 0; i < 64; i++) {
|
|
124
|
+
const b = i < k.length ? k[i] : 0;
|
|
125
|
+
ipad[i] = b ^ 0x36;
|
|
126
|
+
opad[i] = b ^ 0x5c;
|
|
127
|
+
}
|
|
128
|
+
const inner = new Sha256().update(ipad);
|
|
129
|
+
for (const p of parts)
|
|
130
|
+
inner.update(p);
|
|
131
|
+
return new Sha256().update(opad).update(inner.digest()).digest();
|
|
132
|
+
}
|
|
133
|
+
/** Compares two byte strings in time that depends only on their length. */
|
|
134
|
+
function timingSafeEqual(a, b) {
|
|
135
|
+
if (a.length !== b.length)
|
|
136
|
+
return false;
|
|
137
|
+
let diff = 0;
|
|
138
|
+
for (let i = 0; i < a.length; i++)
|
|
139
|
+
diff |= a[i] ^ b[i];
|
|
140
|
+
return diff === 0;
|
|
141
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const textEncoder: TextEncoder;
|
|
2
|
+
/** True if `s` has no lone surrogates, i.e. it can be encoded as UTF-8 losslessly. */
|
|
3
|
+
export declare function isWellFormed(s: string): boolean;
|
|
4
|
+
export declare function loneSurrogateError(): Error;
|
|
5
|
+
/** Exact UTF-8 byte length of `s`; throws `invalid_utf8` for a lone surrogate. */
|
|
6
|
+
export declare function utf8Length(s: string): number;
|
|
7
|
+
/**
|
|
8
|
+
* Orders strings by their UTF-8 bytes (SPEC §4.4), which equals code point order. Plain
|
|
9
|
+
* `<` compares UTF-16 code units, which puts astral characters (surrogate pairs,
|
|
10
|
+
* U+D800–U+DFFF) before U+E000–U+FFFF; this corrects exactly that case.
|
|
11
|
+
*/
|
|
12
|
+
export declare function compareUtf8(a: string, b: string): number;
|
|
13
|
+
/**
|
|
14
|
+
* Decodes `length` bytes at `start`. Strict mode rejects ill-formed UTF-8 (RFC 3629:
|
|
15
|
+
* overlongs, surrogates, > U+10FFFF, truncated sequences) via TextDecoder's fatal mode;
|
|
16
|
+
* non-strict mode substitutes U+FFFD. Returns `undefined` for invalid input so the caller
|
|
17
|
+
* can report the position.
|
|
18
|
+
*/
|
|
19
|
+
export declare function decodeUtf8(bytes: Uint8Array, start: number, length: number, strict: boolean): string | undefined;
|
package/dist/cjs/utf8.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.textEncoder = void 0;
|
|
4
|
+
exports.isWellFormed = isWellFormed;
|
|
5
|
+
exports.loneSurrogateError = loneSurrogateError;
|
|
6
|
+
exports.utf8Length = utf8Length;
|
|
7
|
+
exports.compareUtf8 = compareUtf8;
|
|
8
|
+
exports.decodeUtf8 = decodeUtf8;
|
|
9
|
+
const errors_js_1 = require("./errors.js");
|
|
10
|
+
exports.textEncoder = new TextEncoder();
|
|
11
|
+
// ignoreBOM: true keeps a leading U+FEFF instead of silently stripping it.
|
|
12
|
+
const fatalDecoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
13
|
+
const lenientDecoder = new TextDecoder('utf-8', { ignoreBOM: true });
|
|
14
|
+
const LONE_SURROGATE = /(?:^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])/;
|
|
15
|
+
const HAS_SURROGATE = /[\uD800-\uDFFF]/;
|
|
16
|
+
const nativeIsWellFormed = typeof String.prototype.isWellFormed === 'function';
|
|
17
|
+
/** True if `s` has no lone surrogates, i.e. it can be encoded as UTF-8 losslessly. */
|
|
18
|
+
function isWellFormed(s) {
|
|
19
|
+
if (nativeIsWellFormed)
|
|
20
|
+
return s.isWellFormed();
|
|
21
|
+
return !HAS_SURROGATE.test(s) || !LONE_SURROGATE.test(s);
|
|
22
|
+
}
|
|
23
|
+
function loneSurrogateError() {
|
|
24
|
+
return (0, errors_js_1.fail)('invalid_utf8', 'string contains a lone surrogate and cannot be encoded as UTF-8');
|
|
25
|
+
}
|
|
26
|
+
/** Exact UTF-8 byte length of `s`; throws `invalid_utf8` for a lone surrogate. */
|
|
27
|
+
function utf8Length(s) {
|
|
28
|
+
const n = s.length;
|
|
29
|
+
let bytes = n;
|
|
30
|
+
for (let i = 0; i < n; i++) {
|
|
31
|
+
const c = s.charCodeAt(i);
|
|
32
|
+
if (c < 0x80)
|
|
33
|
+
continue;
|
|
34
|
+
if (c < 0x800) {
|
|
35
|
+
bytes += 1;
|
|
36
|
+
}
|
|
37
|
+
else if (c < 0xd800 || c > 0xdfff) {
|
|
38
|
+
bytes += 2;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const d = c <= 0xdbff && i + 1 < n ? s.charCodeAt(i + 1) : 0;
|
|
42
|
+
if (d < 0xdc00 || d > 0xdfff)
|
|
43
|
+
throw loneSurrogateError();
|
|
44
|
+
i++;
|
|
45
|
+
bytes += 2; // 4 bytes for 2 code units
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return bytes;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Orders strings by their UTF-8 bytes (SPEC §4.4), which equals code point order. Plain
|
|
52
|
+
* `<` compares UTF-16 code units, which puts astral characters (surrogate pairs,
|
|
53
|
+
* U+D800–U+DFFF) before U+E000–U+FFFF; this corrects exactly that case.
|
|
54
|
+
*/
|
|
55
|
+
function compareUtf8(a, b) {
|
|
56
|
+
if (a === b)
|
|
57
|
+
return 0;
|
|
58
|
+
const n = a.length < b.length ? a.length : b.length;
|
|
59
|
+
for (let i = 0; i < n; i++) {
|
|
60
|
+
const x = a.charCodeAt(i);
|
|
61
|
+
const y = b.charCodeAt(i);
|
|
62
|
+
if (x !== y) {
|
|
63
|
+
if (x >= 0xd800 && y >= 0xd800) {
|
|
64
|
+
const xs = x <= 0xdfff;
|
|
65
|
+
const ys = y <= 0xdfff;
|
|
66
|
+
if (xs !== ys)
|
|
67
|
+
return xs ? 1 : -1;
|
|
68
|
+
}
|
|
69
|
+
return x - y;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return a.length - b.length;
|
|
73
|
+
}
|
|
74
|
+
const fromCharCode = String.fromCharCode;
|
|
75
|
+
/**
|
|
76
|
+
* Decodes `length` bytes at `start`. Strict mode rejects ill-formed UTF-8 (RFC 3629:
|
|
77
|
+
* overlongs, surrogates, > U+10FFFF, truncated sequences) via TextDecoder's fatal mode;
|
|
78
|
+
* non-strict mode substitutes U+FFFD. Returns `undefined` for invalid input so the caller
|
|
79
|
+
* can report the position.
|
|
80
|
+
*/
|
|
81
|
+
function decodeUtf8(bytes, start, length, strict) {
|
|
82
|
+
if (length <= 32) {
|
|
83
|
+
// Short ASCII: one pass, no TextDecoder call.
|
|
84
|
+
let s = '';
|
|
85
|
+
let i = start;
|
|
86
|
+
const end = start + length;
|
|
87
|
+
for (; i < end; i++) {
|
|
88
|
+
const b = bytes[i];
|
|
89
|
+
if (b >= 0x80)
|
|
90
|
+
break;
|
|
91
|
+
s += fromCharCode(b);
|
|
92
|
+
}
|
|
93
|
+
if (i === end)
|
|
94
|
+
return s;
|
|
95
|
+
}
|
|
96
|
+
const view = bytes.subarray(start, start + length);
|
|
97
|
+
try {
|
|
98
|
+
return (strict ? fatalDecoder : lenientDecoder).decode(view);
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
// The fatal decoder reports ill-formed input as a TypeError; anything else is the
|
|
102
|
+
// engine refusing to build a string that large.
|
|
103
|
+
if (strict && e instanceof TypeError)
|
|
104
|
+
return undefined;
|
|
105
|
+
throw (0, errors_js_1.fail)('size_limit', 'string is too long for this JavaScript engine', e);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** True for a Uint8Array from any realm (including Node.js Buffers). */
|
|
2
|
+
export declare function isUint8Array(v: unknown): v is Uint8Array;
|
|
3
|
+
/**
|
|
4
|
+
* A generic MessagePack extension value: a signed 8-bit type id and an opaque payload.
|
|
5
|
+
*
|
|
6
|
+
* Decoding returns `Ext` for every extension id BTOON does not define (application ids
|
|
7
|
+
* 0…127 and the reserved negative ids). Encoding writes it verbatim with the smallest
|
|
8
|
+
* header. An `Ext` may also carry one of the ids BTOON defines (-1…-6, -10) — this is how
|
|
9
|
+
* to emit a `date` (-2) or `datetime` (-3) value, which have no JavaScript type of their
|
|
10
|
+
* own — but its payload must then be well-formed for that type, or encoding fails with
|
|
11
|
+
* `invalid_argument`.
|
|
12
|
+
*/
|
|
13
|
+
export declare class Ext {
|
|
14
|
+
/** Extension type id, an integer in [-128, 127]. */
|
|
15
|
+
readonly type: number;
|
|
16
|
+
/** The payload bytes (not copied by the constructor). */
|
|
17
|
+
readonly data: Uint8Array;
|
|
18
|
+
constructor(type: number, data: Uint8Array);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A BTOON timestamp (extension -1): an instant with nanosecond precision and an optional
|
|
22
|
+
* UTC offset.
|
|
23
|
+
*
|
|
24
|
+
* `seconds` and `nanoseconds` always describe the instant in UTC. `offsetMinutes` is
|
|
25
|
+
* presentation metadata ("local time is UTC + offset") and is encoded only when present.
|
|
26
|
+
* Decoding returns `Timestamp` objects when `timestamps: 'object'` is set, and for
|
|
27
|
+
* instants outside the range of `Date`.
|
|
28
|
+
*/
|
|
29
|
+
export declare class Timestamp {
|
|
30
|
+
/** Whole seconds since the Unix epoch (UTC), in the int64 range. */
|
|
31
|
+
readonly seconds: bigint;
|
|
32
|
+
/** Nanoseconds within the second, in [0, 999 999 999]. */
|
|
33
|
+
readonly nanoseconds: number;
|
|
34
|
+
/** UTC offset in minutes, in [-1080, 1080], or `undefined` for a plain UTC timestamp. */
|
|
35
|
+
readonly offsetMinutes?: number;
|
|
36
|
+
/**
|
|
37
|
+
* @param seconds Seconds since the Unix epoch; a `bigint`, or a `number` that is a safe integer.
|
|
38
|
+
* @param nanoseconds Nanoseconds within the second, an integer in [0, 1e9).
|
|
39
|
+
* @param offsetMinutes Optional UTC offset in minutes, an integer in [-1080, 1080].
|
|
40
|
+
* @throws {BtoonError} `invalid_argument` if a field is out of range.
|
|
41
|
+
*/
|
|
42
|
+
constructor(seconds: bigint | number, nanoseconds: number, offsetMinutes?: number);
|
|
43
|
+
/**
|
|
44
|
+
* The instant as a `Date`. Sub-millisecond nanoseconds are truncated toward the past and
|
|
45
|
+
* the offset is dropped (a `Date` has no offset).
|
|
46
|
+
* @throws {BtoonError} `invalid_argument` if the instant is outside the range of `Date`.
|
|
47
|
+
*/
|
|
48
|
+
toDate(): Date;
|
|
49
|
+
/**
|
|
50
|
+
* The UTC timestamp of a `Date` (millisecond precision, no offset).
|
|
51
|
+
* @throws {BtoonError} `invalid_argument` for an invalid `Date`.
|
|
52
|
+
*/
|
|
53
|
+
static fromDate(date: Date): Timestamp;
|
|
54
|
+
}
|
|
55
|
+
/** Milliseconds of a Date (from any realm); throws for an invalid Date. */
|
|
56
|
+
export declare function dateTime(date: unknown): number;
|
|
57
|
+
export declare function isExt(v: object): v is Ext;
|
|
58
|
+
export declare function isTimestamp(v: object): v is Timestamp;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Timestamp = exports.Ext = void 0;
|
|
4
|
+
exports.isUint8Array = isUint8Array;
|
|
5
|
+
exports.dateTime = dateTime;
|
|
6
|
+
exports.isExt = isExt;
|
|
7
|
+
exports.isTimestamp = isTimestamp;
|
|
8
|
+
const constants_js_1 = require("./constants.js");
|
|
9
|
+
const errors_js_1 = require("./errors.js");
|
|
10
|
+
const INT64_MIN = -(1n << 63n);
|
|
11
|
+
const INT64_MAX = (1n << 63n) - 1n;
|
|
12
|
+
/** True for a Uint8Array from any realm (including Node.js Buffers). */
|
|
13
|
+
function isUint8Array(v) {
|
|
14
|
+
return ArrayBuffer.isView(v) && v[Symbol.toStringTag] === 'Uint8Array';
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A generic MessagePack extension value: a signed 8-bit type id and an opaque payload.
|
|
18
|
+
*
|
|
19
|
+
* Decoding returns `Ext` for every extension id BTOON does not define (application ids
|
|
20
|
+
* 0…127 and the reserved negative ids). Encoding writes it verbatim with the smallest
|
|
21
|
+
* header. An `Ext` may also carry one of the ids BTOON defines (-1…-6, -10) — this is how
|
|
22
|
+
* to emit a `date` (-2) or `datetime` (-3) value, which have no JavaScript type of their
|
|
23
|
+
* own — but its payload must then be well-formed for that type, or encoding fails with
|
|
24
|
+
* `invalid_argument`.
|
|
25
|
+
*/
|
|
26
|
+
class Ext {
|
|
27
|
+
constructor(type, data) {
|
|
28
|
+
if (typeof type !== 'number' || !Number.isInteger(type) || type < -128 || type > 127) {
|
|
29
|
+
throw (0, errors_js_1.fail)('invalid_argument', `Ext type must be an integer in [-128, 127], got ${String(type)}`);
|
|
30
|
+
}
|
|
31
|
+
if (!isUint8Array(data)) {
|
|
32
|
+
throw (0, errors_js_1.fail)('invalid_argument', 'Ext data must be a Uint8Array');
|
|
33
|
+
}
|
|
34
|
+
this.type = type;
|
|
35
|
+
this.data = data;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.Ext = Ext;
|
|
39
|
+
Object.defineProperty(Ext.prototype, constants_js_1.EXT_BRAND, { value: true });
|
|
40
|
+
/**
|
|
41
|
+
* A BTOON timestamp (extension -1): an instant with nanosecond precision and an optional
|
|
42
|
+
* UTC offset.
|
|
43
|
+
*
|
|
44
|
+
* `seconds` and `nanoseconds` always describe the instant in UTC. `offsetMinutes` is
|
|
45
|
+
* presentation metadata ("local time is UTC + offset") and is encoded only when present.
|
|
46
|
+
* Decoding returns `Timestamp` objects when `timestamps: 'object'` is set, and for
|
|
47
|
+
* instants outside the range of `Date`.
|
|
48
|
+
*/
|
|
49
|
+
class Timestamp {
|
|
50
|
+
/**
|
|
51
|
+
* @param seconds Seconds since the Unix epoch; a `bigint`, or a `number` that is a safe integer.
|
|
52
|
+
* @param nanoseconds Nanoseconds within the second, an integer in [0, 1e9).
|
|
53
|
+
* @param offsetMinutes Optional UTC offset in minutes, an integer in [-1080, 1080].
|
|
54
|
+
* @throws {BtoonError} `invalid_argument` if a field is out of range.
|
|
55
|
+
*/
|
|
56
|
+
constructor(seconds, nanoseconds, offsetMinutes) {
|
|
57
|
+
let s;
|
|
58
|
+
if (typeof seconds === 'bigint') {
|
|
59
|
+
s = seconds;
|
|
60
|
+
}
|
|
61
|
+
else if (typeof seconds === 'number' && Number.isSafeInteger(seconds)) {
|
|
62
|
+
s = BigInt(seconds);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
throw (0, errors_js_1.fail)('invalid_argument', 'Timestamp seconds must be a bigint or a safe integer');
|
|
66
|
+
}
|
|
67
|
+
if (s < INT64_MIN || s > INT64_MAX) {
|
|
68
|
+
throw (0, errors_js_1.fail)('invalid_argument', 'Timestamp seconds must fit in a signed 64-bit integer');
|
|
69
|
+
}
|
|
70
|
+
if (typeof nanoseconds !== 'number' ||
|
|
71
|
+
!Number.isInteger(nanoseconds) ||
|
|
72
|
+
nanoseconds < 0 ||
|
|
73
|
+
nanoseconds >= constants_js_1.NANOS_PER_SECOND) {
|
|
74
|
+
throw (0, errors_js_1.fail)('invalid_argument', 'Timestamp nanoseconds must be an integer in [0, 999999999]');
|
|
75
|
+
}
|
|
76
|
+
if (offsetMinutes !== undefined &&
|
|
77
|
+
(typeof offsetMinutes !== 'number' ||
|
|
78
|
+
!Number.isInteger(offsetMinutes) ||
|
|
79
|
+
offsetMinutes < -constants_js_1.MAX_OFFSET_MINUTES ||
|
|
80
|
+
offsetMinutes > constants_js_1.MAX_OFFSET_MINUTES)) {
|
|
81
|
+
throw (0, errors_js_1.fail)('invalid_argument', 'Timestamp offsetMinutes must be an integer in [-1080, 1080]');
|
|
82
|
+
}
|
|
83
|
+
this.seconds = s;
|
|
84
|
+
this.nanoseconds = nanoseconds;
|
|
85
|
+
this.offsetMinutes = offsetMinutes;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The instant as a `Date`. Sub-millisecond nanoseconds are truncated toward the past and
|
|
89
|
+
* the offset is dropped (a `Date` has no offset).
|
|
90
|
+
* @throws {BtoonError} `invalid_argument` if the instant is outside the range of `Date`.
|
|
91
|
+
*/
|
|
92
|
+
toDate() {
|
|
93
|
+
const ms = this.seconds * 1000n + BigInt(Math.floor(this.nanoseconds / 1e6));
|
|
94
|
+
if (ms > BigInt(constants_js_1.MAX_DATE_MS) || ms < -BigInt(constants_js_1.MAX_DATE_MS)) {
|
|
95
|
+
throw (0, errors_js_1.fail)('invalid_argument', 'Timestamp is outside the range of Date');
|
|
96
|
+
}
|
|
97
|
+
return new Date(Number(ms));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The UTC timestamp of a `Date` (millisecond precision, no offset).
|
|
101
|
+
* @throws {BtoonError} `invalid_argument` for an invalid `Date`.
|
|
102
|
+
*/
|
|
103
|
+
static fromDate(date) {
|
|
104
|
+
const ms = dateTime(date);
|
|
105
|
+
const seconds = Math.floor(ms / 1000);
|
|
106
|
+
return new Timestamp(BigInt(seconds), (ms - seconds * 1000) * 1e6);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
exports.Timestamp = Timestamp;
|
|
110
|
+
Object.defineProperty(Timestamp.prototype, constants_js_1.TIMESTAMP_BRAND, { value: true });
|
|
111
|
+
/** Milliseconds of a Date (from any realm); throws for an invalid Date. */
|
|
112
|
+
function dateTime(date) {
|
|
113
|
+
let ms;
|
|
114
|
+
try {
|
|
115
|
+
ms = Date.prototype.getTime.call(date);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
throw (0, errors_js_1.fail)('invalid_argument', 'expected a Date');
|
|
119
|
+
}
|
|
120
|
+
if (!Number.isFinite(ms))
|
|
121
|
+
throw (0, errors_js_1.fail)('invalid_argument', 'cannot encode an invalid Date');
|
|
122
|
+
return ms;
|
|
123
|
+
}
|
|
124
|
+
function isExt(v) {
|
|
125
|
+
return v[constants_js_1.EXT_BRAND] === true;
|
|
126
|
+
}
|
|
127
|
+
function isTimestamp(v) {
|
|
128
|
+
return v[constants_js_1.TIMESTAMP_BRAND] === true;
|
|
129
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CompressionAlgorithm } from './options.js';
|
|
2
|
+
/** The Compression Streams format name for `alg`, if this runtime supports it. */
|
|
3
|
+
export declare function webFormat(alg: CompressionAlgorithm, kind: 'CompressionStream' | 'DecompressionStream'): string | undefined;
|
|
4
|
+
export declare function webCompress(format: string, data: Uint8Array): Promise<Uint8Array>;
|
|
5
|
+
/**
|
|
6
|
+
* Decompresses with a DecompressionStream, stopping as soon as the output exceeds
|
|
7
|
+
* `limit` (the declared size). For zlib it also checks that the payload ends with the
|
|
8
|
+
* Adler-32 of the output: older runtimes silently ignore bytes after the end of the
|
|
9
|
+
* stream, which a strict decoder must reject.
|
|
10
|
+
*/
|
|
11
|
+
export declare function webDecompress(alg: CompressionAlgorithm, format: string, data: Uint8Array, limit: number): Promise<Uint8Array>;
|
|
12
|
+
type Subtle = {
|
|
13
|
+
importKey(format: 'raw', key: Uint8Array, alg: {
|
|
14
|
+
name: string;
|
|
15
|
+
hash: string;
|
|
16
|
+
}, extractable: boolean, usages: string[]): Promise<unknown>;
|
|
17
|
+
sign(alg: string, key: unknown, data: Uint8Array): Promise<ArrayBuffer>;
|
|
18
|
+
verify(alg: string, key: unknown, signature: Uint8Array, data: Uint8Array): Promise<boolean>;
|
|
19
|
+
};
|
|
20
|
+
/** WebCrypto's SubtleCrypto, if available (absent in Node 18 and on insecure origins). */
|
|
21
|
+
export declare function subtle(): Subtle | undefined;
|
|
22
|
+
export declare function webHmacSign(s: Subtle, key: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
|
|
23
|
+
/** Constant-time verification (crypto.subtle.verify compares internally). */
|
|
24
|
+
export declare function webHmacVerify(s: Subtle, key: Uint8Array, mac: Uint8Array, data: Uint8Array): Promise<boolean>;
|
|
25
|
+
export {};
|
package/dist/cjs/web.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.webFormat = webFormat;
|
|
4
|
+
exports.webCompress = webCompress;
|
|
5
|
+
exports.webDecompress = webDecompress;
|
|
6
|
+
exports.subtle = subtle;
|
|
7
|
+
exports.webHmacSign = webHmacSign;
|
|
8
|
+
exports.webHmacVerify = webHmacVerify;
|
|
9
|
+
// Asynchronous implementations on top of web platform APIs: Compression Streams and
|
|
10
|
+
// WebCrypto. Used by encodeAsync/decodeAsync wherever the runtime provides them.
|
|
11
|
+
const errors_js_1 = require("./errors.js");
|
|
12
|
+
const inflate_js_1 = require("./inflate.js");
|
|
13
|
+
const FORMATS = { zlib: 'deflate', brotli: 'brotli', zstd: 'zstd' };
|
|
14
|
+
const CHUNK = 64 * 1024;
|
|
15
|
+
const supported = new Map();
|
|
16
|
+
function streamCtor(kind) {
|
|
17
|
+
const ctor = globalThis[kind];
|
|
18
|
+
return typeof ctor === 'function' ? ctor : undefined;
|
|
19
|
+
}
|
|
20
|
+
/** The Compression Streams format name for `alg`, if this runtime supports it. */
|
|
21
|
+
function webFormat(alg, kind) {
|
|
22
|
+
const format = FORMATS[alg];
|
|
23
|
+
const Ctor = streamCtor(kind);
|
|
24
|
+
if (format === undefined || Ctor === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
const key = `${kind}:${format}`;
|
|
27
|
+
let ok = supported.get(key);
|
|
28
|
+
if (ok === undefined) {
|
|
29
|
+
try {
|
|
30
|
+
new Ctor(format);
|
|
31
|
+
ok = true;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
ok = false;
|
|
35
|
+
}
|
|
36
|
+
supported.set(key, ok);
|
|
37
|
+
}
|
|
38
|
+
return ok ? format : undefined;
|
|
39
|
+
}
|
|
40
|
+
function concat(chunks, total) {
|
|
41
|
+
if (chunks.length === 1 && chunks[0].byteLength === total)
|
|
42
|
+
return chunks[0];
|
|
43
|
+
const out = new Uint8Array(total);
|
|
44
|
+
let p = 0;
|
|
45
|
+
for (const c of chunks) {
|
|
46
|
+
out.set(c, p);
|
|
47
|
+
p += c.byteLength;
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
async function readAll(reader, limit, alg) {
|
|
52
|
+
const chunks = [];
|
|
53
|
+
let total = 0;
|
|
54
|
+
for (;;) {
|
|
55
|
+
const { done, value } = await reader.read();
|
|
56
|
+
if (done)
|
|
57
|
+
break;
|
|
58
|
+
total += value.byteLength;
|
|
59
|
+
if (total > limit) {
|
|
60
|
+
reader.cancel().catch(() => undefined);
|
|
61
|
+
throw (0, errors_js_1.fail)('invalid_envelope', `${alg} payload decompresses to more than the declared size`);
|
|
62
|
+
}
|
|
63
|
+
chunks.push(value);
|
|
64
|
+
}
|
|
65
|
+
return concat(chunks, total);
|
|
66
|
+
}
|
|
67
|
+
/** Feeds `data` in chunks, honouring backpressure, then closes the writer. */
|
|
68
|
+
async function pump(writer, data) {
|
|
69
|
+
for (let i = 0; i < data.length; i += CHUNK) {
|
|
70
|
+
await writer.ready;
|
|
71
|
+
await writer.write(data.subarray(i, Math.min(i + CHUNK, data.length)));
|
|
72
|
+
}
|
|
73
|
+
await writer.close();
|
|
74
|
+
}
|
|
75
|
+
function messageOf(e) {
|
|
76
|
+
return e instanceof Error ? e.message || e.name : String(e);
|
|
77
|
+
}
|
|
78
|
+
async function webCompress(format, data) {
|
|
79
|
+
const stream = new (streamCtor('CompressionStream'))(format);
|
|
80
|
+
const writer = stream.writable.getWriter();
|
|
81
|
+
const writing = pump(writer, data);
|
|
82
|
+
writing.catch(() => undefined);
|
|
83
|
+
const out = await readAll(stream.readable.getReader(), Infinity, format);
|
|
84
|
+
await writing;
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Decompresses with a DecompressionStream, stopping as soon as the output exceeds
|
|
89
|
+
* `limit` (the declared size). For zlib it also checks that the payload ends with the
|
|
90
|
+
* Adler-32 of the output: older runtimes silently ignore bytes after the end of the
|
|
91
|
+
* stream, which a strict decoder must reject.
|
|
92
|
+
*/
|
|
93
|
+
async function webDecompress(alg, format, data, limit) {
|
|
94
|
+
const stream = new (streamCtor('DecompressionStream'))(format);
|
|
95
|
+
const writer = stream.writable.getWriter();
|
|
96
|
+
const writing = pump(writer, data);
|
|
97
|
+
writing.catch(() => undefined);
|
|
98
|
+
let out;
|
|
99
|
+
try {
|
|
100
|
+
out = await readAll(stream.readable.getReader(), limit, alg);
|
|
101
|
+
await writing;
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
writer.abort(e).catch(() => undefined);
|
|
105
|
+
if (e instanceof errors_js_1.BtoonError)
|
|
106
|
+
throw e;
|
|
107
|
+
throw (0, errors_js_1.fail)('invalid_envelope', `${alg} payload is corrupt: ${messageOf(e)}`, e);
|
|
108
|
+
}
|
|
109
|
+
if (alg === 'zlib') {
|
|
110
|
+
const n = data.length;
|
|
111
|
+
const trailer = n >= 4 ? ((data[n - 4] << 24) | (data[n - 3] << 16) | (data[n - 2] << 8) | data[n - 1]) >>> 0 : -1;
|
|
112
|
+
if (trailer !== (0, inflate_js_1.adler32)(out))
|
|
113
|
+
throw (0, errors_js_1.fail)('invalid_envelope', 'zlib payload has trailing bytes');
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
/** WebCrypto's SubtleCrypto, if available (absent in Node 18 and on insecure origins). */
|
|
118
|
+
function subtle() {
|
|
119
|
+
const c = globalThis.crypto;
|
|
120
|
+
const s = c?.subtle;
|
|
121
|
+
return s && typeof s.importKey === 'function' && typeof s.sign === 'function' && typeof s.verify === 'function'
|
|
122
|
+
? s
|
|
123
|
+
: undefined;
|
|
124
|
+
}
|
|
125
|
+
async function importKey(s, key, usage) {
|
|
126
|
+
try {
|
|
127
|
+
return await s.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, [usage]);
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
throw (0, errors_js_1.fail)('invalid_argument', `cannot use hmacKey with WebCrypto: ${messageOf(e)}`, e);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function webHmacSign(s, key, data) {
|
|
134
|
+
const k = await importKey(s, key, 'sign');
|
|
135
|
+
try {
|
|
136
|
+
return new Uint8Array(await s.sign('HMAC', k, data));
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
throw (0, errors_js_1.fail)('invalid_argument', `WebCrypto HMAC signing failed: ${messageOf(e)}`, e);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Constant-time verification (crypto.subtle.verify compares internally). */
|
|
143
|
+
async function webHmacVerify(s, key, mac, data) {
|
|
144
|
+
const k = await importKey(s, key, 'verify');
|
|
145
|
+
try {
|
|
146
|
+
return await s.verify('HMAC', k, mac, data);
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
throw (0, errors_js_1.fail)('authentication_failed', `WebCrypto HMAC verification failed: ${messageOf(e)}`, e);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Version of this library. */
|
|
2
|
+
export declare const VERSION: "0.1.0";
|
|
3
|
+
/** Version of the BTOON specification this library implements. */
|
|
4
|
+
export declare const SPEC_VERSION: "0.1";
|
|
5
|
+
export declare const EXT_TIMESTAMP = -1;
|
|
6
|
+
export declare const EXT_DATE = -2;
|
|
7
|
+
export declare const EXT_DATETIME = -3;
|
|
8
|
+
export declare const EXT_BIGINT = -4;
|
|
9
|
+
export declare const EXT_F32_VECTOR = -5;
|
|
10
|
+
export declare const EXT_F64_VECTOR = -6;
|
|
11
|
+
export declare const EXT_TABULAR = -10;
|
|
12
|
+
export declare const TABULAR_VERSION = 1;
|
|
13
|
+
export declare const MAX_OFFSET_MINUTES = 1080;
|
|
14
|
+
export declare const NANOS_PER_SECOND = 1000000000;
|
|
15
|
+
export declare const DEFAULT_MAX_DEPTH = 128;
|
|
16
|
+
export declare const DEFAULT_MAX_DECOMPRESSED_SIZE: number;
|
|
17
|
+
/** Encoders SHOULD skip compression below this size (SPEC §6). */
|
|
18
|
+
export declare const MIN_COMPRESSION_SIZE = 256;
|
|
19
|
+
export declare const COMPRESSION_HEADER_SIZE = 16;
|
|
20
|
+
export declare const SIGNED_HEADER_SIZE = 40;
|
|
21
|
+
/** Compression algorithm ids of the envelope header (SPEC §6). */
|
|
22
|
+
export declare const ALGORITHM_IDS: {
|
|
23
|
+
readonly zlib: 0;
|
|
24
|
+
readonly lz4: 1;
|
|
25
|
+
readonly zstd: 2;
|
|
26
|
+
readonly brotli: 3;
|
|
27
|
+
readonly snappy: 4;
|
|
28
|
+
};
|
|
29
|
+
export declare const ALGORITHM_NAMES: readonly ["zlib", "lz4", "zstd", "brotli", "snappy"];
|
|
30
|
+
/** Brands that identify {@link Ext} and {@link Timestamp} instances across module copies. */
|
|
31
|
+
export declare const EXT_BRAND: unique symbol;
|
|
32
|
+
export declare const TIMESTAMP_BRAND: unique symbol;
|
|
33
|
+
/** Date's representable range is ±8.64e15 ms around the epoch. */
|
|
34
|
+
export declare const MAX_DATE_MS = 8640000000000000;
|