@inixiative/json-rules 1.1.2 → 1.3.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/dist/index.d.cts CHANGED
@@ -1,119 +1,374 @@
1
- declare enum Operator {
2
- equals = "equals",
3
- notEquals = "notEquals",
4
- lessThan = "lessThan",
5
- lessThanEquals = "lessThanEquals",
6
- greaterThan = "greaterThan",
7
- greaterThanEquals = "greaterThanEquals",
8
- contains = "contains",
9
- notContains = "notContains",
10
- in = "in",
11
- notIn = "notIn",
12
- matches = "matches",
13
- notMatches = "notMatches",
14
- between = "between",
15
- notBetween = "notBetween",
16
- isEmpty = "isEmpty",
17
- notEmpty = "notEmpty",
18
- exists = "exists",
19
- notExists = "notExists",
20
- startsWith = "startsWith",
21
- endsWith = "endsWith"
22
- }
23
- declare enum ArrayOperator {
24
- all = "all",
25
- any = "any",
26
- none = "none",
27
- atLeast = "atLeast",
28
- atMost = "atMost",
29
- exactly = "exactly",
30
- empty = "empty",
31
- notEmpty = "notEmpty"
32
- }
33
- declare enum DateOperator {
34
- before = "before",
35
- after = "after",
36
- onOrBefore = "onOrBefore",
37
- onOrAfter = "onOrAfter",
38
- between = "between",
39
- notBetween = "notBetween",
40
- dayIn = "dayIn",// e.g., ['monday', 'tuesday', 'friday']
41
- dayNotIn = "dayNotIn"
42
- }
1
+ declare const Operator: {
2
+ readonly equals: "equals";
3
+ readonly notEquals: "notEquals";
4
+ readonly lessThan: "lessThan";
5
+ readonly lessThanEquals: "lessThanEquals";
6
+ readonly greaterThan: "greaterThan";
7
+ readonly greaterThanEquals: "greaterThanEquals";
8
+ readonly contains: "contains";
9
+ readonly notContains: "notContains";
10
+ readonly in: "in";
11
+ readonly notIn: "notIn";
12
+ readonly matches: "matches";
13
+ readonly notMatches: "notMatches";
14
+ readonly between: "between";
15
+ readonly notBetween: "notBetween";
16
+ readonly isEmpty: "isEmpty";
17
+ readonly notEmpty: "notEmpty";
18
+ readonly exists: "exists";
19
+ readonly notExists: "notExists";
20
+ readonly startsWith: "startsWith";
21
+ readonly endsWith: "endsWith";
22
+ };
23
+ type Operator = (typeof Operator)[keyof typeof Operator];
24
+ declare const ArrayOperator: {
25
+ readonly all: "all";
26
+ readonly any: "any";
27
+ readonly none: "none";
28
+ readonly atLeast: "atLeast";
29
+ readonly atMost: "atMost";
30
+ readonly exactly: "exactly";
31
+ readonly empty: "empty";
32
+ readonly notEmpty: "notEmpty";
33
+ };
34
+ type ArrayOperator = (typeof ArrayOperator)[keyof typeof ArrayOperator];
35
+ declare const DateOperator: {
36
+ readonly before: "before";
37
+ readonly after: "after";
38
+ readonly onOrBefore: "onOrBefore";
39
+ readonly onOrAfter: "onOrAfter";
40
+ readonly between: "between";
41
+ readonly notBetween: "notBetween";
42
+ readonly dayIn: "dayIn";
43
+ readonly dayNotIn: "dayNotIn";
44
+ };
45
+ type DateOperator = (typeof DateOperator)[keyof typeof DateOperator];
43
46
 
