@nexkit/json-repair 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,29 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## 1.0.0 - 2026-09-03
9
+
10
+ ### Added
11
+
12
+ - Initial release of `@nexkit/json-repair`, a recursive-descent JSON repair
13
+ engine with zero runtime dependencies.
14
+ - `repairJson`, which takes malformed or near-JSON text and returns the
15
+ repaired JSON string.
16
+ - `parseJson`, which repairs and parses in one step, returning the resulting
17
+ JavaScript value.
18
+ - `extractJson`, which locates the best JSON value embedded in a larger body of
19
+ text and returns it verbatim, without repairing it.
20
+ - `extractAllJson`, which returns every JSON value embedded in a larger body of
21
+ text, in document order and likewise verbatim.
22
+ - Safe and aggressive repair modes, so callers can choose between
23
+ conservative fixes and a more permissive best-effort recovery.
24
+ - JSON extraction from Markdown fenced code blocks and from free-form prose,
25
+ for handling output produced by language models and other text sources.
26
+ - The `json-repair` command-line interface, with `--pretty`, `--explain`, and
27
+ `--mode` flags.
28
+ - Dual ESM and CJS builds, published alongside TypeScript type declarations
29
+ for both module formats.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nexkit
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/README.md ADDED
@@ -0,0 +1,365 @@
1
+ # @nexkit/json-repair
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@nexkit/json-repair.svg)](https://www.npmjs.com/package/@nexkit/json-repair)
4
+ [![CI](https://github.com/nathanpixodeo/json-repair/actions/workflows/ci.yml/badge.svg)](https://github.com/nathanpixodeo/json-repair/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/@nexkit/json-repair.svg)](https://github.com/nathanpixodeo/json-repair/blob/main/LICENSE)
6
+
7
+ Repair malformed JSON — from LLM output, logs, or hand-edited config — into valid JSON, deterministically and without running any code.
8
+
9
+ ## Why this package
10
+
11
+ - **Zero runtime dependencies**, and the library core imports no `node:` built-ins, so it runs unchanged in browsers, Deno, Bun and edge runtimes as well as Node.
12
+ - **No regular expressions anywhere** in the engine, which rules out ReDoS as a category of problem, and **no code execution** — input is only ever scanned, never evaluated.
13
+ - **Deterministic and lossless where it can be.** The same input always repairs to the same bytes, valid JSON is returned untouched, and every repair is reported with its position so you can audit exactly what changed.
14
+ - **Two explicit modes.** `safe` only makes changes with a single plausible reading; `aggressive` adds heuristics for messier input, and never guesses silently — `safe` always runs first, so when it succeeds both modes agree byte for byte.
15
+
16
+ ## Install
17
+
18
+ ```sh
19
+ npm install @nexkit/json-repair
20
+ ```
21
+
22
+ Node.js 18 or later. Ships as ESM and CommonJS, with bundled type declarations for both.
23
+
24
+ ## Quick start
25
+
26
+ ```ts
27
+ import { repairJson } from '@nexkit/json-repair';
28
+
29
+ repairJson('{name:"John",age:30,}');
30
+ // '{"name":"John","age":30}'
31
+ ```
32
+
33
+ Valid JSON passes straight through, byte for byte:
34
+
35
+ ```ts
36
+ repairJson('{"name":"John"}');
37
+ // '{"name":"John"}' — the exact same string, untouched
38
+ ```
39
+
40
+ For LLM output wrapped in a code fence or surrounded by prose, extraction runs first automatically:
41
+
42
+ ````ts
43
+ repairJson('Here is the result:\n```json\n{"ok":true}\n```\n');
44
+ // '{"ok":true}'
45
+ ````
46
+
47
+ ## What it repairs
48
+
49
+ `safe` mode (the default) applies every one of these:
50
+
51
+ | Input | Output |
52
+ | ------------------------- | ------------------- |
53
+ | `{"name":"John",}` | `{"name":"John"}` |
54
+ | `[1,2,3,]` | `[1,2,3]` |
55
+ | `{'name':'John'}` | `{"name":"John"}` |
56
+ | `{name:"John"}` | `{"name":"John"}` |
57
+ | ` ```json\n{"a":1}\n``` ` | `{"a":1}` |
58
+ | `Here:\n{"a":1}\nDone.` | `{"a":1}` |
59
+ | `{"users":[1,2,3` | `{"users":[1,2,3]}` |
60
+
61
+ Beyond the table, `safe` mode also: strips `//`, `/* */` and `#` comments; normalizes single, backtick and typographic quotes to `"`; closes strings left open at end of input; drops a stray closing bracket that matches nothing; inserts a missing comma or colon between members; and removes a leading byte order mark. Every one of these behaviors has its own flag in [`RepairOptions`](#options) so you can turn any of them off individually.
62
+
63
+ `safe` mode also normalizes literals that spell a JSON value in another casing or another language: `True`, `FALSE`, `NULL` and Python's `None` all become `true`, `false` and `null`, so the dict repr that LLMs so often emit — `{'ok': True, 'note': None}` — repairs without needing `aggressive`.
64
+
65
+ ## Safe vs aggressive
66
+
67
+ `mode: 'safe'` (the default) only ever applies a change when there is exactly one plausible reading of the input. When the input is genuinely ambiguous, it throws `AMBIGUOUS_REPAIR` rather than guess:
68
+
69
+ ```ts
70
+ import { repairJson, isJsonRepairError } from '@nexkit/json-repair';
71
+
72
+ try {
73
+ repairJson('{"a": NaN}');
74
+ } catch (error) {
75
+ if (isJsonRepairError(error) && error.code === 'AMBIGUOUS_REPAIR') {
76
+ repairJson('{"a": NaN}', { mode: 'aggressive' });
77
+ // '{"a":null}'
78
+ }
79
+ }
80
+ ```
81
+
82
+ `mode: 'aggressive'` additionally:
83
+
84
+ - Quotes bare, unquoted string values: `{a: hello}` → `{"a":"hello"}`.
85
+ - Maps `NaN`, `Infinity` and `undefined` to `null`. (Python's `None` is handled in `safe` mode, alongside `True` and `False`, because `null` is a translation of it rather than an approximation.)
86
+ - Normalizes hex and leading-zero numbers to decimal: `0x1F` → `31`, `0123` → `123`.
87
+ - Fills elided array elements with `null`: `[1,,2]` → `[1,null,2]`.
88
+ - Treats an unescaped inner `"` as string content rather than a syntax error, escaping it in the output.
89
+ - Drops trailing content it cannot make sense of, rather than failing outright.
90
+
91
+ Aggressive mode always tries `safe` mode's rules first, so whenever `safe` mode succeeds on its own, both modes produce identical output.
92
+
93
+ ## API reference
94
+
95
+ ### `repairJson`
96
+
97
+ ```ts
98
+ function repairJson(input: string, options?: RepairOptions & { returnMetadata?: false }): string;
99
+ function repairJson(input: string, options: RepairOptions & { returnMetadata: true }): RepairResult;
100
+ ```
101
+
102
+ Repairs malformed JSON and returns the result as a string, or — with `returnMetadata: true` — as a `RepairResult` describing what changed.
103
+
104
+ ```ts
105
+ repairJson('{a:1,}', { returnMetadata: true });
106
+ // {
107
+ // json: '{"a":1}',
108
+ // changed: true,
109
+ // repairs: [
110
+ // { type: 'quoted-key', position: 1, line: 1, column: 2, message: 'Quoted an unquoted key' },
111
+ // { type: 'removed-trailing-comma', position: 4, line: 1, column: 5, message: 'Removed a trailing comma' }
112
+ // ]
113
+ // }
114
+ ```
115
+
116
+ Throws `JsonRepairError` — see [Errors](#errors) — for input that cannot be repaired, that exceeds a configured limit, or that is ambiguous under `safe` mode.
117
+
118
+ ### `parseJson`
119
+
120
+ ```ts
121
+ function parseJson<T = unknown>(input: string, options?: RepairOptions): T;
122
+ ```
123
+
124
+ Equivalent to `JSON.parse(repairJson(input, options))`, but the text is never parsed twice — the value produced while validating the repair is reused directly.
125
+
126
+ ```ts
127
+ parseJson<{ name: string }>('{name:"John"}').name;
128
+ // 'John'
129
+ ```
130
+
131
+ `T` is an unchecked type assertion, exactly as with `JSON.parse` itself — nothing here validates the parsed value's shape against it. Pair this with a schema validator (Zod, Valibot, or similar) when the input is untrusted.
132
+
133
+ ### `extractJson`
134
+
135
+ ```ts
136
+ function extractJson(input: string, options?: ExtractOptions): string;
137
+ ```
138
+
139
+ Locates the best embedded JSON value in a larger document and returns it **exactly as it appears in the input** — verbatim, not repaired. Use `repairJson` afterward if the extracted text may itself be malformed.
140
+
141
+ ```ts
142
+ extractJson('Here:\n{a:1,}\nDone.');
143
+ // '{a:1,}' — the raw, still-malformed slice
144
+ ```
145
+
146
+ ### `extractAllJson`
147
+
148
+ ```ts
149
+ function extractAllJson(input: string, options?: ExtractOptions): string[];
150
+ ```
151
+
152
+ Returns every JSON value found in a document, in document order, each verbatim. Returns `[]` when nothing is found, rather than throwing — unlike `extractJson`, "nothing here" is treated as an ordinary answer when the caller asked for everything.
153
+
154
+ ```ts
155
+ extractAllJson('a: {"a":1} b: [1,2]');
156
+ // ['{"a":1}', '[1,2]']
157
+
158
+ extractAllJson('no json here at all');
159
+ // []
160
+ ```
161
+
162
+ ### `JsonRepairError` / `isJsonRepairError`
163
+
164
+ ```ts
165
+ class JsonRepairError extends Error {
166
+ readonly code: JsonRepairErrorCode;
167
+ readonly position: number | undefined;
168
+ readonly line: number | undefined;
169
+ readonly column: number | undefined;
170
+ readonly snippet: string | undefined;
171
+ }
172
+ function isJsonRepairError(value: unknown): value is JsonRepairError;
173
+ ```
174
+
175
+ The only error type this package throws. `code` is stable and meant for branching on; `message` text is not part of the contract and may change between releases. Prefer `isJsonRepairError` over `instanceof` — it recognizes instances created by a different copy of the package, which can happen when both the ESM and CommonJS builds are loaded into the same process.
176
+
177
+ ```ts
178
+ try {
179
+ repairJson('not json at all');
180
+ } catch (error) {
181
+ if (isJsonRepairError(error)) {
182
+ console.log(error.code, error.line, error.column, error.snippet);
183
+ // 'NO_JSON_FOUND' 1 1 'not json at all'
184
+ }
185
+ }
186
+ ```
187
+
188
+ ### Constants
189
+
190
+ `DEFAULT_MAX_DEPTH` (`512`), `DEFAULT_MAX_LENGTH` (`10_000_000`), `MAX_SUPPORTED_DEPTH` (`1024`, the hard ceiling `maxDepth` cannot exceed) and `REPAIR_TYPES` (every `RepairType`, in a stable order — useful for building exhaustive UIs or tests without hardcoding the list).
191
+
192
+ ## Options
193
+
194
+ `RepairOptions`, accepted by `repairJson` and `parseJson`:
195
+
196
+ | Option | Type | Default | Effect |
197
+ | -------------------- | ------------------------ | ------------ | ---------------------------------------------------------------------------------------------------------------- |
198
+ | `mode` | `'safe' \| 'aggressive'` | `'safe'` | Repair strategy; see [Safe vs aggressive](#safe-vs-aggressive). |
199
+ | `extract` | `boolean` | `true` | Isolate JSON from Markdown fences and surrounding prose before repairing. |
200
+ | `allowComments` | `boolean` | `true` | Accept and strip `//`, `/* */` and `#` comments. |
201
+ | `allowSingleQuotes` | `boolean` | `true` | Accept single-quoted strings. |
202
+ | `allowUnquotedKeys` | `boolean` | `true` | Accept and quote bare object keys. |
203
+ | `fixTrailingCommas` | `boolean` | `true` | Remove a comma that directly precedes `}` or `]`. |
204
+ | `fixMissingBrackets` | `boolean` | `true` | Close a container that was never closed. |
205
+ | `maxLength` | `number` | `10_000_000` | Maximum accepted input length, in UTF-16 code units. Throws `MAX_LENGTH_EXCEEDED` past this. |
206
+ | `maxDepth` | `number` | `512` | Maximum accepted nesting depth, capped at `MAX_SUPPORTED_DEPTH` (`1024`). Throws `MAX_DEPTH_EXCEEDED` past this. |
207
+ | `maxRepairs` | `number` | `Infinity` | Maximum number of repair operations per attempt. Throws `MAX_REPAIRS_EXCEEDED` past this. |
208
+ | `returnMetadata` | `boolean` | `false` | Return a `RepairResult` instead of a plain string. |
209
+
210
+ Every boolean flag is tri-state: leaving it `undefined` uses the default, while an explicit `true` or `false` always wins — passing `false` disables that repair rather than falling back to a default.
211
+
212
+ `ExtractOptions`, accepted by `extractJson` and `extractAllJson`, is the same set minus `extract` (which extraction obviously always performs) and `returnMetadata`, plus:
213
+
214
+ | Option | Type | Default | Effect |
215
+ | -------- | ------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
216
+ | `select` | `'best' \| 'first' \| 'last' \| 'largest'` | `'best'` | Which candidate wins when a document holds several JSON blocks. `best` picks the longest candidate, then the one needing fewest repairs, then the earliest — so a decoy like the `[1]` in "see [1] for details" loses to a real payload beside it, even when the decoy is already valid and the payload needs repairing. `first`/`last` pick by document order; `largest` picks by span, ties going to the earliest. |
217
+
218
+ ## Errors
219
+
220
+ Every failure is a `JsonRepairError` with one of these codes:
221
+
222
+ | Code | Meaning |
223
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
224
+ | `INVALID_INPUT` | Input was not a string, or an option was out of range or the wrong type. |
225
+ | `NO_JSON_FOUND` | No repairable JSON value could be located in the input. |
226
+ | `UNREPAIRABLE_JSON` | The input cannot be read as JSON at all, even with repairs applied. |
227
+ | `MAX_LENGTH_EXCEEDED` | Input exceeded `maxLength`. |
228
+ | `MAX_DEPTH_EXCEEDED` | Nesting exceeded `maxDepth`. |
229
+ | `MAX_REPAIRS_EXCEEDED` | More repair operations were required than `maxRepairs` allows. |
230
+ | `AMBIGUOUS_REPAIR` | Several readings are possible and `safe` mode declines to choose; the error message names `mode: "aggressive"` as the way to force a choice. |
231
+
232
+ ## CLI
233
+
234
+ ```sh
235
+ npm install -g @nexkit/json-repair
236
+ ```
237
+
238
+ ```
239
+ Usage: json-repair [options] [file]
240
+
241
+ Repair malformed JSON from LLM output, logs, or hand-edited config, and
242
+ print valid JSON to standard output.
243
+
244
+ Reads from FILE when one is given, otherwise from standard input.
245
+
246
+ Options:
247
+ -p, --pretty Pretty-print the output with indentation
248
+ --indent <n> Spaces per indent level, used with --pretty
249
+ (0-10, default: 2)
250
+ -e, --explain Print a summary of applied repairs to stderr
251
+ -m, --mode <mode> Repair strategy: "safe" or "aggressive"
252
+ (default: "safe")
253
+ --no-extract Do not isolate JSON from surrounding text
254
+ --no-comments Reject //, /* */ and # comments
255
+ --no-single-quotes Reject single-quoted strings
256
+ --no-unquoted-keys Reject unquoted object keys
257
+ --no-trailing-commas Reject a comma right before } or ]
258
+ --no-missing-brackets Do not close an unterminated object or array
259
+ --max-length <n> Maximum accepted input length, in characters
260
+ --max-depth <n> Maximum accepted nesting depth
261
+ --max-repairs <n> Maximum number of repairs to apply
262
+ -h, --help Show this help message and exit
263
+ -v, --version Print the version number and exit
264
+
265
+ Examples:
266
+ json-repair data.json
267
+ json-repair --pretty --explain broken.json > fixed.json
268
+ cat broken.json | json-repair --mode aggressive
269
+ ```
270
+
271
+ `--` ends flag parsing, so a filename that starts with `-` can still be passed. Short flags cluster (`-pe` means `-p -e`), and both `--flag value` and `--flag=value` work for options that take one.
272
+
273
+ Repair a file and print compact JSON to stdout:
274
+
275
+ ```sh
276
+ $ echo '{name:"John",age:30,}' | json-repair
277
+ {"name":"John","age":30}
278
+ ```
279
+
280
+ Pretty-print the result:
281
+
282
+ ```sh
283
+ $ echo '{name:"John",age:30,}' | json-repair --pretty
284
+ {
285
+ "name": "John",
286
+ "age": 30
287
+ }
288
+ ```
289
+
290
+ See exactly what was changed, on stderr, while stdout stays clean and pipeable:
291
+
292
+ ```sh
293
+ $ echo '{name:"John",age:30,}' | json-repair --explain > /dev/null
294
+ Applied 3 repairs:
295
+ 1:2 quoted-key Quoted an unquoted key
296
+ 1:14 quoted-key Quoted an unquoted key
297
+ 1:20 removed-trailing-comma Removed a trailing comma
298
+ ```
299
+
300
+ Input that cannot be repaired exits `1` with a diagnostic on stderr, and stdout stays empty:
301
+
302
+ ```sh
303
+ $ printf '%s' 'totally not json' | json-repair
304
+ json-repair: NO_JSON_FOUND: No JSON value was found in the input.
305
+ at line 1, column 1: totally not json
306
+ $ echo $?
307
+ 1
308
+ ```
309
+
310
+ An unrecognized flag exits `2` as a usage error, without attempting to repair anything:
311
+
312
+ ```sh
313
+ $ json-repair --bogus-flag
314
+ json-repair: unknown option '--bogus-flag'
315
+ Try 'json-repair --help' for more information.
316
+ $ echo $?
317
+ 2
318
+ ```
319
+
320
+ Exit codes: `0` on success, `1` when the input could not be repaired, `2` for a usage error in the command line itself.
321
+
322
+ ## Performance
323
+
324
+ The engine targets **under 50 ms to repair a common defect in a 1 MB input, and linear time in input size** — no candidate is re-scanned from the start, so pathological input degrades gracefully rather than quadratically.
325
+
326
+ Measured on the built bundle (`dist/index.js`), Node 25.9 on Windows 11, Intel Core i7-12700. Every fixture is generated from a seeded pseudo-random generator rather than `Math.random()`, so the inputs are identical on every machine and every run. The 1 MB cases use 5 untimed warm-up iterations followed by 20 timed ones; the small and adversarial cases use 10 and 50. Each row reports the median and the 95th percentile of the timed iterations. Reproduce it all with `npm run build && npm run bench` — `bench/bench.mjs` is committed for exactly that reason.
327
+
328
+ | Case | Input | Median | p95 |
329
+ | --------------------------------------- | ------ | ------- | ------- |
330
+ | Valid JSON (fast path) | 977 KB | 6.4 ms | 12.3 ms |
331
+ | Trailing comma | 977 KB | 37.8 ms | 51.8 ms |
332
+ | Truncated mid-string | 586 KB | 23.4 ms | 28.1 ms |
333
+ | Prose plus a ` ```json ` fence | 977 KB | 23.4 ms | 37.0 ms |
334
+ | Single-quoted strings throughout | 977 KB | 43.5 ms | 69.6 ms |
335
+ | Small malformed object | 21 B | 0.01 ms | 0.03 ms |
336
+ | 100,000 consecutive quotes (aggressive) | 98 KB | 12.7 ms | 25.1 ms |
337
+ | 100,000 stray `}` (aggressive) | 98 KB | 0.6 ms | 1.4 ms |
338
+
339
+ Only the trailing-comma row carries the 50 ms budget, it is measured against the median, and `npm run bench` exits non-zero if it is ever missed. CI runs the same benchmark with `--no-budget`, which records the numbers without enforcing them, because a shared GitHub runner is roughly half the speed of the machine above — the trailing-comma case measures around 75 ms there — and enforcing a wall-clock budget on hardware that slow and that variable would report the runner rather than the code. Be aware that the two 1 MB rewriting cases cross 50 ms at the 95th percentile even though their medians sit well inside it: rebuilding an entire megabyte of output is enough work that a badly timed garbage collection shows up in the tail. The single-quote case is the slowest realistic input by some margin, because rewriting every delimiter means nothing can be copied through unchanged. The two adversarial rows are not realistic inputs at all — they exist to show that the engine stays linear under pathological conditions instead of blowing up, which is what matters when the input comes from untrusted text rather than a well-behaved model response.
340
+
341
+ ## Security
342
+
343
+ - **Input limits by default.** `maxLength` (10,000,000 characters) and `maxDepth` (512, hard-capped at 1024) are enforced before and during scanning, so a caller does not have to remember to bound untrusted input themselves.
344
+ - **No code execution.** Input is only ever scanned and re-emitted as text; nothing is evaluated, and the parser never calls back into the input in any way that could execute it.
345
+ - **No regular expressions anywhere in the engine**, which removes ReDoS — catastrophic backtracking on adversarial input — as a possible failure mode entirely.
346
+ - **No prototype pollution.** A `"__proto__"` key in the input is treated as an ordinary JSON string key: it is emitted as plain JSON text, and `JSON.parse` — which every repaired output is validated against — assigns it as an own property rather than mutating `Object.prototype`.
347
+
348
+ ## Limitations
349
+
350
+ - This package repairs syntax, not intent. It does not infer a schema, and in `safe` mode it never invents data that is not implied by a single, unambiguous reading of the input — it fails with `AMBIGUOUS_REPAIR` instead.
351
+ - `aggressive` mode's heuristics (quoting bare values, mapping non-JSON literals to `null`, and so on) are best-effort guesses about what the author meant. They are usually right for LLM output, but they are guesses, not proof.
352
+ - Extraction (`extract: true`, `extractJson`, `extractAllJson`) picks one JSON-shaped region out of a larger document using heuristics — longest, then fewest repairs, then earliest, by default. On genuinely ambiguous prose containing more than one plausible JSON block, it can pick the wrong one; use `select` or `extractAllJson` when you need to check every candidate yourself.
353
+ - Once at least one repair is applied, the output is canonical, compact JSON: key order is preserved, but the original indentation and whitespace style are not. (Input that was already valid JSON is the exception — it is always returned byte for byte, formatting included.)
354
+
355
+ ## Compatibility
356
+
357
+ Node.js 18 and later. Ships as ESM (`import`) and CommonJS (`require`), with TypeScript type declarations for both. The library core (everything except the CLI) has no runtime dependencies and imports no `node:` built-ins, so it also runs in browsers, Deno, Bun and edge/serverless runtimes without modification.
358
+
359
+ ## Contributing
360
+
361
+ Issues and pull requests are welcome at [github.com/nathanpixodeo/json-repair](https://github.com/nathanpixodeo/json-repair).
362
+
363
+ ## License
364
+
365
+ MIT © nexkit