@inixiative/json-rules 1.1.2 → 1.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.
- package/README.md +294 -226
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +308 -77
- package/dist/index.d.ts +308 -77
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# @inixiative/json-rules
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
3
|
+
A TypeScript-first JSON rules library for:
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
- runtime validation with custom error messages
|
|
6
|
+
- Prisma query planning
|
|
7
|
+
- PostgreSQL `WHERE` generation
|
|
8
|
+
|
|
9
|
+
The same rule AST can be evaluated against in-memory data with `check()`, converted into a Prisma query plan with `toPrisma()`, or compiled into SQL with `toSql()`.
|
|
7
10
|
|
|
8
11
|
## Installation
|
|
9
12
|
|
|
@@ -15,326 +18,391 @@ yarn add @inixiative/json-rules
|
|
|
15
18
|
bun add @inixiative/json-rules
|
|
16
19
|
```
|
|
17
20
|
|
|
18
|
-
## Features
|
|
19
|
-
|
|
20
|
-
- 🎯 **Type-safe**: Full TypeScript support with strict type checking
|
|
21
|
-
- 🔧 **Flexible**: 22 standard operators, 8 array operators, and 8 date operators
|
|
22
|
-
- 🌳 **Composable**: Nest rules with logical operators (all/any) and conditional logic (if-then-else)
|
|
23
|
-
- 📊 **Array validation**: Rich array validation with element-wise conditions
|
|
24
|
-
- 📅 **Date handling**: Comprehensive date comparison with timezone support
|
|
25
|
-
- 🔍 **Path-based access**: Reference values from anywhere in your data structure
|
|
26
|
-
- 💬 **Custom errors**: Every rule supports custom error messages
|
|
27
|
-
|
|
28
|
-
## Installation
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npm install json-rules
|
|
32
|
-
# or
|
|
33
|
-
yarn add json-rules
|
|
34
|
-
# or
|
|
35
|
-
bun add json-rules
|
|
36
|
-
```
|
|
37
|
-
|
|
38
21
|
## Quick Start
|
|
39
22
|
|
|
40
|
-
```
|
|
41
|
-
import { check, Operator } from 'json-rules';
|
|
23
|
+
```ts
|
|
24
|
+
import { check, Operator } from '@inixiative/json-rules';
|
|
42
25
|
|
|
43
|
-
// Simple rule
|
|
44
26
|
const rule = {
|
|
45
27
|
field: 'age',
|
|
46
|
-
operator: Operator.
|
|
28
|
+
operator: Operator.greaterThanEquals,
|
|
47
29
|
value: 18,
|
|
48
|
-
error: 'Must be 18 or older'
|
|
30
|
+
error: 'Must be 18 or older',
|
|
49
31
|
};
|
|
50
32
|
|
|
51
|
-
|
|
52
|
-
|
|
33
|
+
check(rule, { age: 21 }); // true
|
|
34
|
+
check(rule, { age: 16 }); // "Must be 18 or older"
|
|
53
35
|
```
|
|
54
36
|
|
|
37
|
+
## What It Supports
|
|
38
|
+
|
|
39
|
+
- scalar comparisons
|
|
40
|
+
- nested logical conditions with `all` / `any`
|
|
41
|
+
- `if` / `then` / `else`
|
|
42
|
+
- array validation against nested object elements
|
|
43
|
+
- date comparisons with timezone-aware runtime evaluation
|
|
44
|
+
- relative value references via `path`
|
|
45
|
+
- custom error messages on every rule
|
|
46
|
+
- compilation to Prisma and PostgreSQL for supported subsets
|
|
47
|
+
|
|
55
48
|
## Operators
|
|
56
49
|
|
|
57
|
-
###
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- `
|
|
61
|
-
- `
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
64
|
-
- `
|
|
65
|
-
- `
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
- `
|
|
69
|
-
- `
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- `
|
|
73
|
-
- `
|
|
74
|
-
- `
|
|
75
|
-
- `
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
- `
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
- `
|
|
83
|
-
- `
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
- `
|
|
87
|
-
- `
|
|
88
|
-
- `
|
|
89
|
-
- `
|
|
90
|
-
|
|
91
|
-
###
|
|
92
|
-
|
|
93
|
-
- `
|
|
94
|
-
- `
|
|
95
|
-
- `
|
|
96
|
-
- `
|
|
97
|
-
- `
|
|
98
|
-
- `
|
|
99
|
-
- `
|
|
100
|
-
- `
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- `onOrAfter` - Date is on or after
|
|
108
|
-
- `between` - Date is between two dates
|
|
109
|
-
- `notBetween` - Date is outside range
|
|
110
|
-
- `dayIn` - Day of week is in list
|
|
111
|
-
- `dayNotIn` - Day of week is not in list
|
|
112
|
-
|
|
113
|
-
#### Timezone Handling
|
|
114
|
-
|
|
115
|
-
Date comparisons are timezone-aware:
|
|
116
|
-
|
|
117
|
-
1. **When condition value has no timezone** (e.g., `'2025-01-20'`), it's interpreted in the field's timezone:
|
|
118
|
-
```typescript
|
|
119
|
-
// Field: Jan 20 10:00 AM Sydney time
|
|
120
|
-
{ eventDate: '2025-01-20T10:00:00+11:00' }
|
|
121
|
-
// Condition: on or after Jan 20 (interpreted as Jan 20 in Sydney)
|
|
122
|
-
{ dateOperator: 'onOrAfter', value: '2025-01-20' } // ✓ passes
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
2. **When condition value has timezone** (e.g., `'2025-01-20T00:00:00Z'`), it's used as-is:
|
|
126
|
-
```typescript
|
|
127
|
-
// Condition: after midnight UTC specifically
|
|
128
|
-
{ dateOperator: 'after', value: '2025-01-20T00:00:00Z' }
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
3. **Fields without timezone** are treated as local time (UTC offset 0)
|
|
132
|
-
|
|
133
|
-
## Rule Types
|
|
134
|
-
|
|
135
|
-
### Basic Rule
|
|
136
|
-
|
|
137
|
-
```typescript
|
|
50
|
+
### Field Operators
|
|
51
|
+
|
|
52
|
+
- `equals`
|
|
53
|
+
- `notEquals`
|
|
54
|
+
- `lessThan`
|
|
55
|
+
- `lessThanEquals`
|
|
56
|
+
- `greaterThan`
|
|
57
|
+
- `greaterThanEquals`
|
|
58
|
+
- `contains`
|
|
59
|
+
- `notContains`
|
|
60
|
+
- `in`
|
|
61
|
+
- `notIn`
|
|
62
|
+
- `matches`
|
|
63
|
+
- `notMatches`
|
|
64
|
+
- `between`
|
|
65
|
+
- `notBetween`
|
|
66
|
+
- `isEmpty`
|
|
67
|
+
- `notEmpty`
|
|
68
|
+
- `exists`
|
|
69
|
+
- `notExists`
|
|
70
|
+
- `startsWith`
|
|
71
|
+
- `endsWith`
|
|
72
|
+
|
|
73
|
+
### Array Operators
|
|
74
|
+
|
|
75
|
+
- `all`
|
|
76
|
+
- `any`
|
|
77
|
+
- `none`
|
|
78
|
+
- `atLeast`
|
|
79
|
+
- `atMost`
|
|
80
|
+
- `exactly`
|
|
81
|
+
- `empty`
|
|
82
|
+
- `notEmpty`
|
|
83
|
+
|
|
84
|
+
### Date Operators
|
|
85
|
+
|
|
86
|
+
- `before`
|
|
87
|
+
- `after`
|
|
88
|
+
- `onOrBefore`
|
|
89
|
+
- `onOrAfter`
|
|
90
|
+
- `between`
|
|
91
|
+
- `notBetween`
|
|
92
|
+
- `dayIn`
|
|
93
|
+
- `dayNotIn`
|
|
94
|
+
|
|
95
|
+
## Rule Shapes
|
|
96
|
+
|
|
97
|
+
### Field Rule
|
|
98
|
+
|
|
99
|
+
```ts
|
|
138
100
|
{
|
|
139
101
|
field: 'status',
|
|
140
|
-
operator: Operator.
|
|
102
|
+
operator: Operator.equals,
|
|
141
103
|
value: 'active'
|
|
142
104
|
}
|
|
143
105
|
```
|
|
144
106
|
|
|
145
|
-
### Logical
|
|
107
|
+
### Logical Rules
|
|
146
108
|
|
|
147
|
-
```
|
|
148
|
-
// All conditions must pass (AND)
|
|
109
|
+
```ts
|
|
149
110
|
{
|
|
150
111
|
all: [
|
|
151
|
-
{ field: 'age', operator: Operator.
|
|
152
|
-
{ field: 'hasLicense', operator: Operator.
|
|
112
|
+
{ field: 'age', operator: Operator.greaterThanEquals, value: 18 },
|
|
113
|
+
{ field: 'hasLicense', operator: Operator.equals, value: true }
|
|
153
114
|
]
|
|
154
115
|
}
|
|
155
116
|
|
|
156
|
-
// At least one must pass (OR)
|
|
157
117
|
{
|
|
158
118
|
any: [
|
|
159
|
-
{ field: 'role', operator: Operator.
|
|
160
|
-
{ field: 'isOwner', operator: Operator.
|
|
119
|
+
{ field: 'role', operator: Operator.equals, value: 'admin' },
|
|
120
|
+
{ field: 'isOwner', operator: Operator.equals, value: true }
|
|
161
121
|
]
|
|
162
122
|
}
|
|
163
123
|
```
|
|
164
124
|
|
|
165
|
-
### Conditional
|
|
125
|
+
### Conditional Rule
|
|
166
126
|
|
|
167
|
-
```
|
|
127
|
+
```ts
|
|
168
128
|
{
|
|
169
|
-
if: { field: 'type', operator: Operator.
|
|
129
|
+
if: { field: 'type', operator: Operator.equals, value: 'premium' },
|
|
170
130
|
then: { field: 'discount', operator: Operator.greaterThan, value: 0 },
|
|
171
|
-
else: { field: 'discount', operator: Operator.
|
|
131
|
+
else: { field: 'discount', operator: Operator.equals, value: 0 }
|
|
172
132
|
}
|
|
173
133
|
```
|
|
174
134
|
|
|
175
|
-
### Array
|
|
135
|
+
### Array Rule
|
|
176
136
|
|
|
177
|
-
```
|
|
137
|
+
```ts
|
|
178
138
|
{
|
|
179
139
|
field: 'orders',
|
|
180
140
|
arrayOperator: ArrayOperator.all,
|
|
181
141
|
condition: {
|
|
182
142
|
field: 'total',
|
|
183
|
-
operator: Operator.
|
|
184
|
-
|
|
143
|
+
operator: Operator.lessThanEquals,
|
|
144
|
+
path: '$.maxBudget'
|
|
185
145
|
}
|
|
186
146
|
}
|
|
187
147
|
```
|
|
188
148
|
|
|
189
|
-
### Date
|
|
149
|
+
### Date Rule
|
|
190
150
|
|
|
191
|
-
```
|
|
151
|
+
```ts
|
|
192
152
|
{
|
|
193
153
|
field: 'expiryDate',
|
|
194
154
|
dateOperator: DateOperator.after,
|
|
195
|
-
value: '
|
|
155
|
+
value: '2026-01-01'
|
|
196
156
|
}
|
|
197
157
|
```
|
|
198
158
|
|
|
199
|
-
##
|
|
159
|
+
## Path Semantics
|
|
200
160
|
|
|
201
|
-
|
|
161
|
+
`path` lets a rule resolve its comparison value from somewhere other than `value`.
|
|
202
162
|
|
|
203
|
-
|
|
163
|
+
### Root Context Reference
|
|
204
164
|
|
|
205
|
-
|
|
165
|
+
In runtime validation, a plain path is resolved from the root context:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
206
168
|
{
|
|
207
169
|
field: 'confirmPassword',
|
|
208
|
-
operator: Operator.
|
|
209
|
-
path: 'password'
|
|
170
|
+
operator: Operator.equals,
|
|
171
|
+
path: 'password'
|
|
210
172
|
}
|
|
211
173
|
```
|
|
212
174
|
|
|
213
|
-
### Array Element
|
|
175
|
+
### Current Array Element Reference
|
|
214
176
|
|
|
215
|
-
|
|
177
|
+
Inside array conditions, `$.` means "read from the current element":
|
|
216
178
|
|
|
217
|
-
```
|
|
179
|
+
```ts
|
|
218
180
|
{
|
|
219
|
-
field: '
|
|
181
|
+
field: 'orders',
|
|
220
182
|
arrayOperator: ArrayOperator.all,
|
|
221
183
|
condition: {
|
|
222
|
-
field: '
|
|
223
|
-
operator: Operator.
|
|
224
|
-
path: '$.
|
|
184
|
+
field: 'total',
|
|
185
|
+
operator: Operator.lessThanEquals,
|
|
186
|
+
path: '$.maxBudget'
|
|
225
187
|
}
|
|
226
188
|
}
|
|
227
189
|
```
|
|
228
190
|
|
|
229
|
-
|
|
191
|
+
## Runtime Validation
|
|
230
192
|
|
|
231
|
-
|
|
193
|
+
`check()` evaluates a rule against data and returns:
|
|
232
194
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
field: 'email',
|
|
236
|
-
operator: Operator.match,
|
|
237
|
-
value: /^[^@]+@[^@]+\.[^@]+$/,
|
|
238
|
-
error: 'Please enter a valid email address'
|
|
239
|
-
}
|
|
240
|
-
```
|
|
195
|
+
- `true` when the rule passes
|
|
196
|
+
- a string when the rule fails
|
|
241
197
|
|
|
242
|
-
|
|
198
|
+
```ts
|
|
199
|
+
import { ArrayOperator, check, Operator } from '@inixiative/json-rules';
|
|
243
200
|
|
|
244
|
-
```typescript
|
|
245
201
|
const rule = {
|
|
246
202
|
all: [
|
|
247
|
-
|
|
248
|
-
{ field: 'status', operator: Operator.equal, value: 'active' },
|
|
249
|
-
|
|
250
|
-
// Age requirement
|
|
251
|
-
{ field: 'age', operator: Operator.between, value: [18, 65] },
|
|
252
|
-
|
|
253
|
-
// Must have at least one verified email
|
|
203
|
+
{ field: 'status', operator: Operator.equals, value: 'active' },
|
|
254
204
|
{
|
|
255
|
-
field: '
|
|
256
|
-
arrayOperator: ArrayOperator.
|
|
257
|
-
|
|
205
|
+
field: 'orders',
|
|
206
|
+
arrayOperator: ArrayOperator.atLeast,
|
|
207
|
+
count: 2,
|
|
208
|
+
condition: { field: 'status', operator: Operator.equals, value: 'completed' },
|
|
258
209
|
},
|
|
259
|
-
|
|
260
|
-
// Conditional premium features
|
|
261
|
-
{
|
|
262
|
-
if: { field: 'subscription', operator: Operator.equal, value: 'premium' },
|
|
263
|
-
then: {
|
|
264
|
-
field: 'features',
|
|
265
|
-
arrayOperator: ArrayOperator.all,
|
|
266
|
-
condition: { field: 'enabled', operator: Operator.equal, value: true }
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
]
|
|
210
|
+
],
|
|
270
211
|
};
|
|
271
212
|
|
|
272
|
-
|
|
213
|
+
check(rule, {
|
|
273
214
|
status: 'active',
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
{
|
|
277
|
-
{
|
|
215
|
+
orders: [
|
|
216
|
+
{ status: 'completed' },
|
|
217
|
+
{ status: 'pending' },
|
|
218
|
+
{ status: 'completed' },
|
|
278
219
|
],
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
]
|
|
284
|
-
};
|
|
220
|
+
}); // true
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Custom Errors
|
|
285
224
|
|
|
286
|
-
|
|
225
|
+
Every rule can define its own error:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
{
|
|
229
|
+
field: 'email',
|
|
230
|
+
operator: Operator.matches,
|
|
231
|
+
value: /^[^@]+@[^@]+\.[^@]+$/,
|
|
232
|
+
error: 'Please enter a valid email address'
|
|
233
|
+
}
|
|
287
234
|
```
|
|
288
235
|
|
|
289
|
-
##
|
|
236
|
+
## Prisma Query Planning
|
|
290
237
|
|
|
291
|
-
|
|
238
|
+
`toPrisma()` converts a rule into a Prisma query plan.
|
|
292
239
|
|
|
293
|
-
|
|
240
|
+
```ts
|
|
241
|
+
import { Operator, toPrisma } from '@inixiative/json-rules';
|
|
294
242
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
243
|
+
const plan = toPrisma({
|
|
244
|
+
field: 'status',
|
|
245
|
+
operator: Operator.equals,
|
|
246
|
+
value: 'active',
|
|
247
|
+
});
|
|
299
248
|
|
|
300
|
-
|
|
249
|
+
// plan.steps => [{ operation: 'where', where: { status: { equals: 'active' } } }]
|
|
250
|
+
```
|
|
301
251
|
|
|
302
|
-
|
|
303
|
-
|
|
252
|
+
Count-based relation filters such as `atLeast`, `atMost`, and `exactly` can produce multi-step plans. Use `executePrismaQueryPlan()` to resolve `groupBy` step references before passing the final `where` into Prisma.
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import {
|
|
256
|
+
ArrayOperator,
|
|
257
|
+
Operator,
|
|
258
|
+
executePrismaQueryPlan,
|
|
259
|
+
toPrisma,
|
|
260
|
+
} from '@inixiative/json-rules';
|
|
261
|
+
|
|
262
|
+
const plan = toPrisma(
|
|
263
|
+
{
|
|
264
|
+
field: 'posts',
|
|
265
|
+
arrayOperator: ArrayOperator.atLeast,
|
|
266
|
+
count: 3,
|
|
267
|
+
condition: {
|
|
268
|
+
field: 'published',
|
|
269
|
+
operator: Operator.equals,
|
|
270
|
+
value: true,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
{ map, model: 'User' },
|
|
274
|
+
);
|
|
304
275
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
value?: any;
|
|
309
|
-
path?: string;
|
|
310
|
-
error?: string;
|
|
311
|
-
};
|
|
276
|
+
const where = await executePrismaQueryPlan(plan, { post: prisma.post });
|
|
277
|
+
await prisma.user.findMany({ where });
|
|
278
|
+
```
|
|
312
279
|
|
|
313
|
-
|
|
314
|
-
field: string;
|
|
315
|
-
arrayOperator: ArrayOperator;
|
|
316
|
-
condition?: Condition;
|
|
317
|
-
count?: number;
|
|
318
|
-
error?: string;
|
|
319
|
-
};
|
|
280
|
+
## PostgreSQL SQL Generation
|
|
320
281
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
282
|
+
`toSql()` converts a rule into a parameterized PostgreSQL `WHERE` clause.
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import { Operator, toSql } from '@inixiative/json-rules';
|
|
286
|
+
|
|
287
|
+
const result = toSql({
|
|
288
|
+
field: 'status',
|
|
289
|
+
operator: Operator.equals,
|
|
290
|
+
value: 'active',
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// {
|
|
294
|
+
// sql: '"status" = $1',
|
|
295
|
+
// params: ['active'],
|
|
296
|
+
// joins: []
|
|
297
|
+
// }
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
With a field map and model, `toSql()` can generate `LEFT JOIN`s for relation traversal:
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
const result = toSql(
|
|
304
|
+
{ field: 'author.email', operator: Operator.equals, value: 'a@b.com' },
|
|
305
|
+
{ map, model: 'Post', alias: 't0' },
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
// result.sql => '"t1"."email" = $1'
|
|
309
|
+
// result.joins => ['LEFT JOIN "User" AS "t1" ON "t1"."id" = "t0"."authorId"']
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Backend Support Matrix
|
|
313
|
+
|
|
314
|
+
Not every backend supports every rule shape.
|
|
315
|
+
|
|
316
|
+
| Capability | `check()` | `toPrisma()` | `toSql()` |
|
|
317
|
+
| --- | --- | --- | --- |
|
|
318
|
+
| Field operators | Yes | Most | Yes |
|
|
319
|
+
| `matches` / `notMatches` | Yes | No | Yes |
|
|
320
|
+
| Logical operators | Yes | Yes | Yes |
|
|
321
|
+
| Array `all` / `any` / `none` | Yes | Yes | No |
|
|
322
|
+
| Array `atLeast` / `atMost` / `exactly` | Yes | Yes, with `map` + `model` | No |
|
|
323
|
+
| Array `empty` / `notEmpty` | Yes | Yes | Yes |
|
|
324
|
+
| Date comparisons | Yes | Most | Yes |
|
|
325
|
+
| `dayIn` / `dayNotIn` | Yes | No | Yes |
|
|
326
|
+
| `path: '$.field'` current-element / same-row refs | Yes | No | Yes |
|
|
327
|
+
|
|
328
|
+
### Prisma Limitations
|
|
329
|
+
|
|
330
|
+
- `matches` and `notMatches` are not supported by Prisma output
|
|
331
|
+
- `dayIn` and `dayNotIn` are not supported by Prisma output
|
|
332
|
+
- `path: '$.field'` column-to-column comparisons are not supported by Prisma `WHERE`
|
|
333
|
+
- count-based relation operators require `{ map, model }`
|
|
334
|
+
|
|
335
|
+
### SQL Limitations
|
|
336
|
+
|
|
337
|
+
- complex array element operators are not supported in SQL output:
|
|
338
|
+
- `all`
|
|
339
|
+
- `any`
|
|
340
|
+
- `none`
|
|
341
|
+
- `atLeast`
|
|
342
|
+
- `atMost`
|
|
343
|
+
- `exactly`
|
|
344
|
+
- `toSql()` generates `WHERE` fragments and `LEFT JOIN`s, not complete queries
|
|
345
|
+
|
|
346
|
+
## TypeScript Types
|
|
347
|
+
|
|
348
|
+
The public rule types are generic over comparison payloads:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
type Condition<TRuleValue = RuleValue, TDateValue = DateRuleValue> =
|
|
352
|
+
| Rule<TRuleValue>
|
|
353
|
+
| ArrayRule<TRuleValue, TDateValue>
|
|
354
|
+
| DateRule<TDateValue>
|
|
355
|
+
| All<TRuleValue, TDateValue>
|
|
356
|
+
| Any<TRuleValue, TDateValue>
|
|
357
|
+
| IfThenElse<TRuleValue, TDateValue>
|
|
358
|
+
| boolean;
|
|
328
359
|
```
|
|
329
360
|
|
|
361
|
+
Useful exports:
|
|
362
|
+
|
|
363
|
+
- `check`
|
|
364
|
+
- `toPrisma`
|
|
365
|
+
- `executePrismaQueryPlan`
|
|
366
|
+
- `toSql`
|
|
367
|
+
- `validateRule`
|
|
368
|
+
- `assertValidRule`
|
|
369
|
+
- `Operator`
|
|
370
|
+
- `ArrayOperator`
|
|
371
|
+
- `DateOperator`
|
|
372
|
+
- `Condition`
|
|
373
|
+
- `StrictCondition`
|
|
374
|
+
- `Rule`
|
|
375
|
+
- `ArrayRule`
|
|
376
|
+
- `DateRule`
|
|
377
|
+
|
|
330
378
|
## Error Handling
|
|
331
379
|
|
|
332
|
-
The
|
|
333
|
-
|
|
334
|
-
-
|
|
335
|
-
-
|
|
336
|
-
-
|
|
380
|
+
The library throws when a rule is structurally invalid, for example:
|
|
381
|
+
|
|
382
|
+
- array operators used against non-arrays
|
|
383
|
+
- missing `count` for count-based array rules
|
|
384
|
+
- invalid date values
|
|
385
|
+
- unsupported backend translations
|
|
386
|
+
|
|
387
|
+
It returns string errors only from runtime `check()`.
|
|
388
|
+
|
|
389
|
+
If rules come from JSON, a database, an API, or an editor, validate them first:
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
import { assertValidRule, validateRule } from '@inixiative/json-rules';
|
|
393
|
+
|
|
394
|
+
const result = validateRule(rule, { target: 'check' });
|
|
395
|
+
if (!result.ok) {
|
|
396
|
+
console.error(result.errors);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
assertValidRule(rule, { target: 'toPrisma' });
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Examples
|
|
403
|
+
|
|
404
|
+
See [`examples/basic-validation.ts`](./examples/basic-validation.ts), [`examples/array-operations.ts`](./examples/array-operations.ts), [`examples/date-operations.ts`](./examples/date-operations.ts), and [`examples/advanced-features.ts`](./examples/advanced-features.ts).
|
|
337
405
|
|
|
338
406
|
## License
|
|
339
407
|
|
|
340
|
-
MIT
|
|
408
|
+
MIT
|