44
- type Rule = {
47
+ type OperatorValues = typeof Operator;
48
+ type ArrayOperatorValues = typeof ArrayOperator;
49
+ type DateOperatorValues = typeof DateOperator;
50
+ type RuleScalar = string | number | boolean | null | undefined;
51
+ type AggregateMode = 'sum' | 'avg';
52
+ type RuleValue = RuleScalar | Date | RegExp | RuleValue[] | {
53
+ [key: string]: RuleValue;
54
+ };
55
+ type OrderedRuleValue = string | number | Date;
56
+ type DateInputValue = string | number | Date;
57
+ type DateRuleValue = DateInputValue | [DateInputValue, DateInputValue] | string[];
58
+ type ValueSource<TValue> = {
59
+ value: TValue;
60
+ path?: never;
61
+ } | {
62
+ path: string;
63
+ value?: never;
64
+ };
65
+ type NoValueSource = {
66
+ value?: never;
67
+ path?: never;
68
+ };
69
+ type RuleBase<TOperator extends Operator> = {
70
+ field: string;
71
+ operator: TOperator;
72
+ error?: string;
73
+ };
74
+ type DateRuleBase<TOperator extends DateOperator> = {
75
+ field: string;
76
+ dateOperator: TOperator;
77
+ error?: string;
78
+ };
79
+ type StrictEqualityRule<TValue = RuleValue> = (RuleBase<OperatorValues['equals']> & ValueSource<TValue>) | (RuleBase<OperatorValues['notEquals']> & ValueSource<TValue>);
80
+ type StrictOrderedComparisonRule = (RuleBase<OperatorValues['lessThan']> & ValueSource<OrderedRuleValue>) | (RuleBase<OperatorValues['lessThanEquals']> & ValueSource<OrderedRuleValue>) | (RuleBase<OperatorValues['greaterThan']> & ValueSource<OrderedRuleValue>) | (RuleBase<OperatorValues['greaterThanEquals']> & ValueSource<OrderedRuleValue>);
81
+ type StrictMembershipRule<TValue = RuleValue> = (RuleBase<OperatorValues['in']> & ValueSource<TValue[]>) | (RuleBase<OperatorValues['notIn']> & ValueSource<TValue[]>);
82
+ type StrictContainsRule<TValue = RuleValue> = (RuleBase<OperatorValues['contains']> & ValueSource<TValue>) | (RuleBase<OperatorValues['notContains']> & ValueSource<TValue>);
83
+ type StrictPatternRule = (RuleBase<OperatorValues['matches']> & ValueSource<RegExp | string>) | (RuleBase<OperatorValues['notMatches']> & ValueSource<RegExp | string>);
84
+ type StrictStringBoundaryRule = (RuleBase<OperatorValues['startsWith']> & ValueSource<string>) | (RuleBase<OperatorValues['endsWith']> & ValueSource<string>);
85
+ type StrictRangeRule = (RuleBase<OperatorValues['between']> & ValueSource<[OrderedRuleValue, OrderedRuleValue]>) | (RuleBase<OperatorValues['notBetween']> & ValueSource<[OrderedRuleValue, OrderedRuleValue]>);
86
+ type StrictPresenceRule = (RuleBase<OperatorValues['isEmpty']> & NoValueSource) | (RuleBase<OperatorValues['notEmpty']> & NoValueSource) | (RuleBase<OperatorValues['exists']> & NoValueSource) | (RuleBase<OperatorValues['notExists']> & NoValueSource);
87
+ type StrictRule<TValue = RuleValue> = StrictEqualityRule<TValue> | StrictOrderedComparisonRule | StrictMembershipRule<TValue> | StrictContainsRule<TValue> | StrictPatternRule | StrictStringBoundaryRule | StrictRangeRule | StrictPresenceRule;
88
+ type ArrayRuleBase<TOperator extends ArrayOperator> = {
89
+ field: string;
90
+ arrayOperator: TOperator;
91
+ error?: string;
92
+ };
93
+ type StrictArrayPredicateRule<TRuleValue = RuleValue, TDateValue = DateRuleValue> = (ArrayRuleBase<ArrayOperatorValues['all']> & {
94
+ condition: StrictCondition<TRuleValue, TDateValue>;
95
+ count?: never;
96
+ }) | (ArrayRuleBase<ArrayOperatorValues['any']> & {
97
+ condition: StrictCondition<TRuleValue, TDateValue>;
98
+ count?: never;
99
+ }) | (ArrayRuleBase<ArrayOperatorValues['none']> & {
100
+ condition: StrictCondition<TRuleValue, TDateValue>;
101
+ count?: never;
102
+ });
103
+ type StrictArrayCountRule<TRuleValue = RuleValue, TDateValue = DateRuleValue> = (ArrayRuleBase<ArrayOperatorValues['atLeast']> & {
104
+ condition?: StrictCondition<TRuleValue, TDateValue>;
105
+ count?: number;
106
+ }) | (ArrayRuleBase<ArrayOperatorValues['atMost']> & {
107
+ condition?: StrictCondition<TRuleValue, TDateValue>;
108
+ count?: number;
109
+ }) | (ArrayRuleBase<ArrayOperatorValues['exactly']> & {
110
+ condition?: StrictCondition<TRuleValue, TDateValue>;
111
+ count?: number;
112
+ });
113
+ type StrictArrayPresenceRule = (ArrayRuleBase<ArrayOperatorValues['empty']> & {
114
+ condition?: never;
115
+ count?: never;
116
+ }) | (ArrayRuleBase<ArrayOperatorValues['notEmpty']> & {
117
+ condition?: never;
118
+ count?: never;
119
+ });
120
+ type StrictArrayRule<TRuleValue = RuleValue, TDateValue = DateRuleValue> = StrictArrayPredicateRule<TRuleValue, TDateValue> | StrictArrayCountRule<TRuleValue, TDateValue> | StrictArrayPresenceRule;
121
+ type StrictDateComparisonRule = (DateRuleBase<DateOperatorValues['before']> & ValueSource<DateInputValue>) | (DateRuleBase<DateOperatorValues['after']> & ValueSource<DateInputValue>) | (DateRuleBase<DateOperatorValues['onOrBefore']> & ValueSource<DateInputValue>) | (DateRuleBase<DateOperatorValues['onOrAfter']> & ValueSource<DateInputValue>);
122
+ type StrictDateRangeRule = (DateRuleBase<DateOperatorValues['between']> & ValueSource<[DateInputValue, DateInputValue]>) | (DateRuleBase<DateOperatorValues['notBetween']> & ValueSource<[DateInputValue, DateInputValue]>);
123
+ type StrictDateDayRule = (DateRuleBase<DateOperatorValues['dayIn']> & {
124
+ value: string[];
125
+ path?: never;
126
+ }) | (DateRuleBase<DateOperatorValues['dayNotIn']> & {
127
+ value: string[];
128
+ path?: never;
129
+ });
130
+ type StrictDateRule = StrictDateComparisonRule | StrictDateRangeRule | StrictDateDayRule;
131
+ type AggregateRuleBase = {
132
+ field: string;
133
+ aggregate: {
134
+ mode: AggregateMode;
135
+ field?: string;
136
+ };
137
+ error?: string;
138
+ };
139
+ type AggregateSingleOperator = OperatorValues['equals'] | OperatorValues['notEquals'] | OperatorValues['lessThan'] | OperatorValues['lessThanEquals'] | OperatorValues['greaterThan'] | OperatorValues['greaterThanEquals'];
140
+ type AggregateRangeOperator = OperatorValues['between'] | OperatorValues['notBetween'];
141
+ type StrictAggregateRule = (AggregateRuleBase & {
142
+ operator: AggregateSingleOperator;
143
+ } & ValueSource<number>) | (AggregateRuleBase & {
144
+ operator: AggregateRangeOperator;
145
+ } & ValueSource<[number, number]>);
146
+ type AggregateRule = {
147
+ field: string;
148
+ aggregate: {
149
+ mode: AggregateMode;
150
+ field?: string;
151
+ };
152
+ operator: Operator;
153
+ value?: number | [number, number];
154
+ path?: string;
155
+ error?: string;
156
+ };
157
+ type Rule<TValue = RuleValue> = {
45
158
  field: string;
46
159
  operator: Operator;
47
- value?: any;
160
+ value?: TValue;
48
161
  path?: string;
49
162
  error?: string;
50
163
  };
51
- type ArrayType = 'jsonb' | 'native';
52
- type ArrayRule = {
164
+ type ArrayRule<TRuleValue = RuleValue, TDateValue = DateRuleValue> = {
53
165
  field: string;
54
166
  arrayOperator: ArrayOperator;
55
- arrayType?: ArrayType;
56
- condition?: Condition;
167
+ condition?: Condition<TRuleValue, TDateValue>;
57
168
  count?: number;
58
169
  error?: string;
59
170
  };
60
- type DateRule = {
171
+ type DateRule<TValue = DateRuleValue> = {
61
172
  field: string;
62
173
  dateOperator: DateOperator;
63
- value?: any;
174
+ value?: TValue;
64
175
  path?: string;
65
176
  error?: string;
66
177
  };
67
- type All = {
68
- all: Condition[];
178
+ type All<TRuleValue = RuleValue, TDateValue = DateRuleValue> = {
179
+ all: Condition<TRuleValue, TDateValue>[];
69
180
  error?: string;
70
181
  };
71
- type Any = {
72
- any: Condition[];
182
+ type Any<TRuleValue = RuleValue, TDateValue = DateRuleValue> = {
183
+ any: Condition<TRuleValue, TDateValue>[];
73
184
  error?: string;
74
185
  };
75
- type IfThenElse = {
76
- if: Condition;
77
- then: Condition;
78
- else?: Condition;
186
+ type IfThenElse<TRuleValue = RuleValue, TDateValue = DateRuleValue> = {
187
+ if: Condition<TRuleValue, TDateValue>;
188
+ then: Condition<TRuleValue, TDateValue>;
189
+ else?: Condition<TRuleValue, TDateValue>;
79
190
  error?: string;
80
191
  };
81
- type Condition = Rule | ArrayRule | DateRule | All | Any | IfThenElse | boolean;
192
+ type Condition<TRuleValue = RuleValue, TDateValue = DateRuleValue> = Rule<TRuleValue> | AggregateRule | ArrayRule<TRuleValue, TDateValue> | DateRule<TDateValue> | All<TRuleValue, TDateValue> | Any<TRuleValue, TDateValue> | IfThenElse<TRuleValue, TDateValue> | boolean;
193
+ type StrictAll<TRuleValue = RuleValue, TDateValue = DateRuleValue> = {
194
+ all: StrictCondition<TRuleValue, TDateValue>[];
195
+ error?: string;
196
+ };
197
+ type StrictAny<TRuleValue = RuleValue, TDateValue = DateRuleValue> = {
198
+ any: StrictCondition<TRuleValue, TDateValue>[];
199
+ error?: string;
200
+ };
201
+ type StrictIfThenElse<TRuleValue = RuleValue, TDateValue = DateRuleValue> = {
202
+ if: StrictCondition<TRuleValue, TDateValue>;
203
+ then: StrictCondition<TRuleValue, TDateValue>;
204
+ else?: StrictCondition<TRuleValue, TDateValue>;
205
+ error?: string;
206
+ };
207
+ type StrictCondition<TRuleValue = RuleValue, TDateValue = DateRuleValue> = StrictRule<TRuleValue> | StrictAggregateRule | StrictArrayRule<TRuleValue, TDateValue> | StrictDateRule | StrictAll<TRuleValue, TDateValue> | StrictAny<TRuleValue, TDateValue> | StrictIfThenElse<TRuleValue, TDateValue> | boolean;
82
208
 
83
- declare const check: (conditions: Condition, data: any, context?: any) => boolean | string;
209
+ declare const check: <TData extends Record<string, unknown>>(conditions: Condition, data: TData, context?: TData) => boolean | string;
210
+
211
+ type PrismaWhere = Record<string, unknown>;
212
+ type FieldMapEntry = {
213
+ kind: 'scalar' | 'object' | 'enum';
214
+ type: string;
215
+ isList?: boolean;
216
+ fromFields?: string[];
217
+ toFields?: string[];
218
+ relationName?: string;
219
+ };
220
+ interface FieldMap {
221
+ [modelName: string]: {
222
+ dbName?: string | null;
223
+ fields: {
224
+ [fieldName: string]: FieldMapEntry;
225
+ };
226
+ };
227
+ }
228
+ type StepRef = {
229
+ __step: number;
230
+ };
231
+ type GroupByStep = {
232
+ operation: 'groupBy';
233
+ model: string;
234
+ args: {
235
+ by: string[];
236
+ where: Record<string, unknown>;
237
+ having: Record<string, unknown>;
238
+ };
239
+ extract: string;
240
+ };
241
+ type WhereStep = {
242
+ operation: 'where';
243
+ where: Record<string, unknown>;
244
+ };
245
+ type PrismaStep = GroupByStep | WhereStep;
246
+ type ToPrismaResult = {
247
+ steps: PrismaStep[];
248
+ };
249
+ type BuildOptions = {
250
+ map?: FieldMap;
251
+ model?: string;
252
+ context?: Record<string, unknown>;
253
+ };
254
+
255
+ /**
256
+ * Execute a Prisma query plan produced by toPrisma().
257
+ *
258
+ * The plan is a flat list of steps where all but the last are `groupBy` steps
259
+ * that feed results (via { __step: N } sentinels) into subsequent steps.
260
+ * The final step is always a `where` step whose resolved WHERE clause is returned.
261
+ *
262
+ * @param result - Result from toPrisma()
263
+ * @param prismaDelegate - Map of camelCase model name → Prisma delegate
264
+ * e.g. { post: prisma.post, user: prisma.user }
265
+ * @returns The resolved WHERE clause (ready for findMany/count/etc.)
266
+ *
267
+ * @example
268
+ * const plan = toPrisma(condition, { map, model: 'User' });
269
+ * const where = await executePrismaQueryPlan(plan, { post: prisma.post });
270
+ * await prisma.user.findMany({ where });
271
+ */
272
+ declare const executePrismaQueryPlan: (result: ToPrismaResult, prismaDelegate: Record<string, Record<string, (...args: unknown[]) => unknown>>) => Promise<Record<string, unknown>>;
273
+
274
+ /**
275
+ * Convert a json-rules Condition to a Prisma query plan.
276
+ *
277
+ * Returns a `ToPrismaResult` with:
278
+ * - `where` – the Prisma WHERE clause
279
+ * - `steps` – optional array of groupBy steps for count-based relation filters
280
+ * (only present when `atLeast`/`atMost`/`exactly` operators are used with a map)
281
+ *
282
+ * When `steps` is present, pass the result to `executePrismaQueryPlan` to
283
+ * resolve step refs before using `where` in a Prisma query.
284
+ *
285
+ * @param condition - The rule condition to convert
286
+ * @param options - Optional map, model, and context
287
+ *
288
+ * @example
289
+ * ```typescript
290
+ * // Simple scalar
291
+ * toPrisma({ field: 'status', operator: Operator.equals, value: 'active' })
292
+ * // → { where: { status: { equals: 'active' } } }
293
+ *
294
+ * // JSON field detection (map required)
295
+ * toPrisma({ field: 'metadata.theme', operator: Operator.equals, value: 'dark' }, { map, model: 'User' })
296
+ * // → { where: { metadata: { path: ['theme'], equals: 'dark' } } }
297
+ *
298
+ * // Context path ref
299
+ * toPrisma({ field: 'userId', operator: Operator.equals, path: 'currentUser.id' }, { context: { currentUser: { id: '123' } } })
300
+ * // → { where: { userId: { equals: '123' } } }
301
+ *
302
+ * // Multi-step (map required)
303
+ * const plan = toPrisma({ field: 'posts', arrayOperator: 'atLeast', count: 3, condition: {...} }, { map, model: 'User' });
304
+ * const where = await executePrismaQueryPlan(plan, { post: prisma.post });
305
+ * await prisma.user.findMany({ where });
306
+ * ```
307
+ */
308
+ declare const toPrisma: (condition: Condition, options?: BuildOptions) => ToPrismaResult;
84
309
 
85
310
  type SqlResult = {
86
311
  sql: string;
87
312
  params: unknown[];
313
+ joins: string[];
88
314
  };
89
315
 
316
+ type SqlBuildOptions = {
317
+ map?: FieldMap;
318
+ model?: string;
319
+ alias?: string;
320
+ context?: Record<string, unknown>;
321
+ };
90
322
  /**
91
323
  * Convert a json-rules Condition to a PostgreSQL WHERE clause.
92
324
  *
93
325
  * @param condition - The rule condition to convert
94
- * @returns Object with `sql` (WHERE clause fragment) and `params` array
326
+ * @param options - Optional map/model/alias for JOIN generation; context for path refs
327
+ * @returns Object with `sql`, `params`, and `joins` (LEFT JOIN clauses)
95
328
  *
96
329
  * @example
97
330
  * ```typescript
98
- * import { toSql, Operator } from '@inixiative/json-rules';
99
- *
100
- * const rule = { field: 'status', operator: Operator.equals, value: 'active' };
101
- * const { sql, params } = toSql(rule);
331
+ * // Simple field
332
+ * const { sql, params } = toSql({ field: 'status', operator: Operator.equals, value: 'active' });
102
333
  * // sql: '"status" = $1'
103
- * // params: ['active']
104
334
  *
105
- * // Complex rule
106
- * const rule2 = {
107
- * all: [
108
- * { field: 'deletedAt', operator: Operator.equals, value: null },
109
- * { field: 'status', operator: Operator.in, value: ['active', 'pending'] }
110
- * ]
111
- * };
112
- * const { sql: sql2, params: params2 } = toSql(rule2);
113
- * // sql: '("deletedAt" IS NULL AND "status" = ANY($1))'
114
- * // params: [['active', 'pending']]
335
+ * // Relation traversal with JOINs (map required)
336
+ * const { sql, params, joins } = toSql(
337
+ * { field: 'author.email', operator: Operator.equals, value: 'a@b.com' },
338
+ * { map, model: 'Post', alias: 't0' }
339
+ * );
340
+ * // sql: '"t1"."email" = $1'
341
+ * // joins: ['LEFT JOIN "User" AS "t1" ON "t1"."id" = "t0"."authorId"']
342
+ *
343
+ * // Same-record field comparison ($.field)
344
+ * const { sql: sql2 } = toSql({ field: 'endDate', operator: Operator.greaterThan, path: '$.startDate' });
345
+ * // sql2: '"endDate" > "startDate"'
346
+ *
347
+ * // External context ref
348
+ * const { sql: sql3 } = toSql(
349
+ * { field: 'userId', operator: Operator.equals, path: 'currentUser.id' },
350
+ * { context: { currentUser: { id: '123' } } }
351
+ * );
352
+ * // sql3: '"userId" = $1' params: ['123']
115
353
  * ```
116
354
  */
117
- declare const toSql: (condition: Condition) => SqlResult;
355
+ declare const toSql: (condition: Condition, options?: SqlBuildOptions) => SqlResult;
356
+
357
+ type RuleValidationTarget = 'check' | 'toPrisma' | 'toSql';
358
+ type ValidationIssue = {
359
+ path: string;
360
+ message: string;
361
+ code: string;
362
+ };
363
+ type ValidationResult = {
364
+ ok: boolean;
365
+ errors: ValidationIssue[];
366
+ };
367
+ declare const validateRule: (condition: unknown, options?: {
368
+ target?: RuleValidationTarget;
369
+ }) => ValidationResult;
370
+ declare const assertValidRule: (condition: unknown, options?: {
371
+ target?: RuleValidationTarget;
372
+ }) => asserts condition is Condition;
118
373
 
119
- export { type All, type Any, ArrayOperator, type ArrayRule, type ArrayType, type Condition, DateOperator, type DateRule, type IfThenElse, Operator, type Rule, type SqlResult, check, toSql };
374
+ export { type AggregateMode, type AggregateRule, type All, type Any, ArrayOperator, type ArrayRule, type BuildOptions, type Condition, type DateInputValue, DateOperator, type DateRule, type DateRuleValue, type FieldMap, type FieldMapEntry, type GroupByStep, type IfThenElse, Operator, type OrderedRuleValue, type PrismaStep, type PrismaWhere, type Rule, type RuleScalar, type RuleValidationTarget, type RuleValue, type SqlResult, type StepRef, type StrictAggregateRule, type StrictAll, type StrictAny, type StrictArrayCountRule, type StrictArrayPredicateRule, type StrictArrayPresenceRule, type StrictArrayRule, type StrictCondition, type StrictContainsRule, type StrictDateComparisonRule, type StrictDateDayRule, type StrictDateRangeRule, type StrictDateRule, type StrictEqualityRule, type StrictIfThenElse, type StrictMembershipRule, type StrictOrderedComparisonRule, type StrictPatternRule, type StrictPresenceRule, type StrictRangeRule, type StrictRule, type StrictStringBoundaryRule, type ToPrismaResult, type ValidationIssue, type ValidationResult, type WhereStep, assertValidRule, check, executePrismaQueryPlan, toPrisma, toSql, validateRule };