@jarenjs/json 0.9.2 → 0.34.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.
Files changed (74) hide show
  1. package/ARCHITECTURE.md +86 -13
  2. package/README.md +248 -23
  3. package/dist/types/canonical.d.ts +37 -0
  4. package/dist/types/cow.d.ts +28 -0
  5. package/dist/types/errors.d.ts +45 -0
  6. package/dist/types/index.d.ts +3 -0
  7. package/dist/types/jslt/errors.d.ts +15 -8
  8. package/dist/types/jslt/index.d.ts +22 -0
  9. package/dist/types/jslt/packs/finance.d.ts +119 -0
  10. package/dist/types/jslt/packs/index.d.ts +310 -0
  11. package/dist/types/jslt/packs/math.d.ts +159 -0
  12. package/dist/types/jslt/packs/stats.d.ts +48 -0
  13. package/dist/types/jslt/registry.d.ts +65 -0
  14. package/dist/types/jtlt/errors.d.ts +3 -6
  15. package/dist/types/option-variants.d.ts +29 -0
  16. package/dist/types/patch.d.ts +214 -0
  17. package/dist/types/path.d.ts +139 -9
  18. package/dist/types/pointer.d.ts +100 -9
  19. package/dist/types/query/compile.d.ts +12 -0
  20. package/dist/types/query/errors.d.ts +72 -8
  21. package/dist/types/query/index.d.ts +317 -25
  22. package/dist/types/query/normalize.d.ts +24 -0
  23. package/dist/types/query/operators.d.ts +241 -1
  24. package/dist/types/query/runtime.d.ts +5 -8
  25. package/dist/types/query/types.d.ts +34 -0
  26. package/dist/types/segments.d.ts +31 -0
  27. package/dist/types/write.d.ts +204 -0
  28. package/dist/types/xquery/parse.d.ts +2 -3
  29. package/docs/JSLT-FORMAT.md +74 -3
  30. package/docs/JSLT-PRELUDE.md +1 -1
  31. package/docs/QUERY-FORMAT.md +695 -33
  32. package/package.json +18 -4
  33. package/schemas/geojson.draft-07.schema.json +323 -0
  34. package/schemas/geojson.jaren.schema.json +863 -0
  35. package/schemas/geojson.schema.json +172 -0
  36. package/schemas/jaren-jslt.authoring.schema.json +142 -0
  37. package/schemas/jaren-jslt.draft-07.schema.json +152 -11
  38. package/schemas/jaren-jslt.llm-profile.schema.json +782 -0
  39. package/schemas/jaren-jslt.schema.json +152 -11
  40. package/schemas/jaren-query.draft-07.schema.json +152 -11
  41. package/schemas/jaren-query.llm-profile.schema.json +619 -0
  42. package/schemas/jaren-query.schema.json +82 -15
  43. package/src/basic.js +1 -1
  44. package/src/canonical.js +170 -0
  45. package/src/cow.js +106 -0
  46. package/src/errors.js +68 -0
  47. package/src/index.js +3 -0
  48. package/src/jslt/dispatch.js +178 -28
  49. package/src/jslt/errors.js +19 -14
  50. package/src/jslt/index.js +37 -29
  51. package/src/jslt/packs/finance.js +49 -0
  52. package/src/jslt/packs/index.js +18 -0
  53. package/src/jslt/packs/math.js +46 -0
  54. package/src/jslt/packs/stats.js +65 -0
  55. package/src/jslt/registry.js +200 -0
  56. package/src/jslt/stylesheet.js +14 -23
  57. package/src/jtlt/desugar.js +2 -3
  58. package/src/jtlt/errors.js +6 -12
  59. package/src/jtlt/index.js +12 -29
  60. package/src/jtlt/template.js +9 -18
  61. package/src/option-variants.js +54 -0
  62. package/src/patch.js +1052 -0
  63. package/src/path.js +319 -52
  64. package/src/pointer.js +225 -44
  65. package/src/query/compile.js +790 -75
  66. package/src/query/errors.js +72 -12
  67. package/src/query/index.js +274 -42
  68. package/src/query/normalize.js +489 -78
  69. package/src/query/operators.js +620 -23
  70. package/src/query/runtime.js +5 -19
  71. package/src/query/types.js +213 -0
  72. package/src/segments.js +409 -64
  73. package/src/write.js +660 -0
  74. package/src/xquery/parse.js +37 -53
@@ -1,18 +1,15 @@
1
+ import { CodedDocPathError } from '../errors.js';
1
2
  /**
2
3
  * Error thrown when a JTLT template is rejected at compile time
3
4
  * (`TL0xxx` codes).
4
5
  */
