json-p3 0.1.0 → 0.2.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.
@@ -1,5 +1,5 @@
1
1
  /*
2
- * json-p3 version 0.1.0
2
+ * json-p3 version 0.2.0
3
3
  * https://github.com/jg-rp/json-p3
4
4
  *
5
5
  * MIT License
@@ -122,6 +122,20 @@ class JSONPathSyntaxError extends JSONPathError {
122
122
  }
123
123
  }
124
124
 
125
+ /**
126
+ * Error thrown when the maximum recursion depth is reached.
127
+ */
128
+ class JSONPathRecursionLimitError extends JSONPathError {
129
+ constructor(message, token) {
130
+ super(message, token);
131
+ this.message = message;
132
+ this.token = token;
133
+ Object.setPrototypeOf(this, new.target.prototype);
134
+ this.name = "JSONPathRecursionLimitError";
135
+ this.message = withErrorContext(message, token);
136
+ }
137
+ }
138
+
125
139
  /**
126
140
  * Common types and type predicates.
127
141
  */
@@ -230,6 +244,10 @@ class JSONPointerError extends Error {
230
244
  this.name = "JSONPointerError";
231
245
  }
232
246
  }
247
+
248
+ /**
249
+ * Base class for JSON Pointer resolution errors.
250
+ */
233
251
  class JSONPointerResolutionError extends JSONPointerError {
234
252
  constructor(message) {
235
253
  super(message);
@@ -238,6 +256,10 @@ class JSONPointerResolutionError extends JSONPointerError {
238
256
  this.name = "JSONPointerResolutionError";
239
257
  }
240
258
  }
259
+
260
+ /**
261
+ * Error thrown due to an out of range index when resolving a JSON Pointer.
262
+ */
241
263
  class JSONPointerIndexError extends JSONPointerResolutionError {
242
264
  constructor(message) {
243
265
  super(message);
@@ -246,6 +268,10 @@ class JSONPointerIndexError extends JSONPointerResolutionError {
246
268
  this.name = "JSONPointerIndexError";
247
269
  }
248
270
  }
271
+
272
+ /**
273
+ * Error thrown due to a missing property when resolving a JSON Pointer.
274
+ */
249
275
  class JSONPointerKeyError extends JSONPointerResolutionError {
250
276
  constructor(message) {
251
277
  super(message);
@@ -254,6 +280,10 @@ class JSONPointerKeyError extends JSONPointerResolutionError {
254
280
  this.name = "JSONPointerKeyError";
255
281
  }
256
282
  }
283
+
284
+ /**
285
+ * Error thrown due to invalid JSON Pointer syntax.
286
+ */
257
287
  class JSONPointerSyntaxError extends JSONPointerError {
258
288
  constructor(message) {
259
289
  super(message);
@@ -262,6 +292,10 @@ class JSONPointerSyntaxError extends JSONPointerError {
262
292
  this.name = "JSONPointerSyntaxError";
263
293
  }
264
294
  }
295
+
296
+ /**
297
+ * Error thrown when trying to resolve a property or index against a primitive value.
298
+ */
265
299
  class JSONPointerTypeError extends JSONPointerResolutionError {
266
300
  constructor(message) {
267
301
  super(message);
@@ -321,8 +355,6 @@ class JSONPointer {
321
355
  }
322
356
  }
323
357
 
324
- // TODO: add `resolveWithFallback` to handle explicit `undefined`
325
-
326
358
  /**
327
359
  *
328
360
  * @param value -
@@ -361,6 +393,8 @@ class JSONPointer {
361
393
  }
362
394
  return pointer.split("/").map(token => token.replaceAll("~1", "/").replaceAll("~0", "~")).slice(1);
363
395
  }
396
+
397
+ // eslint-disable-next-line sonarjs/cognitive-complexity
364
398
  getItem(val, token, idx) {
365
399
  // NOTE:
366
400
  // - string primitives "have own" indices and `length`.
@@ -370,12 +404,23 @@ class JSONPointer {
370
404
  if (isArray(val)) {
371
405
  if (token !== "length" && Object.hasOwn(val, token)) {
372
406
  return val[Number(token)];
407
+ } else if (token.startsWith("#")) {
408
+ // handle non-standard '#' from relative json pointer
409
+ const maybeIndex = token.slice(1);
410
+ if (RE_INT.test(maybeIndex) && Object.hasOwn(val, maybeIndex)) {
411
+ return Number(maybeIndex);
412
+ } else {
413
+ throw new JSONPointerIndexError(`index out of range '${JSONPointer.encode(this.tokens.slice(0, idx + 1))}'`);
414
+ }
373
415
  } else {
374
416
  throw new JSONPointerIndexError(`index out of range '${JSONPointer.encode(this.tokens.slice(0, idx + 1))}'`);
375
417
  }
376
418
  } else if (isObject(val)) {
377
419
  if (Object.hasOwn(val, token)) {
378
420
  return val[token];
421
+ } else if (token.startsWith("#") && Object.hasOwn(val, token.slice(1))) {
422
+ // handle non-standard '#' from relative json pointer
423
+ return token.slice(1);
379
424
  } else {
380
425
  throw new JSONPointerKeyError(`no such property '${JSONPointer.encode(this.tokens.slice(0, idx + 1))}'`);
381
426
  }
@@ -395,10 +440,12 @@ class JSONPointer {
395
440
 
396
441
  /**
397
442
  * Join this pointer with _tokens_.
443
+ *
398
444
  * @param tokens - JSON Pointer strings, possibly without leading slashes.
399
445
  * If a token or "part" does have a leading slash, the previous pointer is
400
446
  * ignored and a new `JSONPointer` is created, then processing of the
401
447
  * remaining tokens continues.
448
+ *
402
449
  * @returns A new JSON Pointer that is the concatenation of all tokens or
403
450
  * "parts".
404
451
  */
@@ -448,8 +495,111 @@ class JSONPointer {
448
495
  }
449
496
  return new JSONPointer(JSONPointer.encode(this.tokens.slice(0, this.tokens.length - 1)));
450
497
  }
498
+ to(rel) {
499
+ const relativePointer = isString(rel) ? new RelativeJSONPointer(rel) : rel;
500
+ return relativePointer.to(this);
501
+ }
502
+ }
503
+ const RE_RELATIVE_POINTER = /(?<ORIGIN>\d+)(?<INDEX_G>(?<SIGN>[+-])(?<INDEX>\d))?(?<POINTER>.*)/s;
504
+ const RE_INT = /(0|[1-9][0-9]*)/;
505
+
506
+ /**
507
+ * A relative JSON Pointer.
508
+ *
509
+ * See https://www.ietf.org/id/draft-hha-relative-json-pointer-00.html
510
+ */
511
+ class RelativeJSONPointer {
512
+ /**
513
+ *
514
+ * @param rel -
515
+ */
516
+ constructor(rel) {
517
+ [this.origin, this.index, this.pointer] = this.parse(rel);
518
+ }
519
+
520
+ /**
521
+ *
522
+ * @returns
523
+ */
524
+ toString() {
525
+ const sign = this.index > 0 ? "+" : "";
526
+ const index = this.index === 0 ? "" : `${sign}${this.index}`;
527
+ return `${this.origin}${index}${this.pointer}`;
528
+ }
529
+
530
+ /**
531
+ *
532
+ * @param pointer -
533
+ */
534
+ to(pointer) {
535
+ const p = isString(pointer) ? new JSONPointer(pointer) : pointer;
536
+
537
+ // move to origin
538
+ if (this.origin > p.tokens.length) {
539
+ throw new JSONPointerIndexError(`origin (${this.origin}) exceeds root (${p.tokens.length})`);
540
+ }
541
+ const tokens = this.origin < 1 ? p.tokens.slice() : p.tokens.slice(0, -this.origin);
542
+
543
+ // array index offset
544
+ if (this.index && tokens.length && this.isIntLike(tokens.at(-1))) {
545
+ const newIndex = Number(tokens.at(-1)) + this.index;
546
+ if (newIndex < 0) {
547
+ throw new JSONPointerIndexError(`index offset out of range (${newIndex})`);
548
+ }
549
+ tokens[tokens.length - 1] = String(newIndex);
550
+ }
551
+
552
+ // pointer or index/property
553
+ if (this.pointer instanceof JSONPointer) {
554
+ tokens.push(...this.pointer.tokens);
555
+ } else {
556
+ tokens[tokens.length - 1] = `#${tokens[tokens.length - 1]}`;
557
+ }
558
+ return new JSONPointer(JSONPointer.encode(tokens));
559
+ }
560
+ parse(rel) {
561
+ const match = RE_RELATIVE_POINTER.exec(rel);
562
+ if (!match || !match.groups) {
563
+ throw new JSONPointerSyntaxError("failed to parse relative pointer");
564
+ }
451
565
 
452
- // TODO: to (relative pointer)
566
+ // steps to move
567
+ const origin = this.parseInt(match.groups.ORIGIN);
568
+
569
+ // optional index manipulation
570
+ let index = 0;
571
+ if (match.groups["INDEX_G"]) {
572
+ index = this.parseInt(match.groups.INDEX);
573
+ if (index === 0) {
574
+ throw new JSONPointerSyntaxError("index offset can't be zero");
575
+ }
576
+ if (match.groups.SIGN === "-") {
577
+ index = -index;
578
+ }
579
+ }
580
+
581
+ // pointer or '#'. an empty string is OK.
582
+ if (match.groups.POINTER === "#") {
583
+ return [origin, index, "#"];
584
+ }
585
+ return [origin, index, new JSONPointer(match.groups.POINTER)];
586
+ }
587
+ parseInt(s) {
588
+ if (s.startsWith("0") && s.length > 1) {
589
+ throw new JSONPointerSyntaxError("unexpected leading zero");
590
+ }
591
+ if (RE_INT.test(s)) {
592
+ return Number(s);
593
+ }
594
+ throw new JSONPointerSyntaxError(`expected an integer, found '${s}'`);
595
+ }
596
+ isIntLike(value) {
597
+ if (value === undefined || isNumber(value)) {
598
+ return true;
599
+ } else {
600
+ return RE_INT.test(value);
601
+ }
602
+ }
453
603
  }
454
604
 
455
605
  /**
@@ -484,6 +634,7 @@ var index$3 = /*#__PURE__*/Object.freeze({
484
634
  JSONPointerResolutionError: JSONPointerResolutionError,
485
635
  JSONPointerSyntaxError: JSONPointerSyntaxError,
486
636
  JSONPointerTypeError: JSONPointerTypeError,
637
+ RelativeJSONPointer: RelativeJSONPointer,
487
638
  UNDEFINED: UNDEFINED,
488
639
  resolve: resolve
489
640
  });
@@ -781,7 +932,7 @@ class FunctionExtension extends FilterExpression {
781
932
  this.args = args;
782
933
  }
783
934
  evaluate(context) {
784
- const func = context.environment.filterRegister.get(this.name);
935
+ const func = context.environment.functionRegister.get(this.name);
785
936
  if (!func) {
786
937
  throw new UndefinedFilterFunctionError(`filter function '${this.name}' is undefined`, this.token);
787
938
  }
@@ -801,13 +952,6 @@ function isTruthy(value) {
801
952
  if (value instanceof JSONPathNodeList && value.empty()) return false;
802
953
  return !(typeof value === "boolean" && value === false);
803
954
  }
804
-
805
- /**
806
- *
807
- * @param left -
808
- * @param operator -
809
- * @param right -
810
- */
811
955
  function compare(left, operator, right) {
812
956
  switch (operator) {
813
957
  case "==":
@@ -861,7 +1005,8 @@ var expression = /*#__PURE__*/Object.freeze({
861
1005
  PrefixExpression: PrefixExpression,
862
1006
  RelativeQuery: RelativeQuery,
863
1007
  RootQuery: RootQuery,
864
- StringLiteral: StringLiteral
1008
+ StringLiteral: StringLiteral,
1009
+ compare: compare
865
1010
  });
866
1011
 
867
1012
  class Count {
@@ -1092,7 +1237,7 @@ class TokenStream {
1092
1237
 
1093
1238
  // These regular expressions are to be used with Lexer.acceptMatchRun(),
1094
1239
  // which expects the sticky flag to be set.
1095
- const exponentPattern = /e[+-]\d+/y;
1240
+ const exponentPattern = /e[+-]?\d+/y;
1096
1241
  const functionNamePattern = /[a-z][a-z_0-9]*/y;
1097
1242
  const indexPattern = /-?\d+/y;
1098
1243
  const intPattern = /-?[0-9]+/y;
@@ -1385,6 +1530,10 @@ function lexInsideFilter(l) {
1385
1530
  case "":
1386
1531
  case "]":
1387
1532
  l.filterLevel -= 1;
1533
+ if (l.parenStack.length === 1) {
1534
+ l.error("unbalanced parentheses");
1535
+ return null;
1536
+ }
1388
1537
  l.backup();
1389
1538
  return lexInsideBracketedSelection;
1390
1539
  case ",":
@@ -1614,7 +1763,7 @@ class IndexSelector extends JSONPathSelector {
1614
1763
  this.environment = environment;
1615
1764
  this.token = token;
1616
1765
  this.index = index;
1617
- if (index < this.environment.options.minIntIndex || index > this.environment.options.maxIntIndex) {
1766
+ if (index < this.environment.minIntIndex || index > this.environment.maxIntIndex) {
1618
1767
  throw new JSONPathIndexError("index out of range", this.token);
1619
1768
  }
1620
1769
  }
@@ -1669,15 +1818,11 @@ class SliceSelector extends JSONPathSelector {
1669
1818
  indices[_key] = arguments[_key];
1670
1819
  }
1671
1820
  for (const index of indices) {
1672
- if (index !== undefined && (index < this.environment.options.minIntIndex || index > this.environment.options.maxIntIndex)) {
1821
+ if (index !== undefined && (index < this.environment.minIntIndex || index > this.environment.maxIntIndex)) {
1673
1822
  throw new JSONPathIndexError("index out of range", this.token);
1674
1823
  }
1675
1824
  }
1676
1825
  }
1677
- normalizedIndex(length, index) {
1678
- if (index < 0 && length >= Math.abs(index)) return Math.min(length + index, length - 1);
1679
- return Math.min(index, length - 1);
1680
- }
1681
1826
 
1682
1827
  // eslint-disable-next-line sonarjs/cognitive-complexity
1683
1828
  slice(arr, start, stop, step) {
@@ -1761,17 +1906,21 @@ class RecursiveDescentSegment extends JSONPathSelector {
1761
1906
  return "..";
1762
1907
  }
1763
1908
  visit(node) {
1909
+ let depth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
1910
+ if (depth >= this.environment.maxRecursionDepth) {
1911
+ throw new JSONPathRecursionLimitError("recursion limit reached", this.token);
1912
+ }
1764
1913
  const rv = [];
1765
1914
  if (node.value instanceof String) return new JSONPathNodeList(rv);
1766
1915
  if (isArray(node.value)) {
1767
1916
  for (let i = 0; i < node.value.length; i++) {
1768
1917
  const _node = new JSONPathNode(node.value[i], node.location.concat(i), node.root);
1769
- rv.push(_node, ...this.visit(_node));
1918
+ rv.push(_node, ...this.visit(_node, depth + 1));
1770
1919
  }
1771
1920
  } else if (isObject(node.value)) {
1772
1921
  for (const [key, value] of Object.entries(node.value)) {
1773
1922
  const _node = new JSONPathNode(value, node.location.concat(key), node.root);
1774
- rv.push(_node, ...this.visit(_node));
1923
+ rv.push(_node, ...this.visit(_node, depth + 1));
1775
1924
  }
1776
1925
  }
1777
1926
  return new JSONPathNodeList(rv);
@@ -1881,6 +2030,18 @@ class JSONPath {
1881
2030
  return nodes;
1882
2031
  }
1883
2032
 
2033
+ /**
2034
+ * Return a {@link JSONPathNode} instance for the first object found in
2035
+ * _value_ matching this query.
2036
+ *
2037
+ * @param value - JSON-like data to which this query will be applied.
2038
+ * @returns The first node in _value_ matching this query, or `undefined` if
2039
+ * there are no matches.
2040
+ */
2041
+ match(value) {
2042
+ return this.query(value).nodes.at(0);
2043
+ }
2044
+
1884
2045
  /**
1885
2046
  *
1886
2047
  */
@@ -2042,7 +2203,7 @@ class Parser {
2042
2203
  const tok = stream.next();
2043
2204
  const expr = this.parseFilterExpression(stream);
2044
2205
  if (expr instanceof FunctionExtension) {
2045
- const func = this.environment.filterRegister.get(expr.name);
2206
+ const func = this.environment.functionRegister.get(expr.name);
2046
2207
  if (func && func.returnType === FunctionExpressionType.ValueType) {
2047
2208
  throw new JSONPathTypeError(`result of ${expr.name}() must be compared`, expr.token);
2048
2209
  }
@@ -2112,7 +2273,16 @@ class Parser {
2112
2273
  if (!func) {
2113
2274
  throw new JSONPathSyntaxError(`unexpected '${stream.current.value}'`, stream.current);
2114
2275
  }
2115
- args.push(func.bind(this)(stream));
2276
+ let expr = func.bind(this)(stream);
2277
+
2278
+ // Could be a comparison/logical expression
2279
+ let peekKind = stream.peek.kind;
2280
+ while (BINARY_OPERATORS.has(peekKind)) {
2281
+ stream.next();
2282
+ expr = this.parseInfixExpression(stream, expr);
2283
+ peekKind = stream.peek.kind;
2284
+ }
2285
+ args.push(expr);
2116
2286
  if (stream.peek.kind !== TokenKind.RPAREN) {
2117
2287
  if (stream.peek.kind === TokenKind.RBRACKET) break;
2118
2288
  stream.expectPeek(TokenKind.COMMA);
@@ -2120,6 +2290,7 @@ class Parser {
2120
2290
  }
2121
2291
  stream.next();
2122
2292
  }
2293
+ stream.expect(TokenKind.RPAREN);
2123
2294
  return new FunctionExtension(tok, tok.value, this.environment.checkWellTypedness(tok, args));
2124
2295
  }
2125
2296
  parseFilterExpression(stream) {
@@ -2164,7 +2335,7 @@ class Parser {
2164
2335
  }
2165
2336
  throwForNonComparableFunction(expr) {
2166
2337
  if (!(expr instanceof FunctionExtension)) return;
2167
- const func = this.environment.filterRegister.get(expr.name);
2338
+ const func = this.environment.functionRegister.get(expr.name);
2168
2339
  if (func && func.returnType !== FunctionExpressionType.ValueType) {
2169
2340
  throw new JSONPathTypeError(`result of ${expr.name}() is not comparable`, expr.token);
2170
2341
  }
@@ -2172,30 +2343,53 @@ class Parser {
2172
2343
  }
2173
2344
 
2174
2345
  /**
2175
- *
2346
+ * JSONPath environment options. The defaults are in compliance with JSONPath
2347
+ * standards.
2176
2348
  */
2177
2349
 
2178
- const defaultOptions = {
2179
- strict: true,
2180
- maxIntIndex: Math.pow(2, 53) - 1,
2181
- minIntIndex: -Math.pow(2, 53) - 1
2182
- };
2183
-
2184
2350
  /**
2185
2351
  *
2186
2352
  */
2187
2353
  class JSONPathEnvironment {
2188
2354
  /**
2355
+ * Indicates if the environment should to be strict about its compliance with
2356
+ * JSONPath standards.
2189
2357
  *
2358
+ * Defaults to `true`. Setting `strict` to `false` currently has no effect.
2359
+ * If/when we add non-standard features, the environment's strictness will
2360
+ * control their availability.
2361
+ */
2362
+
2363
+ /**
2364
+ * The maximum number allowed when indexing or slicing an array. Defaults to
2365
+ * 2**53 -1.
2366
+ */
2367
+
2368
+ /**
2369
+ * The minimum number allowed when indexing or slicing an array. Defaults to
2370
+ * -(2**53) -1.
2371
+ */
2372
+
2373
+ /**
2374
+ * The maximum number of objects and/or arrays the recursive descent selector
2375
+ * can visit before a `JSONPathRecursionLimitError` is thrown.
2376
+ */
2377
+
2378
+ /**
2379
+ * A map of function names to objects implementing the {@link FilterFunction}
2380
+ * interface. You are free to set or delete custom filter functions directly.
2190
2381
  */
2191
- filterRegister = new Map();
2382
+ functionRegister = new Map();
2192
2383
  /**
2193
2384
  *
2194
2385
  * @param options -
2195
2386
  */
2196
2387
  constructor() {
2197
- let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions;
2198
- this.options = options;
2388
+ let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
2389
+ this.strict = options.strict ?? true;
2390
+ this.maxIntIndex = options.maxIntIndex ?? Math.pow(2, 53) - 1;
2391
+ this.minIntIndex = options.maxIntIndex ?? -Math.pow(2, 53) - 1;
2392
+ this.maxRecursionDepth = options.maxRecursionDepth ?? 50;
2199
2393
  this.parser = new Parser(this);
2200
2394
  this.setupFilterFunctions();
2201
2395
  }
@@ -2218,12 +2412,25 @@ class JSONPathEnvironment {
2218
2412
  query(path, value) {
2219
2413
  return this.compile(path).query(value);
2220
2414
  }
2415
+
2416
+ /**
2417
+ * Return a {@link JSONPathNode} instance for the first object found in
2418
+ * _value_ matching _path_.
2419
+ *
2420
+ * @param path - A JSONPath query.
2421
+ * @param value - JSON-like data to which the query _path_ will be applied.
2422
+ * @returns The first node in _value_ matching _path_, or `undefined` if
2423
+ * there are no matches.
2424
+ */
2425
+ match(path, value) {
2426
+ return this.compile(path).match(value);
2427
+ }
2221
2428
  setupFilterFunctions() {
2222
- this.filterRegister.set("count", new Count());
2223
- this.filterRegister.set("length", new Length());
2224
- this.filterRegister.set("search", new Search());
2225
- this.filterRegister.set("match", new Match());
2226
- this.filterRegister.set("value", new Value());
2429
+ this.functionRegister.set("count", new Count());
2430
+ this.functionRegister.set("length", new Length());
2431
+ this.functionRegister.set("search", new Search());
2432
+ this.functionRegister.set("match", new Match());
2433
+ this.functionRegister.set("value", new Value());
2227
2434
  }
2228
2435
 
2229
2436
  /**
@@ -2233,7 +2440,7 @@ class JSONPathEnvironment {
2233
2440
  */
2234
2441
  // eslint-disable-next-line sonarjs/cognitive-complexity
2235
2442
  checkWellTypedness(token, args) {
2236
- const func = this.filterRegister.get(token.value);
2443
+ const func = this.functionRegister.get(token.value);
2237
2444
  if (!func) {
2238
2445
  throw new UndefinedFilterFunctionError(`no such function '${token.value}'`, token);
2239
2446
  }
@@ -2247,17 +2454,17 @@ class JSONPathEnvironment {
2247
2454
  for (const [typ, arg, idx] of func.argTypes.map((t, i) => [t, args[i], i])) {
2248
2455
  switch (typ) {
2249
2456
  case FunctionExpressionType.ValueType:
2250
- if (!(arg instanceof FilterExpressionLiteral || arg instanceof JSONPathQuery && arg.path.singularQuery())) {
2457
+ if (!(arg instanceof FilterExpressionLiteral || arg instanceof JSONPathQuery && arg.path.singularQuery() || arg instanceof FunctionExtension && this.functionRegister.get(arg.name)?.returnType === FunctionExpressionType.ValueType)) {
2251
2458
  throw new JSONPathTypeError(`${token.value}() argument ${idx} must be of ValueType`, arg.token);
2252
2459
  }
2253
2460
  break;
2254
2461
  case FunctionExpressionType.LogicalType:
2255
- if (!(arg instanceof BooleanLiteral)) {
2462
+ if (!(arg instanceof JSONPathQuery || arg instanceof InfixExpression)) {
2256
2463
  throw new JSONPathTypeError(`${token.value}() argument ${idx} must be of LogicalType`, arg.token);
2257
2464
  }
2258
2465
  break;
2259
2466
  case FunctionExpressionType.NodesType:
2260
- if (!(arg instanceof JSONPathQuery)) {
2467
+ if (!(arg instanceof JSONPathQuery || arg instanceof FunctionExtension && this.functionRegister.get(arg.name)?.returnType === FunctionExpressionType.NodesType)) {
2261
2468
  throw new JSONPathTypeError(`${token.value}() argument ${idx} must be of NodesType`, arg.token);
2262
2469
  }
2263
2470
  }
@@ -2312,6 +2519,19 @@ function compile(path) {
2312
2519
  return DEFAULT_ENVIRONMENT.compile(path);
2313
2520
  }
2314
2521
 
2522
+ /**
2523
+ * Return a {@link JSONPathNode} instance for the first object found in
2524
+ * _value_ matching _path_.
2525
+ *
2526
+ * @param path - A JSONPath query.
2527
+ * @param value - JSON-like data to which the query _path_ will be applied.
2528
+ * @returns The first node in _value_ matching _path_, or `undefined` if
2529
+ * there are no matches.
2530
+ */
2531
+ function match(path, value) {
2532
+ return DEFAULT_ENVIRONMENT.match(path, value);
2533
+ }
2534
+
2315
2535
  var index$1 = /*#__PURE__*/Object.freeze({
2316
2536
  __proto__: null,
2317
2537
  DEFAULT_ENVIRONMENT: DEFAULT_ENVIRONMENT,
@@ -2323,6 +2543,7 @@ var index$1 = /*#__PURE__*/Object.freeze({
2323
2543
  JSONPathLexerError: JSONPathLexerError,
2324
2544
  JSONPathNode: JSONPathNode,
2325
2545
  JSONPathNodeList: JSONPathNodeList,
2546
+ JSONPathRecursionLimitError: JSONPathRecursionLimitError,
2326
2547
  JSONPathSyntaxError: JSONPathSyntaxError,
2327
2548
  JSONPathTypeError: JSONPathTypeError,
2328
2549
  Nothing: Nothing,
@@ -2331,6 +2552,7 @@ var index$1 = /*#__PURE__*/Object.freeze({
2331
2552
  compile: compile,
2332
2553
  expressions: expression,
2333
2554
  functions: index$2,
2555
+ match: match,
2334
2556
  query: query,
2335
2557
  selectors: selectors
2336
2558
  });
@@ -2631,6 +2853,15 @@ class JSONPatch {
2631
2853
  }
2632
2854
  }
2633
2855
 
2856
+ /**
2857
+ * @returns an iterator over ops in this patch.
2858
+ */
2859
+ *[Symbol.iterator]() {
2860
+ for (const op of this.ops) {
2861
+ yield op.toObject();
2862
+ }
2863
+ }
2864
+
2634
2865
  /**
2635
2866
  *
2636
2867
  * @param path -
@@ -2806,6 +3037,6 @@ var index = /*#__PURE__*/Object.freeze({
2806
3037
  apply: apply
2807
3038
  });
2808
3039
 
2809
- const version = "0.1.0";
3040
+ const version = "0.2.0";
2810
3041
 
2811
- export { FunctionExpressionType, JSONPatch, JSONPatchError, JSONPatchTestFailure, JSONPath, JSONPathEnvironment, JSONPathError, JSONPathIndexError, JSONPathLexerError, JSONPathNode, JSONPathNodeList, JSONPathSyntaxError, JSONPathTypeError, JSONPointer, Nothing, Token, TokenKind, UNDEFINED, apply, compile, index as jsonpatch, index$1 as jsonpath, index$3 as jsonpointer, query, resolve, version };
3042
+ export { DEFAULT_ENVIRONMENT, FunctionExpressionType, JSONPatch, JSONPatchError, JSONPatchTestFailure, JSONPath, JSONPathEnvironment, JSONPathError, JSONPathIndexError, JSONPathLexerError, JSONPathNode, JSONPathNodeList, JSONPathRecursionLimitError, JSONPathSyntaxError, JSONPathTypeError, JSONPointer, Nothing, RelativeJSONPointer, Token, TokenKind, UNDEFINED, apply, compile, index as jsonpatch, index$1 as jsonpath, index$3 as jsonpointer, query, resolve, version };