@swaggerexpert/jsonpath 3.2.5 → 4.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.
Files changed (84) hide show
  1. package/README.md +225 -18
  2. package/cjs/errors/JSONNormalizedPathError.cjs +8 -0
  3. package/cjs/errors/JSONPathError.cjs +1 -1
  4. package/cjs/errors/{JSONPathCompileError.cjs → JSONPathEvaluateError.cjs} +2 -2
  5. package/cjs/evaluate/evaluators/comparable.cjs +44 -0
  6. package/cjs/evaluate/evaluators/comparison-expr.cjs +37 -0
  7. package/cjs/evaluate/evaluators/filter-query.cjs +182 -0
  8. package/cjs/evaluate/evaluators/function-expr.cjs +106 -0
  9. package/cjs/evaluate/evaluators/literal.cjs +25 -0
  10. package/cjs/evaluate/evaluators/logical-expr.cjs +96 -0
  11. package/cjs/evaluate/evaluators/singular-query.cjs +103 -0
  12. package/cjs/evaluate/functions/count.cjs +35 -0
  13. package/cjs/evaluate/functions/index.cjs +15 -0
  14. package/cjs/evaluate/functions/length.cjs +42 -0
  15. package/cjs/evaluate/functions/match.cjs +49 -0
  16. package/cjs/evaluate/functions/search.cjs +49 -0
  17. package/cjs/evaluate/functions/value.cjs +36 -0
  18. package/cjs/evaluate/index.cjs +182 -0
  19. package/cjs/evaluate/realms/EvaluationRealm.cjs +154 -0
  20. package/cjs/evaluate/realms/json/index.cjs +246 -0
  21. package/cjs/evaluate/utils/guards.cjs +129 -0
  22. package/cjs/evaluate/utils/i-regexp.cjs +118 -0
  23. package/cjs/evaluate/visitors/bracketed-selection.cjs +35 -0
  24. package/cjs/evaluate/visitors/filter-selector.cjs +43 -0
  25. package/cjs/evaluate/visitors/index-selector.cjs +55 -0
  26. package/cjs/evaluate/visitors/name-selector.cjs +38 -0
  27. package/cjs/evaluate/visitors/segment.cjs +99 -0
  28. package/cjs/evaluate/visitors/selector.cjs +47 -0
  29. package/cjs/evaluate/visitors/slice-selector.cjs +115 -0
  30. package/cjs/evaluate/visitors/wildcard-selector.cjs +32 -0
  31. package/cjs/index.cjs +16 -7
  32. package/cjs/normalized-path.cjs +145 -0
  33. package/cjs/parse/callbacks/cst.cjs +2 -4
  34. package/cjs/parse/index.cjs +3 -1
  35. package/cjs/parse/translators/ASTTranslator/index.cjs +1 -1
  36. package/cjs/parse/translators/ASTTranslator/transformers.cjs +246 -5
  37. package/cjs/parse/translators/CSTOptimizedTranslator.cjs +1 -3
  38. package/cjs/parse/translators/CSTTranslator.cjs +1 -2
  39. package/cjs/test/index.cjs +4 -2
  40. package/es/errors/JSONNormalizedPathError.mjs +3 -0
  41. package/es/errors/JSONPathError.mjs +1 -1
  42. package/es/errors/JSONPathEvaluateError.mjs +3 -0
  43. package/es/evaluate/evaluators/comparable.mjs +38 -0
  44. package/es/evaluate/evaluators/comparison-expr.mjs +31 -0
  45. package/es/evaluate/evaluators/filter-query.mjs +175 -0
  46. package/es/evaluate/evaluators/function-expr.mjs +99 -0
  47. package/es/evaluate/evaluators/literal.mjs +21 -0
  48. package/es/evaluate/evaluators/logical-expr.mjs +89 -0
  49. package/es/evaluate/evaluators/singular-query.mjs +97 -0
  50. package/es/evaluate/functions/count.mjs +30 -0
  51. package/es/evaluate/functions/index.mjs +13 -0
  52. package/es/evaluate/functions/length.mjs +37 -0
  53. package/es/evaluate/functions/match.mjs +44 -0
  54. package/es/evaluate/functions/search.mjs +44 -0
  55. package/es/evaluate/functions/value.mjs +31 -0
  56. package/es/evaluate/index.mjs +174 -0
  57. package/es/evaluate/realms/EvaluationRealm.mjs +148 -0
  58. package/es/evaluate/realms/json/index.mjs +240 -0
  59. package/es/evaluate/utils/guards.mjs +114 -0
  60. package/es/evaluate/utils/i-regexp.mjs +113 -0
  61. package/es/evaluate/visitors/bracketed-selection.mjs +29 -0
  62. package/es/evaluate/visitors/filter-selector.mjs +37 -0
  63. package/es/evaluate/visitors/index-selector.mjs +51 -0
  64. package/es/evaluate/visitors/name-selector.mjs +34 -0
  65. package/es/evaluate/visitors/segment.mjs +91 -0
  66. package/es/evaluate/visitors/selector.mjs +41 -0
  67. package/es/evaluate/visitors/slice-selector.mjs +111 -0
  68. package/es/evaluate/visitors/wildcard-selector.mjs +28 -0
  69. package/es/index.mjs +7 -3
  70. package/es/normalized-path.mjs +136 -0
  71. package/es/parse/callbacks/cst.mjs +2 -4
  72. package/es/parse/index.mjs +3 -1
  73. package/es/parse/translators/ASTTranslator/index.mjs +1 -1
  74. package/es/parse/translators/ASTTranslator/transformers.mjs +246 -5
  75. package/es/parse/translators/CSTOptimizedTranslator.mjs +1 -3
  76. package/es/parse/translators/CSTTranslator.mjs +1 -2
  77. package/es/test/index.mjs +4 -2
  78. package/package.json +4 -2
  79. package/types/index.d.ts +114 -11
  80. package/cjs/compile.cjs +0 -50
  81. package/cjs/escape.cjs +0 -59
  82. package/es/compile.mjs +0 -45
  83. package/es/errors/JSONPathCompileError.mjs +0 -3
  84. package/es/escape.mjs +0 -55
