jilt 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sandro Hawke
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,432 @@
1
+ # jilt - JSON object filtering
2
+
3
+ Toolkit for turning human-readable/human-writable filter expressions
4
+ into executable functions which check if a JSON object matches.
5
+
6
+ Vibe coded by codex using model gpt-5.2-codex.
7
+
8
+ ## Quickstart
9
+
10
+ ```js
11
+ import jilt from '@walkable/jilt'
12
+
13
+ const expr1 = jilt.parse('color = "red" and weight < 3.2 and size.x > 1')
14
+
15
+ console.log(expr1.tree())
16
+ // ['and', ['eq', ['field', 'color'], ['literal', 'red']], ...]
17
+
18
+ const func1 = expr1.compile()
19
+
20
+ console.log(func1({color: 'red', weight: 3, size: {x: 2, y: 2}}))
21
+ // => true
22
+
23
+ console.log(func1({color: 'red', weight: 3, size: {x: 1, y: 2}}))
24
+ // => false
25
+ ```
26
+
27
+ ## CLI
28
+
29
+ ```bash
30
+ # Test an expression against JSON input
31
+ echo '{"color": "red", "weight": 3}' | jilt 'color = "red" and weight < 5'
32
+ # => true
33
+
34
+ # Parse and display the AST
35
+ jilt --tree 'color = "red" and weight < 5'
36
+ # => ['and', ['eq', ['field', 'color'], ['literal', 'red']], ['lt', ['field', 'weight'], ['literal', 5]]]
37
+
38
+ # Interactive mode - enter JSON objects, see if they match
39
+ jilt -i 'price < 100 and inStock = true'
40
+ > {"price": 50, "inStock": true}
41
+ true
42
+ > {"price": 150, "inStock": true}
43
+ false
44
+ ```
45
+
46
+ ## Expression Syntax
47
+
48
+ ### Literals
49
+
50
+ | Type | Examples |
51
+ |------|----------|
52
+ | String | `"hello"`, `'hello'` |
53
+ | Number | `42`, `3.14`, `-10`, `2.5e10` |
54
+ | Boolean | `true`, `false` |
55
+ | Null | `null` |
56
+
57
+ ### Field Access
58
+
59
+ Access object properties using dot notation or bracket notation:
60
+
61
+ ```
62
+ name # top-level field
63
+ user.name # nested field
64
+ user.address.city # deeply nested
65
+ items[0] # array index
66
+ items[0].name # array index then field
67
+ user["first name"] # bracket notation for special characters
68
+ ```
69
+
70
+ ### Comparison Operators
71
+
72
+ | Operator | Aliases | Description |
73
+ |----------|---------|-------------|
74
+ | `=` | `==`, `eq` | Equal |
75
+ | `!=` | `<>`, `ne` | Not equal |
76
+ | `<` | `lt` | Less than |
77
+ | `<=` | `le` | Less than or equal |
78
+ | `>` | `gt` | Greater than |
79
+ | `>=` | `ge` | Greater than or equal |
80
+
81
+ ### Logical Operators
82
+
83
+ | Operator | Description |
84
+ |----------|-------------|
85
+ | `and` | Logical AND |
86
+ | `or` | Logical OR |
87
+ | `not` | Logical NOT |
88
+
89
+ Parentheses control precedence:
90
+
91
+ ```
92
+ (color = "red" or color = "blue") and weight < 5
93
+ not (status = "deleted")
94
+ ```
95
+
96
+ ### Arithmetic Operators
97
+
98
+ | Operator | Description |
99
+ |----------|-------------|
100
+ | `+` | Addition |
101
+ | `-` | Subtraction |
102
+ | `*` | Multiplication |
103
+ | `/` | Division |
104
+ | `%` | Modulo |
105
+
106
+ ```
107
+ price * quantity > 100
108
+ (width + height) / 2 >= 10
109
+ count % 2 = 0
110
+ ```
111
+
112
+ ### String Functions
113
+
114
+ | Function | Description | Example |
115
+ |----------|-------------|---------|
116
+ | `length(s)` | String length | `length(name) > 5` |
117
+ | `upper(s)` | Uppercase | `upper(status) = "ACTIVE"` |
118
+ | `lower(s)` | Lowercase | `lower(email) = "test@example.com"` |
119
+ | `trim(s)` | Remove leading/trailing whitespace | `trim(input) != ""` |
120
+ | `contains(s, sub)` | Check if string contains substring | `contains(title, "urgent")` |
121
+ | `startsWith(s, prefix)` | Check if string starts with prefix | `startsWith(id, "usr_")` |
122
+ | `endsWith(s, suffix)` | Check if string ends with suffix | `endsWith(filename, ".json")` |
123
+ | `substr(s, start)` | Substring from start index | `substr(code, 0, 2) = "US"` |
124
+ | `substr(s, start, len)` | Substring with length | `substr(code, 0, 2) = "US"` |
125
+ | `concat(s1, s2, ...)` | Concatenate strings | `concat(first, " ", last)` |
126
+ | `replace(s, old, new)` | Replace first occurrence | `replace(path, "/old/", "/new/")` |
127
+ | `split(s, delim)` | Split string into array | `length(split(tags, ",")) > 2` |
128
+
129
+ ### Pattern Matching
130
+
131
+ | Function | Description | Example |
132
+ |----------|-------------|---------|
133
+ | `regex(s, pattern)` | Match against regular expression | `regex(email, "^[a-z]+@[a-z]+\\.[a-z]+$")` |
134
+ | `like(s, pattern)` | SQL-style wildcards: `%` = any chars, `_` = single char | `like(email, "%@gmail.com")` |
135
+ | `glob(s, pattern)` | Shell-style wildcards: `*` = any chars, `?` = single char | `glob(filename, "*.json")` |
136
+
137
+ Pattern matching examples:
138
+
139
+ ```
140
+ # SQL-style LIKE
141
+ like(name, "John%") # starts with "John"
142
+ like(code, "A_B") # "A" + any single char + "B"
143
+ like(email, "%@%.com") # contains "@" and ends with ".com"
144
+
145
+ # Shell-style glob
146
+ glob(file, "*.txt") # ends with ".txt"
147
+ glob(path, "/home/*/docs") # any user's docs folder
148
+ glob(name, "test?.js") # test1.js, testA.js, etc.
149
+
150
+ # Regular expressions
151
+ regex(phone, "^\\d{3}-\\d{4}$") # 123-4567
152
+ regex(id, "^[A-Z]{2}\\d{6}$") # US123456
153
+ regex(version, "^v\\d+\\.\\d+\\.\\d+$") # v1.2.3
154
+ ```
155
+
156
+ ### Path Functions
157
+
158
+ | Function | Description | Example |
159
+ |----------|-------------|---------|
160
+ | `basename(path)` | Get filename from path | `basename("/foo/bar.txt") = "bar.txt"` |
161
+ | `dirname(path)` | Get directory from path | `dirname("/foo/bar.txt") = "/foo"` |
162
+ | `ext(path)` | Get file extension (without dot) | `ext("/foo/bar.txt") = "txt"` |
163
+
164
+ Path function examples:
165
+
166
+ ```
167
+ ext(filename) = "json"
168
+ basename(path) = "config.yaml"
169
+ dirname(file) = "/etc/myapp"
170
+ startsWith(dirname(path), "/home/admin")
171
+ ```
172
+
173
+ ### Array Functions
174
+
175
+ | Function | Description | Example |
176
+ |----------|-------------|---------|
177
+ | `length(arr)` | Array length | `length(items) > 0` |
178
+ | `contains(arr, val)` | Check if array contains value | `contains(tags, "featured")` |
179
+ | `first(arr)` | First element | `first(scores) > 90` |
180
+ | `last(arr)` | Last element | `last(events).type = "complete"` |
181
+ | `at(arr, index)` | Element at index | `at(items, 2).price < 10` |
182
+ | `sum(arr)` | Sum of numeric array | `sum(prices) < 1000` |
183
+ | `avg(arr)` | Average of numeric array | `avg(scores) >= 70` |
184
+ | `min(arr)` | Minimum value | `min(bids) > 100` |
185
+ | `max(arr)` | Maximum value | `max(temperatures) < 30` |
186
+ | `join(arr, delim)` | Join array into string | `join(names, ", ")` |
187
+ | `reverse(arr)` | Reverse array | `first(reverse(items))` |
188
+ | `sort(arr)` | Sort array ascending | `first(sort(scores))` |
189
+ | `unique(arr)` | Remove duplicates | `length(unique(tags)) = length(tags)` |
190
+
191
+ ### Array Predicates
192
+
193
+ Test conditions across array elements using `@` to reference the current element:
194
+
195
+ | Function | Description | Example |
196
+ |----------|-------------|---------|
197
+ | `any(arr, pred)` | True if any element matches | `any(items, @.price > 100)` |
198
+ | `all(arr, pred)` | True if all elements match | `all(scores, @ >= 60)` |
199
+ | `none(arr, pred)` | True if no elements match | `none(items, @.status = "error")` |
200
+ | `count(arr, pred)` | Count matching elements | `count(items, @.inStock) > 5` |
201
+ | `filter(arr, pred)` | Filter to matching elements | `length(filter(users, @.active)) > 0` |
202
+ | `map(arr, expr)` | Transform elements | `sum(map(items, @.price * @.qty))` |
203
+
204
+ ### Type Checking
205
+
206
+ | Function | Description | Example |
207
+ |----------|-------------|---------|
208
+ | `type(val)` | Get type as string | `type(data) = "array"` |
209
+ | `isString(val)` | Check if string | `isString(name)` |
210
+ | `isNumber(val)` | Check if number | `isNumber(count)` |
211
+ | `isBool(val)` | Check if boolean | `isBool(enabled)` |
212
+ | `isNull(val)` | Check if null | `isNull(deletedAt)` |
213
+ | `isArray(val)` | Check if array | `isArray(tags)` |
214
+ | `isObject(val)` | Check if object | `isObject(metadata)` |
215
+
216
+ ### Conditional
217
+
218
+ ```
219
+ if(condition, thenValue, elseValue)
220
+ ```
221
+
222
+ Example:
223
+ ```
224
+ if(premium, price * 0.9, price) < 50
225
+ ```
226
+
227
+ ### Null Handling
228
+
229
+ | Operator | Description | Example |
230
+ |----------|-------------|---------|
231
+ | `??` | Null coalescing | `(nickname ?? name) = "Admin"` |
232
+ | `?.` | Optional chaining | `user?.profile?.avatar != null` |
233
+
234
+ ## Operator Precedence
235
+
236
+ From highest to lowest:
237
+
238
+ 1. Parentheses `()`
239
+ 2. Function calls, field access `.`, `[]`, `?.`
240
+ 3. Unary `not`, `-`
241
+ 4. Multiplicative `*`, `/`, `%`
242
+ 5. Additive `+`, `-`
243
+ 6. Comparison `=`, `!=`, `<`, `<=`, `>`, `>=`
244
+ 7. Logical `and`
245
+ 8. Logical `or`
246
+ 9. Null coalescing `??`
247
+
248
+ ## Complex Examples
249
+
250
+ ### Computed string comparison
251
+
252
+ Check if concatenated name fields match the full name:
253
+
254
+ ```
255
+ concat(firstName, " ", lastName) = fullName
256
+ ```
257
+
258
+ With case-insensitive comparison:
259
+
260
+ ```
261
+ lower(concat(firstName, " ", lastName)) = lower(fullName)
262
+ ```
263
+
264
+ ### Validating data consistency
265
+
266
+ Check that line items sum to the total:
267
+
268
+ ```
269
+ sum(map(items, @.price * @.quantity)) = orderTotal
270
+ ```
271
+
272
+ Verify no duplicate IDs in an array:
273
+
274
+ ```
275
+ length(unique(map(items, @.id))) = length(items)
276
+ ```
277
+
278
+ ### Complex filtering conditions
279
+
280
+ Find orders with at least one high-value item that's in stock:
281
+
282
+ ```
283
+ any(items, @.price > 100 and @.inStock = true) and status != "cancelled"
284
+ ```
285
+
286
+ Filter files by extension and location:
287
+
288
+ ```
289
+ ext(path) = "log" and startsWith(dirname(path), "/var/log")
290
+ ```
291
+
292
+ Match versioned config files:
293
+
294
+ ```
295
+ glob(filename, "config-v*.json") and dirname(path) = "/etc/myapp"
296
+ ```
297
+
298
+ ### Conditional pricing logic
299
+
300
+ Apply tiered discount based on quantity:
301
+
302
+ ```
303
+ price * quantity * if(quantity > 100, 0.8, if(quantity > 50, 0.9, 1.0)) < budget
304
+ ```
305
+
306
+ ### Working with nested data
307
+
308
+ Check that all addresses in a user's profile have a valid zip code:
309
+
310
+ ```
311
+ all(user.addresses, regex(@.zipCode, "^\\d{5}(-\\d{4})?$"))
312
+ ```
313
+
314
+ Find users who have made a purchase in a specific category:
315
+
316
+ ```
317
+ any(purchases, @.category = "electronics" and @.amount > 500)
318
+ ```
319
+
320
+ ### Null-safe operations
321
+
322
+ Handle potentially missing nested fields:
323
+
324
+ ```
325
+ (user?.profile?.email ?? user?.email ?? "unknown") != "unknown"
326
+ ```
327
+
328
+ Check optional array:
329
+
330
+ ```
331
+ length(tags ?? []) > 0
332
+ ```
333
+
334
+ ## AST Format
335
+
336
+ The parsed expression is represented as a JSON-compatible tree:
337
+
338
+ ```js
339
+ // Input: 'color = "red" and price < 100'
340
+ // AST:
341
+ ['and',
342
+ ['eq', ['field', 'color'], ['literal', 'red']],
343
+ ['lt', ['field', 'price'], ['literal', 100]]
344
+ ]
345
+ ```
346
+
347
+ ### Node Types
348
+
349
+ | Node | Format |
350
+ |------|--------|
351
+ | Literal | `['literal', value]` |
352
+ | Field access | `['field', 'name']` or `['field', 'parent', 'child']` |
353
+ | Comparison | `['eq' \| 'ne' \| 'lt' \| 'le' \| 'gt' \| 'ge', left, right]` |
354
+ | Logical | `['and' \| 'or', ...children]` or `['not', child]` |
355
+ | Arithmetic | `['add' \| 'sub' \| 'mul' \| 'div' \| 'mod', left, right]` |
356
+ | Function call | `['call', 'funcName', ...args]` |
357
+ | Array index | `['index', array, indexExpr]` |
358
+ | Conditional | `['if', condition, thenExpr, elseExpr]` |
359
+ | Null coalesce | `['coalesce', left, right]` |
360
+ | Optional chain | `['optfield', base, 'fieldName']` |
361
+
362
+ ## JavaScript API
363
+
364
+ ### `jilt.parse(expression: string): Expression`
365
+
366
+ Parse an expression string into an Expression object.
367
+
368
+ ```js
369
+ const expr = jilt.parse('status = "active"')
370
+ ```
371
+
372
+ ### `Expression.tree(): Array`
373
+
374
+ Get the AST representation.
375
+
376
+ ```js
377
+ expr.tree()
378
+ // => ['eq', ['field', 'status'], ['literal', 'active']]
379
+ ```
380
+
381
+ ### `Expression.compile(): (obj: any) => boolean`
382
+
383
+ Compile to an executable function.
384
+
385
+ ```js
386
+ const match = expr.compile()
387
+ match({status: 'active'}) // => true
388
+ match({status: 'deleted'}) // => false
389
+ ```
390
+
391
+ ### `Expression.toString(): string`
392
+
393
+ Convert back to expression string (normalized form).
394
+
395
+ ```js
396
+ jilt.parse('x=1 AND y = 2').toString()
397
+ // => 'x = 1 and y = 2'
398
+ ```
399
+
400
+ ### `jilt.evaluate(expression: string, obj: any): boolean`
401
+
402
+ Parse, compile, and evaluate in one step.
403
+
404
+ ```js
405
+ jilt.evaluate('price < 100', {price: 50})
406
+ // => true
407
+ ```
408
+
409
+ ### `jilt.evaluateConstant(node: ExprNode, options?): unknown`
410
+
411
+ Evaluate an AST node as a constant expression (no input object needed).
412
+
413
+ ```js
414
+ const expr = jilt.parse('2 + 3 * 4')
415
+ jilt.evaluateConstant(expr.tree())
416
+ // => 14
417
+ ```
418
+
419
+ ## Error Handling
420
+
421
+ Parse errors include position information:
422
+
423
+ ```js
424
+ try {
425
+ jilt.parse('name = ')
426
+ } catch (e) {
427
+ console.log(e.message)
428
+ // => "Unexpected end of expression at position 7"
429
+ console.log(e.position)
430
+ // => 7
431
+ }
432
+ ```
package/dist/ast.d.ts ADDED
@@ -0,0 +1,62 @@
1
+ export type LiteralValue = string | number | boolean | null | unknown[];
2
+ export type LiteralNode = {
3
+ type: 'literal';
4
+ value: LiteralValue;
5
+ };
6
+ export type FieldNode = {
7
+ type: 'field';
8
+ base: ExprNode | null;
9
+ parts: string[];
10
+ };
11
+ export type OptFieldNode = {
12
+ type: 'optfield';
13
+ base: ExprNode;
14
+ field: string;
15
+ };
16
+ export type IndexNode = {
17
+ type: 'index';
18
+ base: ExprNode;
19
+ index: ExprNode;
20
+ };
21
+ export type CallNode = {
22
+ type: 'call';
23
+ name: string;
24
+ args: ExprNode[];
25
+ };
26
+ export type CompareOp = 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge';
27
+ export type CompareNode = {
28
+ type: 'compare';
29
+ op: CompareOp;
30
+ left: ExprNode;
31
+ right: ExprNode;
32
+ };
33
+ export type LogicalNode = {
34
+ type: 'and' | 'or';
35
+ left: ExprNode;
36
+ right: ExprNode;
37
+ };
38
+ export type UnaryNode = {
39
+ type: 'not' | 'neg';
40
+ expr: ExprNode;
41
+ };
42
+ export type BinaryOp = 'add' | 'sub' | 'mul' | 'div' | 'mod';
43
+ export type BinaryNode = {
44
+ type: 'binary';
45
+ op: BinaryOp;
46
+ left: ExprNode;
47
+ right: ExprNode;
48
+ };
49
+ export type IfNode = {
50
+ type: 'if';
51
+ cond: ExprNode;
52
+ thenExpr: ExprNode;
53
+ elseExpr: ExprNode;
54
+ };
55
+ export type CoalesceNode = {
56
+ type: 'coalesce';
57
+ left: ExprNode;
58
+ right: ExprNode;
59
+ };
60
+ export type ExprNode = LiteralNode | FieldNode | OptFieldNode | IndexNode | CallNode | CompareNode | LogicalNode | UnaryNode | BinaryNode | IfNode | CoalesceNode;
61
+ export type TreeNode = ['literal', LiteralValue] | ['field', ...string[]] | ['field', TreeNode, ...string[]] | ['optfield', TreeNode, string] | ['index', TreeNode, TreeNode] | ['call', string, ...TreeNode[]] | ['eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge', TreeNode, TreeNode] | ['and' | 'or', ...TreeNode[]] | ['not', TreeNode] | ['add' | 'sub' | 'mul' | 'div' | 'mod', TreeNode, TreeNode] | ['if', TreeNode, TreeNode, TreeNode] | ['coalesce', TreeNode, TreeNode];
62
+ //# sourceMappingURL=ast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"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"}
package/dist/ast.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.js","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":""}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ import { parse } from './index.js';
3
+ import { getHelp } from './help.js';
4
+ import { createInterface } from 'node:readline';
5
+ function printUsage() {
6
+ console.log(`jilt - JSON object filtering\n\nUsage:\n jilt help [topic]\n jilt [--tree] '<expr>'\n jilt -i '<expr>'\n\nExamples:\n echo '{"color":"red"}' | jilt 'color = "red"'\n jilt --tree 'color = "red" and weight < 5'\n jilt -i 'price < 100 and inStock = true'\n`);
7
+ }
8
+ async function main() {
9
+ const args = process.argv.slice(2);
10
+ const helpFlag = args.includes('--help') || args.includes('-h');
11
+ const helpCommand = args[0] === 'help';
12
+ if (helpFlag || helpCommand) {
13
+ const topic = helpCommand
14
+ ? args[1]
15
+ : args.find((arg) => arg !== '--help' && arg !== '-h');
16
+ console.log(getHelp(topic));
17
+ return;
18
+ }
19
+ const hasTree = args.includes('--tree');
20
+ const interactive = args.includes('-i');
21
+ const exprArg = args.filter((arg) => arg !== '--tree' && arg !== '-i')[0];
22
+ if (!exprArg) {
23
+ printUsage();
24
+ process.exit(1);
25
+ }
26
+ const expression = parse(exprArg);
27
+ if (hasTree) {
28
+ console.log(JSON.stringify(expression.tree()));
29
+ return;
30
+ }
31
+ if (interactive) {
32
+ const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
33
+ rl.on('line', (line) => {
34
+ const trimmed = line.trim();
35
+ if (!trimmed)
36
+ return;
37
+ try {
38
+ const obj = JSON.parse(trimmed);
39
+ const result = expression.compile()(obj);
40
+ console.log(String(result));
41
+ }
42
+ catch (err) {
43
+ const message = err instanceof Error ? err.message : String(err);
44
+ console.error(message);
45
+ }
46
+ });
47
+ return;
48
+ }
49
+ const input = await readStdin();
50
+ if (!input.trim()) {
51
+ throw new Error('Expected JSON input on stdin');
52
+ }
53
+ const obj = JSON.parse(input);
54
+ const result = expression.compile()(obj);
55
+ console.log(String(result));
56
+ }
57
+ main().catch((err) => {
58
+ const message = err instanceof Error ? err.message : String(err);
59
+ console.error(message);
60
+ process.exit(1);
61
+ });
62
+ async function readStdin() {
63
+ const chunks = [];
64
+ for await (const chunk of process.stdin) {
65
+ chunks.push(Buffer.from(chunk));
66
+ }
67
+ return Buffer.concat(chunks).toString('utf8');
68
+ }
69
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { ExprNode, TreeNode } from './ast.js';
2
+ export type Strictness = boolean | null | undefined;
3
+ export declare class EvaluationError extends Error {
4
+ code?: 'missing' | 'type' | 'arity';
5
+ constructor(message: string, code?: 'missing' | 'type' | 'arity');
6
+ }
7
+ export type EvalOptions = {
8
+ strict?: Strictness;
9
+ allowFields?: boolean;
10
+ };
11
+ export declare function normalizeStrict(strict: Strictness): boolean;
12
+ export declare function compile(node: ExprNode, options?: EvalOptions): (obj: unknown) => boolean;
13
+ export declare function evaluate(node: ExprNode, obj: unknown, options?: EvalOptions): boolean;
14
+ export declare function evaluateConstant(node: ExprNode, options?: EvalOptions): unknown;
15
+ export declare function toTree(node: ExprNode): TreeNode;
16
+ export type { TreeNode };
17
+ //# sourceMappingURL=compiler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAMV,QAAQ,EAOR,QAAQ,EAET,MAAM,UAAU,CAAA;AAEjB,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAA;AAEnD,qBAAa,eAAgB,SAAQ,KAAK;IACxC,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAA;gBAEvB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO;CAKjE;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,CAAA;AASD,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAG3D;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,GAAE,WAAgB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAO5F;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAEzF;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAInF;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CA8B/C;AAisBD,YAAY,EAAE,QAAQ,EAAE,CAAA"}