@opensaas/stack-core 0.37.0 → 0.38.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.
Files changed (62) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +68 -0
  3. package/CLAUDE.md +21 -3
  4. package/dist/access/access-filter.d.ts +30 -118
  5. package/dist/access/access-filter.d.ts.map +1 -1
  6. package/dist/access/access-filter.js +70 -206
  7. package/dist/access/access-filter.js.map +1 -1
  8. package/dist/access/access-filter.test.js +148 -188
  9. package/dist/access/access-filter.test.js.map +1 -1
  10. package/dist/access/declared-dependencies.d.ts +66 -26
  11. package/dist/access/declared-dependencies.d.ts.map +1 -1
  12. package/dist/access/declared-dependencies.js +67 -17
  13. package/dist/access/declared-dependencies.js.map +1 -1
  14. package/dist/access/declared-dependencies.test.d.ts +2 -0
  15. package/dist/access/declared-dependencies.test.d.ts.map +1 -0
  16. package/dist/access/declared-dependencies.test.js +226 -0
  17. package/dist/access/declared-dependencies.test.js.map +1 -0
  18. package/dist/access/depth-limits.d.ts +8 -7
  19. package/dist/access/depth-limits.d.ts.map +1 -1
  20. package/dist/access/depth-limits.js +8 -7
  21. package/dist/access/depth-limits.js.map +1 -1
  22. package/dist/access/errors.d.ts +12 -8
  23. package/dist/access/errors.d.ts.map +1 -1
  24. package/dist/access/errors.js +16 -12
  25. package/dist/access/errors.js.map +1 -1
  26. package/dist/access/field-visibility.d.ts +2 -1
  27. package/dist/access/field-visibility.d.ts.map +1 -1
  28. package/dist/access/field-visibility.js +91 -17
  29. package/dist/access/field-visibility.js.map +1 -1
  30. package/dist/access/index.d.ts +1 -2
  31. package/dist/access/index.d.ts.map +1 -1
  32. package/dist/access/index.js +1 -1
  33. package/dist/access/index.js.map +1 -1
  34. package/dist/access/relationship-count.d.ts +1 -1
  35. package/dist/context/index.d.ts.map +1 -1
  36. package/dist/context/index.js +38 -27
  37. package/dist/context/index.js.map +1 -1
  38. package/dist/query/index.d.ts +29 -0
  39. package/dist/query/index.d.ts.map +1 -1
  40. package/dist/query/index.js +27 -0
  41. package/dist/query/index.js.map +1 -1
  42. package/dist/query/relationship-options.d.ts +1 -1
  43. package/dist/query/relationship-options.js +1 -1
  44. package/package.json +1 -1
  45. package/src/access/access-filter.test.ts +205 -275
  46. package/src/access/access-filter.ts +84 -267
  47. package/src/access/declared-dependencies.test.ts +277 -0
  48. package/src/access/declared-dependencies.ts +122 -37
  49. package/src/access/depth-limits.ts +8 -7
  50. package/src/access/errors.ts +16 -12
  51. package/src/access/field-visibility.ts +99 -14
  52. package/src/access/index.ts +1 -7
  53. package/src/access/relationship-count.ts +1 -1
  54. package/src/context/index.ts +52 -33
  55. package/src/query/index.ts +53 -0
  56. package/src/query/relationship-options.ts +1 -1
  57. package/tests/access-relationships.test.ts +18 -16
  58. package/tests/computed-field-selective-evaluation.test.ts +418 -0
  59. package/tests/context.test.ts +27 -0
  60. package/tests/needs-declared-dependencies.test.ts +7 -4
  61. package/tests/resolve-chain.test.ts +11 -11
  62. package/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,4 @@
1
1
 
2
- > @opensaas/stack-core@0.37.0 build /home/runner/work/stack/stack/packages/core
2
+ > @opensaas/stack-core@0.38.0 build /home/runner/work/stack/stack/packages/core
3
3
  > tsc
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,73 @@
1
1
  # @opensaas/stack-core
2
2
 