@@ -0,0 +1,113 @@
1
+ /**
2
+ * I-Regexp (RFC 9485) utilities for JSONPath match() and search() functions.
3
+ *
4
+ * @see https://www.rfc-editor.org/rfc/rfc9485 - I-Regexp specification
5
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4.1 - match() function
6
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4.2 - search() function
7
+ */
8
+
9
+ const regexpCache = new Map();
10
+
11
+ /**
12
+ * Validate and transform I-Regexp pattern to ECMAScript regex pattern.
13
+ * Returns null if pattern contains non-I-Regexp features:
14
+ * - Backreferences (\1, \2, etc.)
15
+ * - Lookahead/lookbehind assertions
16
+ * - Named capture groups
17
+ * - Word boundaries outside character classes
18
+ *
19
+ * Transforms `.` to `[^\n\r]` outside character classes (I-Regexp semantics).
20
+ *
21
+ * @param {string} pattern
22
+ * @returns {string | null} - Transformed pattern or null if invalid
23
+ */
24
+ const transformIRegexp = pattern => {
25
+ let result = '';
26
+ let inCharClass = false;
27
+ let i = 0;
28
+ while (i < pattern.length) {
29
+ const ch = pattern[i];
30
+
31
+ // Handle escape sequences
32
+ if (ch === '\\' && i + 1 < pattern.length) {
33
+ const next = pattern[i + 1];
34
+
35
+ // Reject backreferences (\1-\9)
36
+ if (next >= '1' && next <= '9') return null;
37
+
38
+ // Reject word boundaries outside character classes
39
+ if (!inCharClass && (next === 'b' || next === 'B')) return null;
40
+ result += ch + next;
41
+ i += 2;
42
+ continue;
43
+ }
44
+
45
+ // Track character class boundaries
46
+ if (ch === '[' && !inCharClass) {
47
+ inCharClass = true;
48
+ result += ch;
49
+ i += 1;
50
+ continue;
51
+ }
52
+ if (ch === ']' && inCharClass) {
53
+ inCharClass = false;
54
+ result += ch;
55
+ i += 1;
56
+ continue;
57
+ }
58
+
59
+ // Check for lookahead/lookbehind/named groups: (?=, (?!, (?<=, (?<!, (?<name>
60
+ if (ch === '(' && i + 2 < pattern.length && pattern[i + 1] === '?') {
61
+ const next2 = pattern[i + 2];
62
+ // Reject lookahead (?= (?!
63
+ if (next2 === '=' || next2 === '!') return null;
64
+ // Check for lookbehind or named groups
65
+ if (next2 === '<' && i + 3 < pattern.length) {
66
+ const next3 = pattern[i + 3];
67
+ // Reject lookbehind (?<= (?<!
68
+ if (next3 === '=' || next3 === '!') return null;
69
+ // Reject named capture groups (?<name>
70
+ if (/[a-zA-Z]/.test(next3)) return null;
71
+ }
72
+ }
73
+
74
+ // Transform `.` to `[^\n\r]` outside character classes
75
+ if (ch === '.' && !inCharClass) {
76
+ result += '[^\\n\\r]';
77
+ i += 1;
78
+ continue;
79
+ }
80
+ result += ch;
81
+ i += 1;
82
+ }
83
+ return result;
84
+ };
85
+
86
+ /**
87
+ * Construct a RegExp from I-Regexp pattern.
88
+ * Validates the pattern, transforms it, and caches the result.
89
+ *
90
+ * @param {string} pattern - I-Regexp pattern
91
+ * @param {boolean} [anchor=false] - If true, anchor pattern with ^(?:...)$
92
+ * @returns {RegExp | null} - Compiled regex or null if invalid
93
+ */
94
+ export const constructRegex = (pattern, anchor = false) => {
95
+ const cacheKey = anchor ? `anchored:${pattern}` : `unanchored:${pattern}`;
96
+ if (regexpCache.has(cacheKey)) {
97
+ return regexpCache.get(cacheKey);
98
+ }
99
+ const transformed = transformIRegexp(pattern);
100
+ if (transformed === null) {
101
+ regexpCache.set(cacheKey, null);
102
+ return null;
103
+ }
104
+ try {
105
+ const finalPattern = anchor ? `^(?:${transformed})$` : transformed;
106
+ const regex = new RegExp(finalPattern, 'u');
107
+ regexpCache.set(cacheKey, regex);
108
+ return regex;
109
+ } catch {
110
+ regexpCache.set(cacheKey, null);
111
+ return null;
112
+ }
113
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Bracketed selection visitor.
3
+ *
4
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.5.1
5
+ *
6
+ * A bracketed selection contains one or more selectors.
7
+ * Each selector's results are concatenated.
8
+ */
9
+ import visitSelector from "./selector.mjs";
10
+ /**
11
+ * Visit a bracketed selection.
12
+ *
13
+ * @param {object} ctx - Evaluation context
14
+ * @param {unknown} value - Current value
15
+ * @param {object} node - AST node
16
+ * @param {object[]} node.selectors - Array of selector AST nodes
17
+ * @param {(value: unknown, segment: string | number) => void} emit - Callback to emit selected value
18
+ */
19
+ const visitBracketedSelection = (ctx, value, node, emit) => {
20
+ const {
21
+ selectors
22
+ } = node;
23
+
24
+ // Visit each selector and emit its results
25
+ for (const selector of selectors) {
26
+ visitSelector(ctx, value, selector, emit);
27
+ }
28
+ };
29
+ export default visitBracketedSelection;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Filter selector visitor.
3
+ *
4
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.5
5
+ *
6
+ * A filter selector [?expr] selects all children where the expression is true.
7
+ * For arrays: tests each element
8
+ * For objects: tests each member value
9
+ */
10
+ import evaluateLogicalExpr from "../evaluators/logical-expr.mjs";
11
+ /**
12
+ * Visit a filter selector.
13
+ *
14
+ * @param {object} ctx - Evaluation context
15
+ * @param {object} ctx.realm - Data realm
16
+ * @param {object} ctx.root - Root value ($)
17
+ * @param {unknown} value - Current value
18
+ * @param {object} node - AST node
19
+ * @param {object} node.expression - Logical expression to evaluate
20
+ * @param {(value: unknown, segment: string | number) => void} emit - Callback to emit selected value
21
+ */
22
+ const visitFilterSelector = (ctx, value, node, emit) => {
23
+ const {
24
+ realm,
25
+ root
26
+ } = ctx;
27
+ const {
28
+ expression
29
+ } = node;
30
+ for (const [key, child] of realm.entries(value)) {
31
+ const result = evaluateLogicalExpr(ctx, root, child, expression);
32
+ if (result) {
33
+ emit(child, key);
34
+ }
35
+ }
36
+ };
37
+ export default visitFilterSelector;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Index selector visitor.
3
+ *
4
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.3
5
+ *
6
+ * An index selector selects at most one element from an array.
7
+ * Supports negative indices (count from end).
8
+ */
9
+
10
+ /**
11
+ * Normalize an array index.
12
+ * Negative indices count from the end.
13
+ *
14
+ * @param {number} index - The index (may be negative)
15
+ * @param {number} length - Array length
16
+ * @returns {number} - Normalized index (non-negative or out of bounds)
17
+ */
18
+ const normalizeIndex = (index, length) => {
19
+ if (index >= 0) return index;
20
+ return length + index;
21
+ };
22
+
23
+ /**
24
+ * Visit an index selector.
25
+ *
26
+ * @param {object} ctx - Evaluation context
27
+ * @param {object} ctx.realm - Data realm
28
+ * @param {unknown} value - Current value
29
+ * @param {object} node - AST node
30
+ * @param {number} node.value - Index to select
31
+ * @param {(value: unknown, segment: string | number) => void} emit - Callback to emit selected value
32
+ */
33
+ const visitIndexSelector = (ctx, value, node, emit) => {
34
+ const {
35
+ realm
36
+ } = ctx;
37
+ const {
38
+ value: index
39
+ } = node;
40
+ if (!realm.isArray(value)) return;
41
+ const length = realm.getLength(value);
42
+ const normalizedIndex = normalizeIndex(index, length);
43
+
44
+ // Check bounds
45
+ if (normalizedIndex >= 0 && normalizedIndex < length) {
46
+ const selected = realm.getElement(value, normalizedIndex);
47
+ emit(selected, normalizedIndex);
48
+ }
49
+ // If out of bounds, yield nothing
50
+ };
51
+ export default visitIndexSelector;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Name selector visitor.
3
+ *
4
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.1
5
+ *
6
+ * A name selector selects at most one member value from an object.
7
+ * If the object has the specified member, yield its value.
8
+ * If not, yield nothing.
9
+ */
10
+
11
+ /**
12
+ * Visit a name selector.
13
+ *
14
+ * @param {object} ctx - Evaluation context
15
+ * @param {object} ctx.realm - Data realm
16
+ * @param {unknown} value - Current value
17
+ * @param {object} node - AST node
18
+ * @param {string} node.value - Property name to select
19
+ * @param {(value: unknown, segment: string | number) => void} emit - Callback to emit selected value
20
+ */
21
+ const visitNameSelector = (ctx, value, node, emit) => {
22
+ const {
23
+ realm
24
+ } = ctx;
25
+ const {
26
+ value: name
27
+ } = node;
28
+ if (realm.isObject(value) && realm.hasProperty(value, name)) {
29
+ const selected = realm.getProperty(value, name);
30
+ emit(selected, name);
31
+ }
32
+ // If not an object or property doesn't exist, yield nothing
33
+ };
34
+ export default visitNameSelector;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Segment visitor dispatcher.
3
+ *
4
+ * Handles ChildSegment and DescendantSegment types.
5
+ *
6
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.5
7
+ */
8
+ import visitSelector from "./selector.mjs";
9
+ import visitBracketedSelection from "./bracketed-selection.mjs";
10
+ /**
11
+ * Visit a segment's selector.
12
+ *
13
+ * @param {object} ctx - Evaluation context
14
+ * @param {unknown} value - Current value
15
+ * @param {object} selector - Selector AST node
16
+ * @param {(value: unknown, segment: string | number) => void} emit - Callback to emit selected value
17
+ */
18
+ const visitSegmentSelector = (ctx, value, selector, emit) => {
19
+ switch (selector.type) {
20
+ case 'BracketedSelection':
21
+ visitBracketedSelection(ctx, value, selector, emit);
22
+ break;
23
+ case 'NameSelector':
24
+ case 'WildcardSelector':
25
+ case 'IndexSelector':
26
+ case 'SliceSelector':
27
+ case 'FilterSelector':
28
+ visitSelector(ctx, value, selector, emit);
29
+ break;
30
+ default:
31
+ break;
32
+ }
33
+ };
34
+
35
+ /**
36
+ * Visit a child segment.
37
+ * Applies selector to current value.
38
+ *
39
+ * @param {object} ctx - Evaluation context
40
+ * @param {unknown} value - Current value
41
+ * @param {object} node - ChildSegment AST node
42
+ * @param {(value: unknown, segment: string | number) => void} emit - Callback to emit selected value
43
+ */
44
+ export const visitChildSegment = (ctx, value, node, emit) => {
45
+ const {
46
+ selector
47
+ } = node;
48
+ visitSegmentSelector(ctx, value, selector, emit);
49
+ };
50
+
51
+ /**
52
+ * Visit a descendant segment.
53
+ * Applies selector to current value and all descendants.
54
+ *
55
+ * Note: This is used by exec.js which handles the recursive descent
56
+ * by pushing descendants onto the stack. This function only applies
57
+ * the selector at the current level.
58
+ *
59
+ * @param {object} ctx - Evaluation context
60
+ * @param {unknown} value - Current value
61
+ * @param {object} node - DescendantSegment AST node
62
+ * @param {(value: unknown, segment: string | number) => void} emit - Callback to emit selected value
63
+ */
64
+ export const visitDescendantSegment = (ctx, value, node, emit) => {
65
+ const {
66
+ selector
67
+ } = node;
68
+ visitSegmentSelector(ctx, value, selector, emit);
69
+ };
70
+
71
+ /**
72
+ * Visit a segment and emit selected values.
73
+ *
74
+ * @param {object} ctx - Evaluation context
75
+ * @param {unknown} value - Current value
76
+ * @param {object} node - Segment AST node
77
+ * @param {(value: unknown, segment: string | number) => void} emit - Callback to emit selected value
78
+ */
79
+ const visitSegment = (ctx, value, node, emit) => {
80
+ switch (node.type) {
81
+ case 'ChildSegment':
82
+ visitChildSegment(ctx, value, node, emit);
83
+ break;
84
+ case 'DescendantSegment':
85
+ visitDescendantSegment(ctx, value, node, emit);
86
+ break;
87
+ default:
88
+ break;
89
+ }
90
+ };
91
+ export default visitSegment;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Selector visitor dispatcher.
3
+ *
4
+ * Routes to the appropriate selector visitor based on AST node type.
5
+ */
6
+ import visitNameSelector from "./name-selector.mjs";
7
+ import visitIndexSelector from "./index-selector.mjs";
8
+ import visitWildcardSelector from "./wildcard-selector.mjs";
9
+ import visitSliceSelector from "./slice-selector.mjs";
10
+ import visitFilterSelector from "./filter-selector.mjs";
11
+ /**
12
+ * Visit a selector and emit selected values.
13
+ *
14
+ * @param {object} ctx - Evaluation context
15
+ * @param {unknown} value - Current value
16
+ * @param {object} node - Selector AST node
17
+ * @param {(value: unknown, segment: string | number) => void} emit - Callback to emit selected value
18
+ */
19
+ const visitSelector = (ctx, value, node, emit) => {
20
+ switch (node.type) {
21
+ case 'NameSelector':
22
+ visitNameSelector(ctx, value, node, emit);
23
+ break;
24
+ case 'IndexSelector':
25
+ visitIndexSelector(ctx, value, node, emit);
26
+ break;
27
+ case 'WildcardSelector':
28
+ visitWildcardSelector(ctx, value, node, emit);
29
+ break;
30
+ case 'SliceSelector':
31
+ visitSliceSelector(ctx, value, node, emit);
32
+ break;
33
+ case 'FilterSelector':
34
+ visitFilterSelector(ctx, value, node, emit);
35
+ break;
36
+ default:
37
+ // Unknown selector type, yield nothing
38
+ break;
39
+ }
40
+ };
41
+ export default visitSelector;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Slice selector visitor.
3
+ *
4
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.4
5
+ *
6
+ * A slice selector [start:end:step] selects elements from an array.
7
+ * - start: first index (default 0 for positive step, len-1 for negative)
8
+ * - end: upper bound (default len for positive step, -len-1 for negative)
9
+ * - step: step size (default 1, must not be 0)
10
+ */
11
+
12
+ /**
13
+ * Normalize a slice bound according to RFC 9535.
14
+ * @param {number} index - The bound value
15
+ * @param {number} length - Array length
16
+ * @returns {number} - Normalized bound
17
+ */
18
+ const normalizeBound = (index, length) => {
19
+ if (index >= 0) {
20
+ return Math.min(index, length);
21
+ }
22
+ return Math.max(length + index, 0);
23
+ };
24
+
25
+ /**
26
+ * Get slice bounds and step according to RFC 9535 Section 2.3.4.2.
27
+ *
28
+ * @param {number | null} start
29
+ * @param {number | null} end
30
+ * @param {number | null} step
31
+ * @param {number} length - Array length
32
+ * @returns {{ lower: number, upper: number, step: number } | null}
33
+ */
34
+ const getSliceBounds = (start, end, step, length) => {
35
+ // Default step is 1
36
+ const actualStep = step ?? 1;
37
+
38
+ // Step of 0 is not allowed
39
+ if (actualStep === 0) return null;
40
+ let lower;
41
+ let upper;
42
+ if (actualStep > 0) {
43
+ // Forward iteration
44
+ const defaultStart = 0;
45
+ const defaultEnd = length;
46
+ const normalizedStart = start !== null ? normalizeBound(start, length) : defaultStart;
47
+ const normalizedEnd = end !== null ? normalizeBound(end, length) : defaultEnd;
48
+ lower = Math.max(normalizedStart, 0);
49
+ upper = Math.min(normalizedEnd, length);
50
+ } else {
51
+ // Backward iteration
52
+ const defaultStart = length - 1;
53
+ const defaultEnd = -length - 1;
54
+ const normalizedStart = start !== null ? start >= 0 ? Math.min(start, length - 1) : Math.max(length + start, -1) : defaultStart;
55
+ const normalizedEnd = end !== null ? end >= 0 ? Math.min(end, length - 1) : Math.max(length + end, -1) : defaultEnd;
56
+ upper = Math.min(normalizedStart, length - 1);
57
+ lower = Math.max(normalizedEnd, -1);
58
+ }
59
+ return {
60
+ lower,
61
+ upper,
62
+ step: actualStep
63
+ };
64
+ };
65
+
66
+ /**
67
+ * Visit a slice selector.
68
+ *
69
+ * @param {object} ctx - Evaluation context
70
+ * @param {object} ctx.realm - Data realm
71
+ * @param {unknown} value - Current value
72
+ * @param {object} node - AST node
73
+ * @param {number | null} node.start - Start index
74
+ * @param {number | null} node.end - End index
75
+ * @param {number | null} node.step - Step
76
+ * @param {(value: unknown, segment: string | number) => void} emit - Callback to emit selected value
77
+ */
78
+ const visitSliceSelector = (ctx, value, node, emit) => {
79
+ const {
80
+ realm
81
+ } = ctx;
82
+ const {
83
+ start,
84
+ end,
85
+ step
86
+ } = node;
87
+ if (!realm.isArray(value)) return;
88
+ const length = realm.getLength(value);
89
+ const bounds = getSliceBounds(start, end, step, length);
90
+ if (bounds === null) return; // step was 0
91
+
92
+ const {
93
+ lower,
94
+ upper,
95
+ step: actualStep
96
+ } = bounds;
97
+ if (actualStep > 0) {
98
+ // Forward iteration
99
+ for (let i = lower; i < upper; i += actualStep) {
100
+ const selected = realm.getElement(value, i);
101
+ emit(selected, i);
102
+ }
103
+ } else {
104
+ // Backward iteration
105
+ for (let i = upper; i > lower; i += actualStep) {
106
+ const selected = realm.getElement(value, i);
107
+ emit(selected, i);
108
+ }
109
+ }
110
+ };
111
+ export default visitSliceSelector;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Wildcard selector visitor.
3
+ *
4
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.2
5
+ *
6
+ * A wildcard selector selects all children of a value:
7
+ * - For arrays: all elements
8
+ * - For objects: all member values
9
+ */
10
+
11
+ /**
12
+ * Visit a wildcard selector.
13
+ *
14
+ * @param {object} ctx - Evaluation context
15
+ * @param {object} ctx.realm - Data realm
16
+ * @param {unknown} value - Current value
17
+ * @param {object} node - AST node (unused for wildcard)
18
+ * @param {(value: unknown, segment: string | number) => void} emit - Callback to emit selected value
19
+ */
20
+ const visitWildcardSelector = (ctx, value, node, emit) => {
21
+ const {
22
+ realm
23
+ } = ctx;
24
+ for (const [key, child] of realm.entries(value)) {
25
+ emit(child, key);
26
+ }
27
+ };
28
+ export default visitWildcardSelector;
package/es/index.mjs CHANGED
@@ -6,8 +6,12 @@ export { default as ASTTranslator } from "./parse/translators/ASTTranslator/inde
6
6
  export { default as XMLTranslator } from "./parse/translators/XMLTranslator.mjs";
7
7
  export { default as Trace } from "./parse/trace/Trace.mjs";
8
8
  export { default as test } from "./test/index.mjs";
9
- export { default as compile } from "./compile.mjs";
10
- export { default as escape } from "./escape.mjs";
9
+ export * as NormalizedPath from "./normalized-path.mjs";
10
+ export { default as evaluate } from "./evaluate/index.mjs";
11
+ export * as functions from "./evaluate/functions/index.mjs";
12
+ export { default as EvaluationRealm } from "./evaluate/realms/EvaluationRealm.mjs";
13
+ export { default as JSONEvaluationRealm } from "./evaluate/realms/json/index.mjs";
11
14
  export { default as JSONPathError } from "./errors/JSONPathError.mjs";
12
15
  export { default as JSONPathParseError } from "./errors/JSONPathParseError.mjs";
13
- export { default as JSONPathCompileError } from "./errors/JSONPathCompileError.mjs";
16
+ export { default as JSONNormalizedPathError } from "./errors/JSONNormalizedPathError.mjs";
17
+ export { default as JSONPathEvaluateError } from "./errors/JSONPathEvaluateError.mjs";
@@ -0,0 +1,136 @@
1
+ import parse from "./parse/index.mjs";
2
+ import testFn from "./test/index.mjs";
3
+ import JSONNormalizedPathError from "./errors/JSONNormalizedPathError.mjs";
4
+ /**
5
+ * Tests if a string is a valid normalized JSONPath.
6
+ *
7
+ * @param {string} normalizedPath - The string to test
8
+ * @returns {boolean} True if valid normalized path, false otherwise
9
+ */
10
+ export const test = normalizedPath => testFn(normalizedPath, {
11
+ normalized: true
12
+ });
13
+
14
+ /**
15
+ * Escapes a string for use in a normalized JSONPath name selector.
16
+ * Follows RFC 9535 Section 2.7 escaping rules for single-quoted strings.
17
+ *
18
+ * @param {string} selector - The string to escape
19
+ * @returns {string} The escaped string (without surrounding quotes)
20
+ */
21
+ export const escape = selector => {
22
+ if (typeof selector !== 'string') {
23
+ throw new TypeError('Selector must be a string');
24
+ }
25
+ let escaped = '';
26
+ for (const char of selector) {
27
+ const codePoint = char.codePointAt(0);
28
+ switch (codePoint) {
29
+ case 0x08:
30
+ // backspace
31
+ escaped += '\\b';
32
+ break;
33
+ case 0x09:
34
+ // horizontal tab
35
+ escaped += '\\t';
36
+ break;
37
+ case 0x0a:
38
+ // line feed
39
+ escaped += '\\n';
40
+ break;
41
+ case 0x0c:
42
+ // form feed
43
+ escaped += '\\f';
44
+ break;
45
+ case 0x0d:
46
+ // carriage return
47
+ escaped += '\\r';
48
+ break;
49
+ case 0x27:
50
+ // apostrophe '
51
+ escaped += "\\'";
52
+ break;
53
+ case 0x5c:
54
+ // backslash \
55
+ escaped += '\\\\';
56
+ break;
57
+ default:
58
+ // Other control characters (U+0000-U+001F except those handled above)
59
+ if (codePoint <= 0x1f) {
60
+ escaped += `\\u${codePoint.toString(16).padStart(4, '0')}`;
61
+ } else {
62
+ escaped += char;
63
+ }
64
+ }
65
+ }
66
+ return escaped;
67
+ };
68
+
69
+ /**
70
+ * Creates a normalized path string from a list of selectors.
71
+ * Name selectors are automatically escaped.
72
+ *
73
+ * @param {Array<string|number>} selectors - Array of name selectors (strings) or index selectors (numbers)
74
+ * @returns {string} A normalized JSONPath string
75
+ * @throws {JSONNormalizedPathError} If selectors is not an array or contains invalid selector types
76
+ */
77
+ export const from = selectors => {
78
+ if (!Array.isArray(selectors)) {
79
+ throw new JSONNormalizedPathError(`Selectors must be an array, got: ${typeof selectors}`, {
80
+ selectors
81
+ });
82
+ }
83
+ try {
84
+ const segments = selectors.map(selector => {
85
+ if (typeof selector === 'string') {
86
+ // Name selector: escape and wrap in single quotes
87
+ return `['${escape(selector)}']`;
88
+ }
89
+ if (typeof selector === 'number') {
90
+ // Index selector: must be a non-negative safe integer (RFC 9535 Section 2.1)
91
+ if (!Number.isSafeInteger(selector) || selector < 0) {
92
+ throw new TypeError(`Index selector must be a non-negative safe integer, got: ${selector}`);
93
+ }
94
+ return `[${selector}]`;
95
+ }
96
+ throw new TypeError(`Selector must be a string or non-negative integer, got: ${typeof selector}`);
97
+ });
98
+ return `$${segments.join('')}`;
99
+ } catch (error) {
100
+ throw new JSONNormalizedPathError('Failed to compile normalized JSONPath', {
101
+ cause: error,
102
+ selectors
103
+ });
104
+ }
105
+ };
106
+
107
+ /**
108
+ * Parses a normalized path string and returns a list of selectors.
109
+ *
110
+ * @param {string} normalizedPath - A normalized JSONPath string
111
+ * @returns {Array<string|number>} Array of name selectors (strings) or index selectors (numbers)
112
+ * @throws {JSONNormalizedPathError} If the normalized path is invalid
113
+ */
114
+ export const to = normalizedPath => {
115
+ if (typeof normalizedPath !== 'string') {
116
+ throw new JSONNormalizedPathError(`Normalized path must be a string, got: ${typeof normalizedPath}`, {
117
+ normalizedPath
118
+ });
119
+ }
120
+ const parseResult = parse(normalizedPath, {
121
+ normalized: true
122
+ });
123
+ if (!parseResult.result.success) {
124
+ throw new JSONNormalizedPathError('Invalid normalized path', {
125
+ normalizedPath
126
+ });
127
+ }
128
+ const {
129
+ tree
130
+ } = parseResult;
131
+
132
+ // Extract selectors from AST segments
133
+ // Normalized path grammar only allows NameSelector and IndexSelector
134
+ // For normalized paths, segment.selector is directly the selector (not BracketedSelection)
135
+ return tree.segments.map(segment => segment.selector.value);
136
+ };