@inixiative/json-rules 3.5.4 → 3.7.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 +68 -1
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -2
- package/dist/index.d.ts +69 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -645,6 +645,7 @@ import { engineGlobals } from '@inixiative/json-rules';
|
|
|
645
645
|
engineGlobals.set('string.caseInsensitive', true);
|
|
646
646
|
engineGlobals.get('string.caseInsensitive'); // true
|
|
647
647
|
engineGlobals.set('prismaOptions.datasource.provider', 'mysql');
|
|
648
|
+
engineGlobals.set('sql.dialect', SqlDialect.mysql); // what toWhereSql / toLensWhereSql write
|
|
648
649
|
engineGlobals.reset(); // back to the defaults
|
|
649
650
|
|
|
650
651
|
// A scoped override: merged over the current state for the duration of a synchronous callback.
|
|
@@ -694,6 +695,70 @@ as `toPrisma()` takes it. A set without `mapName` throws.
|
|
|
694
695
|
toSql(rule, { map: lens, mapName: 'prisma', model: 'Post' });
|
|
695
696
|
```
|
|
696
697
|
|
|
698
|
+
## Inline Predicates — `toWhereSql`, `toLensWhereSql`
|
|
699
|
+
|
|
700
|
+
Two small helpers write a condition, or a lens's row filters, as a SQL predicate for a caller
|
|
701
|
+
that names its own columns and writes its own joins (an Omni topic, for one). They write the
|
|
702
|
+
engine's `sql.dialect` — `SqlDialect.postgres` by default, `SqlDialect.mysql` the other — and
|
|
703
|
+
are not a second `toSql`: `toSql` and `toPrisma` are unchanged.
|
|
704
|
+
|
|
705
|
+
```ts
|
|
706
|
+
import { engineGlobals, Operator, SqlDialect, toLensWhereSql, toWhereSql } from '@inixiative/json-rules';
|
|
707
|
+
|
|
708
|
+
engineGlobals.set('sql.dialect', SqlDialect.mysql);
|
|
709
|
+
|
|
710
|
+
toWhereSql(
|
|
711
|
+
{ field: 'affiliation', operator: Operator.in, value: ['advocate', 'consultant'] },
|
|
712
|
+
{ columns: { affiliation: '${fan_user.affiliation}' }, map, mapName: 'prisma', model: 'FanUser' },
|
|
713
|
+
);
|
|
714
|
+
// => "${fan_user.affiliation} IN ('advocate', 'consultant')"
|
|
715
|
+
|
|
716
|
+
toLensWhereSql(lens, { columns });
|
|
717
|
+
// => { root: '…' | null, relations: {}, mapDefaults: { prisma: { models: { BrandReferenceRequestFanUser: "${brand_reference_request_fan_user.affiliation} = 'advocate'" } } } }
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
- **Operators:** `equals`, `notEquals`, `in`, `notIn`, `exists`, `notExists`, inside `all` / `any`
|
|
721
|
+
(and `true` / `false`). Anything else — another operator, a date, array or aggregate rule,
|
|
722
|
+
`if` / `then` / `else` — throws, naming it. So do binds, `path` (column-to-column), offsets,
|
|
723
|
+
`coerceType`, case-insensitivity, a path across a bridge or through a to-many relation, anything
|
|
724
|
+
Json (a stored JSON null is a value to SQL and null to `check()`), and a DateTime or list
|
|
725
|
+
*value*. A DateTime's or list's existence compiles: `deletedAt notExists`, a lens's soft-delete
|
|
726
|
+
`where`, is `… IS NULL`. A rule with no `value` throws, as on every rail.
|
|
727
|
+
- **Map:** `map` / `mapName` / `model` are required. A value is checked against its column's
|
|
728
|
+
kind (`leafFitViolations`, as `validateNarrowing` checks it) and an undeclared path is refused —
|
|
729
|
+
MySQL casts a mistyped literal instead of erroring, so `score = TRUE` would match 1 where
|
|
730
|
+
`check()` matches nothing. The map also finds a to-one relation's key for its `exists`
|
|
731
|
+
(`columns['<relation>.<key>']`: the related row's key, NULL when there is none), and says which
|
|
732
|
+
values have no form here.
|
|
733
|
+
- **Columns:** `columns` maps a field path to the caller's SQL, used as written; the engine writes
|
|
734
|
+
no joins and no identifiers. A path missing from it throws, naming the path — it never falls back
|
|
735
|
+
to a quoted name, which MySQL's default mode reads as a string literal.
|
|
736
|
+
- **Values** are written inline — there are no placeholders to fill. A string doubles `'`; on
|
|
737
|
+
MySQL it doubles `\` too (MySQL's default mode reads a backslash as an escape;
|
|
738
|
+
`NO_BACKSLASH_ESCAPES` is not supported). `in` is `IN (…)`; an enum compares as written, with no
|
|
739
|
+
cast.
|
|
740
|
+
- **NULL rows:** negations keep them, as on every rail: `notEquals` / `notIn` are ORed with
|
|
741
|
+
`IS NULL`; `in [x, null]` matches NULL, `notIn [x, null]` doesn't.
|
|
742
|
+
- **Collation:** `=` and `IN` compare under the column's collation. check() compares strings
|
|
743
|
+
exactly, so a `_ci` column (MySQL's default) also matches `'Advocate'` for `'advocate'`; compare a
|
|
744
|
+
binary-collated column where that matters.
|
|
745
|
+
|
|
746
|
+
`toLensWhereSql(lens, { columns })` mirrors the lens: the root path's `where`s under `root` (null
|
|
747
|
+
when it has none), each relation path's own under `relations`, keyed by the path, and each model
|
|
748
|
+
default's (`mapDefaults[map].models[model].where`) under `mapDefaults`, keyed the same way — one
|
|
749
|
+
predicate per model however many paths reach it. Each is the `where` as its layer wrote it: a
|
|
750
|
+
parent layer's clamp on a hop is reported at that hop's path, never folded into what a later
|
|
751
|
+
layer wrote through it. A `where` that reads through a relation the lens hides throws — that
|
|
752
|
+
hop's clamps have no path to report at, and `check()` still carries them. A lens that spans maps
|
|
753
|
+
reports every map's defaults; a caller writing one database reads its own map's. A path `where` reads `columns` by its path from the lens model (`status` at the root,
|
|
754
|
+
`brandReferenceRequestFanUsers.id` below it); a model default by `'<Model>.<field>'`
|
|
755
|
+
(`BrandReferenceRequestFanUser.affiliation`). It does not compile through `toSql`'s `{ lens }`
|
|
756
|
+
path, which would fold the lens's `where`s into the predicate a second time.
|
|
757
|
+
|
|
758
|
+
**Absent, not hidden.** A row a relation's `where` filters out is absent from the SQL join, so a
|
|
759
|
+
negation through that relation holds for it; `check()` under the lens treats the row as hidden.
|
|
760
|
+
The lens is a projection: the difference is accepted and documented.
|
|
761
|
+
|
|
697
762
|
## Backend Support Matrix
|
|
698
763
|
|
|
699
764
|
Not every backend supports every rule shape.
|
|
@@ -845,6 +910,7 @@ Lens & bridges:
|
|
|
845
910
|
- `projectLens`, `walkLensPath`, `readLensValue`, `LensValue`, `describeRule`, `describeRuleSources`
|
|
846
911
|
- `toLensSelect`, `projectRows`, `LensSelect`, `LensRelationSelect`, `LensSelectOptions`, `ProjectRowsOptions`
|
|
847
912
|
- `toSourceQueries`, `materializeSources`, `materializeSourceQuery`
|
|
913
|
+
- `toLensWhereSql`, `LensWhereSql`, `LensWhereSqlOptions`
|
|
848
914
|
- `PathProjection`, `ProjectedVisit`, `ProjectLensOptions`, `LensPathHop`, `LensPathResolution`, `RuleDescription`, `RuleSourceDescription`, `SourceQuery`, `SourcePrismaQuery`, `SourceSqlQuery`, `SourceSelect`, `SourceValues`, `SourceRowShape`, `MaterializeSourceQueryOptions`
|
|
849
915
|
|
|
850
916
|
A lens has three forms, each with its own job:
|
|
@@ -875,7 +941,7 @@ const lens = composeLens('delegate-7', { user, 'org-acme': orgAcme, 'delegate-7'
|
|
|
875
941
|
### Operator Catalog
|
|
876
942
|
|
|
877
943
|
What a rule builder can offer for a field. The catalog's constants are `FieldKind`,
|
|
878
|
-
`RuleTarget`, `ValueShape`, `NUMERIC_KINDS` and `ALL_KINDS`; `getValueShape(operator, family)` takes an
|
|
944
|
+
`RuleTarget`, `ValueShape`, `ConditionShape`, `NUMERIC_KINDS` and `ALL_KINDS`; `getValueShape(operator, family)` takes an
|
|
879
945
|
`OperatorFamily` (`'field' | 'date' | 'array'`).
|
|
880
946
|
|
|
881
947
|
```ts
|
|
@@ -902,6 +968,7 @@ getValueShape('between', 'date'); // 'dateRange'
|
|
|
902
968
|
| `getArrayOperators(target?)` | The array operators, narrowed to one target when given. |
|
|
903
969
|
| `getAggregateOperators()` | The comparisons an aggregate rule takes. Every target compiles all of them. |
|
|
904
970
|
| `getValueShape(operator, family)` | The operand an operator takes (`'scalar'`, `'range'`, `'dayList'`, …). `family` is `'field'`, `'date'` or `'array'`, since `between` is both a field and a date operator. Throws on an operator the family doesn't have. |
|
|
971
|
+
| `getConditionShapeKeys(shape)` | `{ required, optional }`: the keys a node of a `ConditionShape` (`'all'`, `'any'`, `'if'`, `'field'`, `'aggregate'`, `'array'`, `'date'`) must have and the keys it may. A literal `true` / `false` is a condition too. |
|
|
905
972
|
|
|
906
973
|
To ask whether a whole rule runs on a target (windows, scope refs and operators together), use
|
|
907
974
|
`validateRule(rule, { target })`.
|