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