jsonc-effect 0.2.1 → 0.3.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/ast.js ADDED
@@ -0,0 +1,255 @@
1
+ import { Effect, Function, Option } from "effect";
2
+
3
+ //#region src/ast.ts
4
+ /**
5
+ * AST navigation functions for JSONC parse trees.
6
+ *
7
+ * Operates on JsoncNode trees produced by parseTree().
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ /**
12
+ * Find an AST node at a specific JSON path.
13
+ *
14
+ * Supports {@link https://effect.website/docs/effect/function#dual | Function.dual}
15
+ * for both data-first and data-last (pipeline) usage.
16
+ *
17
+ * @param root - The AST root node obtained from {@link parseTree}.
18
+ * @param path - An array of string keys and numeric indices describing the path to traverse.
19
+ *
20
+ * @returns `Effect<Option<JsoncNode>>` — the node at the given path, or `Option.none()` if the
21
+ * path does not exist in the tree.
22
+ *
23
+ * @remarks
24
+ * String segments navigate object properties and number segments navigate array indices.
25
+ * Returns `Option.none()` when any segment along the path cannot be resolved — for example,
26
+ * accessing a property on a non-object node or an out-of-bounds array index.
27
+ *
28
+ * @see {@link parseTree} — produces the AST root this function operates on
29
+ * @see {@link getNodeValue} — reconstructs a JS value from a found node
30
+ * @see {@link JsoncNode} — the AST node type
31
+ * @see {@link (JsoncPath:type)} — the path segment array type
32
+ *
33
+ * @example Data-first usage
34
+ * ```ts
35
+ * import { Effect, Option } from "effect";
36
+ * import type { JsoncNode } from "jsonc-effect";
37
+ * import { parseTree, findNode } from "jsonc-effect";
38
+ *
39
+ * const program = Effect.gen(function* () {
40
+ * const root: Option.Option<JsoncNode> = yield* parseTree('{ "a": { "b": 1 } }');
41
+ * if (Option.isNone(root)) return Option.none();
42
+ * return yield* findNode(root.value, ["a", "b"]);
43
+ * });
44
+ *
45
+ * const result = Effect.runSync(program);
46
+ * // result is Option.some(node) where node.value === 1
47
+ * ```
48
+ *
49
+ * @example Data-last pipeline usage
50
+ * ```ts
51
+ * import { Effect, Option, pipe } from "effect";
52
+ * import type { JsoncNode } from "jsonc-effect";
53
+ * import { parseTree, findNode } from "jsonc-effect";
54
+ *
55
+ * const program = Effect.gen(function* () {
56
+ * const root: Option.Option<JsoncNode> = yield* parseTree('{ "x": [10, 20] }');
57
+ * if (Option.isNone(root)) return Option.none();
58
+ * return yield* pipe(root.value, findNode(["x", 1]));
59
+ * });
60
+ *
61
+ * const result = Effect.runSync(program);
62
+ * // result is Option.some(node) where node.value === 20
63
+ * ```
64
+ *
65
+ * @privateRemarks
66
+ * Wrapped in `Effect.sync`; the underlying traversal is fully synchronous.
67
+ *
68
+ * @public
69
+ */
70
+ const findNode = Function.dual(2, (root, path) => Effect.sync(() => findNodeImpl(root, path)));
71
+ /**
72
+ * Find the innermost AST node covering a character offset.
73
+ *
74
+ * Supports {@link https://effect.website/docs/effect/function#dual | Function.dual}
75
+ * for both data-first and data-last (pipeline) usage.
76
+ *
77
+ * @param root - The AST root node obtained from {@link parseTree}.
78
+ * @param offset - A zero-based character offset into the original JSONC string.
79
+ *
80
+ * @returns `Effect<Option<JsoncNode>>` — the most deeply nested node whose span
81
+ * includes the offset, or `Option.none()` if the offset is outside the tree.
82
+ *
83
+ * @remarks
84
+ * This is useful for editor integrations such as hover information, go-to-definition,
85
+ * and code completions where you need to identify the token under the cursor.
86
+ *
87
+ * @see {@link parseTree} — produces the AST root this function operates on
88
+ * @see {@link getNodePath} — returns the JSON path to the node at an offset
89
+ *
90
+ * @example Finding a node at an offset
91
+ * ```ts
92
+ * import { Effect, Option } from "effect";
93
+ * import type { JsoncNode } from "jsonc-effect";
94
+ * import { parseTree, findNodeAtOffset } from "jsonc-effect";
95
+ *
96
+ * const program = Effect.gen(function* () {
97
+ * const root: Option.Option<JsoncNode> = yield* parseTree('{ "key": "value" }');
98
+ * if (Option.isNone(root)) return Option.none();
99
+ * // Offset 10 is inside the "value" string literal
100
+ * return yield* findNodeAtOffset(root.value, 10);
101
+ * });
102
+ *
103
+ * const result = Effect.runSync(program);
104
+ * // result is Option.some(node) where node.type === "string"
105
+ * ```
106
+ *
107
+ * @privateRemarks
108
+ * Wrapped in `Effect.sync`; the underlying traversal is fully synchronous.
109
+ *
110
+ * @public
111
+ */
112
+ const findNodeAtOffset = Function.dual(2, (root, offset) => Effect.sync(() => findNodeAtOffsetImpl(root, offset)));
113
+ /**
114
+ * Get the JSON path to the node at a specific character offset.
115
+ *
116
+ * Supports {@link https://effect.website/docs/effect/function#dual | Function.dual}
117
+ * for both data-first and data-last (pipeline) usage.
118
+ *
119
+ * @param root - The AST root node obtained from {@link parseTree}.
120
+ * @param targetOffset - A zero-based character offset into the original JSONC string.
121
+ *
122
+ * @returns `Effect<Option<JsoncPath>>` — an array of path segments (string keys
123
+ * and numeric indices) leading to the innermost node at the offset, or
124
+ * `Option.none()` if the offset is outside the tree.
125
+ *
126
+ * @remarks
127
+ * Returns the path segments leading to the innermost node that spans the given
128
+ * offset. This is the inverse of {@link findNode} — given an offset you get the path,
129
+ * and given a path you get the node.
130
+ *
131
+ * @see {@link findNodeAtOffset} — returns the node itself rather than its path
132
+ * @see {@link (JsoncPath:type)} — the path segment array type
133
+ *
134
+ * @example Getting the path at an offset
135
+ * ```ts
136
+ * import { Effect, Option } from "effect";
137
+ * import type { JsoncNode, JsoncPath } from "jsonc-effect";
138
+ * import { parseTree, getNodePath } from "jsonc-effect";
139
+ *
140
+ * const program = Effect.gen(function* () {
141
+ * const root: Option.Option<JsoncNode> = yield* parseTree('{ "a": { "b": 42 } }');
142
+ * if (Option.isNone(root)) return Option.none();
143
+ * // Offset 15 points inside the value 42
144
+ * return yield* getNodePath(root.value, 15);
145
+ * });
146
+ *
147
+ * const result: Option.Option<JsoncPath> = Effect.runSync(program);
148
+ * // result is Option.some(["a", "b"])
149
+ * ```
150
+ *
151
+ * @public
152
+ */
153
+ const getNodePath = Function.dual(2, (root, targetOffset) => Effect.sync(() => buildPath(root, targetOffset, [])));
154
+ /**
155
+ * Reconstruct a plain JavaScript value from an AST subtree.
156
+ *
157
+ * @param node - The AST node to evaluate, typically obtained via {@link findNode} or
158
+ * as the root from {@link parseTree}.
159
+ *
160
+ * @returns `Effect<unknown>` — the reconstructed JS value (object, array, string,
161
+ * number, boolean, or null).
162
+ *
163
+ * @remarks
164
+ * Recursively evaluates the node tree to produce a plain JavaScript value.
165
+ * This is the inverse of {@link parseTree}: where `parseTree` turns a JSONC string
166
+ * into an AST, `getNodeValue` turns an AST subtree back into a JS value.
167
+ *
168
+ * @see {@link parseTree} — produces the AST that this function evaluates
169
+ * @see {@link findNode} — locates a subtree to pass to this function
170
+ *
171
+ * @example Extracting the value of a found node
172
+ * ```ts
173
+ * import { Effect, Option } from "effect";
174
+ * import type { JsoncNode } from "jsonc-effect";
175
+ * import { parseTree, findNode, getNodeValue } from "jsonc-effect";
176
+ *
177
+ * const program = Effect.gen(function* () {
178
+ * const root: Option.Option<JsoncNode> = yield* parseTree('{ "items": [1, 2, 3] }');
179
+ * if (Option.isNone(root)) return undefined;
180
+ * const node: Option.Option<JsoncNode> = yield* findNode(root.value, ["items"]);
181
+ * if (Option.isNone(node)) return undefined;
182
+ * return yield* getNodeValue(node.value);
183
+ * });
184
+ *
185
+ * const result = Effect.runSync(program);
186
+ * // result is [1, 2, 3]
187
+ * ```
188
+ *
189
+ * @privateRemarks
190
+ * Useful after {@link findNode} to extract a subtree's value without manual
191
+ * AST traversal.
192
+ *
193
+ * @public
194
+ */
195
+ const getNodeValue = (node) => Effect.sync(() => evaluateNode(node));
196
+ function findNodeImpl(root, path) {
197
+ let current = root;
198
+ for (const segment of path) {
199
+ if (!current?.children) return Option.none();
200
+ if (typeof segment === "string") {
201
+ if (current.type !== "object") return Option.none();
202
+ current = current.children.find((child) => child.type === "property" && child.children !== void 0 && child.children[0]?.value === segment)?.children?.[1];
203
+ } else {
204
+ if (current.type !== "array") return Option.none();
205
+ current = current.children[segment];
206
+ }
207
+ }
208
+ return current ? Option.some(current) : Option.none();
209
+ }
210
+ function findNodeAtOffsetImpl(root, offset) {
211
+ if (offset < root.offset || offset >= root.offset + root.length) return Option.none();
212
+ if (!root.children) return Option.some(root);
213
+ for (const child of root.children) if (offset >= child.offset && offset < child.offset + child.length) return findNodeAtOffsetImpl(child, offset);
214
+ return Option.some(root);
215
+ }
216
+ function buildPath(node, targetOffset, currentPath) {
217
+ if (targetOffset < node.offset || targetOffset >= node.offset + node.length) return Option.none();
218
+ if (!node.children) return Option.some(currentPath);
219
+ if (node.type === "object") {
220
+ for (const prop of node.children) if (prop.type === "property" && prop.children !== void 0 && targetOffset >= prop.offset && targetOffset < prop.offset + prop.length) {
221
+ const key = prop.children[0]?.value;
222
+ const valuePath = [...currentPath, key];
223
+ const valueChild = prop.children[1];
224
+ if (valueChild && targetOffset >= valueChild.offset && targetOffset < valueChild.offset + valueChild.length) return buildPath(valueChild, targetOffset, valuePath);
225
+ return Option.some(valuePath);
226
+ }
227
+ } else if (node.type === "array") for (let i = 0; i < node.children.length; i++) {
228
+ const child = node.children[i];
229
+ if (targetOffset >= child.offset && targetOffset < child.offset + child.length) return buildPath(child, targetOffset, [...currentPath, i]);
230
+ }
231
+ return Option.some(currentPath);
232
+ }
233
+ function evaluateNode(node) {
234
+ switch (node.type) {
235
+ case "object": {
236
+ const obj = {};
237
+ if (node.children) {
238
+ for (const prop of node.children) if (prop.type === "property" && prop.children !== void 0 && prop.children.length === 2) {
239
+ const key = prop.children[0].value;
240
+ obj[key] = evaluateNode(prop.children[1]);
241
+ }
242
+ }
243
+ return obj;
244
+ }
245
+ case "array": return (node.children ?? []).map(evaluateNode);
246
+ case "property": return node.children?.[1] ? evaluateNode(node.children[1]) : void 0;
247
+ case "string":
248
+ case "number":
249
+ case "boolean":
250
+ case "null": return node.value;
251
+ }
252
+ }
253
+
254
+ //#endregion
255
+ export { findNode, findNodeAtOffset, getNodePath, getNodeValue };
package/equality.js ADDED
@@ -0,0 +1,166 @@
1
+ import { parse } from "./parse.js";
2
+ import { Effect, Function } from "effect";
3
+
4
+ //#region src/equality.ts
5
+ /**
6
+ * JSONC equality comparisons — semantic equivalence for JSONC documents.
7
+ *
8
+ * Compares parsed values ignoring comments, whitespace, formatting,
9
+ * and object key ordering.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ /**
14
+ * Deep-compare two plain JS values for structural equality (key-order independent for objects, order sensitive for arrays).
15
+ *
16
+ * @internal
17
+ */
18
+ function deepEqual(a, b) {
19
+ if (a === b) return true;
20
+ if (a === null || b === null) return false;
21
+ if (typeof a !== typeof b) return false;
22
+ if (Array.isArray(a)) {
23
+ if (!Array.isArray(b)) return false;
24
+ if (a.length !== b.length) return false;
25
+ for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
26
+ return true;
27
+ }
28
+ if (Array.isArray(b)) return false;
29
+ if (typeof a === "object" && typeof b === "object") {
30
+ const aObj = a;
31
+ const bObj = b;
32
+ const aKeys = Object.keys(aObj);
33
+ const bKeys = Object.keys(bObj);
34
+ if (aKeys.length !== bKeys.length) return false;
35
+ for (const key of aKeys) {
36
+ if (!Object.hasOwn(bObj, key)) return false;
37
+ if (!deepEqual(aObj[key], bObj[key])) return false;
38
+ }
39
+ return true;
40
+ }
41
+ return false;
42
+ }
43
+ /**
44
+ * Compare two JSONC strings for semantic equality.
45
+ *
46
+ * Supports {@link https://effect.website/docs/effect/function#dual | Function.dual}
47
+ * for both data-first and data-last (pipeline) usage.
48
+ *
49
+ * @param self - The first JSONC string.
50
+ * @param that - The second JSONC string.
51
+ *
52
+ * @returns `Effect<boolean, JsoncParseError>` — `true` when both strings parse to
53
+ * semantically equivalent values, `false` otherwise. Fails with
54
+ * {@link JsoncParseError} if either string is malformed.
55
+ *
56
+ * @remarks
57
+ * Both strings are parsed via {@link parse} and then deep-compared. The comparison
58
+ * ignores comments, whitespace, formatting, and object key ordering. Array order
59
+ * IS significant. Uses `Effect.all` internally, so the effect fails on the first
60
+ * parse error encountered.
61
+ *
62
+ * @see {@link equalsValue} — compare a JSONC string against an existing JS value
63
+ * @see {@link parse} — the underlying parser used for both strings
64
+ *
65
+ * @example Data-first comparison
66
+ * ```ts
67
+ * import { Effect } from "effect";
68
+ * import { equals } from "jsonc-effect";
69
+ *
70
+ * const result = Effect.runSync(
71
+ * equals('{ "a": 1, "b": 2 }', '{"b":2,"a":1}')
72
+ * );
73
+ * // result is true
74
+ * ```
75
+ *
76
+ * @example Key-order independence
77
+ * ```ts
78
+ * import { Effect } from "effect";
79
+ * import { equals } from "jsonc-effect";
80
+ *
81
+ * // Object key order does not matter
82
+ * const sameKeys = Effect.runSync(
83
+ * equals('{"z":1,"a":2}', '{"a":2,"z":1}')
84
+ * );
85
+ * // sameKeys is true
86
+ *
87
+ * // Array order DOES matter
88
+ * const differentOrder = Effect.runSync(
89
+ * equals('[1, 2]', '[2, 1]')
90
+ * );
91
+ * // differentOrder is false
92
+ * ```
93
+ *
94
+ * @example Error handling
95
+ * ```ts
96
+ * import { Effect, Either } from "effect";
97
+ * import type { JsoncParseError } from "jsonc-effect";
98
+ * import { equals } from "jsonc-effect";
99
+ *
100
+ * const result: Either.Either<boolean, JsoncParseError> = Effect.runSync(
101
+ * Effect.either(equals('{ invalid }', '{}'))
102
+ * );
103
+ * // result is Either.left(JsoncParseError)
104
+ * ```
105
+ *
106
+ * @privateRemarks
107
+ * Uses a simple recursive `deepEqual` helper rather than Effect's `Equal` module
108
+ * because the parsed values are plain JS objects and arrays, not Effect data types.
109
+ *
110
+ * @public
111
+ */
112
+ const equals = Function.dual(2, (self, that) => Effect.map(Effect.all([parse(self), parse(that)]), ([a, b]) => deepEqual(a, b)));
113
+ /**
114
+ * Compare a JSONC string against a JavaScript value for semantic equality.
115
+ *
116
+ * Supports {@link https://effect.website/docs/effect/function#dual | Function.dual}
117
+ * for both data-first and data-last (pipeline) usage.
118
+ *
119
+ * @param self - The JSONC string to parse.
120
+ * @param value - The JavaScript value to compare against.
121
+ *
122
+ * @returns `Effect<boolean, JsoncParseError>` — `true` when the parsed JSONC
123
+ * is semantically equivalent to the provided value, `false` otherwise.
124
+ * Fails with {@link JsoncParseError} if the string is malformed.
125
+ *
126
+ * @remarks
127
+ * Only the JSONC string is parsed; the JS value is used as-is. This makes
128
+ * `equalsValue` useful for assertions and testing where the expected value
129
+ * is already a JS object. The comparison semantics are the same as
130
+ * {@link equals}: comments, whitespace, formatting, and object key ordering
131
+ * are ignored, while array order IS significant.
132
+ *
133
+ * @see {@link equals} — compare two JSONC strings against each other
134
+ * @see {@link parse} — the underlying parser
135
+ *
136
+ * @example Basic comparison
137
+ * ```ts
138
+ * import { Effect } from "effect";
139
+ * import { equalsValue } from "jsonc-effect";
140
+ *
141
+ * const result = Effect.runSync(
142
+ * equalsValue('{"port": 3000, "host": "localhost"}', { host: "localhost", port: 3000 })
143
+ * );
144
+ * // result is true
145
+ * ```
146
+ *
147
+ * @example Pipeline usage for testing
148
+ * ```ts
149
+ * import { Effect, pipe } from "effect";
150
+ * import { equalsValue } from "jsonc-effect";
151
+ *
152
+ * const jsonc = '{ "enabled": true, "count": 5 }';
153
+ * const expected = { enabled: true, count: 5 };
154
+ *
155
+ * const result = Effect.runSync(
156
+ * pipe(jsonc, equalsValue(expected))
157
+ * );
158
+ * // result is true
159
+ * ```
160
+ *
161
+ * @public
162
+ */
163
+ const equalsValue = Function.dual(2, (self, value) => Effect.map(parse(self), (parsed) => deepEqual(parsed, value)));
164
+
165
+ //#endregion
166
+ export { equals, equalsValue };
package/errors.js ADDED
@@ -0,0 +1,218 @@
1
+ import { Data, Schema } from "effect";
2
+
3
+ //#region src/errors.ts
4
+ /**
5
+ * JSONC error types using Effect's Data.TaggedError pattern.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ /**
10
+ * Error codes representing specific JSONC parse failures.
11
+ *
12
+ * @remarks
13
+ * Each code maps to a distinct syntactic error the parser can encounter,
14
+ * from invalid symbols and number formats to missing delimiters and
15
+ * unexpected end-of-input conditions.
16
+ *
17
+ * @see {@link JsoncParseErrorDetail} — carries one of these codes alongside
18
+ * position information
19
+ *
20
+ * @public
21
+ */
22
+ const JsoncParseErrorCode = Schema.Literal("InvalidSymbol", "InvalidNumberFormat", "PropertyNameExpected", "ValueExpected", "ColonExpected", "CommaExpected", "CloseBraceExpected", "CloseBracketExpected", "EndOfFileExpected", "InvalidCommentToken", "UnexpectedEndOfComment", "UnexpectedEndOfString", "UnexpectedEndOfNumber", "InvalidUnicode", "InvalidEscapeCharacter", "InvalidCharacter");
23
+ /**
24
+ * Detail for a single parse error, including the error code, a human-readable
25
+ * message, and the exact position within the source document.
26
+ *
27
+ * @remarks
28
+ * - `code` — a {@link (JsoncParseErrorCode:type)} identifying the error kind.
29
+ * - `message` — a descriptive message suitable for display.
30
+ * - `offset` — zero-based character offset where the error occurred.
31
+ * - `length` — character length of the problematic span.
32
+ * - `startLine` — zero-based line number of the error.
33
+ * - `startCharacter` — zero-based column within `startLine`.
34
+ *
35
+ * @see {@link JsoncParseError} — aggregates an array of these details
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * import { JsoncParseErrorDetail } from "jsonc-effect";
40
+ *
41
+ * const detail = new JsoncParseErrorDetail({
42
+ * code: "ValueExpected",
43
+ * message: "Value expected",
44
+ * offset: 5,
45
+ * length: 1,
46
+ * startLine: 0,
47
+ * startCharacter: 5,
48
+ * });
49
+ *
50
+ * console.log(detail.code); // "ValueExpected"
51
+ * console.log(detail.offset); // 5
52
+ * ```
53
+ *
54
+ * @public
55
+ */
56
+ var JsoncParseErrorDetail = class extends Schema.Class("JsoncParseErrorDetail")({
57
+ code: JsoncParseErrorCode,
58
+ message: Schema.String,
59
+ offset: Schema.Number,
60
+ length: Schema.Number,
61
+ startLine: Schema.Number,
62
+ startCharacter: Schema.Number
63
+ }) {};
64
+ /**
65
+ * Base class for {@link JsoncParseError}; not intended to be constructed or
66
+ * caught directly — use `JsoncParseError` instead.
67
+ *
68
+ * @privateRemarks
69
+ * The `*Base` pattern is required because `Data.TaggedError` produces complex
70
+ * type signatures involving intersection types and branded generics that
71
+ * api-extractor cannot roll up into a single `.d.ts` bundle. Exporting the
72
+ * base separately lets the public `JsoncParseError` class extend it with
73
+ * concrete fields, giving api-extractor a simple class declaration to work
74
+ * with. It is tagged `@public` (rather than `@internal`) because it appears
75
+ * in `JsoncParseError`'s heritage clause in the public `.d.ts`, and API
76
+ * Extractor requires release tags to be compatible across a signature.
77
+ *
78
+ * @public
79
+ */
80
+ const JsoncParseErrorBase = Data.TaggedError("JsoncParseError");
81
+ /**
82
+ * Error raised when JSONC parsing encounters one or more syntax errors.
83
+ *
84
+ * @remarks
85
+ * Contains the full source `text`, the `options` used for parsing, and an
86
+ * `errors` array of {@link JsoncParseErrorDetail} instances with precise
87
+ * position information for each problem found.
88
+ *
89
+ * @see {@link parse} — may fail with this error
90
+ * @see {@link parseTree} — may fail with this error
91
+ *
92
+ * @example Catching with `Effect.catchTag`
93
+ * ```ts
94
+ * import { Effect } from "effect";
95
+ * import { parse } from "jsonc-effect";
96
+ *
97
+ * const program = parse("{ invalid }").pipe(
98
+ * Effect.catchTag("JsoncParseError", (e) => {
99
+ * console.error(e.errors); // Array of JsoncParseErrorDetail
100
+ * return Effect.succeed({});
101
+ * }),
102
+ * );
103
+ * ```
104
+ *
105
+ * @example Inspecting error details
106
+ * ```ts
107
+ * import { Effect } from "effect";
108
+ * import { parse } from "jsonc-effect";
109
+ *
110
+ * const program = parse("{ invalid }").pipe(
111
+ * Effect.catchTag("JsoncParseError", (e) => {
112
+ * for (const detail of e.errors) {
113
+ * console.error(
114
+ * `[${detail.code}] ${detail.message} at line ${detail.startLine}:${detail.startCharacter}`,
115
+ * );
116
+ * }
117
+ * return Effect.succeed({});
118
+ * }),
119
+ * );
120
+ * ```
121
+ *
122
+ * @public
123
+ */
124
+ var JsoncParseError = class extends JsoncParseErrorBase {
125
+ get message() {
126
+ const count = this.errors.length;
127
+ return `JSONC parse failed with ${count} error${count !== 1 ? "s" : ""}: ${this.errors.map((e) => e.message).join("; ")}`;
128
+ }
129
+ };
130
+ /**
131
+ * Base class for {@link JsoncNodeNotFoundError}; not intended to be
132
+ * constructed or caught directly — use `JsoncNodeNotFoundError` instead.
133
+ *
134
+ * @privateRemarks
135
+ * Uses the same `*Base` pattern as {@link JsoncParseErrorBase} to work
136
+ * around api-extractor's inability to roll up the complex type produced
137
+ * by `Data.TaggedError` into a single `.d.ts` declaration. Tagged `@public`
138
+ * for the same heritage-clause-compatibility reason as `JsoncParseErrorBase`.
139
+ *
140
+ * @public
141
+ */
142
+ const JsoncNodeNotFoundErrorBase = Data.TaggedError("JsoncNodeNotFoundError");
143
+ /**
144
+ * Error raised when AST navigation fails to find a node at the given path.
145
+ *
146
+ * @remarks
147
+ * Contains the `path` that was searched and the `rootNodeType` of the tree
148
+ * that was traversed.
149
+ *
150
+ * @see {@link findNode} — may fail with this error
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * import { Effect } from "effect";
155
+ * import { parseTree, findNode } from "jsonc-effect";
156
+ *
157
+ * const program = parseTree('{ "a": 1 }').pipe(
158
+ * Effect.flatMap((root) => findNode(root, ["missing", "path"])),
159
+ * Effect.catchTag("JsoncNodeNotFoundError", (e) => {
160
+ * console.error(`Not found: [${e.path.join(", ")}] in ${e.rootNodeType}`);
161
+ * return Effect.succeed(undefined);
162
+ * }),
163
+ * );
164
+ * ```
165
+ *
166
+ * @public
167
+ */
168
+ var JsoncNodeNotFoundError = class extends JsoncNodeNotFoundErrorBase {
169
+ get message() {
170
+ return `Node not found at path [${this.path.join(", ")}] in ${this.rootNodeType} node`;
171
+ }
172
+ };
173
+ /**
174
+ * Base class for {@link JsoncModificationError}; not intended to be
175
+ * constructed or caught directly — use `JsoncModificationError` instead.
176
+ *
177
+ * @privateRemarks
178
+ * Uses the same `*Base` pattern as {@link JsoncParseErrorBase} to work
179
+ * around api-extractor's inability to roll up the complex type produced
180
+ * by `Data.TaggedError` into a single `.d.ts` declaration. Tagged `@public`
181
+ * for the same heritage-clause-compatibility reason as `JsoncParseErrorBase`.
182
+ *
183
+ * @public
184
+ */
185
+ const JsoncModificationErrorBase = Data.TaggedError("JsoncModificationError");
186
+ /**
187
+ * Error raised when {@link modify} produces invalid edits or encounters
188
+ * an unsupported modification scenario.
189
+ *
190
+ * @remarks
191
+ * Contains the `path` where modification was attempted and a `reason`
192
+ * string explaining why it failed.
193
+ *
194
+ * @see {@link modify} — may fail with this error
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * import { Effect } from "effect";
199
+ * import { modify } from "jsonc-effect";
200
+ *
201
+ * const program = modify("{}", ["deep", "path"], 42).pipe(
202
+ * Effect.catchTag("JsoncModificationError", (e) => {
203
+ * console.error(`Failed at [${e.path.join(", ")}]: ${e.reason}`);
204
+ * return Effect.succeed([]);
205
+ * }),
206
+ * );
207
+ * ```
208
+ *
209
+ * @public
210
+ */
211
+ var JsoncModificationError = class extends JsoncModificationErrorBase {
212
+ get message() {
213
+ return `Modification failed at path [${this.path.join(", ")}]: ${this.reason}`;
214
+ }
215
+ };
216
+
217
+ //#endregion
218
+ export { JsoncModificationError, JsoncModificationErrorBase, JsoncNodeNotFoundError, JsoncNodeNotFoundErrorBase, JsoncParseError, JsoncParseErrorBase, JsoncParseErrorCode, JsoncParseErrorDetail };