@thi.ng/bencode 2.1.147 → 3.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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2024-12-27T14:11:37Z
3
+ - **Last updated**: 2025-01-14T12:23:33Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -9,6 +9,21 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ # [3.0.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/bencode@3.0.0) (2025-01-14)
13
+
14
+ #### 🛑 Breaking changes
15
+
16
+ - major internal refactor & MUCH faster decode ([ce6d528](https://github.com/thi-ng/umbrella/commit/ce6d528))
17
+ - BREAKING CHANGE: `decode()` now requires `Uint8Array` as input
18
+ - update `decode()` & `encode()` internals
19
+ - update non-UTF-8 decoding (also add note to readme)
20
+ - avoid use of iterators in `decode()`
21
+ - update `__readBytes()` to return sub-arrays (zero copy op)
22
+ - remove [@thi.ng/defmulti](https://github.com/thi-ng/umbrella/tree/main/packages/defmulti) dependency
23
+ - remove internal use of `const enum`s
24
+ - update tests
25
+ - update readme
26
+
12
27
  ### [2.1.128](https://github.com/thi-ng/umbrella/tree/@thi.ng/bencode@2.1.128) (2024-07-22)
13
28
 
14
29
  #### 🩹 Bug fixes
package/README.md CHANGED
@@ -43,6 +43,12 @@ All JS strings will be UTF-8 encoded. To write raw bytes without
43
43
  transformation, wrap them as `Uint8Array`. These too will be written as
44
44
  Bencode strings (e.g. `len:xxx...`), but are used as is.
45
45
 
46
+ When decoding, UTF-8 is used by default, but can be disabled by passing `false`
47
+ as 2nd argument to
48
+ [`decode()`](https://docs.thi.ng/umbrella/bencode/functions/decode.html). In
49
+ that case, strings will be decoded as `Uint8Array`s, with the exception of
50
+ dictionary keys, which will be decoded via `String.fromCharCode()`.
51
+
46
52
  #### Floating point values
47
53
 
48
54
  This implementation has optional (non-standard) support for floating point
@@ -86,18 +92,20 @@ For Node.js REPL:
86
92
  const bc = await import("@thi.ng/bencode");
87
93
  ```
88
94
 
89
- Package sizes (brotli'd, pre-treeshake): ESM: 1.18 KB
95
+ Package sizes (brotli'd, pre-treeshake): ESM: 1.22 KB
90
96
 
91
97
  ## Dependencies
92
98
 
99
+ - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api)
93
100
  - [@thi.ng/arrays](https://github.com/thi-ng/umbrella/tree/develop/packages/arrays)
94
101
  - [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
95
- - [@thi.ng/defmulti](https://github.com/thi-ng/umbrella/tree/develop/packages/defmulti)
96
102
  - [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/develop/packages/errors)
97
103
  - [@thi.ng/strings](https://github.com/thi-ng/umbrella/tree/develop/packages/strings)
98
104
  - [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/develop/packages/transducers)
99
105
  - [@thi.ng/transducers-binary](https://github.com/thi-ng/umbrella/tree/develop/packages/transducers-binary)
100
106
 
107
+ Note: @thi.ng/api is in _most_ cases a type-only import (not used at runtime)
108
+
101
109
  ## API
102
110
 
103
111
  [Generated API docs](https://docs.thi.ng/umbrella/bencode/)
@@ -145,4 +153,4 @@ If this project contributes to an academic publication, please cite it as:
145
153
 
146
154
  ## License
147
155
 
148
- © 2016 - 2024 Karsten Schmidt // Apache License 2.0
156
+ © 2016 - 2025 Karsten Schmidt // Apache License 2.0
package/decode.d.ts CHANGED
@@ -1,2 +1,13 @@
1
- export declare const decode: (buf: Iterable<number>, utf8?: boolean) => any;
1
+ /**
2
+ * Decodes given byte array back into JS data(structure).
3
+ *
4
+ * @remarks
5
+ * UTF-8 is used by default, but can be disabled by setting `utf8` to false. In
6
+ * that case, strings will be decoded as `Uint8Array`s, with the exception of
7
+ * dictionary keys, which will be decoded via `String.fromCharCode()`.
8
+ *
9
+ * @param buf
10
+ * @param utf8
11
+ */
12
+ export declare const decode: (buf: Uint8Array, utf8?: boolean) => any;
2
13
  //# sourceMappingURL=decode.d.ts.map
package/decode.js CHANGED
@@ -1,66 +1,54 @@
1
1
  import { peek } from "@thi.ng/arrays/peek";
2
- import { isArray } from "@thi.ng/checks/is-array";
3
2
  import { assert } from "@thi.ng/errors/assert";
4
3
  import { illegalState } from "@thi.ng/errors/illegal-state";
5
4
  import { utf8Decode } from "@thi.ng/transducers-binary/utf8";
6
- var Type = /* @__PURE__ */ ((Type2) => {
7
- Type2[Type2["INT"] = 0] = "INT";
8
- Type2[Type2["FLOAT"] = 1] = "FLOAT";
9
- Type2[Type2["STR"] = 2] = "STR";
10
- Type2[Type2["BINARY"] = 3] = "BINARY";
11
- Type2[Type2["DICT"] = 4] = "DICT";
12
- Type2[Type2["LIST"] = 5] = "LIST";
13
- return Type2;
14
- })(Type || {});
15
- var Lit = /* @__PURE__ */ ((Lit2) => {
16
- Lit2[Lit2["MINUS"] = 45] = "MINUS";
17
- Lit2[Lit2["DOT"] = 46] = "DOT";
18
- Lit2[Lit2["ZERO"] = 48] = "ZERO";
19
- Lit2[Lit2["NINE"] = 57] = "NINE";
20
- Lit2[Lit2["COLON"] = 58] = "COLON";
21
- Lit2[Lit2["DICT"] = 100] = "DICT";
22
- Lit2[Lit2["END"] = 101] = "END";
23
- Lit2[Lit2["FLOAT"] = 102] = "FLOAT";
24
- Lit2[Lit2["INT"] = 105] = "INT";
25
- Lit2[Lit2["LIST"] = 108] = "LIST";
26
- return Lit2;
27
- })(Lit || {});
5
+ const [MINUS, DOT, ZERO, NINE, COLON, DICT, END, FLOAT, INT, LIST] = [
6
+ 45,
7
+ 46,
8
+ 48,
9
+ 57,
10
+ 58,
11
+ 100,
12
+ 101,
13
+ 102,
14
+ 105,
15
+ 108
16
+ ];
28
17
  const decode = (buf, utf8 = true) => {
29
- const iter = buf[Symbol.iterator]();
18
+ const ctx = { i: 0, n: buf.length, buf };
30
19
  const stack = [];
31
- let i;
32
20
  let x;
33
- while (!(i = iter.next()).done) {
34
- x = i.value;
21
+ while (ctx.i < buf.length) {
22
+ x = buf[ctx.i++];
35
23
  switch (x) {
36
- case 100 /* DICT */:
24
+ case DICT:
37
25
  __ensureNotKey(stack, "dict");
38
- stack.push({ type: 4 /* DICT */, val: {} });
26
+ stack.push({ type: DICT, val: {} });
39
27
  break;
40
- case 108 /* LIST */:
28
+ case LIST:
41
29
  __ensureNotKey(stack, "list");
42
- stack.push({ type: 5 /* LIST */, val: [] });
30
+ stack.push({ type: LIST, val: [] });
43
31
  break;
44
- case 105 /* INT */:
45
- x = __collect(stack, __readInt(iter, 0));
32
+ case INT:
33
+ x = __collect(stack, __readInt(ctx, 0));
46
34
  if (x !== void 0) {
47
35
  return x;
48
36
  }
49
37
  break;
50
- case 102 /* FLOAT */:
51
- x = __collect(stack, __readFloat(iter));
38
+ case FLOAT:
39
+ x = __collect(stack, __readFloat(ctx));
52
40
  if (x !== void 0) {
53
41
  return x;
54
42
  }
55
43
  break;
56
- case 101 /* END */:
44
+ case END:
57
45
  x = stack.pop();
58
46
  if (x) {
59
47
  const parent = peek(stack);
60
48
  if (parent) {
61
- if (parent.type === 5 /* LIST */) {
49
+ if (parent.type === LIST) {
62
50
  parent.val.push(x.val);
63
- } else if (parent.type === 4 /* DICT */) {
51
+ } else if (parent.type === DICT) {
64
52
  parent.val[parent.key] = x.val;
65
53
  parent.key = null;
66
54
  }
@@ -72,19 +60,14 @@ const decode = (buf, utf8 = true) => {
72
60
  }
73
61
  break;
74
62
  default:
75
- if (x >= 48 /* ZERO */ && x <= 57 /* NINE */) {
76
- x = __readBytes(
77
- iter,
78
- __readInt(iter, x - 48 /* ZERO */, 58 /* COLON */)
79
- );
63
+ if (x >= ZERO && x <= NINE) {
64
+ x = __readBytes(ctx, __readInt(ctx, x - ZERO, COLON));
80
65
  x = __collect(stack, x, utf8);
81
66
  if (x !== void 0) {
82
67
  return x;
83
68
  }
84
69
  } else {
85
- illegalState(
86
- `unexpected value type: 0x${i.value.toString(16)}`
87
- );
70
+ illegalState(`unexpected value type: 0x${x.toString(16)}`);
88
71
  }
89
72
  }
90
73
  }
@@ -92,34 +75,31 @@ const decode = (buf, utf8 = true) => {
92
75
  };
93
76
  const __ensureNotKey = (stack, type) => {
94
77
  const x = peek(stack);
95
- assert(
96
- !x || x.type !== 4 /* DICT */ || x.key,
97
- type + " not supported as dict key"
98
- );
78
+ assert(!x || x.type !== DICT || x.key, type + " not supported as dict key");
99
79
  };
100
80
  const __collect = (stack, x, utf8 = false) => {
101
81
  const parent = peek(stack);
102
82
  if (!parent) return x;
103
- if (parent.type === 5 /* LIST */) {
104
- parent.val.push(utf8 && isArray(x) ? utf8Decode(x) : x);
83
+ utf8 &&= x instanceof Uint8Array;
84
+ if (parent.type === LIST) {
85
+ parent.val.push(utf8 ? utf8Decode(x) : x);
105
86
  } else {
106
87
  if (!parent.key) {
107
- parent.key = isArray(x) ? utf8Decode(x) : x;
88
+ parent.key = utf8 ? utf8Decode(x) : __decodeAscii(x);
108
89
  } else {
109
90
  parent.val[parent.key] = utf8 ? utf8Decode(x) : x;
110
91
  parent.key = null;
111
92
  }
112
93
  }
113
94
  };
114
- const __readInt = (iter, acc, end = 101 /* END */) => {
115
- let i;
95
+ const __readInt = (ctx, acc, end = END) => {
116
96
  let x;
117
97
  let isSigned = false;
118
- while (!(i = iter.next()).done) {
119
- x = i.value;
120
- if (x >= 48 /* ZERO */ && x <= 57 /* NINE */) {
121
- acc = acc * 10 + x - 48 /* ZERO */;
122
- } else if (x === 45 /* MINUS */) {
98
+ while (ctx.i < ctx.n) {
99
+ x = ctx.buf[ctx.i++];
100
+ if (x >= ZERO && x <= NINE) {
101
+ acc = acc * 10 + x - ZERO;
102
+ } else if (x === MINUS) {
123
103
  assert(!isSigned, `invalid int literal`);
124
104
  isSigned = true;
125
105
  } else if (x === end) {
@@ -130,29 +110,31 @@ const __readInt = (iter, acc, end = 101 /* END */) => {
130
110
  }
131
111
  illegalState(`incomplete int`);
132
112
  };
133
- const __readFloat = (iter) => {
134
- let i;
113
+ const __readFloat = (ctx) => {
135
114
  let x;
136
115
  let acc = "";
137
- while (!(i = iter.next()).done) {
138
- x = i.value;
139
- if (x >= 48 /* ZERO */ && x <= 57 /* NINE */ || x === 46 /* DOT */ || x === 45 /* MINUS */) {
116
+ while (ctx.i < ctx.n) {
117
+ x = ctx.buf[ctx.i++];
118
+ if (x >= ZERO && x <= NINE || x === DOT || x === MINUS) {
140
119
  acc += String.fromCharCode(x);
141
- } else if (x === 101 /* END */) {
142
- return parseFloat(acc);
120
+ } else if (x === END) {
121
+ return +acc;
143
122
  } else {
144
123
  illegalState(`expected digit or dot, got 0x${x.toString(16)}`);
145
124
  }
146
125
  }
147
126
  illegalState(`incomplete float`);
148
127
  };
149
- const __readBytes = (iter, len) => {
150
- let i;
151
- let buf = [];
152
- while (len-- > 0 && !(i = iter.next()).done) {
153
- buf.push(i.value);
154
- }
155
- return len < 0 ? buf : illegalState(`expected string, reached EOF`);
128
+ const __readBytes = (ctx, len) => {
129
+ if (ctx.i + len > ctx.n)
130
+ illegalState(`expected ${len} bytes, but reached EOF`);
131
+ return ctx.buf.subarray(ctx.i, ctx.i += len);
132
+ };
133
+ const __decodeAscii = (buf) => {
134
+ let res = "";
135
+ for (let i = 0, n = buf.length; i < n; i++)
136
+ res += String.fromCharCode(buf[i]);
137
+ return res;
156
138
  };
157
139
  export {
158
140
  decode
package/encode.js CHANGED
@@ -3,65 +3,39 @@ import { isBoolean } from "@thi.ng/checks/is-boolean";
3
3
  import { isNumber } from "@thi.ng/checks/is-number";
4
4
  import { isPlainObject } from "@thi.ng/checks/is-plain-object";
5
5
  import { isString } from "@thi.ng/checks/is-string";
6
- import { defmulti } from "@thi.ng/defmulti/defmulti";
7
6
  import { assert } from "@thi.ng/errors/assert";
8
7
  import { unsupported } from "@thi.ng/errors/unsupported";
9
8
  import { utf8Length } from "@thi.ng/strings/utf8";
10
9
  import { bytes, str, u8, u8array } from "@thi.ng/transducers-binary/bytes";
11
10
  import { mapcat } from "@thi.ng/transducers/mapcat";
12
- var Type = /* @__PURE__ */ ((Type2) => {
13
- Type2[Type2["INT"] = 0] = "INT";
14
- Type2[Type2["FLOAT"] = 1] = "FLOAT";
15
- Type2[Type2["STR"] = 2] = "STR";
16
- Type2[Type2["BINARY"] = 3] = "BINARY";
17
- Type2[Type2["DICT"] = 4] = "DICT";
18
- Type2[Type2["LIST"] = 5] = "LIST";
19
- return Type2;
20
- })(Type || {});
21
- var Lit = /* @__PURE__ */ ((Lit2) => {
22
- Lit2[Lit2["DICT"] = 100] = "DICT";
23
- Lit2[Lit2["END"] = 101] = "END";
24
- Lit2[Lit2["LIST"] = 108] = "LIST";
25
- return Lit2;
26
- })(Lit || {});
27
11
  const FLOAT_RE = /^[0-9.-]+$/;
12
+ const ENCODERS = {
13
+ i: (x) => {
14
+ __ensureValidNumber(x);
15
+ return [str(`i${Math.floor(x)}e`)];
16
+ },
17
+ f: (x) => {
18
+ __ensureValidNumber(x);
19
+ assert(
20
+ FLOAT_RE.test(x.toString()),
21
+ `values requiring exponential notation not allowed (${x})`
22
+ );
23
+ return [str(`f${x}e`)];
24
+ },
25
+ b: (buf) => [str(buf.length + ":"), u8array(buf)],
26
+ s: (x) => [str(utf8Length(x) + ":" + x)],
27
+ l: (x) => [u8(108), ...mapcat(__encodeBin, x), u8(101)],
28
+ d: (x) => [
29
+ u8(100),
30
+ ...mapcat(
31
+ (k) => __encodeBin(k).concat(__encodeBin(x[k])),
32
+ Object.keys(x).sort()
33
+ ),
34
+ u8(101)
35
+ ]
36
+ };
28
37
  const encode = (x, cap = 1024) => bytes(cap, __encodeBin(x));
29
- const __encodeBin = defmulti(
30
- (x) => isNumber(x) ? Math.floor(x) !== x ? 1 /* FLOAT */ : 0 /* INT */ : isBoolean(x) ? 0 /* INT */ : isString(x) ? 2 /* STR */ : x instanceof Uint8Array ? 3 /* BINARY */ : isPlainObject(x) ? 4 /* DICT */ : isArrayLike(x) ? 5 /* LIST */ : unsupported(`unsupported data type: ${x}`),
31
- {},
32
- {
33
- [0 /* INT */]: (x) => {
34
- __ensureValidNumber(x);
35
- return [str(`i${Math.floor(x)}e`)];
36
- },
37
- [1 /* FLOAT */]: (x) => {
38
- __ensureValidNumber(x);
39
- assert(
40
- FLOAT_RE.test(x.toString()),
41
- `values requiring exponential notation not allowed (${x})`
42
- );
43
- return [str(`f${x}e`)];
44
- },
45
- [3 /* BINARY */]: (buf) => [
46
- str(buf.length + ":"),
47
- u8array(buf)
48
- ],
49
- [2 /* STR */]: (x) => [str(utf8Length(x) + ":" + x)],
50
- [5 /* LIST */]: (x) => [
51
- u8(108 /* LIST */),
52
- ...mapcat(__encodeBin, x),
53
- u8(101 /* END */)
54
- ],
55
- [4 /* DICT */]: (x) => [
56
- u8(100 /* DICT */),
57
- ...mapcat(
58
- (k) => __encodeBin(k).concat(__encodeBin(x[k])),
59
- Object.keys(x).sort()
60
- ),
61
- u8(101 /* END */)
62
- ]
63
- }
64
- );
38
+ const __encodeBin = (x) => ENCODERS[isNumber(x) ? Math.floor(x) !== x ? "f" : "i" : isBoolean(x) ? "i" : isString(x) ? "s" : x instanceof Uint8Array ? "b" : isPlainObject(x) ? "d" : isArrayLike(x) ? "l" : unsupported(`unsupported data type: ${x}`)](x);
65
39
  const __ensureValidNumber = (x) => {
66
40
  assert(isFinite(x), `can't encode infinite value`);
67
41
  assert(!isNaN(x), `can't encode NaN`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/bencode",
3
- "version": "2.1.147",
3
+ "version": "3.0.0",
4
4
  "description": "Bencode binary encoder / decoder with optional UTF8 encoding & floating point support",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -40,13 +40,13 @@
40
40
  "tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@thi.ng/arrays": "^2.10.8",
44
- "@thi.ng/checks": "^3.6.17",
45
- "@thi.ng/defmulti": "^3.0.53",
46
- "@thi.ng/errors": "^2.5.21",
47
- "@thi.ng/strings": "^3.8.13",
48
- "@thi.ng/transducers": "^9.2.11",
49
- "@thi.ng/transducers-binary": "^2.1.145"
43
+ "@thi.ng/api": "^8.11.16",
44
+ "@thi.ng/arrays": "^2.10.10",
45
+ "@thi.ng/checks": "^3.6.19",
46
+ "@thi.ng/errors": "^2.5.22",
47
+ "@thi.ng/strings": "^3.9.0",
48
+ "@thi.ng/transducers": "^9.2.13",
49
+ "@thi.ng/transducers-binary": "^2.1.147"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@microsoft/api-extractor": "^7.48.1",
@@ -89,5 +89,5 @@
89
89
  "thi.ng": {
90
90
  "alias": "bc"
91
91
  },
92
- "gitHead": "48bf4c22bf23f88ac99f435106af2214f79a0be1\n"
92
+ "gitHead": "6542b842120bef47cc18d45a1b1db68307a7f04b\n"
93
93
  }