@inixiative/json-rules 3.4.0 → 3.4.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 CHANGED
@@ -1228,8 +1228,7 @@ const [query] = toSourceQueries(narrowing);
1228
1228
  // query.composedWhere => the node's `where` AND the source's eligibility, narrowed as a rule is
1229
1229
  // query.prisma => { model: 'User', distinct: ['region'], select: { region: true }, where: { AND: [...] } }
1230
1230
  // query.sql => { sql: 'SELECT DISTINCT "t0"."region" FROM "User" AS "t0" WHERE (...)', params: ['t-42', true] }
1231
- // `prisma` is null for a source read across a bridge (see "Sources across a bridge" below).
1232
- if (query.prisma === null) throw new Error(query.sql.error);
1231
+ // A query with `recheck` (a source across a bridge) returns candidates; see "Sources across a bridge".
1233
1232
  const { distinct, select, where } = query.prisma;
1234
1233
  const rows = await prisma.user.findMany({ distinct, select, where });
1235
1234
  const values = materializeSourceQuery(query, rows); // { path, mapName, model, field, options: [{ value }] }
@@ -1243,8 +1242,8 @@ const projection = projectLens(narrowing, { sourceValues: [values] });
1243
1242
 
1244
1243
  | Function | Purpose |
1245
1244
  | --- | --- |
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 where that crosses a bridge has no query at all: `prisma` is `null` and `sql.error` says so — a database holds one side of it, so materialize it with `materializeSources` over rows holding both. `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? })` | 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. |
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`. `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
+ | `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
1247
  | `materializeSources(lensOrNarrowing, rows, options?)` | `SourceValues[]` for every sourced field, from the rows the lens fetches — `toLensSelect`'s rows as fetched, or as `projectRows(…, { keepGrantColumns: 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 grant 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 grants met, and each row it reaches must meet, through `check()` with `options`, its visit's grants, 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
1248
 
1250
1249
  Options never offer a value the lens disallows: `projectLens` drops fetched values outside a
@@ -1296,29 +1295,57 @@ carries down only in the layer that declares it: every layer before or after it
1296
1295
  theirs, so a child's pointer can only narrow what its parent gave, and a tenant layer added after a
1297
1296
  pointer always narrows it. How depends on how it scopes: through `mapDefaults` (the model's own
1298
1297
  grant or source) the pointer still offers unlinked rows; through a root `where` the grant can only
1299
- reach the pointer down the path, so it offers just the linked rows — and across a bridge, where no
1300
- grant crosses, no query at all (it is routed to caller rows, below). Scope tenancy through `mapDefaults` to keep a pointer's unlinked rows.
1298
+ reach the pointer down the path, so it offers just the linked rows — and across a bridge the
1299
+ grant comes back as the query's `recheck` (below). Scope tenancy through `mapDefaults` to keep a pointer's unlinked rows.
1301
1300
  A pointer whose model declares no source fails `validateNarrowing` (`invalid_source`) and throws
1302
1301
  from `projectLens` (by path) / `toSourceQueries` / `materializeSources`, even where a layer hides
1303
1302
  its field; `projectLens(…, { by: 'model' })` and `describeRuleSources` read the lens without
1304
1303
  validating it. Across a bridge it is how a picker gets options at all: the
1305
1304
  model source compiles against the far map alone, with that map's own tenancy, where a path source
1306
- has no query (see "Sources across a bridge"). A grant a path can't carry down — a relation whose
1305
+ over-fetches and re-checks (see "Sources across a bridge"). A grant a path can't carry down — a relation whose
1307
1306
  map declares no inverse — is a `LensRefusal`, never an empty list. `materializeSources` refuses a pointer — a fetched collection can't hold unlinked
1308
1307
  rows; query it with `toSourceQueries` and `materializeSourceQuery`.
1309
1308
 
1310
1309
  #### Sources across a bridge
1311
1310
 
1312
- A source whose path, `where`, `label` or an axis reads across a bridge has neither a database form (no
1313
- database holds both sides) nor a fetch form (`toLensSelect` selects no bridge). `toSourceQueries`
1314
- returns it with `prisma: null`, `sql.sql: null` and an `sql.error` that names the path. Materialize
1315
- it yourself: pass `materializeSources` the rows at the source's path, each holding the bridged
1316
- row inline under its bridge field — for a `FanUser.email` source reading
1317
- `salesforce:Contact.industry`, `{ id, email, crmId, 'salesforce:Contact': { id, industry } }`
1318
- (the `indexBridges` output). A pointer whose model source crosses a bridge is materialized the same
1319
- way, over the rows you supply. Every key a read walks must be there — the bridged row as one row
1320
- where the bridge names one, each column a source or a grant reads — or `materializeSources` throws a
1321
- `UsageError` rather than offer a wrong set.
1311
+ A source whose path, `where`, `label` or an axis reads across a bridge can't be decided by one
1312
+ database — each holds one side. `toSourceQueries` still returns a real query for the side it can
1313
+ see, and marks it with `recheck`:
1314
+
1315
+ - **The query over-fetches.** What reads across the bridge compiles to TRUE (the compilers'
1316
+ over-fetch), so the query never pre-filters on what it can't see: its rows are a **superset** of
1317
+ the true options. Everything local is still decided by the database.
1318
+ - **`recheck` is what the database couldn't decide** — the conjuncts of `composedWhere` that read
1319
+ across a bridge, or `true` when only the label or an axis does. **If `recheck` is present, the
1320
+ query's rows are candidates, not options.** A source that reads no bridge has no `recheck`.
1321
+ - **The query selects local columns only:** the value, a local label and local axes, the local
1322
+ columns `recheck` reads, and each crossed bridge's local `on` key (under the local relations the
1323
+ read crosses first). It drops `distinct`, since candidates differ in what the re-check reads. A
1324
+ label or axis across the bridge is not selected.
1325
+ - **Load the far side, then materialize.** Put the far row inline on each candidate under its
1326
+ bridge field (the `indexBridges` shape — one row, or a list where the bridge names many) and
1327
+ call `materializeSourceQuery(query, rows, { lens, rowShape?, now? })`. It requires every key the
1328
+ re-check and a bridged label or axis read, as `materializeSources` does — a candidate without
1329
+ the far side, a far row without a column read, or a list where the bridge names one row throws a
1330
+ `UsageError` — keeps the candidates `check(recheck, row)` holds, and reads the label and axes
1331
+ across the bridge from the far side, in either row shape. Without `lens` it throws.
1332
+ - **A source past a bridge** (its path crosses one) is queried against its own model's map; the
1333
+ grants above it are carried back across the bridge through the far model's bridge field, and
1334
+ come back as its `recheck` — so each candidate holds the near rows inline
1335
+ (`{ id, industry, 'prisma:FanUser': [{ email, … }] }`).
1336
+ - **SQL rows are flat**, so a bridged query whose re-check reads through a local relation has
1337
+ `sql.sql: null` (with `sql.error`); run the Prisma form.
1338
+
1339
+ ```ts
1340
+ const [query] = toSourceQueries(lens, { now });
1341
+ const candidates = await prisma[query.model].findMany(query.prisma); // superset
1342
+ const rows = query.recheck === undefined ? candidates : await loadFarSide(candidates); // your join
1343
+ const values = materializeSourceQuery(query, rows, { lens, now });
1344
+ ```
1345
+
1346
+ `materializeSources` over fetched rows still answers a path source across a bridge when the caller
1347
+ supplies the root rows with the far side inline (`{ id, email, crmId, 'salesforce:Contact': { id,
1348
+ industry } }`); a pointer (`from: 'mapDefaults'`) always goes through the query.
1322
1349
 
1323
1350
  ### Fetching Under a Lens
1324
1351