@stackline/stable-stringify 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/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ All notable changes are documented here. This project follows Semantic
4
+ Versioning.
5
+
6
+ ## [1.0.0] - 2026-08-16
7
+
8
+ ### Added
9
+
10
+ - Deterministic recursive JSON serialization with zero runtime dependencies.
11
+ - Compatible `fast-json-stable-stringify` comparator and cycle options.
12
+ - Circular object and array policies: throw, marker, path, and null.
13
+ - Iterative traversal tested with 25,000 nested containers.
14
+ - BigInt, replacer, indentation, accessor, and `toJSON` policies.
15
+ - Opt-in depth, entry, and output-length limits with structured errors.
16
+ - Safe diagnostic serialization with bounded defaults and controlled fallback.
17
+ - Strict RFC 8785 JSON canonicalization and UTF-8 byte output.
18
+ - ESM, callable CommonJS, browser-global, and source-map distributions.
19
+ - TypeScript declarations tested from 3.9 through 7.0.
20
+ - 30,000 differential cases against `fast-json-stable-stringify@2.1.0`.
21
+ - Public documentation, security policy, CI matrix, and live playground.
22
+
23
+ [1.0.0]: https://github.com/alexandroit/stackline-stable-stringify/releases/tag/v1.0.0
@@ -0,0 +1,54 @@
1
+ # Contributing
2
+
3
+ Thank you for improving `@stackline/stable-stringify`.
4
+
5
+ ## Development
6
+
7
+ Use Node.js 20 or newer for the development toolchain. Published runtime
8
+ support is tested separately.
9
+
10
+ ```bash
11
+ npm ci
12
+ npm test
13
+ npm run test:attw
14
+ npm run benchmark
15
+ ```
16
+
17
+ ## Pull requests
18
+
19
+ Keep each change focused. Include:
20
+
21
+ - the behavior being changed;
22
+ - tests for normal and adversarial input;
23
+ - compatibility impact;
24
+ - performance and security impact for traversal changes;
25
+ - documentation for public API changes.
26
+
27
+ Do not reduce canonical strictness, safe-mode limits, or error visibility to
28
+ improve a benchmark.
29
+
30
+ ## Compatibility changes
31
+
32
+ The default API tracks documented `fast-json-stable-stringify@2.1.0` behavior.
33
+ Before changing stable-mode semantics:
34
+
35
+ 1. add a focused local regression;
36
+ 2. update the differential corpus when applicable;
37
+ 3. document every intentional difference;
38
+ 4. validate direct and npm-alias installation shapes.
39
+
40
+ Canonical mode follows RFC 8785 and I-JSON constraints. Standards changes need
41
+ a source citation and interoperability vector.
42
+
43
+ ## Security changes
44
+
45
+ Do not discuss a suspected vulnerability in a public issue before a patched
46
+ release exists. Follow [SECURITY.md](SECURITY.md).
47
+
48
+ ## Commits
49
+
50
+ Use a short imperative subject. Keep generated output and unrelated formatting
51
+ out of the same commit.
52
+
53
+ By contributing, you agree that your contribution is licensed under the MIT
54
+ License.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexandro Paixao Marques and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,21 @@
1
+ @stackline/stable-stringify
2
+ Copyright (c) 2026 Alexandro Paixao Marques and contributors
3
+
4
+ This package is an independent implementation. Its default public behavior is
5
+ designed to support migration from fast-json-stable-stringify version 2.1.0,
6
+ a separate MIT-licensed project originally authored by James Halliday and
7
+ maintained by Evgeny Poberezkin and contributors:
8
+
9
+ https://github.com/epoberezkin/fast-json-stable-stringify
10
+
11
+ The upstream package is used only as a development dependency and behavioral
12
+ reference in differential tests. Its source code is not bundled and it is not
13
+ a runtime dependency.
14
+
15
+ The package names "fast-json-stable-stringify", "safe-stable-stringify",
16
+ "fast-safe-stringify", and "json-stringify-safe" are used descriptively in
17
+ compatibility tests, benchmarks, and market research. This project is not
18
+ affiliated with or endorsed by their maintainers.
19
+
20
+ RFC 8785, JSON Canonicalization Scheme (JCS), is an Internet Engineering Task
21
+ Force standards-track document. Standards references do not imply endorsement.
package/README.md ADDED
@@ -0,0 +1,290 @@
1
+ # @stackline/stable-stringify
2
+
3
+ [![npm version](https://img.shields.io/npm/v/%40stackline%2Fstable-stringify)](https://www.npmjs.com/package/@stackline/stable-stringify)
4
+ [![CI](https://github.com/alexandroit/stackline-stable-stringify/actions/workflows/ci.yml/badge.svg)](https://github.com/alexandroit/stackline-stable-stringify/actions/workflows/ci.yml)
5
+ [![CodeQL](https://github.com/alexandroit/stackline-stable-stringify/actions/workflows/codeql.yml/badge.svg)](https://github.com/alexandroit/stackline-stable-stringify/actions/workflows/codeql.yml)
6
+ [![license](https://img.shields.io/npm/l/%40stackline%2Fstable-stringify)](LICENSE)
7
+
8
+ Deterministic JSON serialization, safe diagnostics, and RFC 8785 canonical
9
+ JSON in one zero-dependency package.
10
+
11
+ The default export follows the familiar `fast-json-stable-stringify` API. It
12
+ also handles circular arrays, deeply nested data without recursive call-stack
13
+ failure, BigInt policies, resource limits, safe logging, and strict JSON
14
+ Canonicalization Scheme output.
15
+
16
+ ## Why this package
17
+
18
+ Stable serialization is infrastructure for cache keys, signatures, snapshots,
19
+ deduplication, logs, and reproducible builds. The JavaScript ecosystem has
20
+ separate high-volume packages for deterministic key order, circular data, safe
21
+ logging, and canonical JSON. This package provides those contracts through one
22
+ small, tested runtime while keeping the established call shape.
23
+
24
+ - recursive deterministic key ordering;
25
+ - compatible comparator and `{ cmp, cycles }` options;
26
+ - cycle policies for objects and arrays;
27
+ - safe defaults for diagnostics and logging;
28
+ - RFC 8785 / JCS canonical strings and UTF-8 bytes;
29
+ - iterative traversal for very deep input;
30
+ - opt-in depth, entry, and output-size limits;
31
+ - ESM, callable CommonJS, browser global, and TypeScript declarations;
32
+ - zero runtime dependencies.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ npm install @stackline/stable-stringify
38
+ ```
39
+
40
+ Keep an existing package name and imports with an npm alias:
41
+
42
+ ```bash
43
+ npm install fast-json-stable-stringify@npm:@stackline/stable-stringify
44
+ ```
45
+
46
+ ```js
47
+ const stringify = require('fast-json-stable-stringify');
48
+ ```
49
+
50
+ ## Quick start
51
+
52
+ ```js
53
+ import stringify from '@stackline/stable-stringify';
54
+
55
+ const first = { z: 1, nested: { y: 2, x: 3 }, a: true };
56
+ const second = { a: true, nested: { x: 3, y: 2 }, z: 1 };
57
+
58
+ stringify(first) === stringify(second); // true
59
+ // {"a":true,"nested":{"x":3,"y":2},"z":1}
60
+ ```
61
+
62
+ Arrays retain their input order. Object keys are sorted at every depth. Input
63
+ objects are never mutated.
64
+
65
+ ## Safe serialization
66
+
67
+ `safeStringify` is intended for logs, diagnostics, and error reporting. It
68
+ serializes BigInt as strings, marks cycles, and applies bounded defaults.
69
+
70
+ ```js
71
+ import { safeStringify } from '@stackline/stable-stringify';
72
+
73
+ const event = { id: 42n };
74
+ event.request = event;
75
+
76
+ safeStringify(event);
77
+ // {"id":"42","request":"[Circular]"}
78
+ ```
79
+
80
+ Its default budgets are `maxDepth: 100`, `maxEntries: 100000`, and
81
+ `maxLength: 1000000`. A hostile getter, throwing `toJSON`, or exceeded limit
82
+ returns a controlled JSON string. Set `throwOnError: true` to propagate the
83
+ original error.
84
+
85
+ ```js
86
+ safeStringify(value, replacer, 2, {
87
+ depthLimit: 20,
88
+ edgesLimit: 5000,
89
+ maxLength: 250000,
90
+ throwOnError: true
91
+ });
92
+ ```
93
+
94
+ ## RFC 8785 canonical JSON
95
+
96
+ Use `canonicalize` when bytes must be reproducible across systems, such as
97
+ digital signatures, content-addressed storage, or cryptographic hashes.
98
+
99
+ ```js
100
+ import {
101
+ canonicalize,
102
+ canonicalizeBytes
103
+ } from '@stackline/stable-stringify';
104
+
105
+ canonicalize({ amount: 4.50, currency: 'CAD', active: true });
106
+ // {"active":true,"amount":4.5,"currency":"CAD"}
107
+
108
+ const bytes = canonicalizeBytes({ z: 1, a: 2 });
109
+ // Uint8Array containing UTF-8 bytes for {"a":2,"z":1}
110
+ ```
111
+
112
+ Canonical mode intentionally rejects data that cannot satisfy RFC 8785 and
113
+ I-JSON, including:
114
+
115
+ - `NaN`, `Infinity`, BigInt, `undefined`, functions, and symbols;
116
+ - circular references and sparse arrays;
117
+ - lone UTF-16 surrogates;
118
+ - accessors, enumerable symbol properties, and class instances.
119
+
120
+ It does not invoke getters or `toJSON`. Convert application objects to plain
121
+ JSON data before canonicalization.
122
+
123
+ ## API
124
+
125
+ ### `stringify(value, options?)`
126
+
127
+ The default export and the named `stableStringify` and `stringify` exports are
128
+ the same function.
129
+
130
+ ```js
131
+ import stringify from '@stackline/stable-stringify';
132
+
133
+ stringify(value);
134
+ stringify(value, comparator);
135
+ stringify(value, { cmp: comparator, cycles: true });
136
+ ```
137
+
138
+ Like `JSON.stringify`, a root `undefined`, function, or symbol returns
139
+ `undefined`. Unsupported object properties are omitted and unsupported array
140
+ items become `null`.
141
+
142
+ ### Stable options
143
+
144
+ | Option | Default | Purpose |
145
+ | :--- | :--- | :--- |
146
+ | `cmp` | key order | Compare `{ key, value }` records |
147
+ | `cycles` | `false` | Compatible shortcut for a `__cycle__` marker |
148
+ | `onCycle` | `throw` | `throw`, `marker`, `path`, or `null` |
149
+ | `cycleValue` | `__cycle__` | Custom marker for `onCycle: 'marker'` |
150
+ | `bigint` | `throw` | `throw`, `string`, or safe `number` |
151
+ | `replacer` | none | Function or JSON-style property list |
152
+ | `space` | none | JSON-style indentation, capped at 10 characters |
153
+ | `accessors` | `invoke` | `invoke`, `omit`, or `throw` |
154
+ | `toJSON` | `true` | Invoke compatible `toJSON` hooks |
155
+ | `maxDepth` | unlimited | Maximum container nesting depth |
156
+ | `maxEntries` | unlimited | Maximum visited array items and object keys |
157
+ | `maxLength` | unlimited | Maximum UTF-16 output length |
158
+
159
+ Unlimited defaults preserve compatibility. Apply explicit limits at exposed
160
+ trust boundaries, or use `safeStringify`.
161
+
162
+ ### Cycle paths
163
+
164
+ ```js
165
+ const value = { id: 1 };
166
+ value.child = { owner: value };
167
+
168
+ stringify(value, { onCycle: 'path' });
169
+ // {"child":{"owner":"[Circular <root>]"},"id":1}
170
+ ```
171
+
172
+ Repeated sibling references are serialized normally; only references to an
173
+ active ancestor are cycles.
174
+
175
+ ### Comparator
176
+
177
+ ```js
178
+ const descending = (left, right) =>
179
+ right.key.localeCompare(left.key);
180
+
181
+ stringify({ a: 1, c: 3, b: 2 }, descending);
182
+ // {"c":3,"b":2,"a":1}
183
+ ```
184
+
185
+ ### `configure(defaults?)`
186
+
187
+ Create a reusable serializer without rebuilding option objects at call sites.
188
+
189
+ ```js
190
+ import { configure } from '@stackline/stable-stringify';
191
+
192
+ const cacheKey = configure({
193
+ maxDepth: 50,
194
+ maxEntries: 10000,
195
+ maxLength: 500000
196
+ });
197
+
198
+ cacheKey({ route: '/users', query: { page: 2 } });
199
+ ```
200
+
201
+ Per-call options override configured defaults.
202
+
203
+ ### Errors
204
+
205
+ `StableStringifyLimitError` extends `RangeError` and exposes `code`, `kind`,
206
+ `limit`, and `path`. `CanonicalizationError` extends `TypeError` and exposes
207
+ `code`, `reason`, and `path`.
208
+
209
+ ## CommonJS
210
+
211
+ ```js
212
+ const stringify = require('@stackline/stable-stringify');
213
+
214
+ stringify({ z: 1, a: 2 });
215
+ stringify.safeStringify(value);
216
+ stringify.canonicalize(value);
217
+ ```
218
+
219
+ ## Browser
220
+
221
+ ```html
222
+ <script src="https://cdn.jsdelivr.net/npm/@stackline/stable-stringify/dist/index.min.js"></script>
223
+ <script>
224
+ const output = StacklineStableStringify({ z: 1, a: 2 });
225
+ </script>
226
+ ```
227
+
228
+ The browser global is callable and includes the same helper methods as the
229
+ CommonJS export.
230
+
231
+ ## Compatibility
232
+
233
+ The default contract is tested against `fast-json-stable-stringify@2.1.0`.
234
+ The regression suite includes 30,000 deterministic differential
235
+ serializations across generated values and comparator forms.
236
+
237
+ Intentional additions do not alter default output:
238
+
239
+ - circular arrays are handled instead of overflowing the call stack;
240
+ - very deep structures use an iterative task stack;
241
+ - replacer, indentation, BigInt, accessor, and resource policies are opt-in;
242
+ - safe and canonical modes are separate named APIs.
243
+
244
+ See [compatibility details](docs/COMPATIBILITY.md) before replacing a
245
+ serializer that relies on undocumented edge behavior.
246
+
247
+ ## Runtime support
248
+
249
+ The package is tested on Node.js 14.17 through current releases, Linux,
250
+ Windows, macOS, Deno 2, Bun, and browsers. Type declarations are compiled in a
251
+ matrix from TypeScript 3.9 through 7.0.
252
+
253
+ ## Performance
254
+
255
+ Run the local comparison:
256
+
257
+ ```bash
258
+ npm run benchmark
259
+ ```
260
+
261
+ The benchmark reports native `JSON.stringify`,
262
+ `fast-json-stable-stringify@2.1.0`, stable mode, safe mode, and RFC 8785 mode
263
+ on the same payload. Native JSON is shown only as a throughput baseline; it
264
+ does not recursively sort object keys. Measure with representative production
265
+ data before selecting limits or modes.
266
+
267
+ ## Verification
268
+
269
+ Every release runs:
270
+
271
+ - 100% statement, line, and function coverage with at least 95% branches;
272
+ - differential compatibility tests;
273
+ - TypeScript 3.9 through 7.0 compilation;
274
+ - clean tarball installs for direct and aliased package names;
275
+ - ESM, CommonJS, browser, Deno, and Bun runtime checks;
276
+ - `publint`, `attw`, dependency audit, CodeQL, SBOM, and checksum generation.
277
+
278
+ The [live playground](https://alexandro.net/docs/vanilla/stable-stringify/)
279
+ runs the published browser bundle.
280
+
281
+ ## Security
282
+
283
+ Read [SECURITY.md](SECURITY.md) for the supported versions, trust boundaries,
284
+ resource-limit guidance, and private reporting process.
285
+
286
+ ## License
287
+
288
+ MIT. This is an independent implementation and is not affiliated with or
289
+ endorsed by the maintainers of the comparison packages. See [NOTICE](NOTICE)
290
+ for attribution and compatibility context.
package/SECURITY.md ADDED
@@ -0,0 +1,55 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ | Version | Security updates |
6
+ | :--- | :---: |
7
+ | `1.x` | Yes |
8
+ | `< 1.0.0` | No public releases |
9
+
10
+ ## Reporting a vulnerability
11
+
12
+ Do not open a public issue for a suspected vulnerability. Use the repository's
13
+ [private security advisory form](https://github.com/alexandroit/stackline-stable-stringify/security/advisories/new).
14
+
15
+ Include the affected version, a minimal reproducer, observed impact, runtime,
16
+ module system, and proposed mitigation when available. Complete reports are
17
+ normally acknowledged within three business days. Confirmed issues are fixed
18
+ privately, covered by regression tests, and disclosed after a patched release.
19
+
20
+ ## Trust boundaries
21
+
22
+ Stable mode preserves `JSON.stringify`-style behavior for compatibility. It
23
+ may invoke a value's `toJSON`, a replacer callback, custom comparator, or
24
+ property getter. Those are executable application code and must not be sourced
25
+ from untrusted input.
26
+
27
+ For hostile or externally supplied object graphs:
28
+
29
+ - use `accessors: 'omit'` or `accessors: 'throw'`;
30
+ - use `toJSON: false`;
31
+ - set finite `maxDepth`, `maxEntries`, and `maxLength` values;
32
+ - do not accept comparator or replacer functions from users.
33
+
34
+ `safeStringify` applies finite defaults but preserves compatible getter and
35
+ `toJSON` behavior unless explicitly disabled. Its fallback prevents a logging
36
+ path from throwing by default; use `throwOnError: true` when failure must be
37
+ observable by the caller.
38
+
39
+ ## Canonical mode
40
+
41
+ `canonicalize` is the strictest input boundary. It does not invoke getters or
42
+ `toJSON` and rejects accessors, class instances, symbol members, cycles, sparse
43
+ arrays, non-finite numbers, BigInt, and malformed Unicode. It still traverses
44
+ the supplied data, so keep finite limits when the input size is attacker
45
+ controlled.
46
+
47
+ Canonical JSON only provides deterministic bytes. It does not validate domain
48
+ schemas, authenticate input, create signatures, or choose a cryptographic hash.
49
+
50
+ ## Denial-of-service controls
51
+
52
+ The iterative traversal avoids JavaScript call-stack exhaustion. Memory and
53
+ CPU use still grow with visited input and produced output. Resource limits are
54
+ part of the security contract for public endpoints, log ingestion, and build
55
+ systems processing third-party data.