@inixiative/json-rules 3.4.1 → 3.5.1
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 +47 -32
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -24
- package/dist/index.d.ts +50 -24
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -308,7 +308,7 @@ predicate. Pipeline: filter → order → skip → take. Direction comes from `o
|
|
|
308
308
|
```
|
|
309
309
|
|
|
310
310
|
`filter` is a condition each element must pass to enter the window (`narrowRule` puts a lens
|
|
311
|
-
|
|
311
|
+
clamp there under `all`). `orderBy` is a non-empty array of `{ field, dir: 'asc' | 'desc' }` (multi-key);
|
|
312
312
|
`take`/`skip` are non-negative integers. **Empty-window semantics are author-driven**:
|
|
313
313
|
`all` is vacuously true on an empty window, `atLeast: 1` (or `any`) is false. To require
|
|
314
314
|
"the windowed element matches **and** one exists," combine `all` with `notEmpty` / `atLeast: 1`.
|
|
@@ -830,7 +830,7 @@ Rules:
|
|
|
830
830
|
- `Condition`, `StrictCondition`, `Rule`, `AggregateRule`, `AggregateMode`, `ArrayRule`, `DateRule`, `Row`, `CheckData`
|
|
831
831
|
- `GroupByStep`, `WhereStep`, `PrismaStep`, `PrismaWhere`, `StepRef` (a Prisma plan's steps); `ScopeRef`, `ScopedRef`, `ScopeOutOfBounds` (scope refs)
|
|
832
832
|
- `CheckOptions`, `CompileOptions`, `ToPrismaOptions`, `ToSqlOptions`, `ToSqlResult`, `ToPrismaResult`, `ValidateRuleOptions`, `ListBindingsOptions`, `ValidationIssue`, `ValidationResult`
|
|
833
|
-
- rule parts: `All`, `Any`, `IfThenElse`, `RuleValue`, `RuleScalar`, `OrderedRuleValue`, `ValueSourceOf`, `ValueSourceFields`, `NumberOffset`, `Magnitude`, `WindowFields`, `OrderBy`, `SortDir`
|
|
833
|
+
- rule parts: `All`, `Any`, `IfThenElse`, `RuleValue`, `RuleScalar`, `OrderedRuleValue`, `ValueSourceOf`, `ValueSourceFields`, `NumberOffset`, `Magnitude`, `WindowFields`, `OrderBy`, `SortDir` (and `orderRecords` to sort records by an `OrderBy`)
|
|
834
834
|
- dates: `DateRuleValue`, `DateInputValue`, `DateInputOrExpr`, `DateExpr`, `RollingExpr`, `PeriodExpr`, `EdgeExpr`, `PeriodUnit`, `RelativeUnits`, `DateOffset`, `DateConfig`, `TimeZoneConfig`, `WeekStart`
|
|
835
835
|
- the strict shapes, which pair each operator with its operand's type: `StrictCondition`, `StrictAll`, `StrictAny`, `StrictIfThenElse`, `StrictRule`, `StrictEqualityRule`, `StrictMembershipRule`, `StrictOrderedComparisonRule`, `StrictRangeRule`, `StrictContainsRule`, `StrictStringBoundaryRule`, `StrictPatternRule`, `StrictPresenceRule`, `StrictDateRule`, `StrictDateComparisonRule`, `StrictDateRangeRule`, `StrictDateDayRule`, `StrictArrayRule`, `StrictArrayPredicateRule`, `StrictArrayCountRule`, `StrictArrayPresenceRule`, `StrictAggregateRule`
|
|
836
836
|
- `engineGlobals`, `EngineGlobalsState`, `PrismaProvider`, `FuzzyConfig`
|
|
@@ -841,7 +841,7 @@ Lens & bridges:
|
|
|
841
841
|
- `FieldMap`, `FieldMapEntry`, `ModelEntry`, `SourceOption`, `FieldMapSet`, `Bridge`, `BridgeEndpoint`, `BridgeCardinality`, `BridgeDictionary`
|
|
842
842
|
- `createLens`, `storeLens`, `composeLens`, `StoredLens`, `stitchFieldMaps`, `indexBridges`, `validateFieldMaps`, `assertValidFieldMaps`
|
|
843
843
|
- `validateNarrowing`, `assertValidNarrowing`, `validateRuleInLens`, `narrowRule`, `coerceRule`
|
|
844
|
-
- `bindLens`, `listLensBindings`, `getLensRoot`
|
|
844
|
+
- `bindLens`, `listLensBindings`, `getLensRoot`, `clampLens`, `LensClamps`
|
|
845
845
|
- `projectLens`, `walkLensPath`, `readLensValue`, `LensValue`, `describeRule`, `describeRuleSources`
|
|
846
846
|
- `toLensSelect`, `projectRows`, `LensSelect`, `LensRelationSelect`, `LensSelectOptions`, `ProjectRowsOptions`
|
|
847
847
|
- `toSourceQueries`, `materializeSources`, `materializeSourceQuery`
|
|
@@ -867,9 +867,9 @@ A lens has three forms, each with its own job:
|
|
|
867
867
|
narrowing it came from.
|
|
868
868
|
|
|
869
869
|
```ts
|
|
870
|
-
const records = storeLens(
|
|
871
|
-
// later: fetch '
|
|
872
|
-
const lens = composeLens('
|
|
870
|
+
const records = storeLens(delegateLens, ['user', 'org-acme', 'delegate-7']); // persist each record
|
|
871
|
+
// later: fetch 'delegate-7', then the ids in its `parents`
|
|
872
|
+
const lens = composeLens('delegate-7', { user, 'org-acme': orgAcme, 'delegate-7': delegate7 });
|
|
873
873
|
```
|
|
874
874
|
|
|
875
875
|
### Operator Catalog
|
|
@@ -957,7 +957,7 @@ Two error classes say whose input went wrong; each sets `name`, so check with `i
|
|
|
957
957
|
| Class | Thrown when | Fix |
|
|
958
958
|
| --- | --- | --- |
|
|
959
959
|
| `UsageError` | The caller's input is missing or malformed: no `now` for a relative date, an invalid `now` or time zone, a bind never bound (`bindRule` / `bindLens` before compiling; `bindings` for `check`). | Supply the input. |
|
|
960
|
-
| `LensRefusal` (`code`) | The lens itself can't do it: a later layer's
|
|
960
|
+
| `LensRefusal` (`code`) | The lens itself can't do it: a later layer's clamp reads what its parent hides, a clamp or source a compile has no form for, a source path that can't carry its link. | Fix the lens — `validateNarrowing` reports the same refusal as an issue. |
|
|
961
961
|
|
|
962
962
|
```ts
|
|
963
963
|
import { LensRefusal, UsageError } from '@inixiative/json-rules';
|
|
@@ -1014,7 +1014,7 @@ boundary is **enforced, not documented**:
|
|
|
1014
1014
|
|
|
1015
1015
|
- a rule authored against a lens provably can't reference outside it —
|
|
1016
1016
|
`validateRuleInLens`, at author time;
|
|
1017
|
-
- the row-scope **`where` is the
|
|
1017
|
+
- the row-scope **`where` is the clamp, applied server-side at execution** via
|
|
1018
1018
|
`narrowRule` — the authored rule never sees it and can't escape it;
|
|
1019
1019
|
- what reaches an untrusted party reveals nothing hidden — `projectLens(…, { by: 'model' })`.
|
|
1020
1020
|
|
|
@@ -1022,7 +1022,7 @@ A lens defines a **surface area**, reused for distinct, separately-enforced
|
|
|
1022
1022
|
constraints that may **diverge**: the *data-flow* surface (what you receive / pass
|
|
1023
1023
|
into an interpolated template / expose to a client) vs the *reasoning* surface
|
|
1024
1024
|
(what you may author predicates against — which can be narrower than what you
|
|
1025
|
-
actually get back). One predicate DSL (`Condition`) expresses both the **
|
|
1025
|
+
actually get back). One predicate DSL (`Condition`) expresses both the **clamp**
|
|
1026
1026
|
(`where`) and the **use** (rules), compiling to `check` / `toPrisma` / `toSql`.
|
|
1027
1027
|
That's why the same primitive backs permissions, email targeting/conditions,
|
|
1028
1028
|
feature flags, and state-transition guards — it's the authority/visibility spine
|
|
@@ -1117,11 +1117,11 @@ validateRuleInLens({ field: 'posts', arrayOperator: ArrayOperator.any, condition
|
|
|
1117
1117
|
`root.relations`; a spelled node grows its own tree. Every posture walks the same tree, so they
|
|
1118
1118
|
agree and stay small. `lensVisit(lens, 'org.users')` resolves one visit on demand, as
|
|
1119
1119
|
`projectLens` would give it, or `null`.
|
|
1120
|
-
- **
|
|
1120
|
+
- **Clamps.** The first narrowing's `where`s (and source eligibility `where`s) may read any
|
|
1121
1121
|
relation on the schema; a later layer's may read only what its parent shows — a delegate can't
|
|
1122
1122
|
probe a relation it can't see (`validateNarrowing` reports it; every posture throws). A bare
|
|
1123
|
-
value `path` reads the root row, so only `root.where` may hold one. `toLensSelect` fetches exactly the columns
|
|
1124
|
-
`projectRows({
|
|
1123
|
+
value `path` reads the root row, so only `root.where` may hold one. `toLensSelect` fetches exactly the columns clamps read, and
|
|
1124
|
+
`projectRows({ keepClampColumns: true })` keeps them for a re-check.
|
|
1125
1125
|
|
|
1126
1126
|
### LensNarrowing & `where`
|
|
1127
1127
|
|
|
@@ -1146,7 +1146,7 @@ const narrowing: LensNarrowing = {
|
|
|
1146
1146
|
};
|
|
1147
1147
|
```
|
|
1148
1148
|
|
|
1149
|
-
Composition across chained narrowings is pure intersection: relations are turned on by the first narrowing and only hidden after it. `where` clauses are anchored to the model they describe — `root.where` ANDs at the lens anchor, `mapDefaults[X].models[Y].where` injects at every visit of Y in map X, and `root.relations[R]...where` injects when the rule descends through R. Under the `all` array operator the
|
|
1149
|
+
Composition across chained narrowings is pure intersection: relations are turned on by the first narrowing and only hidden after it. `where` clauses are anchored to the model they describe — `root.where` ANDs at the lens anchor, `mapDefaults[X].models[Y].where` injects at every visit of Y in map X, and `root.relations[R]...where` injects when the rule descends through R. Under the `all` array operator the clamp goes into the rule's window `filter`, so out-of-scope rows are dropped before the user's "every row matches" check; `check()` and `toPrisma` run it. See [docs/LENS.md](./docs/LENS.md) for the full anchor semantics.
|
|
1150
1150
|
|
|
1151
1151
|
### Lens Utilities
|
|
1152
1152
|
|
|
@@ -1159,10 +1159,12 @@ Composition across chained narrowings is pure intersection: relations are turned
|
|
|
1159
1159
|
| `validateRuleInLens(rule, lens)` | Validates a user rule's field paths and enum values against the narrowed lens, path-aware. Returns `{ ok, errors: { path, message, code }[] }` like `validateRule` (codes such as `not_in_lens`, `operator_kind_mismatch`, `invalid_value`, `value_not_allowed`). Every relation a field, a value ref, an offset or an `orderBy` / aggregate field crosses must be turned on. A bare value `path` is a root-row column, gated like a field. The security gate. |
|
|
1160
1160
|
| `describeRule(rule, lens)` | `{ sources, bridgesCrossed, supportedTargets, errors }`: the maps a rule reads, whether it crosses a bridge, which of `check` / `toPrisma` / `toSql` can run it (by the rule's shape, as `validateRule` reads it — a compiler may still refuse a field's kind, such as a date rule on a String column on Prisma), and the lens gate's `ValidationIssue`s. For routing and UX; `validateRuleInLens` stays the gate. |
|
|
1161
1161
|
| `getLensRoot(lensOrNarrowing)` | The base lens a narrowing chain is rooted at; a lens is its own. Throws on a cyclic chain. |
|
|
1162
|
+
| `clampLens(lens, clamps)` | The lens with `clamps` (`{ root?: { where }, mapDefaults?: { [map]: { models: { [model]: { where?, sources? } } } } }`) ANDed into its first narrowing over the base lens, one added over a bare lens; later layers kept as they were. Only the first layer's clamps read the whole schema, so a clamp on what a later layer hides goes here. A source's `where` ANDs; its `label` / `groupBy` win, and a source clamp on a field with none adds that option set. Wheres only narrow. |
|
|
1162
1163
|
| `lensVisit(lens, relationPath, options?)` | One visit as `projectLens` (by path) gives it — its shown fields with values and options, sources, labels and axes — at a dotted relation path from the anchor (`''` for the anchor), resolved on demand: nothing is enumerated, so it is cheap on any schema. `null` when a relation on the path isn't shown there (off, omitted, or outside the model-default tree). A builder walks a lens with it instead of re-deriving the lens's rules. |
|
|
1163
1164
|
| `walkLensPath(lens, path)` | Resolves one dotted path through the lens hop by hop: `{ outcome: 'resolved', hops, terminal, jsonSubPath }`, or `hidden` (a column it doesn't keep, or a relation it doesn't turn on there) / `missing` / `pastScalar` with the failing `index`. |
|
|
1164
|
-
| `readLensValue(lens, row, path, options?)` | One value off a row, as the lens shows it: `{ ok: true, value }`, or `{ ok: false, reason }` — `hidden` / `missing` / `pastScalar` (the walk `validateRuleInLens` gates a field with), `relation` (the path ends on rows, not a value) or `list` (it crosses a to-many). Each row on the way, the root included, is checked against its visit's
|
|
1165
|
-
| `
|
|
1165
|
+
| `readLensValue(lens, row, path, options?)` | One value off a row, as the lens shows it: `{ ok: true, value }`, or `{ ok: false, reason }` — `hidden` / `missing` / `pastScalar` (the walk `validateRuleInLens` gates a field with), `relation` (the path ends on rows, not a value) or `list` (it crosses a to-many). Each row on the way, the root included, is checked against its visit's clamps: one a clamp hides, or a missing one, reads `null`. Only own properties are read, into a Json column too. `options` is what each clamp is checked with (`now`, `bindings`). For values a template interpolates. |
|
|
1166
|
+
| `orderRecords(items, orderBy)` | Records sorted by an `OrderBy` exactly as a window orders them on every rail: each key in turn, own-property path reads, `dir` order, NULL or absent last in either direction, ties in input order. Returns a new array. Strings sort in JavaScript code-unit order, which can differ from a database's collation (see [Where the Rails Differ](#where-the-rails-differ)). |
|
|
1167
|
+
| `narrowRule(rule, narrowing)` | Composes the user rule with the lens's `where` clauses, injecting each at its anchor in the rule tree. Under an `all`, the clamp goes into the rule's window `filter`, which `check()` evaluates and `toPrisma` folds into the rule (`toSql` compiles no relation arrays). Other rules pass to `check` / `toPrisma` / `toSql`. |
|
|
1166
1168
|
| `coerceRule(rule, lens)` | Stamps each field rule with its field's `coerceType` from the lens (`Int`, `Float`, `Decimal`, `BigInt`, `DateTime`, `Boolean`, `String`). Leaves date rules, aggregate comparisons, rules that already carry a `coerceType`, and anything below a Json column alone. |
|
|
1167
1169
|
|
|
1168
1170
|
```ts
|
|
@@ -1208,6 +1210,7 @@ to each field as `options`.
|
|
|
1208
1210
|
|
|
1209
1211
|
```ts
|
|
1210
1212
|
import {
|
|
1213
|
+
executePrismaPlan,
|
|
1211
1214
|
materializeSourceQuery,
|
|
1212
1215
|
materializeSources,
|
|
1213
1216
|
projectLens,
|
|
@@ -1229,8 +1232,13 @@ const [query] = toSourceQueries(narrowing);
|
|
|
1229
1232
|
// query.prisma => { model: 'User', distinct: ['region'], select: { region: true }, where: { AND: [...] } }
|
|
1230
1233
|
// query.sql => { sql: 'SELECT DISTINCT "t0"."region" FROM "User" AS "t0" WHERE (...)', params: ['t-42', true] }
|
|
1231
1234
|
// A query with `recheck` (a source across a bridge) returns candidates; see "Sources across a bridge".
|
|
1232
|
-
|
|
1233
|
-
const
|
|
1235
|
+
// `steps` (a count step, a column reference) resolve through executePrismaPlan first.
|
|
1236
|
+
const { distinct, select, where, steps } = query.prisma; // query.model === 'User'
|
|
1237
|
+
const rows = await prisma.user.findMany({
|
|
1238
|
+
distinct,
|
|
1239
|
+
select,
|
|
1240
|
+
where: steps ? await executePrismaPlan({ steps }, prisma) : where,
|
|
1241
|
+
});
|
|
1234
1242
|
const values = materializeSourceQuery(query, rows); // { path, mapName, model, field, options: [{ value }] }
|
|
1235
1243
|
|
|
1236
1244
|
// Or from rows already fetched under the lens (relations inline):
|
|
@@ -1242,15 +1250,15 @@ const projection = projectLens(narrowing, { sourceValues: [values] });
|
|
|
1242
1250
|
|
|
1243
1251
|
| Function | Purpose |
|
|
1244
1252
|
| --- | --- |
|
|
1245
|
-
| `toSourceQueries(lensOrNarrowing, options?)` | `SourceQuery[]`, one per sourced field: `{ path, mapName, model, field, label?, groupBy?, composedWhere, prisma, sql, recheck? }`. `options` is the clock (`now`, `timeZone`, `weekStart`) a relative date in the where compiles with — required for one, a plain usage error without it; bind a lens's binds with `bindLens` first. `prisma.steps` is present when the where needs `executePrismaPlan
|
|
1253
|
+
| `toSourceQueries(lensOrNarrowing, options?)` | `SourceQuery[]`, one per sourced field: `{ path, mapName, model, field, label?, groupBy?, composedWhere, prisma, sql, recheck? }`. `options` is the clock (`now`, `timeZone`, `weekStart`) a relative date in the where compiles with — required for one, a plain usage error without it; bind a lens's binds with `bindLens` first. `prisma.steps` is present when the where needs `executePrismaPlan` (a count step, or a column reference to resolve): query with the where it returns. `sql.sql` is `null` with an `error` when SQL can't express the where. A source that reads across a bridge gets an over-fetching query and a `recheck`: its rows are candidates, not options (see "Sources across a bridge"). `distinct` is the value and a sibling label column; a grouped source or a dotted label drops it, so every label comes back and the least one is picked. |
|
|
1246
1254
|
| `materializeSourceQuery(query, rows, { rowShape?, lens?, now?, … })` | One query's fetched rows as `SourceValues`. `rowShape` is `'prisma'` (default: a dotted `label` and each `groupBy` axis come nested) or `'sql'` (they come flat as `__label` / `__group_i`). Options are deduplicated and sorted. A query with `recheck` needs `lens` and rows holding the far side: it re-checks each candidate (`check`, with the clock and bindings given) and reads a bridged label or axis from the far side; a missing far side is a `UsageError`. |
|
|
1247
|
-
| `materializeSources(lensOrNarrowing, rows, options?)` | `SourceValues[]` for every sourced field, from the rows the lens fetches — `toLensSelect`'s rows as fetched, or as `projectRows(…, {
|
|
1255
|
+
| `materializeSources(lensOrNarrowing, rows, options?)` | `SourceValues[]` for every sourced field, from the rows the lens fetches — `toLensSelect`'s rows as fetched, or as `projectRows(…, { keepClampColumns: true })` keeps them. A viewer's projection drops what sources read: a row lacking any key a read walks — through each relation and list element to the column — that a source or a clamp on its path reads throws a `UsageError` (a fetch returns every key it selects, NULL as `null`). The path is walked down the rows — the tree is its link — each level's clamps met, and each row it reaches must meet, through `check()` with `options`, its visit's clamps, its source `where` narrowed as a rule is, the guards of the relations its label and axes cross and the values the lens allows — so it offers what `toSourceQueries` does. A scalar-list field gives one option per element; a value takes its least label. A `from: 'mapDefaults'` source throws (see below). |
|
|
1248
1256
|
|
|
1249
1257
|
Options never offer a value the lens disallows: `projectLens` drops fetched values outside a
|
|
1250
1258
|
field's allowed set. Nor do they come through a row the lens hides: each source `where` is
|
|
1251
1259
|
narrowed under the whole lens as `narrowRule` narrows a rule — every relation it crosses carries
|
|
1252
|
-
that visit's
|
|
1253
|
-
a window or no condition) and on each hop and terminal relation of a dotted path. A
|
|
1260
|
+
that visit's clamps, inside an array condition (into its `condition`, or its `filter` under `all`,
|
|
1261
|
+
a window or no condition) and on each hop and terminal relation of a dotted path. A clamp a rule
|
|
1254
1262
|
couldn't carry there (one narrowRule can't re-root, a to-many relation read flat) is refused, as
|
|
1255
1263
|
it is for a rule; so is a source whose query has a window toPrisma can't compile (by its shape,
|
|
1256
1264
|
before anything compiles — `materializeSources` refuses it too, so the two never disagree).
|
|
@@ -1258,7 +1266,7 @@ before anything compiles — `materializeSources` refuses it too, so the two nev
|
|
|
1258
1266
|
#### Two kinds of source
|
|
1259
1267
|
|
|
1260
1268
|
A source declared down a relation path offers the rows **reachable** from there: the path is
|
|
1261
|
-
carried down through each relation's inverse (where the map declares one), and every
|
|
1269
|
+
carried down through each relation's inverse (where the map declares one), and every clamp above
|
|
1262
1270
|
it with it, so a Tag source under `User.tagAttachments.tag` offers the tags a live
|
|
1263
1271
|
attachment of an in-tenant user points at. When a field should offer every row the lens lets its
|
|
1264
1272
|
model show — linked or not, what a rule may *name* — point the path source at the model's own
|
|
@@ -1290,19 +1298,19 @@ mapDefaults: {
|
|
|
1290
1298
|
|
|
1291
1299
|
`from: 'mapDefaults'` resolves where it sits — `mapDefaults[<this path's map>].models[<this path's
|
|
1292
1300
|
model>].sources[<field>]` — and takes that source's eligibility (tenancy included), label and
|
|
1293
|
-
axes; its own `where`, and child layers, only narrow it. A pointer drops the
|
|
1301
|
+
axes; its own `where`, and child layers, only narrow it. A pointer drops the clamps the path
|
|
1294
1302
|
carries down only in the layer that declares it: every layer before or after it still carries
|
|
1295
1303
|
theirs, so a child's pointer can only narrow what its parent gave, and a tenant layer added after a
|
|
1296
1304
|
pointer always narrows it. How depends on how it scopes: through `mapDefaults` (the model's own
|
|
1297
|
-
|
|
1305
|
+
clamp or source) the pointer still offers unlinked rows; through a root `where` the clamp can only
|
|
1298
1306
|
reach the pointer down the path, so it offers just the linked rows — and across a bridge the
|
|
1299
|
-
|
|
1307
|
+
clamp comes back as the query's `recheck` (below). Scope tenancy through `mapDefaults` to keep a pointer's unlinked rows.
|
|
1300
1308
|
A pointer whose model declares no source fails `validateNarrowing` (`invalid_source`) and throws
|
|
1301
1309
|
from `projectLens` (by path) / `toSourceQueries` / `materializeSources`, even where a layer hides
|
|
1302
1310
|
its field; `projectLens(…, { by: 'model' })` and `describeRuleSources` read the lens without
|
|
1303
1311
|
validating it. Across a bridge it is how a picker gets options at all: the
|
|
1304
1312
|
model source compiles against the far map alone, with that map's own tenancy, where a path source
|
|
1305
|
-
over-fetches and re-checks (see "Sources across a bridge"). A
|
|
1313
|
+
over-fetches and re-checks (see "Sources across a bridge"). A clamp a path can't carry down — a relation whose
|
|
1306
1314
|
map declares no inverse — is a `LensRefusal`, never an empty list. `materializeSources` refuses a pointer — a fetched collection can't hold unlinked
|
|
1307
1315
|
rows; query it with `toSourceQueries` and `materializeSourceQuery`.
|
|
1308
1316
|
|
|
@@ -1330,15 +1338,22 @@ see, and marks it with `recheck`:
|
|
|
1330
1338
|
`UsageError` — keeps the candidates `check(recheck, row)` holds, and reads the label and axes
|
|
1331
1339
|
across the bridge from the far side, in either row shape. Without `lens` it throws.
|
|
1332
1340
|
- **A source past a bridge** (its path crosses one) is queried against its own model's map; the
|
|
1333
|
-
|
|
1341
|
+
clamps above it are carried back across the bridge through the far model's bridge field, and
|
|
1334
1342
|
come back as its `recheck` — so each candidate holds the near rows inline
|
|
1335
1343
|
(`{ id, industry, 'prisma:FanUser': [{ email, … }] }`).
|
|
1344
|
+
- **A re-check reading into a Json column selects the column whole** (`meta: true`, never a nested
|
|
1345
|
+
select into it); the re-check reads inside it in memory.
|
|
1336
1346
|
- **SQL rows are flat**, so a bridged query whose re-check reads through a local relation has
|
|
1337
1347
|
`sql.sql: null` (with `sql.error`); run the Prisma form.
|
|
1338
1348
|
|
|
1339
1349
|
```ts
|
|
1340
1350
|
const [query] = toSourceQueries(lens, { now });
|
|
1341
|
-
const
|
|
1351
|
+
const { distinct, select, where, steps } = query.prisma; // query.model === 'User'
|
|
1352
|
+
const candidates = await prisma.user.findMany({
|
|
1353
|
+
distinct,
|
|
1354
|
+
select,
|
|
1355
|
+
where: steps ? await executePrismaPlan({ steps }, prisma) : where,
|
|
1356
|
+
}); // a superset when `recheck` is present
|
|
1342
1357
|
const rows = query.recheck === undefined ? candidates : await loadFarSide(candidates); // your join
|
|
1343
1358
|
const values = materializeSourceQuery(query, rows, { lens, now });
|
|
1344
1359
|
```
|
|
@@ -1357,15 +1372,15 @@ import { check, executePrismaPlan, narrowRule, projectRows, toLensSelect, toPris
|
|
|
1357
1372
|
const where = await executePrismaPlan(toPrisma(true, { lens: narrowing, now }), prisma);
|
|
1358
1373
|
const rows = await prisma.user.findMany({ where, ...toLensSelect(narrowing, { now }) });
|
|
1359
1374
|
const shown = projectRows(narrowing, rows, { now }); // what a viewer may see
|
|
1360
|
-
// To re-test the
|
|
1361
|
-
const forRecheck = projectRows(narrowing, rows, {
|
|
1375
|
+
// To re-test the clamps in memory later — never to return to a viewer:
|
|
1376
|
+
const forRecheck = projectRows(narrowing, rows, { keepClampColumns: true, now });
|
|
1362
1377
|
const holds = forRecheck.filter((row) => check(narrowRule(rule, narrowing), row, { now }) === true);
|
|
1363
1378
|
```
|
|
1364
1379
|
|
|
1365
1380
|
| Function | Purpose |
|
|
1366
1381
|
| --- | --- |
|
|
1367
|
-
| `toLensSelect(lensOrNarrowing, options?)` | `{ select }` for `findMany` at the base model. It selects each visit's visible columns and the relations turned on there (one that is off is not fetched; the model-default tree bounds it), and every column a `where` on the way reads. A to-many relation carries its visit's
|
|
1368
|
-
| `projectRows(lensOrNarrowing, rows, options?)` | Rows cut to what the lens shows, recursively. Hidden columns, and relations that are off or omitted, are removed. A row a visit's `where` hides is dropped from the root or a list, and a to-one row becomes `null`. `
|
|
1382
|
+
| `toLensSelect(lensOrNarrowing, options?)` | `{ select }` for `findMany` at the base model. It selects each visit's visible columns and the relations turned on there (one that is off is not fetched; the model-default tree bounds it), and every column a `where` on the way reads. A to-many relation carries its visit's clamps compiled as its `where`, so related rows come pre-narrowed — unless a clamp reads that list: a clamp reads it whole, as the database does, so it is fetched whole and `projectRows` cuts it. A to-one relation takes no `where` in Prisma, so `projectRows` drops one its clamp hides. A relation that shows no column — one turned on with all its columns hidden, or one a clamp reads only for presence or a count — is fetched by its key alone — the join key, else `id` — even a hidden one, as a clamp's columns are; never another column. The fetch carries it for the re-check and a viewer's projection drops it; a model with no key is not fetched, and presence on it can't be re-checked from fetched rows. A root that shows no column is selected by its `id` likewise. Bridges are skipped. It also selects what each projected source reads — the value, its label and axes, and every column and relation its option query's condition reads — as it does a clamp's columns, so `materializeSources` over the fetched rows offers what the database does; a viewer's projection drops them. For a source across a bridge that is its local reads: what it reads on this side, hidden or not, and each crossed bridge's local `on` key, so the rows plus the far side loaded under its bridge field answer it. A to-many relation's clamp that needs a counting step (a count or an aggregate), or has a window toPrisma can't compile, is refused (a `LensRefusal`, which `validateNarrowing` reports) before anything compiles. `options` is the clock for compiling the clamps. The root's own clamps are the query's `where`: `toPrisma(rule, { lens })`. |
|
|
1383
|
+
| `projectRows(lensOrNarrowing, rows, options?)` | Rows cut to what the lens shows, recursively. Hidden columns, and relations that are off or omitted, are removed. A row a visit's `where` hides is dropped from the root or a list, and a to-one row becomes `null`. `keepClampColumns: true` keeps the columns those `where`s and the projected sources read, even hidden ones, and a hidden to-one row, or a hidden row of a list a clamp reads, as those columns alone, so `check(narrowRule(rule, lens), row)` re-tests the clamps as the database does, for any rule the lens admits; that output carries hidden values, so never return it to a viewer. The other options (`now`, `bindings`) are what each `where` is checked with. Plain JSON in and out. |
|
|
1369
1384
|
|
|
1370
1385
|
### Evaluating Across Bridges
|
|
1371
1386
|
|