smolcanon 0.1.0 → 0.2.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
@@ -8,6 +8,16 @@ This change log follows the format documented in [Keep a CHANGELOG].
8
8
  [semantic versioning]: http://semver.org/
9
9
  [keep a changelog]: http://keepachangelog.com/
10
10
 
11
+ ## v0.2.0 - 2025-08-18
12
+
13
+ ### Changed
14
+
15
+ - Significantly improved performance.
16
+
17
+ ### Fixed
18
+
19
+ - Fixed README initially copied from [`smolxxh`](https://github.com/kossnocorp/smolxxh).
20
+
11
21
  ## v0.1.0 - 2025-08-17
12
22
 
13
23
  Initial version
package/README.md CHANGED
@@ -1,32 +1,76 @@
1
- # Smol xxHash
1
+ # Smol Canon
2
2
 
3
- Tiny [xxHash](https://xxhash.com/) JS implementation.
3
+ Tiny JS values canonicalization for hashing.
4
4
 
5
- It is a modern, faster and smaller alternative to [xxhashjs](https://www.npmjs.com/package/xxhashjs) package. It is 3.8x faster and fits in just `381B`.
5
+ It uses a simple serialization algorithm, generating a consistent string representation of JS values. It is built to use with [Smol xxHash](https://github.com/kossnocorp/smolxxh).
6
6
 
7
- It is just 1.8x slower than [xxhash-wasm](https://www.npmjs.com/package/xxhash-wasm) package.
7
+ It is tiny and efficient. It is just `185B` and 30%+ faster than other stable serialization libraries.
8
8
 
9
- It features dual CJS/ESM support and built-in TypeScript definitions.
9
+ Unlike alternatives, it is focused on hashing and doesn't produce valid JSON, making it more efficient, and also supports more value types, i.e., `undefined`.
10
10
 
11
- It is based on [a reference C implementation](https://github.com/easyaspi314/xxhash-clean/blob/86a04ab3f01277049a23f6c9e2c4a6c174ff50c4/xxhash32-ref.c)
11
+ It features dual CJS/ESM support and built-in TypeScript definitions.
12
12
 
13
13
  ## Installation
14
14
 
15
15
  The package is available on npm:
16
16
 
17
17
  ```sh
18
- npm install smolxxh
18
+ npm install smolcanon
19
19
  ```
20
20
 
21
21
  ## Usage
22
22
 
23
- Pass `Buffer` or `Uint8Array` to `xxh32` function to get the hash of the content:
23
+ Pass any JS value to the `canonize` function to get its string representation:
24
+
25
+ ```ts
26
+ import { canonize } from "smolcanon";
27
+
28
+ const canon = canonize({ foo: "bar", baz: "qux" });
29
+ // => '{foo:"bar";baz:"qux"}'
30
+ ```
31
+
32
+ You can use it with [Smol xxHash](https://github.com/kossnocorp/smolxxh) to produce consistent hashes for your data:
24
33
 
25
34
  ```ts
35
+ import { canonize } from "smolcanon";
26
36
  import { xxh32 } from "smolxxh";
27
37
 
28
- xxh32(Buffer.from("hello world", "utf8")).toString(16);
29
- // => 0x31b7405d
38
+ const canon = canonize({ foo: "bar", baz: "qux" });
39
+ const hash = xxh32(Buffer.from("hello world", "utf8")).toString(16);
40
+ //=> "ed4e5029"
41
+ ```
42
+
43
+ ## Benchmark
44
+
45
+ [The benchmark](./benchmark/benchmark.ts) shows that Smol Canon is significantly faster than other popular libraries for canonicalizing JavaScript values:
46
+
47
+ ```
48
+ canonicalize:
49
+ 4 197 ops/s, ±0.28% | 43.59% slower
50
+
51
+ json-canon:
52
+ 5 171 ops/s, ±2.11% | 30.5% slower
53
+
54
+ fast-json-stable-stringify:
55
+ 4 548 ops/s, ±2.32% | 38.87% slower
56
+
57
+ fast-safe-stringify:
58
+ 5 310 ops/s, ±2.30% | 28.63% slower
59
+
60
+ fast-stable-stringify:
61
+ 4 973 ops/s, ±1.56% | 33.16% slower
62
+
63
+ json-stable-stringify:
64
+ 3 648 ops/s, ±1.93% | 50.97% slower
65
+
66
+ json-stringify-deterministic:
67
+ 3 054 ops/s, ±1.80% | slowest, 58.95% slower
68
+
69
+ safe-stable-stringify:
70
+ 5 345 ops/s, ±2.17% | 28.16% slower
71
+
72
+ smolcanon:
73
+ 7 440 ops/s, ±1.68% | fastest
30
74
  ```
31
75
 
32
76
  ## Changelog
package/index.cjs CHANGED
@@ -13,13 +13,19 @@ exports.canonize = canonize;
13
13
  */
14
14
  function canonize(input) {
15
15
  if (typeof input !== "object" || !input) {
16
- if (Object.is(input, -0)) return "-0";
17
- if (typeof input === "string") return JSON.stringify(input);
16
+ // NOTE: Traditional approach is faster than `Object.is(input, -0)`
17
+ if (input === 0 && 1 / input === -Infinity) return "-0";
18
+ // NOTE: Manual replacing is faster than `JSON.stringify`
19
+ if (typeof input === "string") return `"${input.replace(/"/g, '\\"')}"`;
18
20
  return String(input);
19
21
  }
20
22
  let canon = "";
21
- for (const key of Object.keys(input).sort()) {
23
+ const isArray = Array.isArray(input);
24
+ // NOTE: Skipping sorting for arrays improves performance. We also tried
25
+ // using `for...in` loop for arrays but it didn't affect performance at all.
26
+ const keys = isArray ? Object.keys(input) : Object.keys(input).sort();
27
+ for (const key of keys) {
22
28
  canon += `${key}:${canonize(input[key])};`;
23
29
  }
24
- return Array.isArray(input) ? `[${canon}]` : `{${canon}}`;
30
+ return isArray ? `[${canon}]` : `{${canon}}`;
25
31
  }
package/index.js CHANGED
@@ -10,13 +10,19 @@
10
10
  */
11
11
  export function canonize(input) {
12
12
  if (typeof input !== "object" || !input) {
13
- if (Object.is(input, -0)) return "-0";
14
- if (typeof input === "string") return JSON.stringify(input);
13
+ // NOTE: Traditional approach is faster than `Object.is(input, -0)`
14
+ if (input === 0 && 1 / input === -Infinity) return "-0";
15
+ // NOTE: Manual replacing is faster than `JSON.stringify`
16
+ if (typeof input === "string") return `"${input.replace(/"/g, '\\"')}"`;
15
17
  return String(input);
16
18
  }
17
19
  let canon = "";
18
- for (const key of Object.keys(input).sort()) {
20
+ const isArray = Array.isArray(input);
21
+ // NOTE: Skipping sorting for arrays improves performance. We also tried
22
+ // using `for...in` loop for arrays but it didn't affect performance at all.
23
+ const keys = isArray ? Object.keys(input) : Object.keys(input).sort();
24
+ for (const key of keys) {
19
25
  canon += `${key}:${canonize(input[key])};`;
20
26
  }
21
- return Array.isArray(input) ? `[${canon}]` : `{${canon}}`;
27
+ return isArray ? `[${canon}]` : `{${canon}}`;
22
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smolcanon",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Tiny JS objects canonicalization for hashing",
5
5
  "type": "module",
6
6
  "main": "index.cjs",
@@ -45,10 +45,19 @@
45
45
  "@swc/core": "^1.13.3",
46
46
  "@types/xxhashjs": "^0.2.4",
47
47
  "babel-plugin-replace-import-extension": "^1.1.5",
48
+ "benny": "^3.7.1",
48
49
  "bytes-iec": "^3.1.1",
50
+ "canonicalize": "^2.1.0",
51
+ "fast-json-stable-stringify": "^2.1.0",
52
+ "fast-safe-stringify": "^2.1.1",
53
+ "fast-stable-stringify": "^1.0.0",
49
54
  "glob": "^10.4.5",
55
+ "json-canon": "^1.0.1",
56
+ "json-stable-stringify": "^1.3.0",
57
+ "json-stringify-deterministic": "^1.0.12",
50
58
  "minimatch": "^10.0.3",
51
59
  "picocolors": "^1.1.1",
60
+ "safe-stable-stringify": "^2.5.0",
52
61
  "tsx": "^4.20.4",
53
62
  "typescript": "^5.9.2",
54
63
  "vitest": "^1.6.1"