@yesiree/jsonq 1.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.
package/README.md ADDED
@@ -0,0 +1,891 @@
1
+ # jsonq
2
+
3
+ Developer documentation for contributors.
4
+
5
+ > **User documentation:** See [DOCS.md](DOCS.md)
6
+
7
+ ## Overview
8
+
9
+ jsonq is a lightweight JSON query engine that implements a custom expression language for querying and transforming JSON data. The library provides a functional programming interface with method chaining, similar to JSONPath but with richer expression support.
10
+
11
+ ## Architecture
12
+
13
+ ### Core Components
14
+
15
+ **Tokenizer** (`tokenizePipeline`)
16
+
17
+ - Parses query strings into token streams
18
+ - Handles root reference (`$`), property access (`.prop`), array indexing (`[0]`), and method calls (`.method()`)
19
+ - Tracks token positions for extracting method parameters
20
+
21
+ **Evaluator** (`evaluatePipeline`)
22
+
23
+ - Executes token pipeline against data
24
+ - Maintains current value through transformations
25
+ - Handles nested data navigation
26
+
27
+ **Expression Parser** (`parseExpr`)
28
+
29
+ - Recursive descent parser for expressions
30
+ - Generates AST with nodes: literal, variable, property, arrayIndex, unary, binary, methodCall
31
+ - Supports operators: arithmetic, comparison, logical
32
+
33
+ **Expression Evaluator** (`evalExpr`, `evalNode`)
34
+
35
+ - Detects arrow function syntax with regex
36
+ - Creates variable bindings for named parameters
37
+ - Evaluates AST nodes with context (data, item, index, bindings)
38
+
39
+ **Method Executor** (`applyMethod`)
40
+
41
+ - Switch statement dispatching to method implementations
42
+ - Propagates bindings through nested method calls
43
+ - Handles recursive evaluation for nested transformations
44
+
45
+ ## Public API
46
+
47
+ ```javascript
48
+ export { query };
49
+ ```
50
+
51
+ **query(expression, data)**
52
+
53
+ - Main entry point
54
+ - Returns transformed data based on expression
55
+
56
+ ## Development
57
+
58
+ ### Setup
59
+
60
+ ```bash
61
+ npm install
62
+ npm test
63
+ ```
64
+
65
+ ### Running Tests
66
+
67
+ ```bash
68
+ npm test
69
+ ```
70
+
71
+ Test suite includes 98 tests covering:
72
+
73
+ - Tokenization and parsing
74
+ - All methods (map, filter, sort, etc.)
75
+ - Expression evaluation (arithmetic, logical, comparison)
76
+ - Nested method calls
77
+ - Arrow function syntax and underscore shorthand
78
+ - Root ($) and array ($array) variable access
79
+ - Edge cases and error handling
80
+
81
+ ### Building
82
+
83
+ ```bash
84
+ npm run build
85
+ ```
86
+
87
+ Outputs to `dist/`:
88
+
89
+ - `index.cjs` - CommonJS
90
+ - `index.mjs` - ES Module
91
+ - `index.umd.js` - UMD (browser)
92
+ - `index.d.ts` - TypeScript definitions
93
+
94
+ ## Implementation Details
95
+
96
+ ### Supported Methods
97
+
98
+ ```javascript
99
+ const METHODS = [
100
+ "map",
101
+ "filter",
102
+ "join",
103
+ "sort",
104
+ "unique",
105
+ "first",
106
+ "last",
107
+ "count",
108
+ "sum",
109
+ "avg",
110
+ "min",
111
+ "max",
112
+ ];
113
+ ```
114
+
115
+ ### Token Types
116
+
117
+ - `root` - `$` reference
118
+ - `property` - `.prop` access
119
+ - `arrayIndex` - `[0]` access
120
+ - `method` - `.method()` call with params
121
+
122
+ ### Expression Syntax Features
123
+
124
+ **Variable syntaxes:**
125
+
126
+ 1. Root reference: `$`
127
+ 2. Built-in variables: `$item`, `$index`, `$array`
128
+ 3. Underscore shorthand: `_` (alias for `$item`)
129
+ 4. Arrow functions: `x => x.prop` (creates binding for `x`)
130
+
131
+ **Arrow function detection:**
132
+
133
+ ```javascript
134
+ const arrowMatch = expr.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*=>\s*(.+)$/);
135
+ ```
136
+
137
+ **Binding propagation:**
138
+ Arrow function bindings are passed through nested method calls, enabling parent scope access in nested iterations.
139
+
140
+ ### AST Node Types
141
+
142
+ - `literal` - Numbers, strings, booleans, null, undefined
143
+ - `variable` - `$`, `$item`, `$index`, `$array`, `_`, or named params
144
+ - `property` - Property access (`obj.prop`)
145
+ - `arrayIndex` - Array indexing (`arr[0]`)
146
+ - `unary` - Unary operators (`!`, `-`)
147
+ - `binary` - Binary operators (arithmetic, comparison, logical)
148
+ - `methodCall` - Nested method invocations
149
+
150
+ ### Sort Implementation
151
+
152
+ Descending sort detected by `-` prefix:
153
+
154
+ ```javascript
155
+ const descending = expr.startsWith("-");
156
+ const sortExpr = descending ? expr.substring(1) : expr;
157
+ ```
158
+
159
+ ### Date Handling
160
+
161
+ Dates prefixed with `@` are parsed to timestamps:
162
+
163
+ ```javascript
164
+ if (token.startsWith("@")) {
165
+ return new Date(token.substring(1)).getTime();
166
+ }
167
+ ```
168
+
169
+ ## Contributing
170
+
171
+ ### Code Style
172
+
173
+ - No comments in implementation code
174
+ - Variable names provide documentation
175
+ - Concise, functional style
176
+ - Manual parsing (no external parser libraries)
177
+
178
+ ### Adding Methods
179
+
180
+ 1. Add method name to `METHODS` array
181
+ 2. Add case to `applyMethod` switch statement
182
+ 3. Propagate `bindings` parameter for nested support
183
+ 4. Add tests to `index.test.js`
184
+ 5. Update DOCS.md with user-facing documentation
185
+
186
+ ### Adding Operators
187
+
188
+ 1. Update `tokenizeExpr` regex to include new operator
189
+ 2. Add operator precedence in `parseExpr`
190
+ 3. Handle in `evalNode` binary operator cases
191
+ 4. Add tests
192
+
193
+ ### Testing Guidelines
194
+
195
+ - Test each method with basic usage
196
+ - Test chaining combinations
197
+ - Test edge cases (empty arrays, null values)
198
+ - Test error conditions
199
+ - Test nested method scenarios
200
+ - Test all three syntax styles (`$item`, `_`, arrow functions)
201
+
202
+ ## Project Structure
203
+
204
+ ```
205
+ ├── src/
206
+ │ ├── index.js # Main implementation
207
+ │ └── cli.js # CLI tool (if applicable)
208
+ ├── test/
209
+ │ └── index.test.js # Test suite
210
+ ├── examples/
211
+ │ └── example.js # Usage examples
212
+ ├── dist/ # Build outputs (generated)
213
+ ├── DOCS.md # User documentation
214
+ ├── README.md # Developer documentation (this file)
215
+ ├── LICENSE # MIT License
216
+ ├── .gitignore
217
+ ├── package.json
218
+ ├── rollup.config.mjs
219
+ └── jest.config.js
220
+ ```
221
+
222
+ ## Design Decisions
223
+
224
+ ### Manual Parsing
225
+
226
+ We use a manual tokenizer/parser instead of a library to:
227
+
228
+ - Keep bundle size minimal
229
+ - Avoid external dependencies
230
+ - Maintain full control over syntax
231
+ - Optimize for our specific use case
232
+
233
+ ### Three Syntax Styles
234
+
235
+ Supporting `$item`, `_`, and arrow functions provides:
236
+
237
+ - Backward compatibility (`$item`)
238
+ - Conciseness (`_`)
239
+ - Clarity and parent scope access (arrow functions)
240
+
241
+ ### No Pipeline Operator
242
+
243
+ The `>` operator was removed to avoid:
244
+
245
+ - Confusion with greater-than comparisons
246
+ - Additional parsing complexity
247
+ - Preference for dot-chaining which is more familiar
248
+
249
+ ### Method Chaining Only
250
+
251
+ All operations chain with `.method()` syntax for consistency with JavaScript's native array methods.
252
+
253
+ ## Performance Considerations
254
+
255
+ - No AST caching (expressions evaluated fresh each time)
256
+ - Recursive descent parser (adequate for expression complexity)
257
+ - Methods execute eagerly (no lazy evaluation)
258
+ - Suitable for small to medium datasets
259
+
260
+ ## Known Limitations
261
+
262
+ - No async support
263
+ - No custom function definitions
264
+ - Single-parameter arrow functions only
265
+ - No array/object literal construction in expressions
266
+ - Limited to JavaScript-compatible JSON data
267
+
268
+ ## Release Process
269
+
270
+ 1. Update version in `package.json`
271
+ 2. Run tests: `npm test`
272
+ 3. Build: `npm run build`
273
+ 4. Commit changes
274
+ 5. Tag release: `git tag v1.0.0`
275
+ 6. Push: `git push && git push --tags`
276
+ 7. Publish: `npm publish`
277
+
278
+ ## License
279
+
280
+ MIT
281
+ query("$.users.map($item.name)", data);
282
+
283
+ // Arithmetic
284
+ query("$.items.map(price * 1.1)", data);
285
+ query("$.items.map(\_.price \* 1.1)", data);
286
+
287
+ // Complex expressions
288
+ query("$.users.map(u => u.age \* 2 + 10)", data);
289
+
290
+ ````
291
+
292
+ ### filter(condition)
293
+
294
+ Select elements matching a condition.
295
+
296
+ ```javascript
297
+ // Comparison operators
298
+ query("$.users.filter(age > 25)", data);
299
+ query('$.users.filter(name == "Alice")', data);
300
+
301
+ // Using underscore
302
+ query("$.users.filter(_.age > 25)", data);
303
+
304
+ // Using arrow syntax
305
+ query("$.users.filter(u => u.age > 25 && u.active)", data);
306
+
307
+ // Logical operators
308
+ query("$.users.filter(age > 25 && active == true)", data);
309
+ query("$.users.filter(age < 20 || age > 60)", data);
310
+
311
+ // Negation
312
+ query("$.users.filter(!active)", data);
313
+ query("$.users.filter(!_.deleted)", data);
314
+ ````
315
+
316
+ ### sort(expression)
317
+
318
+ Sort array elements. Use negative expression for descending order.
319
+
320
+ ```javascript
321
+ // Sort ascending (default)
322
+ query("$.users.sort(age)", data);
323
+ query("$.users.sort(name)", data);
324
+
325
+ // Sort descending (use negative)
326
+ query("$.users.sort(-age)", data);
327
+ query("$.users.sort(-name)", data);
328
+
329
+ // Sort with expressions
330
+ query("$.items.sort(price * quantity)", data);
331
+ query("$.items.sort(-(price * quantity))", data); // Descending
332
+ ```
333
+
334
+ ### join(separator)
335
+
336
+ Join array elements into a string.
337
+
338
+ ```javascript
339
+ query('$.tags.join(", ")', data);
340
+ query('$.users.map(name).join(" | ")', data);
341
+ ```
342
+
343
+ ### unique(expression)
344
+
345
+ Get unique values from an array.
346
+
347
+ ```javascript
348
+ // Unique primitives
349
+ query("$.tags.unique($item)", data);
350
+
351
+ // Unique by property
352
+ query("$.users.unique(department)", data);
353
+ ```
354
+
355
+ ### first()
356
+
357
+ Get the first element.
358
+
359
+ ```javascript
360
+ query("$.users.first()", data);
361
+ query("$.users.filter(active).first()", data);
362
+ ```
363
+
364
+ ### last()
365
+
366
+ Get the last element.
367
+
368
+ ```javascript
369
+ query("$.users.last()", data);
370
+ query("$.users.sort(age).last()", data);
371
+ ```
372
+
373
+ ### count()
374
+
375
+ Count array elements.
376
+
377
+ ```javascript
378
+ query("$.users.count()", data);
379
+ query("$.users.filter(active).count()", data);
380
+ ```
381
+
382
+ ### sum(expression)
383
+
384
+ Sum values in an array.
385
+
386
+ ```javascript
387
+ query("$.items.sum(price)", data);
388
+ query("$.numbers.sum($item)", data);
389
+ ```
390
+
391
+ ### avg(expression)
392
+
393
+ Calculate average of values.
394
+
395
+ ```javascript
396
+ query("$.users.avg(age)", data);
397
+ query("$.items.avg(price * quantity)", data);
398
+ ```
399
+
400
+ ### min(expression)
401
+
402
+ Find minimum value.
403
+
404
+ ```javascript
405
+ query("$.users.min(age)", data);
406
+ query("$.items.min(price)", data);
407
+ ```
408
+
409
+ ### max(expression)
410
+
411
+ Find maximum value.
412
+
413
+ ```javascript
414
+ query("$.users.max(age)", data);
415
+ query("$.items.max(price)", data);
416
+ ```
417
+
418
+ ## Expressions
419
+
420
+ ### Variables
421
+
422
+ **Built-in variables:**
423
+
424
+ - `$item` - Current element in iteration
425
+ - `$index` - Current index in iteration
426
+ - `$data` - Root data object
427
+ - `_` - Shorthand for `$item` (concise syntax)
428
+
429
+ **Arrow function syntax:**
430
+ Use arrow functions to name your parameters:
431
+
432
+ ```javascript
433
+ // Named parameter (any name you want)
434
+ query("$.users.map(user => user.name)", data);
435
+ query("$.users.filter(person => person.age > 25)", data);
436
+
437
+ // Access parent scope in nested iterations
438
+ query(
439
+ '$.departments.map(dept => dept.employees.map(emp => dept.name + ": " + emp.name))',
440
+ data,
441
+ );
442
+ ```
443
+
444
+ **Examples:**
445
+
446
+ ```javascript
447
+ // Using built-in variables
448
+ query("$.users.map($item.name)", data);
449
+ query("$.users.map($index)", data);
450
+ query("$.users.filter($item.age > $data.minAge)", data);
451
+
452
+ // Using underscore shorthand (more concise)
453
+ query("$.users.map(_.name)", data);
454
+ query("$.items.filter(_.price < 100)", data);
455
+
456
+ // Using arrow syntax for clarity
457
+ query("$.users.map(u => u.name)", data);
458
+ query("$.items.filter(item => item.price < 100)", data);
459
+ ```
460
+
461
+ **Nested references:**
462
+
463
+ ```javascript
464
+ // Underscore shadows in nested contexts
465
+ query("$.departments.map(_.employees.map(_.name))", data);
466
+ // Outer _ = department, inner _ = employee (shadowed)
467
+
468
+ // Use arrow syntax when you need parent access
469
+ query(
470
+ '$.departments.map(dept => dept.employees.map(emp => dept.name + ": " + emp.name))',
471
+ data,
472
+ );
473
+
474
+ // Mix both styles
475
+ query("$.groups.map(g => g.items.filter(_.value > 10).map(_.name))", data);
476
+ ```
477
+
478
+ ### Operators
479
+
480
+ **Arithmetic**: `+`, `-`, `*`, `/`, `%`
481
+
482
+ ```javascript
483
+ query("$.items.map(price * 1.2)", data);
484
+ query("$.users.filter(age % 2 == 0)", data);
485
+ ```
486
+
487
+ **Comparison**: `==`, `!=`, `<`, `>`, `<=`, `>=`
488
+
489
+ ```javascript
490
+ query("$.users.filter(age >= 18)", data);
491
+ query("$.items.filter(stock != 0)", data);
492
+ ```
493
+
494
+ **Logical**: `&&`, `||`, `!`
495
+
496
+ ```javascript
497
+ query("$.users.filter(age > 18 && active == true)", data);
498
+ query("$.items.filter(inStock || onOrder)", data);
499
+ query("$.users.filter(!deleted)", data);
500
+ ```
501
+
502
+ ### Literals
503
+
504
+ - **Numbers**: `42`, `3.14`, `-10`, `1.5e10`
505
+ - **Strings**: `"hello"`, `'world'`
506
+ - **Booleans**: `true`, `false`
507
+ - **Null**: `null`
508
+ - **Undefined**: `undefined`
509
+
510
+ ```javascript
511
+ query("$.users.filter(age > 25)", data);
512
+ query('$.users.filter(name == "Alice")', data);
513
+ query("$.users.filter(deleted == null)", data);
514
+ ```
515
+
516
+ ### Dates
517
+
518
+ Use `@` prefix for ISO date strings:
519
+
520
+ ```javascript
521
+ const data = [
522
+ { event: "Launch", date: "2024-01-15" },
523
+ { event: "Update", date: "2024-06-20" },
524
+ ];
525
+
526
+ query("filter(date > @2024-03-01).map(event)", data);
527
+ // => ['Update']
528
+
529
+ // With timestamps
530
+ query("filter(createdAt > @2024-01-01T12:00:00Z)", data);
531
+ ```
532
+
533
+ ### Property Access
534
+
535
+ Access nested properties in expressions:
536
+
537
+ ```javascript
538
+ query("$.users.map($item.address.city)", data);
539
+ query("$.users.map(_.address.city)", data);
540
+ query("$.users.map(u => u.address.city)", data);
541
+ query("$.items.filter($item.specs.weight > 100)", data);
542
+ ```
543
+
544
+ ### Array Access
545
+
546
+ Use brackets in expressions:
547
+
548
+ ```javascript
549
+ query("$.users.map($item.tags[0])", data);
550
+ query("$.users.map(_.tags[0])", data);
551
+ query("$.data.map($item.values[$index])", data);
552
+ ```
553
+
554
+ ### Nested Method Calls
555
+
556
+ Call methods within expressions for complex transformations:
557
+
558
+ ```javascript
559
+ const data = {
560
+ departments: [
561
+ {
562
+ name: "Engineering",
563
+ employees: [
564
+ { name: "Alice", age: 30 },
565
+ { name: "Bob", age: 25 },
566
+ { name: "Carol", age: 35 },
567
+ ],
568
+ },
569
+ {
570
+ name: "Sales",
571
+ employees: [
572
+ { name: "Dave", age: 40 },
573
+ { name: "Eve", age: 28 },
574
+ ],
575
+ },
576
+ ],
577
+ };
578
+
579
+ // Filter and map employees within each department (concise)
580
+ query("$.departments.map(_.employees.filter(age > 28).map(name))", data);
581
+ // => [['Alice', 'Carol'], ['Dave']]
582
+
583
+ // With arrow syntax for parent access
584
+ query(
585
+ '$.departments.map(dept => dept.employees.map(emp => dept.name + ": " + emp.name))',
586
+ data,
587
+ );
588
+ // => [['Engineering: Alice', 'Engineering: Bob', 'Engineering: Carol'], ['Sales: Dave', 'Sales: Eve']]
589
+
590
+ // Count employees per department
591
+ query("$.departments.map(_.employees.count())", data);
592
+ // => [3, 2]
593
+
594
+ // Average age per department
595
+ query("$.departments.map(_.employees.avg(age))", data);
596
+ // => [30, 34]
597
+
598
+ // Multiple levels of nesting with underscore
599
+ query("$.departments.map(_.employees.sort(age).first().name)", data);
600
+ // => ['Bob', 'Eve']
601
+ ```
602
+
603
+ ## Examples
604
+
605
+ ### E-commerce
606
+
607
+ ```javascript
608
+ const orders = {
609
+ items: [
610
+ { name: "Laptop", price: 999, quantity: 2, inStock: true },
611
+ { name: "Mouse", price: 25, quantity: 5, inStock: true },
612
+ { name: "Keyboard", price: 75, quantity: 0, inStock: false },
613
+ ],
614
+ };
615
+
616
+ // Total value of in-stock items
617
+ query("$.items.filter(inStock).map(price * quantity).sum($item)", orders);
618
+ // => 2048
619
+
620
+ // Most expensive item name
621
+ query("$.items.sort(price).last().name", orders);
622
+ // => 'Laptop'
623
+
624
+ // Average price
625
+ query("$.items.avg(price)", orders);
626
+ // => 366.33...
627
+ ```
628
+
629
+ ### User Management
630
+
631
+ ```javascript
632
+ const company = {
633
+ departments: [
634
+ {
635
+ name: "Engineering",
636
+ employees: [
637
+ { name: "Alice", salary: 120000, startDate: "2020-01-15" },
638
+ { name: "Bob", salary: 95000, startDate: "2021-06-01" },
639
+ ],
640
+ },
641
+ {
642
+ name: "Sales",
643
+ employees: [{ name: "Carol", salary: 85000, startDate: "2019-03-10" }],
644
+ },
645
+ ],
646
+ };
647
+
648
+ // All department names
649
+ query('$.departments.map(name).join(", ")', company);
650
+ // => 'Engineering, Sales'
651
+
652
+ // High earners in each department
653
+ query(
654
+ "$.departments.map($item.employees.filter(salary > 100000).map(name))",
655
+ company,
656
+ );
657
+ // => [['Alice'], []]
658
+
659
+ // Recent hires
660
+ query(
661
+ "$.departments[0].employees.filter(startDate > @2021-01-01).map(name)",
662
+ company,
663
+ );
664
+ // => ['Bob']
665
+
666
+ // Department sizes
667
+ query("$.departments.map($item.employees.count())", company);
668
+ // => [2, 1]
669
+ ```
670
+
671
+ ### Data Analysis
672
+
673
+ ```javascript
674
+ const metrics = {
675
+ daily: [
676
+ { date: "2024-01-01", views: 1200, clicks: 45 },
677
+ { date: "2024-01-02", views: 1500, clicks: 67 },
678
+ { date: "2024-01-03", views: 980, clicks: 32 },
679
+ ],
680
+ };
681
+
682
+ // Click-through rates
683
+ query("$.daily.map(clicks / views * 100)", metrics);
684
+ // => [3.75, 4.47, 3.27]
685
+
686
+ // Best performing day
687
+ query("$.daily.sort(clicks).last().date", metrics);
688
+ // => '2024-01-02'
689
+
690
+ // Total engagement
691
+ query("$.daily.sum(views + clicks)", metrics);
692
+ // => 3824
693
+ ```
694
+
695
+ ## API Reference
696
+
697
+ ### query(expression, data)
698
+
699
+ Execute a query against data.
700
+
701
+ **Parameters:**
702
+
703
+ - `expression` (string): Query expression
704
+ - `data` (any): Data to query
705
+
706
+ **Returns:** Query result
707
+
708
+ ```javascript
709
+ const result = query("$.users.filter(age > 25)", data);
710
+ ```
711
+
712
+ ### tokenize(expression)
713
+
714
+ Parse expression into tokens (exported for testing/debugging).
715
+
716
+ **Parameters:**
717
+
718
+ - `expression` (string): Query expression
719
+
720
+ **Returns:** Array of tokens
721
+
722
+ ```javascript
723
+ import { tokenize } from "jsonq";
724
+ const tokens = tokenize("$.users.map(name)");
725
+ ```
726
+
727
+ ### evaluate(tokens, data)
728
+
729
+ Evaluate parsed tokens against data (exported for testing/debugging).
730
+
731
+ **Parameters:**
732
+
733
+ - `tokens` (Array): Parsed tokens
734
+ - `data` (any): Data to evaluate
735
+
736
+ **Returns:** Evaluation result
737
+
738
+ ```javascript
739
+ import { evaluate, tokenize } from "jsonq";
740
+ const tokens = tokenize("$.users.count()");
741
+ const result = evaluate(tokens, data);
742
+ ```
743
+
744
+ ## Advanced Usage
745
+
746
+ ### Complex Filters
747
+
748
+ Combine multiple conditions:
749
+
750
+ ```javascript
751
+ query(
752
+ `
753
+ $.users
754
+ .filter(age >= 21 && age <= 65)
755
+ .filter(active == true)
756
+ .filter(verified == true)
757
+ .sort(name)
758
+ `,
759
+ data,
760
+ );
761
+ ```
762
+
763
+ ### Nested Access Patterns
764
+
765
+ Access deeply nested data:
766
+
767
+ ```javascript
768
+ query('$.company.departments[0].teams[2].members.filter(role == "lead")', data);
769
+ ```
770
+
771
+ Combine nested methods with property navigation:
772
+
773
+ ```javascript
774
+ // Get top performer from each department
775
+ query("$.departments.map($item.employees.sort(performance).last())", data);
776
+
777
+ // Filter departments by employee criteria
778
+ query(
779
+ "$.departments.filter($item.employees.filter(certified == true).count() > 5)",
780
+ data,
781
+ );
782
+
783
+ // Aggregate across multiple levels
784
+ query("$.regions.map($item.stores.map($item.sales.sum(amount)))", data);
785
+ ```
786
+
787
+ ### Dynamic Calculations
788
+
789
+ Use expressions in any method:
790
+
791
+ ```javascript
792
+ query("$.products.filter((price - cost) / price > 0.3)", data);
793
+ ```
794
+
795
+ ### Index-Based Operations
796
+
797
+ Use `$index` for position-dependent logic:
798
+
799
+ ```javascript
800
+ query("$.items.filter($index % 2 == 0)", data); // Even indices
801
+ query("$.items.map($index * 10 + $item.value)", data);
802
+ ```
803
+
804
+ ## Type Handling
805
+
806
+ ### Null and Undefined
807
+
808
+ Safe navigation with optional chaining behavior:
809
+
810
+ ```javascript
811
+ const data = [{ user: { name: "Alice" } }, { user: null }, {}];
812
+
813
+ query("map($item.user.name)", data);
814
+ // => ['Alice', undefined, undefined]
815
+ ```
816
+
817
+ ### Missing Properties
818
+
819
+ Accessing non-existent properties returns `undefined`:
820
+
821
+ ```javascript
822
+ query("$.users.map(nonexistent)", data);
823
+ // => [undefined, undefined, ...]
824
+ ```
825
+
826
+ ### Type Coercion
827
+
828
+ Dates are automatically coerced for comparison:
829
+
830
+ ```javascript
831
+ query("filter(dateString > @2024-01-01)", data);
832
+ ```
833
+
834
+ ## Performance Tips
835
+
836
+ 1. **Filter Early**: Apply filters before expensive operations
837
+
838
+ ```javascript
839
+ // Good
840
+ query("$.users.filter(active).map(expensiveOperation)", data);
841
+
842
+ // Less efficient
843
+ query("$.users.map(expensiveOperation).filter(active)", data);
844
+ ```
845
+
846
+ 2. **Avoid Redundant Sorts**: Sort only when necessary
847
+
848
+ ```javascript
849
+ // If you only need first/last, consider avoiding sort
850
+ query("$.users.sort(age).first()", data);
851
+ ```
852
+
853
+ 3. **Use Specific Expressions**: More specific filters run faster
854
+
855
+ ```javascript
856
+ // More specific
857
+ query("$.users.filter(id == 123)", data);
858
+
859
+ // Less specific
860
+ query("$.users.filter(id > 0)", data);
861
+ ```
862
+
863
+ ## Error Handling
864
+
865
+ The library throws errors for:
866
+
867
+ - Unknown methods
868
+ - Invalid expressions
869
+ - Unknown variables
870
+ - Unexpected tokens
871
+ - Invalid literals
872
+
873
+ ```javascript
874
+ try {
875
+ query("$.users.unknownMethod()", data);
876
+ } catch (error) {
877
+ console.error(error.message); // "Unknown method: unknownMethod"
878
+ }
879
+ ```
880
+
881
+ ## Testing
882
+
883
+ Run the test suite:
884
+
885
+ ```bash
886
+ npm test
887
+ ```
888
+
889
+ ## License
890
+
891
+ MIT