trilean 1.0.7 → 1.0.8

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 CHANGED
@@ -49,6 +49,122 @@ await evaluatePredicate(node, { age: 21 }, resolvers);
49
49
 
50
50
  See [Evaluator entry points](#evaluator-entry-points) and [Resolvers](#resolvers) for the full contract, and the [Worked example](#worked-example) for a larger tree combining boolean logic, a formula, and an aggregation.
51
51
 
52
+ ### A nested filter for a REST API search endpoint
53
+
54
+ A search endpoint's filter criteria are exactly the kind of thing this package is for: nested boolean logic, stored as JSON, that a client can construct, a non-developer can edit via a UI, and a server evaluates per record without ever hardcoding the filter or redeploying when it changes. There is no query-string DSL to parse and no ORM query-builder to translate into — the request body already is the tree:
55
+
56
+ ```http
57
+ POST /orders/search HTTP/1.1
58
+ Content-Type: application/json
59
+
60
+ {
61
+ "filter": {
62
+ "kind": "and",
63
+ "left": {
64
+ "kind": "textCompare",
65
+ "op": "equals",
66
+ "left": { "kind": "reference", "key": "status" },
67
+ "right": { "kind": "textLiteral", "value": "active" }
68
+ },
69
+ "right": {
70
+ "kind": "or",
71
+ "left": {
72
+ "kind": "compare",
73
+ "op": "gt",
74
+ "left": { "kind": "reference", "key": "orderTotal" },
75
+ "right": { "kind": "numberLiteral", "value": 100 }
76
+ },
77
+ "right": {
78
+ "kind": "memberOf",
79
+ "op": "in",
80
+ "operand": { "kind": "reference", "key": "category" },
81
+ "candidates": [
82
+ { "kind": "textLiteral", "value": "electronics" },
83
+ { "kind": "textLiteral", "value": "books" }
84
+ ]
85
+ }
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ `status equals "active" AND (orderTotal > 100 OR category is a preferred one)` — two levels of nesting: an `or` inside the right branch of an `and`. The server parses that body's `filter` field as a `PredicateNode` and evaluates it, unmodified, against each candidate order:
92
+
93
+ ```ts
94
+ import { evaluatePredicate, type PredicateNode, type Resolvers } from "trilean";
95
+
96
+ interface Order {
97
+ status: string;
98
+ orderTotal: number;
99
+ category: string;
100
+ }
101
+
102
+ // The parsed `filter` field from the request body above.
103
+ const filter: PredicateNode = {
104
+ kind: "and",
105
+ left: {
106
+ kind: "textCompare",
107
+ op: "equals",
108
+ left: { kind: "reference", key: "status" },
109
+ right: { kind: "textLiteral", value: "active" },
110
+ },
111
+ right: {
112
+ kind: "or",
113
+ left: {
114
+ kind: "compare",
115
+ op: "gt",
116
+ left: { kind: "reference", key: "orderTotal" },
117
+ right: { kind: "numberLiteral", value: 100 },
118
+ },
119
+ right: {
120
+ kind: "memberOf",
121
+ op: "in",
122
+ operand: { kind: "reference", key: "category" },
123
+ candidates: [
124
+ { kind: "textLiteral", value: "electronics" },
125
+ { kind: "textLiteral", value: "books" },
126
+ ],
127
+ },
128
+ },
129
+ };
130
+
131
+ const orderResolvers: Resolvers = {
132
+ async resolveValue(key, context) {
133
+ const order = context as Order;
134
+ switch (key) {
135
+ case "status":
136
+ return { found: true, value: { kind: "text", value: order.status } };
137
+ case "orderTotal":
138
+ return { found: true, value: { kind: "number", value: order.orderTotal } };
139
+ case "category":
140
+ return { found: true, value: { kind: "text", value: order.category } };
141
+ default:
142
+ return { found: false };
143
+ }
144
+ },
145
+ async resolveLookup() {
146
+ return { found: false };
147
+ },
148
+ async resolveCollection() {
149
+ return [];
150
+ },
151
+ };
152
+
153
+ const orders: Order[] = [
154
+ { status: "active", orderTotal: 42, category: "electronics" },
155
+ { status: "active", orderTotal: 150, category: "garden" },
156
+ { status: "cancelled", orderTotal: 200, category: "electronics" },
157
+ ];
158
+
159
+ const results = await Promise.all(
160
+ orders.map((order) => evaluatePredicate(filter, order, orderResolvers)),
161
+ );
162
+ const matching = orders.filter((_, i) => results[i]?.status === "definite" && results[i]?.value === true);
163
+ // => the first two orders match; the cancelled one doesn't reach the "or" at all, since "and" absorbs on its left operand's definite false
164
+ ```
165
+
166
+ See [`and`/`or`](#not-and-or), [`compare`](#compare), [`textCompare`](#textcompare), and [`memberOf`](#memberof) for the full node-kind reference.
167
+
52
168
  ## Build, test, and lint
53
169
 
54
170
  ```sh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trilean",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Consumer-agnostic, three-valued predicate/expression evaluation trees over injected resolvers -- no assumptions about consumer data, no I/O of its own.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1 +1 @@
1
- {"$defs":{"ExpressionNode":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"numberLiteral","type":"string"},"unit":{"additionalProperties":{"type":"number"},"propertyNames":{"type":"string"},"type":"object"},"value":{"type":"number"}},"required":["kind","value"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"textLiteral","type":"string"},"value":{"type":"string"}},"required":["kind","value"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"instantLiteral","type":"string"},"value":{"type":"string"}},"required":["kind","value"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"durationLiteral","type":"string"},"unit":{"enum":["ms","s","min","h","d"],"type":"string"},"value":{"type":"number"}},"required":["kind","value","unit"],"type":"object"},{"additionalProperties":false,"properties":{"key":{"$ref":"#/$defs/schema0"},"kind":{"const":"reference","type":"string"},"unit":{"additionalProperties":{"type":"number"},"propertyNames":{"type":"string"},"type":"object"}},"required":["kind","key"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"arithmetic","type":"string"},"left":{"$ref":"#/$defs/ExpressionNode"},"op":{"enum":["add","subtract","multiply","divide","power","modulo"],"type":"string"},"right":{"$ref":"#/$defs/ExpressionNode"}},"required":["kind","op","left","right"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"negate","type":"string"},"operand":{"$ref":"#/$defs/ExpressionNode"}},"required":["kind","operand"],"type":"object"},{"additionalProperties":false,"properties":{"args":{"items":{"$ref":"#/$defs/ExpressionNode"},"type":"array"},"fn":{"type":"string"},"kind":{"const":"call","type":"string"}},"required":["kind","fn","args"],"type":"object"},{"additionalProperties":false,"properties":{"keys":{"items":{"$ref":"#/$defs/ExpressionNode"},"type":"array"},"kind":{"const":"lookup","type":"string"},"table":{"$ref":"#/$defs/schema0"}},"required":["kind","table","keys"],"type":"object"},{"additionalProperties":false,"properties":{"cases":{"items":{"additionalProperties":false,"properties":{"then":{"$ref":"#/$defs/ExpressionNode"},"when":{"$ref":"#/$defs/PredicateNode"}},"required":["when","then"],"type":"object"},"type":"array"},"fallback":{"$ref":"#/$defs/ExpressionNode"},"kind":{"const":"conditional","type":"string"}},"required":["kind","cases","fallback"],"type":"object"},{"additionalProperties":false,"properties":{"collection":{"$ref":"#/$defs/schema0"},"combiner":{"oneOf":[{"additionalProperties":false,"properties":{"item":{"$ref":"#/$defs/ExpressionNode"},"mode":{"const":"max","type":"string"}},"required":["mode","item"],"type":"object"},{"additionalProperties":false,"properties":{"item":{"$ref":"#/$defs/ExpressionNode"},"mode":{"const":"min","type":"string"}},"required":["mode","item"],"type":"object"},{"additionalProperties":false,"properties":{"combine":{"$ref":"#/$defs/ExpressionNode"},"initial":{"$ref":"#/$defs/ExpressionNode"},"mode":{"const":"reduce","type":"string"}},"required":["mode","initial","combine"],"type":"object"}]},"filter":{"$ref":"#/$defs/PredicateNode"},"kind":{"const":"fold","type":"string"}},"required":["kind","collection","combiner"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"accumulator","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"delegate","type":"string"},"payload":{"$ref":"#/$defs/schema0"},"system":{"type":"string"}},"required":["kind","system","payload"],"type":"object"}]},"PredicateNode":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"not","type":"string"},"operand":{"$ref":"#/$defs/PredicateNode"}},"required":["kind","operand"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"and","type":"string"},"left":{"$ref":"#/$defs/PredicateNode"},"right":{"$ref":"#/$defs/PredicateNode"}},"required":["kind","left","right"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"or","type":"string"},"left":{"$ref":"#/$defs/PredicateNode"},"right":{"$ref":"#/$defs/PredicateNode"}},"required":["kind","left","right"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"allOf","type":"string"},"operands":{"items":{"$ref":"#/$defs/PredicateNode"},"type":"array"}},"required":["kind","operands"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"anyOf","type":"string"},"operands":{"items":{"$ref":"#/$defs/PredicateNode"},"type":"array"}},"required":["kind","operands"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"compare","type":"string"},"left":{"$ref":"#/$defs/ExpressionNode"},"op":{"enum":["gt","gte","lt","lte","eq","neq"],"type":"string"},"right":{"$ref":"#/$defs/ExpressionNode"}},"required":["kind","op","left","right"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"textCompare","type":"string"},"left":{"$ref":"#/$defs/ExpressionNode"},"op":{"enum":["equals","notEquals","matches","notMatches"],"type":"string"},"right":{"$ref":"#/$defs/ExpressionNode"}},"required":["kind","op","left","right"],"type":"object"},{"additionalProperties":false,"properties":{"candidates":{"items":{"$ref":"#/$defs/ExpressionNode"},"type":"array"},"kind":{"const":"memberOf","type":"string"},"op":{"enum":["in","notIn"],"type":"string"},"operand":{"$ref":"#/$defs/ExpressionNode"}},"required":["kind","op","operand","candidates"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"exists","type":"string"},"operand":{"$ref":"#/$defs/ExpressionNode"}},"required":["kind","operand"],"type":"object"},{"additionalProperties":false,"properties":{"collection":{"$ref":"#/$defs/schema0"},"filter":{"$ref":"#/$defs/PredicateNode"},"item":{"$ref":"#/$defs/PredicateNode"},"kind":{"const":"some","type":"string"}},"required":["kind","collection","item"],"type":"object"},{"additionalProperties":false,"properties":{"collection":{"$ref":"#/$defs/schema0"},"filter":{"$ref":"#/$defs/PredicateNode"},"item":{"$ref":"#/$defs/PredicateNode"},"kind":{"const":"every","type":"string"}},"required":["kind","collection","item"],"type":"object"}]},"schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"items":{"$ref":"#/$defs/schema0"},"type":"array"},{"additionalProperties":{"$ref":"#/$defs/schema0"},"propertyNames":{"type":"string"},"type":"object"}]}},"$id":"https://cdn.jsdelivr.net/npm/trilean@1.0.7/schemas/trilean.schema.json","$schema":"https://json-schema.org/draft/2020-12/schema","oneOf":[{"$ref":"#/$defs/PredicateNode"},{"$ref":"#/$defs/ExpressionNode"}]}
1
+ {"$defs":{"ExpressionNode":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"numberLiteral","type":"string"},"unit":{"additionalProperties":{"type":"number"},"propertyNames":{"type":"string"},"type":"object"},"value":{"type":"number"}},"required":["kind","value"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"textLiteral","type":"string"},"value":{"type":"string"}},"required":["kind","value"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"instantLiteral","type":"string"},"value":{"type":"string"}},"required":["kind","value"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"durationLiteral","type":"string"},"unit":{"enum":["ms","s","min","h","d"],"type":"string"},"value":{"type":"number"}},"required":["kind","value","unit"],"type":"object"},{"additionalProperties":false,"properties":{"key":{"$ref":"#/$defs/schema0"},"kind":{"const":"reference","type":"string"},"unit":{"additionalProperties":{"type":"number"},"propertyNames":{"type":"string"},"type":"object"}},"required":["kind","key"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"arithmetic","type":"string"},"left":{"$ref":"#/$defs/ExpressionNode"},"op":{"enum":["add","subtract","multiply","divide","power","modulo"],"type":"string"},"right":{"$ref":"#/$defs/ExpressionNode"}},"required":["kind","op","left","right"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"negate","type":"string"},"operand":{"$ref":"#/$defs/ExpressionNode"}},"required":["kind","operand"],"type":"object"},{"additionalProperties":false,"properties":{"args":{"items":{"$ref":"#/$defs/ExpressionNode"},"type":"array"},"fn":{"type":"string"},"kind":{"const":"call","type":"string"}},"required":["kind","fn","args"],"type":"object"},{"additionalProperties":false,"properties":{"keys":{"items":{"$ref":"#/$defs/ExpressionNode"},"type":"array"},"kind":{"const":"lookup","type":"string"},"table":{"$ref":"#/$defs/schema0"}},"required":["kind","table","keys"],"type":"object"},{"additionalProperties":false,"properties":{"cases":{"items":{"additionalProperties":false,"properties":{"then":{"$ref":"#/$defs/ExpressionNode"},"when":{"$ref":"#/$defs/PredicateNode"}},"required":["when","then"],"type":"object"},"type":"array"},"fallback":{"$ref":"#/$defs/ExpressionNode"},"kind":{"const":"conditional","type":"string"}},"required":["kind","cases","fallback"],"type":"object"},{"additionalProperties":false,"properties":{"collection":{"$ref":"#/$defs/schema0"},"combiner":{"oneOf":[{"additionalProperties":false,"properties":{"item":{"$ref":"#/$defs/ExpressionNode"},"mode":{"const":"max","type":"string"}},"required":["mode","item"],"type":"object"},{"additionalProperties":false,"properties":{"item":{"$ref":"#/$defs/ExpressionNode"},"mode":{"const":"min","type":"string"}},"required":["mode","item"],"type":"object"},{"additionalProperties":false,"properties":{"combine":{"$ref":"#/$defs/ExpressionNode"},"initial":{"$ref":"#/$defs/ExpressionNode"},"mode":{"const":"reduce","type":"string"}},"required":["mode","initial","combine"],"type":"object"}]},"filter":{"$ref":"#/$defs/PredicateNode"},"kind":{"const":"fold","type":"string"}},"required":["kind","collection","combiner"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"accumulator","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"delegate","type":"string"},"payload":{"$ref":"#/$defs/schema0"},"system":{"type":"string"}},"required":["kind","system","payload"],"type":"object"}]},"PredicateNode":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"not","type":"string"},"operand":{"$ref":"#/$defs/PredicateNode"}},"required":["kind","operand"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"and","type":"string"},"left":{"$ref":"#/$defs/PredicateNode"},"right":{"$ref":"#/$defs/PredicateNode"}},"required":["kind","left","right"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"or","type":"string"},"left":{"$ref":"#/$defs/PredicateNode"},"right":{"$ref":"#/$defs/PredicateNode"}},"required":["kind","left","right"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"allOf","type":"string"},"operands":{"items":{"$ref":"#/$defs/PredicateNode"},"type":"array"}},"required":["kind","operands"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"anyOf","type":"string"},"operands":{"items":{"$ref":"#/$defs/PredicateNode"},"type":"array"}},"required":["kind","operands"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"compare","type":"string"},"left":{"$ref":"#/$defs/ExpressionNode"},"op":{"enum":["gt","gte","lt","lte","eq","neq"],"type":"string"},"right":{"$ref":"#/$defs/ExpressionNode"}},"required":["kind","op","left","right"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"textCompare","type":"string"},"left":{"$ref":"#/$defs/ExpressionNode"},"op":{"enum":["equals","notEquals","matches","notMatches"],"type":"string"},"right":{"$ref":"#/$defs/ExpressionNode"}},"required":["kind","op","left","right"],"type":"object"},{"additionalProperties":false,"properties":{"candidates":{"items":{"$ref":"#/$defs/ExpressionNode"},"type":"array"},"kind":{"const":"memberOf","type":"string"},"op":{"enum":["in","notIn"],"type":"string"},"operand":{"$ref":"#/$defs/ExpressionNode"}},"required":["kind","op","operand","candidates"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"exists","type":"string"},"operand":{"$ref":"#/$defs/ExpressionNode"}},"required":["kind","operand"],"type":"object"},{"additionalProperties":false,"properties":{"collection":{"$ref":"#/$defs/schema0"},"filter":{"$ref":"#/$defs/PredicateNode"},"item":{"$ref":"#/$defs/PredicateNode"},"kind":{"const":"some","type":"string"}},"required":["kind","collection","item"],"type":"object"},{"additionalProperties":false,"properties":{"collection":{"$ref":"#/$defs/schema0"},"filter":{"$ref":"#/$defs/PredicateNode"},"item":{"$ref":"#/$defs/PredicateNode"},"kind":{"const":"every","type":"string"}},"required":["kind","collection","item"],"type":"object"}]},"schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"items":{"$ref":"#/$defs/schema0"},"type":"array"},{"additionalProperties":{"$ref":"#/$defs/schema0"},"propertyNames":{"type":"string"},"type":"object"}]}},"$id":"https://cdn.jsdelivr.net/npm/trilean@1.0.8/schemas/trilean.schema.json","$schema":"https://json-schema.org/draft/2020-12/schema","oneOf":[{"$ref":"#/$defs/PredicateNode"},{"$ref":"#/$defs/ExpressionNode"}]}