@inixiative/json-rules 3.4.0 → 3.5.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 +69 -41
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -35
- package/dist/index.d.ts +61 -35
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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`
|
|
@@ -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
|
|
|
@@ -1161,8 +1161,9 @@ Composition across chained narrowings is pure intersection: relations are turned
|
|
|
1161
1161
|
| `getLensRoot(lensOrNarrowing)` | The base lens a narrowing chain is rooted at; a lens is its own. Throws on a cyclic chain. |
|
|
1162
1162
|
| `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
1163
|
| `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
|
-
| `
|
|
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 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. |
|
|
1165
|
+
| `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. |
|
|
1166
|
+
| `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
1167
|
| `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
1168
|
|
|
1168
1169
|
```ts
|
|
@@ -1228,8 +1229,7 @@ const [query] = toSourceQueries(narrowing);
|
|
|
1228
1229
|
// query.composedWhere => the node's `where` AND the source's eligibility, narrowed as a rule is
|
|
1229
1230
|
// query.prisma => { model: 'User', distinct: ['region'], select: { region: true }, where: { AND: [...] } }
|
|
1230
1231
|
// query.sql => { sql: 'SELECT DISTINCT "t0"."region" FROM "User" AS "t0" WHERE (...)', params: ['t-42', true] }
|
|
1231
|
-
//
|
|
1232
|
-
if (query.prisma === null) throw new Error(query.sql.error);
|
|
1232
|
+
// A query with `recheck` (a source across a bridge) returns candidates; see "Sources across a bridge".
|
|
1233
1233
|
const { distinct, select, where } = query.prisma;
|
|
1234
1234
|
const rows = await prisma.user.findMany({ distinct, select, where });
|
|
1235
1235
|
const values = materializeSourceQuery(query, rows); // { path, mapName, model, field, options: [{ value }] }
|
|
@@ -1243,15 +1243,15 @@ const projection = projectLens(narrowing, { sourceValues: [values] });
|
|
|
1243
1243
|
|
|
1244
1244
|
| Function | Purpose |
|
|
1245
1245
|
| --- | --- |
|
|
1246
|
-
| `toSourceQueries(lensOrNarrowing, options?)` | `SourceQuery[]`, one per sourced field: `{ path, mapName, model, field, label?, groupBy?, composedWhere, prisma, sql }`. `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`. `sql.sql` is `null` with an `error` when SQL can't express the where. A
|
|
1247
|
-
| `materializeSourceQuery(query, rows, { rowShape
|
|
1248
|
-
| `materializeSources(lensOrNarrowing, rows, options?)` | `SourceValues[]` for every sourced field, from the rows the lens fetches — `toLensSelect`'s rows as fetched, or as `projectRows(…, {
|
|
1246
|
+
| `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`. `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. |
|
|
1247
|
+
| `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`. |
|
|
1248
|
+
| `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). |
|
|
1249
1249
|
|
|
1250
1250
|
Options never offer a value the lens disallows: `projectLens` drops fetched values outside a
|
|
1251
1251
|
field's allowed set. Nor do they come through a row the lens hides: each source `where` is
|
|
1252
1252
|
narrowed under the whole lens as `narrowRule` narrows a rule — every relation it crosses carries
|
|
1253
|
-
that visit's
|
|
1254
|
-
a window or no condition) and on each hop and terminal relation of a dotted path. A
|
|
1253
|
+
that visit's clamps, inside an array condition (into its `condition`, or its `filter` under `all`,
|
|
1254
|
+
a window or no condition) and on each hop and terminal relation of a dotted path. A clamp a rule
|
|
1255
1255
|
couldn't carry there (one narrowRule can't re-root, a to-many relation read flat) is refused, as
|
|
1256
1256
|
it is for a rule; so is a source whose query has a window toPrisma can't compile (by its shape,
|
|
1257
1257
|
before anything compiles — `materializeSources` refuses it too, so the two never disagree).
|
|
@@ -1259,7 +1259,7 @@ before anything compiles — `materializeSources` refuses it too, so the two nev
|
|
|
1259
1259
|
#### Two kinds of source
|
|
1260
1260
|
|
|
1261
1261
|
A source declared down a relation path offers the rows **reachable** from there: the path is
|
|
1262
|
-
carried down through each relation's inverse (where the map declares one), and every
|
|
1262
|
+
carried down through each relation's inverse (where the map declares one), and every clamp above
|
|
1263
1263
|
it with it, so a Tag source under `User.tagAttachments.tag` offers the tags a live
|
|
1264
1264
|
attachment of an in-tenant user points at. When a field should offer every row the lens lets its
|
|
1265
1265
|
model show — linked or not, what a rule may *name* — point the path source at the model's own
|
|
@@ -1291,34 +1291,62 @@ mapDefaults: {
|
|
|
1291
1291
|
|
|
1292
1292
|
`from: 'mapDefaults'` resolves where it sits — `mapDefaults[<this path's map>].models[<this path's
|
|
1293
1293
|
model>].sources[<field>]` — and takes that source's eligibility (tenancy included), label and
|
|
1294
|
-
axes; its own `where`, and child layers, only narrow it. A pointer drops the
|
|
1294
|
+
axes; its own `where`, and child layers, only narrow it. A pointer drops the clamps the path
|
|
1295
1295
|
carries down only in the layer that declares it: every layer before or after it still carries
|
|
1296
1296
|
theirs, so a child's pointer can only narrow what its parent gave, and a tenant layer added after a
|
|
1297
1297
|
pointer always narrows it. How depends on how it scopes: through `mapDefaults` (the model's own
|
|
1298
|
-
|
|
1299
|
-
reach the pointer down the path, so it offers just the linked rows — and across a bridge
|
|
1300
|
-
|
|
1298
|
+
clamp or source) the pointer still offers unlinked rows; through a root `where` the clamp can only
|
|
1299
|
+
reach the pointer down the path, so it offers just the linked rows — and across a bridge the
|
|
1300
|
+
clamp comes back as the query's `recheck` (below). Scope tenancy through `mapDefaults` to keep a pointer's unlinked rows.
|
|
1301
1301
|
A pointer whose model declares no source fails `validateNarrowing` (`invalid_source`) and throws
|
|
1302
1302
|
from `projectLens` (by path) / `toSourceQueries` / `materializeSources`, even where a layer hides
|
|
1303
1303
|
its field; `projectLens(…, { by: 'model' })` and `describeRuleSources` read the lens without
|
|
1304
1304
|
validating it. Across a bridge it is how a picker gets options at all: the
|
|
1305
1305
|
model source compiles against the far map alone, with that map's own tenancy, where a path source
|
|
1306
|
-
|
|
1306
|
+
over-fetches and re-checks (see "Sources across a bridge"). A clamp a path can't carry down — a relation whose
|
|
1307
1307
|
map declares no inverse — is a `LensRefusal`, never an empty list. `materializeSources` refuses a pointer — a fetched collection can't hold unlinked
|
|
1308
1308
|
rows; query it with `toSourceQueries` and `materializeSourceQuery`.
|
|
1309
1309
|
|
|
1310
1310
|
#### Sources across a bridge
|
|
1311
1311
|
|
|
1312
|
-
A source whose path, `where`, `label` or an axis reads across a bridge
|
|
1313
|
-
database holds
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1312
|
+
A source whose path, `where`, `label` or an axis reads across a bridge can't be decided by one
|
|
1313
|
+
database — each holds one side. `toSourceQueries` still returns a real query for the side it can
|
|
1314
|
+
see, and marks it with `recheck`:
|
|
1315
|
+
|
|
1316
|
+
- **The query over-fetches.** What reads across the bridge compiles to TRUE (the compilers'
|
|
1317
|
+
over-fetch), so the query never pre-filters on what it can't see: its rows are a **superset** of
|
|
1318
|
+
the true options. Everything local is still decided by the database.
|
|
1319
|
+
- **`recheck` is what the database couldn't decide** — the conjuncts of `composedWhere` that read
|
|
1320
|
+
across a bridge, or `true` when only the label or an axis does. **If `recheck` is present, the
|
|
1321
|
+
query's rows are candidates, not options.** A source that reads no bridge has no `recheck`.
|
|
1322
|
+
- **The query selects local columns only:** the value, a local label and local axes, the local
|
|
1323
|
+
columns `recheck` reads, and each crossed bridge's local `on` key (under the local relations the
|
|
1324
|
+
read crosses first). It drops `distinct`, since candidates differ in what the re-check reads. A
|
|
1325
|
+
label or axis across the bridge is not selected.
|
|
1326
|
+
- **Load the far side, then materialize.** Put the far row inline on each candidate under its
|
|
1327
|
+
bridge field (the `indexBridges` shape — one row, or a list where the bridge names many) and
|
|
1328
|
+
call `materializeSourceQuery(query, rows, { lens, rowShape?, now? })`. It requires every key the
|
|
1329
|
+
re-check and a bridged label or axis read, as `materializeSources` does — a candidate without
|
|
1330
|
+
the far side, a far row without a column read, or a list where the bridge names one row throws a
|
|
1331
|
+
`UsageError` — keeps the candidates `check(recheck, row)` holds, and reads the label and axes
|
|
1332
|
+
across the bridge from the far side, in either row shape. Without `lens` it throws.
|
|
1333
|
+
- **A source past a bridge** (its path crosses one) is queried against its own model's map; the
|
|
1334
|
+
clamps above it are carried back across the bridge through the far model's bridge field, and
|
|
1335
|
+
come back as its `recheck` — so each candidate holds the near rows inline
|
|
1336
|
+
(`{ id, industry, 'prisma:FanUser': [{ email, … }] }`).
|
|
1337
|
+
- **SQL rows are flat**, so a bridged query whose re-check reads through a local relation has
|
|
1338
|
+
`sql.sql: null` (with `sql.error`); run the Prisma form.
|
|
1339
|
+
|
|
1340
|
+
```ts
|
|
1341
|
+
const [query] = toSourceQueries(lens, { now });
|
|
1342
|
+
const candidates = await prisma[query.model].findMany(query.prisma); // superset
|
|
1343
|
+
const rows = query.recheck === undefined ? candidates : await loadFarSide(candidates); // your join
|
|
1344
|
+
const values = materializeSourceQuery(query, rows, { lens, now });
|
|
1345
|
+
```
|
|
1346
|
+
|
|
1347
|
+
`materializeSources` over fetched rows still answers a path source across a bridge when the caller
|
|
1348
|
+
supplies the root rows with the far side inline (`{ id, email, crmId, 'salesforce:Contact': { id,
|
|
1349
|
+
industry } }`); a pointer (`from: 'mapDefaults'`) always goes through the query.
|
|
1322
1350
|
|
|
1323
1351
|
### Fetching Under a Lens
|
|
1324
1352
|
|
|
@@ -1330,15 +1358,15 @@ import { check, executePrismaPlan, narrowRule, projectRows, toLensSelect, toPris
|
|
|
1330
1358
|
const where = await executePrismaPlan(toPrisma(true, { lens: narrowing, now }), prisma);
|
|
1331
1359
|
const rows = await prisma.user.findMany({ where, ...toLensSelect(narrowing, { now }) });
|
|
1332
1360
|
const shown = projectRows(narrowing, rows, { now }); // what a viewer may see
|
|
1333
|
-
// To re-test the
|
|
1334
|
-
const forRecheck = projectRows(narrowing, rows, {
|
|
1361
|
+
// To re-test the clamps in memory later — never to return to a viewer:
|
|
1362
|
+
const forRecheck = projectRows(narrowing, rows, { keepClampColumns: true, now });
|
|
1335
1363
|
const holds = forRecheck.filter((row) => check(narrowRule(rule, narrowing), row, { now }) === true);
|
|
1336
1364
|
```
|
|
1337
1365
|
|
|
1338
1366
|
| Function | Purpose |
|
|
1339
1367
|
| --- | --- |
|
|
1340
|
-
| `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
|
|
1341
|
-
| `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`. `
|
|
1368
|
+
| `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. 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 })`. |
|
|
1369
|
+
| `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. |
|
|
1342
1370
|
|
|
1343
1371
|
### Evaluating Across Bridges
|
|
1344
1372
|
|