3
+ ## 0.38.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#873](https://github.com/OpenSaasAU/stack/pull/873) [`b21d8b2`](https://github.com/OpenSaasAU/stack/commit/b21d8b2af43f7a2a7ea10a89cfb39140a856bd68) Thanks [@borisno2](https://github.com/borisno2)! - Naming a relation in an `include` now fetches only that relation's own columns and stops, at every level — not just the root. This completes ADR-0024 (a bare read fetches scalars, never relations): reaching a relation's own relations means naming them too, e.g. `include: { author: { include: { organization: true } } }` rather than relying on `include: { author: true }` to pull `organization` in automatically. A relation nobody named (caller `include`, fragment `query`, or a field's `needs`) never has its list's operation-level `query` access evaluated at all.
8
+
9
+ **This is a silent break — detect it before you upgrade.** An `include` that named a relation bare and read past it (`item.<named>[0].<unnamed>`) now gets `undefined` for the unnamed part, with no error. Grep your codebase for `include: {` calls whose consumers read a second hop off a bare-named relation, and add the deeper relation explicitly:
10
+
11
+ ```typescript
12
+ // Before: relied on `author` auto-expanding its own `organization` relation
13
+ const post = await context.db.post.findUnique({
14
+ where: { id },
15
+ include: { author: true },
16
+ })
17
+ post.author.organization // silently undefined now
18
+
19
+ // After: name the relation you actually need
20
+ const post = await context.db.post.findUnique({
21
+ where: { id },
22
+ include: { author: { include: { organization: true } } },
23
+ })
24
+ post.author.organization // present
25
+ ```
26
+
27
+ `AccessScopeDepthExceededError` (thrown when an `include` names a relation past `READ_INCLUDE_MAX_DEPTH`) keeps its type, fields, and throw sites — only its message wording changed, from describing an inability to scope to describing a cost refusal, since the depth cap is now a cost limit rather than a security boundary (nothing walks the relationship graph unprompted anymore).
28
+
29
+ - [#890](https://github.com/OpenSaasAU/stack/pull/890) [`17eb72f`](https://github.com/OpenSaasAU/stack/commit/17eb72f0a9a4b7508e3f318da66bb8d4c6cbd705) Thanks [@list({](https://github.com/list({)! - A computed field — any field carrying a `resolveOutput` hook, virtual or not — is now computed if and only if a read is actually going to return it. A fragment `query` that selects three fields no longer runs every `resolveOutput` on the list and discards the rest: an unselected field's field-level read access is never evaluated and its hook never runs. Its declared relations (`needs`, ADR-0025) are fetched under exactly the same condition, folded recursively at every nesting level — a nested fragment selecting a subset computes only that subset, while a nested `include` still computes every computed field at that level, matching bare and `include`-based reads, which are unaffected: they still compute every computed field on the list, exactly as before. See ADR-0027.
30
+
31
+ **This is a silent break — detect it before you upgrade, the same way ADR-0024's and ADR-0026's were.** Two independent behaviors changed with no thrown error:
32
+
33
+ 1. **A hook's `item` never carries another computed field's resolved output, on any read path.** Previously a virtual field received the already-assembled, already-resolved object, so a virtual field could read an _earlier-declared_ virtual (or any field carrying its own `resolveOutput`, e.g. a `password()`'s wrapper or a formatted display field) and see its resolved value — working only by declaration order, with reordering two fields silently changing the result. Now every computed field's hook sees only the row's stored columns and its own declared dependencies; reaching for a sibling that is itself computed finds nothing there (or its raw stored form, never the wrapped/resolved value), the same as reaching for a field that was never declared. **Grep your config for a `resolveOutput` whose `item` reads a field that is itself computed** — virtual fields reading other virtual fields, or a hook reading a stored field that carries its own `resolveOutput` (a password wrapper, a formatted date) — and recompute from the shared stored columns instead of relying on another field's hook having already run.
34
+ 2. **A field's hook no longer runs just because it's on the list — only because a read selects it.** If you relied on a `resolveOutput` hook running for a side effect (logging, cache warming) on every read regardless of a fragment's own field selection, that side effect now only fires when the fragment actually names the field. **Grep for a fragment `query` that intentionally omits a field whose hook you were relying on for a side effect**, and select that field explicitly (or move the side effect to a hook that isn't projection-gated, e.g. `afterOperation`).
35
+
36
+ A hookless virtual field (one with `access.read` but no `resolveOutput`) no longer has its read access evaluated at all on any read — such a field can never produce output, so under this rule it does no work at all.
37
+
38
+ ```typescript
39
+ // Before: `displayName` (declared after `fullNameCached`) could read the
40
+ // latter's resolved value purely because of declaration order.
41
+
42
+ fields: {
43
+ firstName: text(),
44
+ lastName: text(),
45
+ fullNameCached: virtual({
46
+ type: 'string',
47
+ hooks: { resolveOutput: ({ item }) => `${item.firstName} ${item.lastName}` },
48
+ }),
49
+ displayName: virtual({
50
+ type: 'string',
51
+ // item.fullNameCached is now always undefined here — recompute from
52
+ // the shared stored columns instead.
53
+ hooks: { resolveOutput: ({ item }) => `${item.fullNameCached} (${item.firstName[0]}.)` },
54
+ }),
55
+ },
56
+ })
57
+
58
+ // After: compute from the stored columns both fields actually share.
59
+ displayName: virtual({
60
+ type: 'string',
61
+ hooks: {
62
+ resolveOutput: ({ item }) => `${item.firstName} ${item.lastName} (${item.firstName[0]}.)`,
63
+ },
64
+ }),
65
+ ```
66
+
67
+ ### Patch Changes
68
+
69
+ - [#873](https://github.com/OpenSaasAU/stack/pull/873) [`b21d8b2`](https://github.com/OpenSaasAU/stack/commit/b21d8b2af43f7a2a7ea10a89cfb39140a856bd68) Thanks [@borisno2](https://github.com/borisno2)! - Fix `needs` declarations being dropped beneath a caller-named relation that revisits a list (e.g. `include: { author: { include: { posts: true } } }`, or a self-referential `parent`), which left the revisited list's computed fields resolving over `undefined`.
70
+
3
71
  ## 0.37.0
4
72
 
5
73
  ### Minor Changes
package/CLAUDE.md CHANGED
@@ -249,7 +249,25 @@ Reads run no `afterOperation` (list or field):
249
249
 
250
250
  ### A Bare Read Fetches Scalars, Not Relations (ADR-0024)
251
251
 
252
- A read with no `include` and no fragment `query` returns the row's own columns plus its virtual fields — **never relations** — matching Prisma's own default. `findUnique`, `findMany`, and a singleton's `get()` all follow this rule uniformly, under sudo and under a session alike. Relations are fetched only when a caller names them via `include` or a fragment `query`, at which point the existing merge-with-access-control path (`mergeIncludeWithAccessControl`) applies exactly as before (#566/#830 unaffected). Foreign-key columns (e.g. `authorId`) are unaffected and always returned, so a relation stays reachable by id without an `include`. A `resolveOutput` hook that issues its own bare `context.db` read is subject to the same rule — reading `item.<relation>` inside such a hook silently returns `undefined` unless the hook's own read names that relation. See `docs/adr/0024-a-read-with-no-include-fetches-scalars-not-relations.md`.
252
+ A read with no `include` and no fragment `query` returns the row's own columns plus its virtual fields — **never relations** — matching Prisma's own default. `findUnique`, `findMany`, and a singleton's `get()` all follow this rule uniformly, under sudo and under a session alike. Relations are fetched only when a caller names them via `include` or a fragment `query`, at which point the caller-directed access-scoping walk (`buildAccessScopedInclude`, ADR-0026) applies (#566/#830 unaffected). Foreign-key columns (e.g. `authorId`) are unaffected and always returned, so a relation stays reachable by id without an `include`. A `resolveOutput` hook that issues its own bare `context.db` read is subject to the same rule — reading `item.<relation>` inside such a hook silently returns `undefined` unless the hook's own read names that relation. See `docs/adr/0024-a-read-with-no-include-fetches-scalars-not-relations.md`.
253
+
254
+ ### Naming a Relation Fetches Its Columns, Not Its Subtree (ADR-0026)
255
+
256
+ The bare-read rule above applies at **every level**, not only the root: naming a relation in an `include` fetches that relation's own columns and stops — its own further relations are returned only if the request nests an `include` for them too. `include: { author: true }` returns `author`'s scalar columns; reaching `author.organization` means writing `include: { author: { include: { organization: true } } }`. This is the "One hop" rule (see the glossary entry in `CONTEXT.md`).
257
+
258
+ The read pipeline is **caller-directed**: `buildAccessScopedInclude` walks only the branches a request (a caller `include`, a fragment `query`'s projection, or a field's folded `needs`) itself names, and never evaluates a related list's operation-level `query` access for a relation nobody asked for. There is no separate "build the full access-scoped tree for the whole list, then reconcile against what was requested" pass — the old `buildIncludeWithAccessControl` + `mergeIncludeWithAccessControl` two-step this replaced. `READ_INCLUDE_MAX_DEPTH` is a cost limit on how deep a request may reach, not a security boundary: nothing walks the relationship graph unprompted anymore, so there is no unscoped subtree left to fail open on past the cap — a request naming a relation at or beyond the cap still throws `AccessScopeDepthExceededError` (ADR-0022), now worded as a cost refusal rather than a scoping failure.
259
+
260
+ **Migration note (silent break):** an `include` that named a relation bare and read past it one hop (`item.<named>[0].<unnamed>`) now gets `undefined` for the unnamed part — no error. Grep for caller includes whose consumers read past the relation actually named, and add the deeper relation explicitly to the `include`.
261
+
262
+ A computed field's declared dependency (`needs`, ADR-0025, below) folds in at **every** relation it's reached through, including one added purely to satisfy another field's own `needs` — the fold recurses through `foldDeclaredDependencies` rather than riding a caller-named relation's auto-expanded subtree, since nothing auto-expands anymore. See `docs/adr/0026-naming-a-relation-fetches-its-columns-not-its-subtree.md`.
263
+
264
+ ### A Computed Field Runs Only When It Is Going To Be Returned (ADR-0027)
265
+
266
+ A computed field — any field carrying a `resolveOutput` hook, virtual or not — is computed **if and only if the read is actually going to return it**, and its declared relations (`needs`) are fetched under exactly the same condition. A fragment `query` selecting three fields runs only those three fields' hooks (and folds only their `needs`); a field it doesn't select does no work at all — neither its field-level `read` access nor its hook runs. This is **projection-aware, never access-aware**: a fragment's own field selection is the only thing that restricts a level this way. A bare read or an `include`-based read is unaffected — every computed field on the list still computes, exactly as before, since neither ever had a narrower field selection to restrict by. The rule applies at every nesting level: a nested fragment selecting a subset computes only that subset there; a nested `include` still computes every computed field at that level.
267
+
268
+ **A computed field's hook never sees another computed field's resolved output**, on any read path — only the row's stored columns and its own declared dependencies. A sibling field that was skipped (unselected by a fragment) or denied by field-level access is absent from what the hook sees, never present holding its raw pre-hook value — reaching for it finds nothing there, the same as reaching for a relation never declared via `needs`. Before this, a virtual field received the already-assembled, already-resolved object, so a virtual field could accidentally read an earlier-declared virtual's resolved value purely by declaration order; reordering two such fields silently changed the result. That accidental coupling is gone: recompute from the stored columns both fields share instead.
269
+
270
+ A hookless virtual field (one with `access.read` but no `resolveOutput`) has its read access evaluated on no read at all — such a field can never produce output, so there's nothing to preserve access side effects for. See `docs/adr/0027-a-computed-field-runs-only-when-it-is-going-to-be-returned.md` and the "Computed field" glossary entry in `CONTEXT.md`.
253
271
 
254
272
  ### Context Type Safety
255
273
 
@@ -402,13 +420,13 @@ User: list({
402
420
 
403
421
  // Usage
404
422
  const user = await context.db.user.findUnique({ where: { id } })
405
- console.log(user.fullName) // "John Doe" — computed via resolveOutput on every read
423
+ console.log(user.fullName) // "John Doe" — computed via resolveOutput whenever the read returns it
406
424
  ```
407
425
 
408
426
  **Key characteristics:**
409
427
 
410
428
  - Not stored in database (no Prisma column created)
411
- - Computed via `resolveOutput` on every read (`select` is not honoured narrow with `include`/fragment `query`)
429
+ - Computed via `resolveOutput` on every bare/`include`-based read; on a fragment `query` read, only when the fragment selects it (ADR-0027) — `select` is still not honoured, narrow with `include`/fragment `query`
412
430
  - Must provide `type` (TypeScript type string) and `resolveOutput` hook
413
431
  - Can optionally provide `resolveInput` for write side effects
414
432
  - Useful for derived values, computed properties, and external API sync
@@ -1,125 +1,38 @@
1
- import type { Session, AccessContext, PrismaFilter } from './types.js';
1
+ import type { Session, AccessContext } from './types.js';
2
2
  import type { OpenSaasConfig, FieldConfig } from '../config/types.js';
3
3
  /**
4
- * Access Filter phase 1 of the two-phase read (pre-query).
5
- *
6
- * This module scopes which rows and relationships the database is allowed to
7
- * return, before the query runs. It evaluates *operation-level* `query` access
8
- * on related lists and turns the results into a Prisma `include`/`where` clause,
9
- * so denied rows and relations never leave the database.
10
- *
11
- * Phase 2 (post-query field stripping + `resolveOutput` + virtual computation)
12
- * lives in `field-visibility.ts`. The two phases cannot be merged: virtual
13
- * fields are computed in JavaScript and post-query field access can depend on
14
- * the fetched row, neither of which is expressible in SQL. See
15
- * `docs/adr/0001-access-control-is-a-two-phase-read.md` and the access-control
16
- * glossary in `CONTEXT.md`.
17
- */
18
- /** A single relation entry in a Prisma `include` object (see below). */
19
- type IncludeEntry = boolean | {
20
- where?: PrismaFilter;
21
- include?: IncludeObject;
22
- take?: number;
23
- };
24
- type IncludeObject = Record<string, IncludeEntry>;
25
- /**
26
- * The result of trying to compute an access-controlled include for a list's
27
- * fields. `buildIncludeWithAccessControl` used to collapse three unrelated
28
- * outcomes into a single overloaded `undefined`: "inside a resolveOutput
29
- * context", "hit the depth cap", and "no relationships to scope" all looked
30
- * identical to callers, which is what let a depth-capped relation pass
31
- * through unscoped (issue #830). This discriminated result keeps them
32
- * distinguishable all the way to `mergeIncludeWithAccessControl`, which is the
33
- * only place that knows whether a caller actually asked for the part that
34
- * couldn't be scoped.
35
- *
36
- * - `scoped`: relationships were found and (to the extent depth allows)
37
- * access-controlled; `include` is the resulting tree.
38
- * - `nothing-to-scope`: the list genuinely has no relationships to scope, OR
39
- * we are inside a resolveOutput/virtual-field context and deliberately did
40
- * not descend into a relation's own nested relations. Passing the caller's
41
- * include through unchanged here is correct, not a leak.
42
- * - `depth-exceeded`: we could not evaluate this level at all because it sits
43
- * at or past `READ_INCLUDE_MAX_DEPTH`. This is a denial: a caller `include`
44
- * that reaches here must be rejected, not passed through.
45
- */
46
- export type AccessIncludeResult = {
47
- kind: 'scoped';
48
- include: RichIncludeObject;
49
- } | {
50
- kind: 'nothing-to-scope';
51
- } | {
52
- kind: 'depth-exceeded';
53
- };
54
- /** A relation entry in the rich, provenance-carrying tree `buildIncludeWithAccessControl` builds internally. */
55
- type RichIncludeEntry = {
56
- where?: PrismaFilter;
57
- nested: AccessIncludeResult;
58
- };
59
- type RichIncludeObject = Record<string, RichIncludeEntry>;
60
- /**
61
- * Collapse an `AccessIncludeResult` to the plain Prisma `include` shape used
62
- * when there is no caller-supplied include to merge against (the direct
63
- * auto-include path). `nothing-to-scope` and `depth-exceeded` both become
64
- * `undefined` here — at this call site nothing was explicitly requested past
65
- * either boundary, so there is nothing to deny.
66
- */
67
- export declare function toPrismaInclude(result: AccessIncludeResult): IncludeObject | undefined;
68
- /**
69
- * Build the access-controlled include for a list's fields.
70
- *
71
- * This allows us to filter relationships at the database level instead of in
72
- * memory. Returns an {@link AccessIncludeResult} rather than a plain include
73
- * object so that `mergeIncludeWithAccessControl` can tell a genuine "nothing
74
- * to scope" apart from "the engine hit its depth cap" (see that type's doc
75
- * comment and ADR-0022).
4
+ * Build the access-scoped `include` for exactly the relations a read
5
+ * requested, recursing only into branches `requestedInclude` itself names.
6
+ *
7
+ * For each key in `requestedInclude`:
8
+ * - Not a config-declared relationship access control does not govern it;
9
+ * passed through unchanged (e.g. a fragment/caller key that isn't a
10
+ * relationship at all).
11
+ * - A declared relationship whose related list's `query` access denies it
12
+ * (`=== false`) dropped entirely, no matter what the request asked for
13
+ * nested beneath it (#566): the caller chooses *which* relations, access
14
+ * control chooses *whether* and *with what filter*.
15
+ * - Otherwise → the access `where` is AND-combined with any caller-supplied
16
+ * nested `where` (never replaced — the other half of #566), a
17
+ * caller-supplied `take` rides through unchanged (#752), and — the "One
18
+ * hop" rule (ADR-0026) nested relations are scoped ONLY if
19
+ * `requestedInclude` itself named a nested `include` here. A bare relation
20
+ * (or one with no nested `include`) fetches its own columns and stops: no
21
+ * recursive call, no access evaluation on anything beneath it.
22
+ *
23
+ * **Depth is a cost limit, not a cycle guard (ADR-0026).** A `requestedInclude`
24
+ * is always a finite literal — the caller's own object, or
25
+ * `foldDeclaredDependencies`'s already-cycle-guarded fold — so this recursion
26
+ * cannot loop unboundedly on its own; nothing here walks the relationship
27
+ * graph unprompted. `READ_INCLUDE_MAX_DEPTH` still bounds how deep a request
28
+ * may reach, fail-closed per ADR-0022: a request naming anything at or past
29
+ * the cap throws `AccessScopeDepthExceededError` rather than silently
30
+ * returning less than what was asked for.
76
31
  */
77
- export declare function buildIncludeWithAccessControl(fieldConfigs: Record<string, FieldConfig>, args: {
32
+ export declare function buildAccessScopedInclude(requestedInclude: Record<string, unknown>, fieldConfigs: Record<string, FieldConfig>, args: {
78
33
  session: Session | null;
79
34
  context: AccessContext;
80
- }, config: OpenSaasConfig, depth?: number, visitedLists?: readonly string[]): Promise<AccessIncludeResult>;
81
- /**
82
- * Merge a caller-supplied `include` with the access-controlled include — phase-1
83
- * row/relation scoping for explicit caller selections.
84
- *
85
- * The caller's `include` decides WHICH relations to fetch; access control decides
86
- * WHETHER each relation may be fetched and WITH WHAT filter. Replacing the
87
- * access-controlled include with the caller's wholesale (the bug in #566) drops
88
- * every per-relation access `where` and denied-relation exclusion, silently
89
- * bypassing row-level access on any non-sudo read that passes `include`.
90
- *
91
- * For each relation the caller asks to include:
92
- * - If the relation is a config-declared relationship but is ABSENT from the
93
- * access-controlled include, its `query` access returned `false` → it is DROPPED
94
- * (not fetched).
95
- * - If it is present (allowed, possibly with a filter), the access entry is used
96
- * as the base: the access `where` is AND-combined with any caller-supplied
97
- * nested `where`, and nested includes are recursively merged using the related
98
- * list's field configs (so deeply-nested selections are filtered at every
99
- * level). A bare caller `true` becomes the access-controlled shape (filter +
100
- * nested filtered include), never bare `true`.
101
- * - If the caller names a key that is NOT a config-declared relationship, it is
102
- * passed through unchanged (access control does not govern it).
103
- *
104
- * `accessControlledInclude` is the {@link AccessIncludeResult} for THIS level:
105
- * - `nothing-to-scope` → nothing to merge against (the list has no
106
- * relationships, or we're inside a resolveOutput context where the caller
107
- * include is irrelevant to begin with). Pass the caller's include through
108
- * unchanged — this is a non-denial outcome, not "every relation denied".
109
- * - `depth-exceeded` → the engine could not compute a scope for THIS level at
110
- * all because it sits at or past `READ_INCLUDE_MAX_DEPTH`. If the caller
111
- * named anything here, that is exactly the case that used to pass through
112
- * unscoped (issue #830): throw `AccessScopeDepthExceededError` instead. An
113
- * empty caller include at this level (nothing further requested) is not an
114
- * error — there's simply nothing to do.
115
- * - `scoped` → the normal per-relation merge below: a declared relationship
116
- * ABSENT from the access include was denied (drop it); one PRESENT is used
117
- * as the base, AND-combining `where`s and recursing into nested includes.
118
- *
119
- * `listKey` and `depth` are carried only to build a useful
120
- * `AccessScopeDepthExceededError` message; they do not affect merge behaviour.
121
- */
122
- export declare function mergeIncludeWithAccessControl(callerInclude: Record<string, unknown>, accessControlledInclude: AccessIncludeResult, fieldConfigs: Record<string, FieldConfig>, config: OpenSaasConfig, listKey: string, depth?: number): Record<string, unknown>;
35
+ }, config: OpenSaasConfig, listKey: string, depth?: number): Promise<Record<string, unknown>>;
123
36
  /**
124
37
  * Remove keys that correspond to `virtual` fields from a Prisma `include`
125
38
  * object, recursing into nested relationship includes using the related
@@ -137,5 +50,4 @@ export declare function mergeIncludeWithAccessControl(callerInclude: Record<stri
137
50
  * no effect on whether the value appears in the result (#628).
138
51
  */
139
52
  export declare function stripVirtualFieldsFromInclude(include: Record<string, unknown> | undefined, fieldConfigs: Record<string, FieldConfig>, config: OpenSaasConfig): Record<string, unknown> | undefined;
140
- export {};
141
53
  //# sourceMappingURL=access-filter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"access-filter.d.ts","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAKrE;;;;;;;;;;;;;;GAcG;AAEH,wEAAwE;AACxE,KAAK,YAAY,GAAG,OAAO,GAAG;IAAE,KAAK,CAAC,EAAE,YAAY,CAAC;IAAC,OAAO,CAAC,EAAE,aAAa,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAC9F,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAA;AAE9B,gHAAgH;AAChH,KAAK,gBAAgB,GAAG;IAAE,KAAK,CAAC,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAA;AAC7E,KAAK,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;AAqBzD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,GAAG,SAAS,CAOtF;AAED;;;;;;;;GAQG;AACH,wBAAsB,6BAA6B,CACjD,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,IAAI,EAAE;IACJ,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,aAAa,CAAA;CACvB,EACD,MAAM,EAAE,cAAc,EACtB,KAAK,GAAE,MAAU,EAIjB,YAAY,GAAE,SAAS,MAAM,EAAO,GACnC,OAAO,CAAC,mBAAmB,CAAC,CAwE9B;AAiDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,uBAAuB,EAAE,mBAAmB,EAC5C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,MAAU,GAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA2EzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,MAAM,EAAE,cAAc,GACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAgCrC"}
1
+ {"version":3,"file":"access-filter.d.ts","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAgB,MAAM,YAAY,CAAA;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AA6ErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,wBAAwB,CAC5C,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,IAAI,EAAE;IACJ,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,aAAa,CAAA;CACvB,EACD,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,MAAU,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAwDlC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,MAAM,EAAE,cAAc,GACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAgCrC"}