5
- export declare class JtltCompileError extends Error {
6
- code: any;
7
- docPath: any;
6
+ export declare class JtltCompileError extends CodedDocPathError {
8
7
  constructor(code: any, message: any, docPath: any, cause?: undefined);
9
8
  }
10
9
  /**
11
10
  * Error thrown when rendering with a compiled JTLT template fails
12
11
  * (`TL2xxx` codes).
13
12
  */
14
- export declare class JtltRuntimeError extends Error {
15
- code: any;
16
- docPath: any;
13
+ export declare class JtltRuntimeError extends CodedDocPathError {
17
14
  constructor(code: any, message: any, docPath: any, cause?: undefined);
18
15
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @file Per-document compilation-variant caching, shared by the JSLT
3
+ * and JTLT one-call entry points. A document compiles differently under
4
+ * different options, so the cache key is the FULL option tuple — every
5
+ * option that changes what compiles must be part of the derived key, or
6
+ * a second call with different options silently reuses the first
7
+ * compilation (the cache-poisoning bug the JTLT cache had before the
8
+ * health pass). Identity-compared option values (hook functions,
9
+ * function-extension registries) are interned to stable per-process ids
10
+ * so the key is a flat string and lookup is O(1) instead of a linear
11
+ * scan over an unbounded variants array.
12
+ */
13
+ /**
14
+ * A stable per-process id for an identity-compared option value.
15
+ * `null`/`undefined` share id 0 ("absent").
16
+ * @param {any} value
17
+ * @returns {number}
18
+ */
19
+ export declare function identityOf(value: any): number;
20
+ /**
21
+ * A two-axis compilation cache: documents by identity (weak, entries
22
+ * die with the document), variants per document by derived option key
23
+ * (bounded LRU — the old variants array grew without bound).
24
+ * @param {number} [limitPerDocument] - variant bound per document
25
+ * @returns {{ getOrCompile: (document: object, key: string, compile: () => any) => any }}
26
+ */
27
+ export declare function createOptionVariantCache(limitPerDocument?: number): {
28
+ getOrCompile: (document: object, key: string, compile: () => any) => any;
29
+ };
@@ -0,0 +1,214 @@
1
+ import { CodedError } from '@jarenjs/core/errors';
2
+ import { CodedDocPathError } from './errors.js';
3
+ /**
4
+ * Error thrown when a JSON Patch document is rejected at compile time
5
+ * (`JP0xxx` codes). `docPath` is an RFC 6901 JSON Pointer into the
6
+ * patch document (e.g. `/2/from`). A wrapped pointer syntax error is
7
+ * exposed through `cause`.
8
+ */
9
+ export declare class JsonPatchCompileError extends CodedDocPathError {
10
+ constructor(code: any, message: any, docPath: any, cause?: undefined);
11
+ }
12
+ /**
13
+ * Error thrown when applying a compiled JSON Patch fails (`JP2xxx`
14
+ * codes). `docPath` points at the failing operation in the patch
15
+ * document; `dataPath` is the operation's target location in the
16
+ * document being patched — both render, separately identifiable, per
17
+ * the `@jarenjs/core` coded contract (`at` = document, `in data` =
18
+ * data).
19
+ */
20
+ export declare class JsonPatchRuntimeError extends CodedError {
21
+ constructor(code: any, reason: any, docPath: any, dataPath: any);
22
+ }
23
+ export type JsonPatchOperation = {
24
+ op: 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test';
25
+ path: string;
26
+ value?: any;
27
+ from?: string;
28
+ };
29
+ export type JsonPatchApplier = (doc: any) => any;
30
+ export type JsonPatchChangesApplier = (doc: any) => {
31
+ doc: any;
32
+ changes: string[];
33
+ };
34
+ export type JsonPatchOptions = {
35
+ /**
36
+ * - Apply in place instead of copy-on-write.
37
+ * Faster, but the input document is modified and a failing operation
38
+ * leaves it partially patched (application is no longer atomic).
39
+ */
40
+ mutate?: boolean;
41
+ /**
42
+ * - How operation values enter the
43
+ * result: `'share'` (default) inserts them by reference, so results of
44
+ * repeated applications share structure with the patch document and
45
+ * must be treated as immutable; `'fresh'` deep-copies per application.
46
+ * In-place mode always behaves as `'fresh'`.
47
+ */
48
+ values?: 'share' | 'fresh';
49
+ /**
50
+ * - Track changed locations: the applier
51
+ * returns `{ doc, changes }` where `changes` is an array of JSON
52
+ * Pointers, one per successful write, in application order and not
53
+ * deduplicated. The reported pointer is chosen to be *sound for
54
+ * invalidation* — everything at or below it (plus the identity of its
55
+ * ancestors) may have changed, and nothing outside the reported set
56
+ * did: object writes, array replaces and array appends report the
57
+ * written location itself; array inserts and removes that shift later
58
+ * elements report the parent array's pointer; a root write reports
59
+ * `''`. `test` operations report nothing.
60
+ */
61
+ changes?: boolean;
62
+ };
63
+ /**
64
+ * Compile a JSON Patch (RFC 6902) into a reusable applier.
65
+ *
66
+ * The patch document is validated once (`JsonPatchCompileError`,
67
+ * `JP0xxx`, with a `docPath` into the patch document); every pointer is
68
+ * pre-parsed and each operation becomes a specialized closure. Applying
69
+ * is copy-on-write: the input document is never mutated, untouched
70
+ * subtrees are shared with the result, and application is atomic - a
71
+ * failing operation (`JsonPatchRuntimeError`, `JP2xxx`) leaves nothing
72
+ * behind.
73
+ *
74
+ * With `changes: true` the applier is specialized at compile time to
75
+ * also report the changed locations: it returns `{ doc, changes }`,
76
+ * where `changes` holds one JSON Pointer per successful write with the
77
+ * invalidation-sound semantics documented on `JsonPatchOptions` — the
78
+ * primitive dirty-path consumers (view re-rendering, rule dependency
79
+ * memoization) build on.
80
+ *
81
+ * @param {JsonPatchOperation[]} patch - The RFC 6902 patch document
82
+ * @param {JsonPatchOptions} [options] - Application options
83
+ * @returns {JsonPatchApplier | JsonPatchChangesApplier} applier
84
+ * returning the patched document (or `{ doc, changes }` with the
85
+ * `changes` option)
86
+ * @throws {JsonPatchCompileError} When the patch document is invalid
87
+ * @example
88
+ * const apply = compileJSONPatch([
89
+ * { op: 'test', path: '/version', value: 5 },
90
+ * { op: 'replace', path: '/user/name', value: 'Bob' },
91
+ * { op: 'add', path: '/user/tags/-', value: 'admin' },
92
+ * ]);
93
+ * const next = apply(doc); // doc is untouched
94
+ * @example
95
+ * const applyTracked = compileJSONPatch(
96
+ * [{ op: 'replace', path: '/user/name', value: 'Bob' }],
97
+ * { changes: true });
98
+ * const { doc: next2, changes } = applyTracked(doc);
99
+ * // changes: ['/user/name']
100
+ */
101
+ export declare function compileJSONPatch(patch: JsonPatchOperation[], options?: JsonPatchOptions): JsonPatchApplier | JsonPatchChangesApplier;
102
+ /**
103
+ * Apply a JSON Patch (RFC 6902) to a document in one shot. Compiles the
104
+ * patch and applies it once; on hot paths prefer `compileJSONPatch` and
105
+ * reuse the applier.
106
+ *
107
+ * @param {any} doc - The document to patch (never mutated unless
108
+ * `options.mutate` is set)
109
+ * @param {JsonPatchOperation[]} patch - The RFC 6902 patch document
110
+ * @param {JsonPatchOptions} [options] - Application options
111
+ * @returns {any} The patched document, or `{ doc, changes }` when
112
+ * `options.changes` is set
113
+ * @throws {JsonPatchCompileError} When the patch document is invalid
114
+ * @throws {JsonPatchRuntimeError} When an operation fails to apply
115
+ */
116
+ export declare function applyJSONPatch(doc: any, patch: JsonPatchOperation[], options?: JsonPatchOptions): any;
117
+ /**
118
+ * Returns true when `patch` is a structurally valid RFC 6902 patch
119
+ * document (an array of well-formed operation objects with valid
120
+ * pointers). Runtime applicability against a document is not checked.
121
+ * @param {any} patch - The candidate patch document
122
+ * @returns {boolean}
123
+ */
124
+ export declare function isValidJSONPatch(patch: any): boolean;
125
+ /**
126
+ * Compute a JSON Patch (RFC 6902) that transforms `source` into
127
+ * `target`: `applyJSONPatch(source, createJSONPatch(source, target))`
128
+ * is deep-equal to `target`.
129
+ *
130
+ * Objects diff member-wise; arrays trim the common prefix/suffix and
131
+ * diff the middle index-wise, so in-place edits and head/tail
132
+ * insertions produce minimal patches while a mid-array insertion falls
133
+ * back to correct (but larger) per-index replaces. Emitted `value`
134
+ * members share references with `target`.
135
+ *
136
+ * `arrayDiff: 'minimal'` aligns the changed middle instead, so a
137
+ * mid-array insertion or deletion is emitted as one `add`/`remove` —
138
+ * the smallest patch, which is what matters when patches go over the
139
+ * wire. The alignment minimizes the patch itself (edit distance with
140
+ * substitutions, not a longest common subsequence: LCS maximizes kept
141
+ * elements, which costs a delete plus an insert on a permutation where
142
+ * one rewrite would do), so it never takes more alignment steps than
143
+ * the index-wise pairing.
144
+ *
145
+ * It is opt-in because it costs O(m*n) time and space in the length of
146
+ * that middle, against the default's linear pass; above a fixed cell
147
+ * budget a single array falls back to the index-wise diff, so the mode
148
+ * never turns a large diff quadratic. Both modes produce patches that
149
+ * reproduce `target` exactly.
150
+ *
151
+ * @param {any} source - The original document
152
+ * @param {any} target - The desired document
153
+ * @param {{ arrayDiff?: 'index' | 'minimal' }} [options] - `arrayDiff`
154
+ * selects the array strategy: `'index'` (default) or `'minimal'`
155
+ * @returns {JsonPatchOperation[]} The patch document (empty when equal)
156
+ * @throws {TypeError} When `arrayDiff` is not a known mode
157
+ * @example
158
+ * createJSONPatch({ a: 1, b: 2 }, { a: 1, b: 3, c: 4 });
159
+ * // [{ op: 'replace', path: '/b', value: 3 },
160
+ * // { op: 'add', path: '/c', value: 4 }]
161
+ * @example
162
+ * const before = [{ id: 1 }, { id: 2 }, { id: 3, n: 0 }];
163
+ * const after = [{ id: 1 }, { id: 9 }, { id: 2 }, { id: 3, n: 1 }];
164
+ * createJSONPatch(before, after, { arrayDiff: 'minimal' });
165
+ * // [{ op: 'add', path: '/1', value: { id: 9 } },
166
+ * // { op: 'replace', path: '/3/n', value: 1 }]
167
+ */
168
+ export declare function createJSONPatch(source: any, target: any, options?: {
169
+ arrayDiff?: 'index' | 'minimal';
170
+ }): JsonPatchOperation[];
171
+ export type JsonMergePatchApplier = (doc: any) => any;
172
+ /**
173
+ * Compile a JSON Merge Patch (RFC 7396) into a reusable applier.
174
+ *
175
+ * The patch pre-splits once into remove/set/merge plans per level.
176
+ * Applying is copy-on-write and identity-preserving: unchanged subtrees
177
+ * (and a wholly unchanged document) come back by reference, set values
178
+ * are shared with the patch document, and the input is never mutated.
179
+ *
180
+ * @param {any} patch - The merge patch (any JSON value; a non-object
181
+ * replaces the document wholesale)
182
+ * @returns {JsonMergePatchApplier} applier returning the patched document
183
+ * @example
184
+ * const apply = compileMergePatch({ age: 31, temp: null });
185
+ * apply({ name: 'Alice', age: 30, temp: 'x' });
186
+ * // { name: 'Alice', age: 31 }
187
+ */
188
+ export declare function compileMergePatch(patch: any): JsonMergePatchApplier;
189
+ /**
190
+ * Apply a JSON Merge Patch (RFC 7396) to a document in one shot. On hot
191
+ * paths prefer `compileMergePatch` and reuse the applier.
192
+ *
193
+ * @param {any} doc - The document to patch (never mutated)
194
+ * @param {any} patch - The merge patch
195
+ * @returns {any} The patched document
196
+ */
197
+ export declare function applyMergePatch(doc: any, patch: any): any;
198
+ /**
199
+ * Compute a JSON Merge Patch (RFC 7396) that transforms `source` into
200
+ * `target`. Removed members become `null`, nested objects diff
201
+ * recursively, and arrays (or any kind change) replace wholesale.
202
+ *
203
+ * RFC 7396 cannot represent a member whose target value is `null`:
204
+ * the diff emits `null` (a removal), so applying yields an absent
205
+ * member instead. Emitted values share references with `target`.
206
+ *
207
+ * @param {any} source - The original document
208
+ * @param {any} target - The desired document
209
+ * @returns {any} The merge patch (`{}` when nothing changed)
210
+ * @example
211
+ * createMergePatch({ a: 'b', c: 1 }, { a: 'x' });
212
+ * // { a: 'x', c: null }
213
+ */
214
+ export declare function createMergePatch(source: any, target: any): any;
@@ -1,3 +1,4 @@
1
+ import { LabeledSyntaxError } from './errors.js';
1
2
  /**
2
3
  * Sentinel for the absence of a value ("Nothing" in RFC 9535 terms), as
3
4
  * distinct from the JSON value `null`.
@@ -7,9 +8,7 @@ export declare const JSONPATH_NOTHING: symbol;
7
8
  * Error thrown when a JSONPath query is not valid RFC 9535 syntax
8
9
  * (including queries that are not well-typed per section 2.4.3).
9
10
  */
10
- export declare class JSONPathSyntaxError extends SyntaxError {
11
- source: any;
12
- position: any;
11
+ export declare class JSONPathSyntaxError extends LabeledSyntaxError {
13
12
  constructor(message: any, source: any, position: any);
14
13
  }
15
14
  export type JSONPathNameSelector = {
@@ -101,11 +100,34 @@ export type JSONPathQuery = ((data: any) => any[]) & {
101
100
  values: (data: any) => any[];
102
101
  first: (data: any) => any;
103
102
  exists: (data: any) => boolean;
103
+ iterate: (data: any) => Generator<any>;
104
104
  nodes: (data: any) => JSONPathNode[];
105
105
  paths: (data: any) => string[];
106
106
  source: string;
107
107
  ast: JSONPathAst;
108
108
  };
109
+ export type JSONPathFunction = {
110
+ /**
111
+ * - Declared parameter types
112
+ */
113
+ params: ('value' | 'nodes' | 'logical')[];
114
+ /**
115
+ * - Declared result type
116
+ */
117
+ returns: 'value' | 'nodes' | 'logical';
118
+ /**
119
+ * - The implementation
120
+ */
121
+ evaluate: (...args: any[]) => any;
122
+ };
123
+ export type JSONPathOptions = {
124
+ /**
125
+ * - Custom
126
+ * function extensions, by name. A name must match the RFC's
127
+ * `function-name` production and must not redefine a built-in.
128
+ */
129
+ pathFunctions?: Record<string, JSONPathFunction>;
130
+ };
109
131
  /**
110
132
  * Selects a named object member (RFC 9535 name selector).
111
133
  * @typedef {Object} JSONPathNameSelector
@@ -173,19 +195,44 @@ export type JSONPathQuery = ((data: any) => any[]) & {
173
195
  * values: (data: any) => any[],
174
196
  * first: (data: any) => any,
175
197
  * exists: (data: any) => boolean,
198
+ * iterate: (data: any) => Generator<any>,
176
199
  * nodes: (data: any) => JSONPathNode[],
177
200
  * paths: (data: any) => string[],
178
201
  * source: string,
179
202
  * ast: JSONPathAst,
180
203
  * }} JSONPathQuery
181
204
  */
205
+ /**
206
+ * A custom JSONPath function extension (RFC 9535 section 2.4). The
207
+ * declared types are what the parser type-checks call sites against
208
+ * (section 2.4.3), exactly as it does for the five built-ins.
209
+ *
210
+ * `evaluate` receives one argument per declared parameter: a `value`
211
+ * parameter arrives as a JSON value or `JSONPATH_NOTHING`, a `nodes`
212
+ * parameter as an array of the selected values, a `logical` parameter
213
+ * as a boolean. Its result must match `returns` - a `value` function
214
+ * may return `undefined` to mean Nothing.
215
+ * @typedef {Object} JSONPathFunction
216
+ * @property {('value'|'nodes'|'logical')[]} params - Declared parameter types
217
+ * @property {'value'|'nodes'|'logical'} returns - Declared result type
218
+ * @property {(...args: any[]) => any} evaluate - The implementation
219
+ */
220
+ /**
221
+ * Options accepted by the JSONPath entry points.
222
+ * @typedef {Object} JSONPathOptions
223
+ * @property {Record<string, JSONPathFunction>} [pathFunctions] - Custom
224
+ * function extensions, by name. A name must match the RFC's
225
+ * `function-name` production and must not redefine a built-in.
226
+ */
182
227
  /**
183
228
  * Parse a JSONPath query string into an AST.
184
229
  * @param {string} source - The JSONPath expression (e.g. `$.store.book[?@.price < 10].title`)
230
+ * @param {JSONPathOptions} [options] - Parse options
185
231
  * @returns {JSONPathAst} The parsed query AST
186
232
  * @throws {JSONPathSyntaxError} When the query violates the RFC 9535 grammar
233
+ * @throws {TypeError} When `options.pathFunctions` is not a valid registry
187
234
  */
188
- export declare function parseJSONPath(source: string): JSONPathAst;
235
+ export declare function parseJSONPath(source: string, options?: JSONPathOptions): JSONPathAst;
189
236
  /**
190
237
  * Compile a JSONPath query (RFC 9535) into a reusable query function.
191
238
  *
@@ -196,6 +243,9 @@ export declare function parseJSONPath(source: string): JSONPathAst;
196
243
  * - `query(data)` / `query.values(data)` - array of matched values
197
244
  * - `query.first(data)` - first matched value, or `undefined`
198
245
  * - `query.exists(data)` - true when the query selects at least one node
246
+ * - `query.iterate(data)` - a generator yielding matched values on
247
+ * demand, in document order; `first`/`exists` are one pull of it, so
248
+ * none of the three builds a nodelist it does not need
199
249
  * - `query.nodes(data)` - array of `{ path, value }` with normalized paths
200
250
  * - `query.paths(data)` - array of normalized paths (RFC 9535 section 2.7)
201
251
  * - `query.source` - the original query string
@@ -203,33 +253,113 @@ export declare function parseJSONPath(source: string): JSONPathAst;
203
253
  * compiled path mode must agree with the eagerly compiled value mode)
204
254
  *
205
255
  * @param {string} source - The JSONPath expression
256
+ * @param {JSONPathOptions} [options] - Compile options
206
257
  * @returns {JSONPathQuery} The compiled query function
207
258
  * @throws {JSONPathSyntaxError} When the query is not valid RFC 9535
259
+ * @throws {TypeError} When `options.pathFunctions` is not a valid registry
208
260
  * @example
209
261
  * const q = compileJSONPath('$.store.book[?@.price < 10].title');
210
262
  * q(data); // ['Sayings of the Century', 'Moby Dick']
211
263
  * q.paths(data); // ["$['store']['book'][0]['title']", ...]
212
264
  */
213
- export declare function compileJSONPath(source: string): JSONPathQuery;
265
+ export declare function compileJSONPath(source: string, options?: JSONPathOptions): JSONPathQuery;
214
266
  /**
215
267
  * Apply a JSONPath query to a JSON value in one call. Compiled queries
216
- * are cached (FIFO, 512 entries), so repeated calls with the same query
217
- * string reuse the compiled function.
268
+ * are cached (bounded LRU, 512 entries), so repeated calls with the
269
+ * same query string reuse the compiled function. A query compiled
270
+ * against a function-extension registry is cached under that registry.
218
271
  * @param {string} source - The JSONPath expression
219
272
  * @param {any} data - The JSON value to query
273
+ * @param {JSONPathOptions} [options] - Compile options
220
274
  * @returns {any[]} Array of matched values
221
275
  * @throws {JSONPathSyntaxError} When the query is not valid RFC 9535
222
276
  */
223
- export declare function queryJSONPath(source: string, data: any): any[];
277
+ export declare function queryJSONPath(source: string, data: any, options?: JSONPathOptions): any[];
224
278
  /**
225
279
  * Validates a JSONPath expression strictly against the RFC 9535 grammar,
226
280
  * including well-typedness of function expressions. Unlike the heuristic
227
281
  * `isValidJSONPath` in basic.js, this uses the full parser.
282
+ *
283
+ * Without options this recognizes the five built-in functions and
284
+ * nothing else, which is what the registered `json-path` string format
285
+ * asserts: a format is a property of the string itself, so it must mean
286
+ * the same thing in every schema, independent of which extensions some
287
+ * host happens to have installed. Pass `options.pathFunctions` to
288
+ * validate against a registry instead - a host that wants its
289
+ * extensions asserted registers a tester bound to them.
290
+ *
228
291
  * @param {string} str - The JSONPath expression to validate
292
+ * @param {JSONPathOptions} [options] - Compile options
229
293
  * @returns {boolean} True when the string is a valid RFC 9535 query
230
294
  * @example
231
295
  * isValidJSONPathStrict('$.store.book[?@.price < 10]'); // true
232
296
  * isValidJSONPathStrict('$.store.book[0 5]'); // false
233
297
  * isValidJSONPathStrict('@.name'); // false (queries start at $)
234
298
  */
235
- export declare function isValidJSONPathStrict(str: string): boolean;
299
+ export declare function isValidJSONPathStrict(str: string, options?: JSONPathOptions): boolean;
300
+ /**
301
+ * The head of a variable-rooted path string: `$` followed by a variable
302
+ * name. The Jaren query format writes a path relative to a bound
303
+ * variable as `$name` plus ordinary RFC 9535 segments, and the query
304
+ * normalizer splits on exactly this production - so schema-time
305
+ * validation and compile-time parsing agree on where the segments start.
306
+ */
307
+ export declare const RE_JSONPATH_VARIABLE_HEAD: RegExp;
308
+ /**
309
+ * Validates a variable-rooted path string: `$name`, optionally followed
310
+ * by RFC 9535 segments (`$book.price`, `$b[?@.isbn]`, `$item`).
311
+ *
312
+ * This is the counterpart of `isValidJSONPathStrict` for paths whose
313
+ * root is a bound variable rather than the document. Such a string is
314
+ * not a valid RFC 9535 query - the RFC's root identifier is `$` alone -
315
+ * so it can only be checked by recognizing the head and validating the
316
+ * tail as segments, which is what the query normalizer does before it
317
+ * raises `JQ0004`. Without this, a schema could only pattern-check the
318
+ * head and had to leave the segment grammar to the compiler.
319
+ *
320
+ * @param {string} str - The variable-rooted path string to validate
321
+ * @param {JSONPathOptions} [options] - Parse options
322
+ * @returns {boolean} True when the string is a well-formed variable-rooted path
323
+ * @example
324
+ * isValidJSONPathSegments('$book.price'); // true
325
+ * isValidJSONPathSegments('$book'); // true (no segments)
326
+ * isValidJSONPathSegments('$book.price['); // false (unterminated segment)
327
+ * isValidJSONPathSegments('$.price'); // false (no variable name; that is json-path)
328
+ */
329
+ export declare function isValidJSONPathSegments(str: string, options?: JSONPathOptions): boolean;
330
+ /**
331
+ * Convert a singular JSONPath query - which includes every RFC 9535
332
+ * normalized path - to an RFC 6901 JSON Pointer, so the two addressing
333
+ * standards compose.
334
+ *
335
+ * Any singular form is accepted (`$['store']['book'][0]`, `$.store.book[0]`);
336
+ * non-singular queries and negative (from-the-end) indexes are rejected,
337
+ * because a pointer cannot express them.
338
+ *
339
+ * @param {string} source - A singular JSONPath query
340
+ * @returns {string} The equivalent JSON Pointer
341
+ * @throws {JSONPathSyntaxError} When the query is invalid, not singular,
342
+ * or uses a negative index
343
+ * @example
344
+ * jsonPointerFromJSONPath("$['store']['book'][0]['a/b']"); // '/store/book/0/a~1b'
345
+ */
346
+ export declare function jsonPointerFromJSONPath(source: string): string;
347
+ /**
348
+ * Convert an RFC 6901 JSON Pointer to an RFC 9535 normalized path.
349
+ *
350
+ * A pointer token is one text with two readings (RFC 6901 lets `"2"`
351
+ * address both a `"2"` member and array element 2); a normalized path
352
+ * must pick one. Convention: a token that is a valid array index (digits,
353
+ * no leading zeros) becomes an index selector `[2]`, everything else a
354
+ * name selector `['name']`. A pointer addressing an object member that
355
+ * merely looks like an index is therefore converted to the index form -
356
+ * convert with the document in hand (e.g. via `query.paths`) when that
357
+ * distinction matters.
358
+ *
359
+ * @param {string} pointer - The JSON Pointer (e.g. `/store/book/0`)
360
+ * @returns {string} The normalized path (e.g. `$['store']['book'][0]`)
361
+ * @throws {JSONPointerSyntaxError} When the pointer is not valid RFC 6901
362
+ * @example
363
+ * jsonPathFromJSONPointer('/store/book/0'); // "$['store']['book'][0]"
364
+ */
365
+ export declare function jsonPathFromJSONPointer(pointer: string): string;
@@ -1,3 +1,4 @@
1
+ import { LabeledSyntaxError } from './errors.js';
1
2
  /**
2
3
  * Sentinel for the absence of a value, as distinct from the JSON value
3
4
  * `null`. This is the same sentinel as `JSONPATH_NOTHING` in path.js, so
@@ -8,9 +9,7 @@ export declare const JSONPOINTER_NOTHING: symbol;
8
9
  * Error thrown when a (relative) JSON Pointer is not valid RFC 6901 /
9
10
  * draft-luff-relative-json-pointer syntax.
10
11
  */
11
- export declare class JSONPointerSyntaxError extends SyntaxError {
12
- source: any;
13
- position: any;
12
+ export declare class JSONPointerSyntaxError extends LabeledSyntaxError {
14
13
  constructor(message: any, source: any, position: any);
15
14
  }
16
15
  /**
@@ -63,6 +62,62 @@ export type RelativeJsonPointerResolver = (dataRoot: any, dataPath: string) => a
63
62
  * @throws {JSONPointerSyntaxError} When the pointer violates the grammar
64
63
  */
65
64
  export declare function parseRelativeJSONPointer(pointer: string): RelativeJsonPointer;
65
+ /**
66
+ * Encode a single reference token for use inside an RFC 6901 JSON
67
+ * Pointer: `~` becomes `~0` and `/` becomes `~1` (RFC 6901 section 3).
68
+ * The write-side inverse of the parser's decode; the escape-free common
69
+ * case returns the input unchanged.
70
+ * @param {string|number} segment - The raw member name or array index
71
+ * @returns {string} The encoded reference token
72
+ * @example
73
+ * encodeJSONPointerSegment('a/b'); // 'a~1b'
74
+ */
75
+ export declare function encodeJSONPointerSegment(segment: string | number): string;
76
+ /**
77
+ * Decode one RFC 6901 reference token: `~1` becomes `/` and `~0` becomes
78
+ * `~`. The order is normative (RFC 6901 section 4) and not an accident of
79
+ * implementation — decoding `~0` first would turn `~01` into `~1` and then
80
+ * into `/`, so a member literally named `~1` would come back as `/`.
81
+ * The escape-free common case returns the input unchanged.
82
+ * @param {string} token - One encoded reference token (no `/` separators)
83
+ * @returns {string} The decoded member name
84
+ * @example
85
+ * decodeJSONPointerSegment('a~1b'); // 'a/b'
86
+ */
87
+ export declare function decodeJSONPointerSegment(token: string): string;
88
+ /**
89
+ * Format decoded reference tokens as an RFC 6901 JSON Pointer; the
90
+ * inverse of `parseJSONPointer`. An empty array formats as the empty
91
+ * (whole-document) pointer.
92
+ * @param {(string|number)[]} segments - Decoded reference tokens
93
+ * @returns {string} The JSON Pointer
94
+ * @example
95
+ * formatJSONPointer(['a/b', 0]); // '/a~1b/0'
96
+ */
97
+ export declare function formatJSONPointer(segments: (string | number)[]): string;
98
+ /**
99
+ * Parse a JSON Pointer into a path array, narrowing every canonical array
100
+ * index to a number and leaving all other tokens as decoded strings.
101
+ *
102
+ * RFC 6901 has no types: `/items/0/id` addresses element 0 of an array and
103
+ * member `"0"` of an object with the same token. This function resolves that
104
+ * lexically, not against a document — a token is a number when it is `0` or a
105
+ * digit sequence without leading zeros within the array-index range, which is
106
+ * the same rule the pointer compiler uses to pre-parse indexes. `"01"`,
107
+ * `"1e0"`, `"-"` and `"1abc"` therefore stay strings.
108
+ *
109
+ * The result is the `(string|number)[]` shape that error reporters and
110
+ * diffing tools use for data locations, so it pairs directly with a
111
+ * validation error's `instancePath`. `formatJSONPointer` is the inverse.
112
+ * @param {string} pointer - The JSON Pointer (e.g. `/store/book/0`)
113
+ * @returns {(string|number)[]} Decoded tokens with array indexes as numbers
114
+ * @throws {JSONPointerSyntaxError} When the pointer violates the grammar
115
+ * @example
116
+ * parseJSONPointerPath('/items/0/id'); // ['items', 0, 'id']
117
+ * parseJSONPointerPath('/items/01'); // ['items', '01']
118
+ * parseJSONPointerPath('/a~1b'); // ['a/b']
119
+ */
120
+ export declare function parseJSONPointerPath(pointer: string): (string | number)[];
66
121
  /**
67
122
  * Compile a JSON Pointer (RFC 6901) into a reusable getter.
68
123
  *
@@ -85,20 +140,51 @@ export declare function compileJSONPointer(pointer: string): JsonPointerGetter;
85
140
  *
86
141
  * The relative part (level count, `#` form, trailing segments) compiles
87
142
  * once; per call only `dataPath` - the current location in `dataRoot` as
88
- * an RFC 6901 pointer - varies. The `#` form resolves to the member name
89
- * or array index of the location **as a string** (`''` at the root),
90
- * matching the historical behavior relied on by the validator's `$data`
91
- * keyword.
143
+ * an RFC 6901 pointer - varies.
144
+ *
145
+ * The root has no name: `0#` there yields `JSONPOINTER_NOTHING`, not `''`,
146
+ * so it stays distinguishable from the member named `''` (`{"": 1}` at
147
+ * `/`), which is a name a document can genuinely have.
148
+ *
149
+ * ### The `#` form and `hashIndex`
150
+ *
151
+ * Relative JSON Pointer says `#` yields the member *name* for an object
152
+ * member and the *index* — a number — for an array element. Telling those
153
+ * apart requires looking at the container, so the two modes cost different
154
+ * things and you choose per compile:
155
+ *
156
+ * - `hashIndex: 'string'` (**default**) answers from `dataPath` alone and
157
+ * never touches `dataRoot`: an array position comes back as the string
158
+ * `'1'`. This is the historical behavior the validator's `$data` keyword
159
+ * relies on, and it is a string operation — tens of nanoseconds.
160
+ * - `hashIndex: 'number'` is the draft's answer. It walks to the parent of
161
+ * the location to see whether it is an array, and returns `1` rather than
162
+ * `'1'` when it is. Object member names are unaffected. When the parent
163
+ * cannot be reached (the location does not exist in `dataRoot`) it falls
164
+ * back to the string, because nothing proves the position is an index.
165
+ *
166
+ * Neither mode verifies that the location itself exists; the caller is
167
+ * expected to pass a location it actually reached. The non-`#` form must
168
+ * walk regardless, because it returns the value.
92
169
  *
93
170
  * @param {string} pointer - The relative pointer (e.g. `1/sibling`, `0#`)
171
+ * @param {{ hashIndex?: 'string'|'number' }} [options] - `hashIndex`
172
+ * selects what the `#` form yields for an array position (default
173
+ * `'string'`)
94
174
  * @returns {RelativeJsonPointerResolver} resolver returning
95
175
  * the addressed value, or `JSONPOINTER_NOTHING`
96
176
  * @throws {JSONPointerSyntaxError} When the pointer is not valid
177
+ * @throws {TypeError} When `hashIndex` is neither `'string'` nor `'number'`
97
178
  * @example
98
179
  * const resolve = compileRelativeJSONPointer('1/limits');
99
180
  * resolve({ limits: { min: 2 } , value: 5 }, '/value'); // { min: 2 }
181
+ * @example
182
+ * const spec = compileRelativeJSONPointer('0#', { hashIndex: 'number' });
183
+ * spec({ a: ['x', 'y'] }, '/a/1'); // 1 (the number, per the draft)
100
184
  */
101
- export declare function compileRelativeJSONPointer(pointer: string): RelativeJsonPointerResolver;
185
+ export declare function compileRelativeJSONPointer(pointer: string, options?: {
186
+ hashIndex?: 'string' | 'number';
187
+ }): RelativeJsonPointerResolver;
102
188
  /**
103
189
  * Compile a data reference - the accepted forms of the validator's
104
190
  * `data`/`$data` keywords - into a reusable resolver. The dispatch is
@@ -106,9 +192,14 @@ export declare function compileRelativeJSONPointer(pointer: string): RelativeJso
106
192
  * Pointer, a leading `/` an absolute JSON Pointer, and `''` the root.
107
193
  *
108
194
  * @param {string} ref - The reference string
195
+ * @param {{ hashIndex?: 'string'|'number' }} [options] - forwarded to
196
+ * {@link compileRelativeJSONPointer}; only the relative forms read it
109
197
  * @returns {RelativeJsonPointerResolver} resolver returning
110
198
  * the addressed value, or `JSONPOINTER_NOTHING`
111
199
  * @throws {JSONPointerSyntaxError} When the reference is none of the
112
200
  * accepted forms
201
+ * @throws {TypeError} When `hashIndex` is neither `'string'` nor `'number'`
113
202
  */
114
- export declare function compileDataRef(ref: string): RelativeJsonPointerResolver;
203
+ export declare function compileDataRef(ref: string, options?: {
204
+ hashIndex?: 'string' | 'number';
205
+ }): RelativeJsonPointerResolver;
@@ -13,6 +13,18 @@ export declare const UNBOUND: unique symbol;
13
13
  * @returns {(frame: any[]) => boolean}
14
14
  */
15
15
  export declare function compileExistsTest(node: object): (frame: any[]) => boolean;
16
+ /**
17
+ * Compile a query's AST root, optionally instrumenting every node
18
+ * evaluation against a step limit.
19
+ * @param {object} root - the AST root from normalizeQuery
20
+ * @param {{ slot: number, limit: number } | null} steps - the step
21
+ * counter's frame slot and its limit, or null for no instrumentation
22
+ * @returns {(frame: any[]) => any} the root getter
23
+ */
24
+ export declare function compileQueryRoot(root: object, steps: {
25
+ slot: number;
26
+ limit: number;
27
+ } | null): (frame: any[]) => any;
16
28
  /**
17
29
  * Compile a normalized AST node into its getter closure.
18
30
  * @param {object} node - a frozen AST node from normalize.js