@opensaas/stack-core 0.31.1 → 0.33.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 (52) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/dist/access/access-filter.d.ts +76 -20
  4. package/dist/access/access-filter.d.ts.map +1 -1
  5. package/dist/access/access-filter.js +97 -70
  6. package/dist/access/access-filter.js.map +1 -1
  7. package/dist/access/access-filter.test.js +171 -10
  8. package/dist/access/access-filter.test.js.map +1 -1
  9. package/dist/access/depth-limits.d.ts +12 -0
  10. package/dist/access/depth-limits.d.ts.map +1 -0
  11. package/dist/access/depth-limits.js +12 -0
  12. package/dist/access/depth-limits.js.map +1 -0
  13. package/dist/access/errors.d.ts +19 -0
  14. package/dist/access/errors.d.ts.map +1 -0
  15. package/dist/access/errors.js +29 -0
  16. package/dist/access/errors.js.map +1 -0
  17. package/dist/access/field-visibility.d.ts.map +1 -1
  18. package/dist/access/field-visibility.js +10 -3
  19. package/dist/access/field-visibility.js.map +1 -1
  20. package/dist/access/index.d.ts +3 -1
  21. package/dist/access/index.d.ts.map +1 -1
  22. package/dist/access/index.js +3 -1
  23. package/dist/access/index.js.map +1 -1
  24. package/dist/context/index.d.ts.map +1 -1
  25. package/dist/context/index.js +14 -8
  26. package/dist/context/index.js.map +1 -1
  27. package/dist/context/nested-operations.d.ts +1 -1
  28. package/dist/context/nested-operations.d.ts.map +1 -1
  29. package/dist/context/nested-operations.js +1 -5
  30. package/dist/context/nested-operations.js.map +1 -1
  31. package/dist/context/transaction-boundary.d.ts.map +1 -1
  32. package/dist/context/transaction-boundary.js +43 -6
  33. package/dist/context/transaction-boundary.js.map +1 -1
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +5 -0
  37. package/dist/index.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/access/access-filter.test.ts +254 -7
  40. package/src/access/access-filter.ts +141 -72
  41. package/src/access/depth-limits.ts +11 -0
  42. package/src/access/errors.ts +32 -0
  43. package/src/access/field-visibility.ts +10 -3
  44. package/src/access/index.ts +4 -0
  45. package/src/context/index.ts +14 -5
  46. package/src/context/nested-operations.ts +0 -7
  47. package/src/context/transaction-boundary.ts +48 -7
  48. package/src/index.ts +6 -0
  49. package/tests/access-relationships.test.ts +77 -63
  50. package/tests/context.test.ts +106 -24
  51. package/tests/transaction-boundary-hooks.test.ts +246 -1
  52. package/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,4 @@
1
1
 
2
- > @opensaas/stack-core@0.31.1 build /home/runner/work/stack/stack/packages/core
2
+ > @opensaas/stack-core@0.33.0 build /home/runner/work/stack/stack/packages/core
3
3
  > tsc
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @opensaas/stack-core
2
2
 
3
+ ## 0.33.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#838](https://github.com/OpenSaasAU/stack/pull/838) [`0caf680`](https://github.com/OpenSaasAU/stack/commit/0caf68007e41b69f1a5d5f74fb15df2548a559dc) Thanks [@borisno2](https://github.com/borisno2)! - **Behavior change:** reads that previously returned deeply-nested relation data unscoped now throw instead. A caller-supplied `include` nested past the Access Filter's read-include depth cap (`READ_INCLUDE_MAX_DEPTH`, 5) used to fail OPEN — the relation was fetched with no row filter and its fields were never access-checked or `resolveOutput`-processed. It now fails CLOSED: the read throws a new `AccessScopeDepthExceededError` (exported from `@opensaas/stack-core`) naming the list, relation field, and depth reached, instead of returning unscoped data.
8
+
9
+ ```typescript
10
+ import { AccessScopeDepthExceededError } from '@opensaas/stack-core'
11
+
12
+ try {
13
+ await context.db.post.findMany({
14
+ include: { author: { include: {/* … nested past the depth cap */} } },
15
+ })
16
+ } catch (err) {
17
+ if (err instanceof AccessScopeDepthExceededError) {
18
+ // err.listKey / err.fieldKey / err.depth — restructure into shallower reads.
19
+ }
20
+ }
21
+ ```
22
+
23
+ An ordinary read with no caller `include`, or one within the depth limit, is unaffected — the auto-include still stops silently at the cap, matching prior behavior. A read issued from inside a `resolveOutput`/virtual-field hook now also row-scopes its immediate relations (previously it skipped scoping entirely at that point). Field-level read access and `resolveOutput` hooks are now applied at every nesting depth on the returned rows, with no independent cap of their own. See ADR-0022 and issue [#830](https://github.com/OpenSaasAU/stack/issues/830).
24
+
25
+ ### Patch Changes
26
+
27
+ - [#839](https://github.com/OpenSaasAU/stack/pull/839) [`1a3f51d`](https://github.com/OpenSaasAU/stack/commit/1a3f51d5837d6e5244ccf04c3d14c41c264701c3) Thanks [@borisno2](https://github.com/borisno2)! - Fix `beforeTransaction`/`afterTransaction` hooks not firing for lists reachable only past the involved-list enumeration's old fixed depth cap. These hooks now fire for every list a write touches regardless of nesting depth, so compensation logic that previously never ran will start running — that was a bug, not a contract.
28
+
29
+ ## 0.32.0
30
+
3
31
  ## 0.31.1
4
32
 
5
33
  ## 0.31.0
@@ -15,17 +15,69 @@ import type { OpenSaasConfig, FieldConfig } from '../config/types.js';
15
15
  * `docs/adr/0001-access-control-is-a-two-phase-read.md` and the access-control
16
16
  * glossary in `CONTEXT.md`.
17
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>;
18
60
  /**
19
- * Build Prisma include object with access control filters
20
- * This allows us to filter relationships at the database level instead of in memory
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).
21
76
  */
22
77
  export declare function buildIncludeWithAccessControl(fieldConfigs: Record<string, FieldConfig>, args: {
23
78
  session: Session | null;
24
79
  context: AccessContext;
25
- }, config: OpenSaasConfig, depth?: number, visitedLists?: readonly string[]): Promise<Record<string, boolean | {
26
- where?: PrismaFilter;
27
- include?: Record<string, boolean | /*elided*/ any>;
28
- }> | undefined>;
80
+ }, config: OpenSaasConfig, depth?: number, visitedLists?: readonly string[]): Promise<AccessIncludeResult>;
29
81
  /**
30
82
  * Merge a caller-supplied `include` with the access-controlled include — phase-1
31
83
  * row/relation scoping for explicit caller selections.
@@ -49,22 +101,25 @@ export declare function buildIncludeWithAccessControl(fieldConfigs: Record<strin
49
101
  * - If the caller names a key that is NOT a config-declared relationship, it is
50
102
  * passed through unchanged (access control does not govern it).
51
103
  *
52
- * The access-controlled include is recursive to `MAX_DEPTH` (see
53
- * `buildIncludeWithAccessControl`); beyond that depth no auto-include exists, so
54
- * deeper caller selections pass through unscoped consistent with the existing
55
- * auto-include behaviour.
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.
56
118
  *
57
- * `accessControlledInclude` being `undefined` is NOT "every relation denied". It
58
- * means no access-controlled include was computed at all — a non-denial outcome
59
- * that `buildIncludeWithAccessControl` returns when inside a `resolveOutput`/
60
- * virtual-field context, at `MAX_DEPTH`, or when the list has no relationships.
61
- * In every one of those cases there is nothing to merge against, so the caller's
62
- * `include` is passed through unchanged (matching the prior `args.include || …`
63
- * fallback). This is distinct from an `undefined` ENTRY inside a defined access
64
- * include, which DOES mean the relation was denied and must be dropped (see the
65
- * per-relation loop below). Only the whole-object `undefined` is a passthrough.
119
+ * `listKey` and `depth` are carried only to build a useful
120
+ * `AccessScopeDepthExceededError` message; they do not affect merge behaviour.
66
121
  */
67
- export declare function mergeIncludeWithAccessControl(callerInclude: Record<string, unknown>, accessControlledInclude: Record<string, unknown> | undefined, fieldConfigs: Record<string, FieldConfig>, config: OpenSaasConfig): Record<string, unknown>;
122
+ export declare function mergeIncludeWithAccessControl(callerInclude: Record<string, unknown>, accessControlledInclude: AccessIncludeResult, fieldConfigs: Record<string, FieldConfig>, config: OpenSaasConfig, listKey: string, depth?: number): Record<string, unknown>;
68
123
  /**
69
124
  * Remove keys that correspond to `virtual` fields from a Prisma `include`
70
125
  * object, recursing into nested relationship includes using the related
@@ -82,4 +137,5 @@ export declare function mergeIncludeWithAccessControl(callerInclude: Record<stri
82
137
  * no effect on whether the value appears in the result (#628).
83
138
  */
84
139
  export declare function stripVirtualFieldsFromInclude(include: Record<string, unknown> | undefined, fieldConfigs: Record<string, FieldConfig>, config: OpenSaasConfig): Record<string, unknown> | undefined;
140
+ export {};
85
141
  //# 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;AAGrE;;;;;;;;;;;;;;GAcG;AAEH;;;GAGG;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;YAeI,YAAY;cAAY,MAAM,CAAC,MAAM,2BAAe;gBAiE7F;AAyDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,MAAM,EAAE,cAAc,GACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmEzB;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,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,CAmE9B;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,39 +1,67 @@
1
1
  import { checkAccess, getRelatedListConfig } from './engine.js';
2
+ import { READ_INCLUDE_MAX_DEPTH } from './depth-limits.js';
3
+ import { AccessScopeDepthExceededError } from './errors.js';
2
4
  /**
3
- * Access Filter phase 1 of the two-phase read (pre-query).
4
- *
5
- * This module scopes which rows and relationships the database is allowed to
6
- * return, before the query runs. It evaluates *operation-level* `query` access
7
- * on related lists and turns the results into a Prisma `include`/`where` clause,
8
- * so denied rows and relations never leave the database.
9
- *
10
- * Phase 2 (post-query field stripping + `resolveOutput` + virtual computation)
11
- * lives in `field-visibility.ts`. The two phases cannot be merged: virtual
12
- * fields are computed in JavaScript and post-query field access can depend on
13
- * the fetched row, neither of which is expressible in SQL. See
14
- * `docs/adr/0001-access-control-is-a-two-phase-read.md` and the access-control
15
- * glossary in `CONTEXT.md`.
5
+ * Collapse a rich, provenance-carrying include entry down to the plain
6
+ * Prisma-shaped form. A `nested` result that is `depth-exceeded` or
7
+ * `nothing-to-scope` contributes no `include` key this is the AUTO-include
8
+ * silently stopping, which is correct when no caller asked for anything past
9
+ * this point (see `AccessIncludeResult` doc comment).
16
10
  */
11
+ function toPrismaEntry(entry) {
12
+ const result = {};
13
+ if (entry.where)
14
+ result.where = entry.where;
15
+ if (entry.nested.kind === 'scoped') {
16
+ const nestedInclude = toPrismaInclude(entry.nested);
17
+ if (nestedInclude && Object.keys(nestedInclude).length > 0) {
18
+ result.include = nestedInclude;
19
+ }
20
+ }
21
+ return Object.keys(result).length > 0 ? result : true;
22
+ }
23
+ /**
24
+ * Collapse an `AccessIncludeResult` to the plain Prisma `include` shape used
25
+ * when there is no caller-supplied include to merge against (the direct
26
+ * auto-include path). `nothing-to-scope` and `depth-exceeded` both become
27
+ * `undefined` here — at this call site nothing was explicitly requested past
28
+ * either boundary, so there is nothing to deny.
29
+ */
30
+ export function toPrismaInclude(result) {
31
+ if (result.kind !== 'scoped')
32
+ return undefined;
33
+ const out = {};
34
+ for (const [key, entry] of Object.entries(result.include)) {
35
+ out[key] = toPrismaEntry(entry);
36
+ }
37
+ return out;
38
+ }
17
39
  /**
18
- * Build Prisma include object with access control filters
19
- * This allows us to filter relationships at the database level instead of in memory
40
+ * Build the access-controlled include for a list's fields.
41
+ *
42
+ * This allows us to filter relationships at the database level instead of in
43
+ * memory. Returns an {@link AccessIncludeResult} rather than a plain include
44
+ * object so that `mergeIncludeWithAccessControl` can tell a genuine "nothing
45
+ * to scope" apart from "the engine hit its depth cap" (see that type's doc
46
+ * comment and ADR-0022).
20
47
  */
21
48
  export async function buildIncludeWithAccessControl(fieldConfigs, args, config, depth = 0,
22
49
  // List names already on the path from the root to here. Used to detect
23
50
  // relationship cycles (A → B → … → A) and stop the auto-include from
24
51
  // re-descending them. Seed it with the root list name at the call site.
25
52
  visitedLists = []) {
26
- const MAX_DEPTH = 5;
27
- if (depth >= MAX_DEPTH) {
28
- return undefined;
29
- }
30
- // Skip auto-including relationships when inside a resolveOutput hook
31
- // This prevents infinite loops when hooks make DB queries that include
32
- // relationships back to the same entity (e.g., User virtual field queries Posts
33
- // which includes author back to User, triggering the virtual field again)
34
- if (args.context._resolveOutputCounter.depth > 0) {
35
- return undefined;
53
+ if (depth >= READ_INCLUDE_MAX_DEPTH) {
54
+ return { kind: 'depth-exceeded' };
36
55
  }
56
+ // Inside a resolveOutput/virtual-field context we still scope each immediate
57
+ // relation (its own access `where`), but do not auto-expand INTO its nested
58
+ // relations — no recursive call is made, so a self-referential relation
59
+ // terminates after one level regardless of cycles. This prevents the
60
+ // infinite loops the original passthrough guarded against (hooks making DB
61
+ // queries that include relationships back to the same entity), while still
62
+ // row-scoping the relation itself rather than skipping scoping altogether
63
+ // (issue #830's second trigger).
64
+ const insideResolveOutput = args.context._resolveOutputCounter.depth > 0;
37
65
  const include = {};
38
66
  let hasRelationships = false;
39
67
  for (const [fieldName, fieldConfig] of Object.entries(fieldConfigs)) {
@@ -51,12 +79,7 @@ visitedLists = []) {
51
79
  if (accessResult === false) {
52
80
  continue;
53
81
  }
54
- // Build the include entry
55
- const includeEntry = {};
56
- // If access returns a filter, add it to the where clause
57
- if (typeof accessResult === 'object') {
58
- includeEntry.where = accessResult;
59
- }
82
+ const where = typeof accessResult === 'object' ? accessResult : undefined;
60
83
  // Cycle guard: if the related list already appears on the path from the
61
84
  // root, DO NOT auto-include its relationships again. On a cyclic
62
85
  // readable-relationship graph (A → B → … → A) the depth-first walk would
@@ -68,20 +91,16 @@ visitedLists = []) {
68
91
  // genuine single-level fetch, not a re-expansion of the full auto-include.
69
92
  // The relation itself is still included (as a FLAT fetch of its own
70
93
  // columns); only its onward relationships are pruned at the back-edge.
94
+ let nested = { kind: 'nothing-to-scope' };
71
95
  const relatedListName = relatedConfig.listName;
72
- if (!visitedLists.includes(relatedListName)) {
73
- // Recursively build nested includes
74
- const nestedInclude = await buildIncludeWithAccessControl(relatedConfig.listConfig.fields, args, config, depth + 1, [...visitedLists, relatedListName]);
75
- if (nestedInclude && Object.keys(nestedInclude).length > 0) {
76
- includeEntry.include = nestedInclude;
77
- }
96
+ if (!insideResolveOutput && !visitedLists.includes(relatedListName)) {
97
+ nested = await buildIncludeWithAccessControl(relatedConfig.listConfig.fields, args, config, depth + 1, [...visitedLists, relatedListName]);
78
98
  }
79
- // Add to include object
80
- include[fieldName] = Object.keys(includeEntry).length > 0 ? includeEntry : true;
99
+ include[fieldName] = { where, nested };
81
100
  }
82
101
  }
83
102
  }
84
- return hasRelationships ? include : undefined;
103
+ return hasRelationships ? { kind: 'scoped', include } : { kind: 'nothing-to-scope' };
85
104
  }
86
105
  /**
87
106
  * Narrow an unknown include value to the structured object form (vs bare `true`
@@ -148,32 +167,37 @@ function andWhere(accessWhere, callerWhere) {
148
167
  * - If the caller names a key that is NOT a config-declared relationship, it is
149
168
  * passed through unchanged (access control does not govern it).
150
169
  *
151
- * The access-controlled include is recursive to `MAX_DEPTH` (see
152
- * `buildIncludeWithAccessControl`); beyond that depth no auto-include exists, so
153
- * deeper caller selections pass through unscoped consistent with the existing
154
- * auto-include behaviour.
170
+ * `accessControlledInclude` is the {@link AccessIncludeResult} for THIS level:
171
+ * - `nothing-to-scope` nothing to merge against (the list has no
172
+ * relationships, or we're inside a resolveOutput context where the caller
173
+ * include is irrelevant to begin with). Pass the caller's include through
174
+ * unchanged — this is a non-denial outcome, not "every relation denied".
175
+ * - `depth-exceeded` → the engine could not compute a scope for THIS level at
176
+ * all because it sits at or past `READ_INCLUDE_MAX_DEPTH`. If the caller
177
+ * named anything here, that is exactly the case that used to pass through
178
+ * unscoped (issue #830): throw `AccessScopeDepthExceededError` instead. An
179
+ * empty caller include at this level (nothing further requested) is not an
180
+ * error — there's simply nothing to do.
181
+ * - `scoped` → the normal per-relation merge below: a declared relationship
182
+ * ABSENT from the access include was denied (drop it); one PRESENT is used
183
+ * as the base, AND-combining `where`s and recursing into nested includes.
155
184
  *
156
- * `accessControlledInclude` being `undefined` is NOT "every relation denied". It
157
- * means no access-controlled include was computed at all — a non-denial outcome
158
- * that `buildIncludeWithAccessControl` returns when inside a `resolveOutput`/
159
- * virtual-field context, at `MAX_DEPTH`, or when the list has no relationships.
160
- * In every one of those cases there is nothing to merge against, so the caller's
161
- * `include` is passed through unchanged (matching the prior `args.include || …`
162
- * fallback). This is distinct from an `undefined` ENTRY inside a defined access
163
- * include, which DOES mean the relation was denied and must be dropped (see the
164
- * per-relation loop below). Only the whole-object `undefined` is a passthrough.
185
+ * `listKey` and `depth` are carried only to build a useful
186
+ * `AccessScopeDepthExceededError` message; they do not affect merge behaviour.
165
187
  */
166
- export function mergeIncludeWithAccessControl(callerInclude, accessControlledInclude, fieldConfigs, config) {
167
- // No access-controlled include was computed (resolveOutput/virtual context,
168
- // MAX_DEPTH, or a list with no relationships) → nothing to scope against, so
169
- // pass the caller's include through unchanged. Dropping relations here would be
170
- // fail-closed data loss, not a denial. Denied relations are dropped only when a
171
- // defined access include OMITS them (handled per-relation below).
172
- if (accessControlledInclude === undefined) {
188
+ export function mergeIncludeWithAccessControl(callerInclude, accessControlledInclude, fieldConfigs, config, listKey, depth = 0) {
189
+ if (accessControlledInclude.kind === 'nothing-to-scope') {
173
190
  return callerInclude;
174
191
  }
192
+ if (accessControlledInclude.kind === 'depth-exceeded') {
193
+ const [firstRelationName] = Object.keys(callerInclude);
194
+ if (firstRelationName !== undefined) {
195
+ throw new AccessScopeDepthExceededError(listKey, firstRelationName, depth);
196
+ }
197
+ return {};
198
+ }
175
199
  const merged = {};
176
- const accessInclude = accessControlledInclude;
200
+ const accessInclude = accessControlledInclude.include;
177
201
  for (const [relationName, callerValue] of Object.entries(callerInclude)) {
178
202
  const fieldConfig = fieldConfigs[relationName];
179
203
  const isDeclaredRelationship = fieldConfig?.type === 'relationship' && 'ref' in fieldConfig && !!fieldConfig.ref;
@@ -182,27 +206,30 @@ export function mergeIncludeWithAccessControl(callerInclude, accessControlledInc
182
206
  merged[relationName] = callerValue;
183
207
  continue;
184
208
  }
185
- const accessValue = accessInclude[relationName];
209
+ const accessEntry = accessInclude[relationName];
186
210
  // Declared relationship absent from the access include → query access denied → drop it.
187
- if (accessValue === undefined) {
211
+ if (accessEntry === undefined) {
188
212
  continue;
189
213
  }
190
- const accessEntry = asEntryObject(accessValue);
191
214
  const callerEntry = asEntryObject(callerValue);
192
215
  // Resolve the related list's field configs so nested includes merge recursively.
193
216
  const relatedConfig = getRelatedListConfig(fieldConfig.ref, config);
194
217
  const relatedFields = relatedConfig?.listConfig.fields;
195
- const mergedWhere = andWhere(accessEntry?.where, callerEntry?.where);
218
+ const mergedWhere = andWhere(accessEntry.where, callerEntry?.where);
196
219
  let mergedNested;
197
- if (callerEntry?.include && relatedFields) {
198
- // Recurse: scope the caller's nested selection against the nested access include.
199
- mergedNested = mergeIncludeWithAccessControl(callerEntry.include, accessEntry?.include, relatedFields, config);
220
+ if (callerEntry?.include && relatedFields && relatedConfig) {
221
+ // Recurse: scope the caller's nested selection against the nested access
222
+ // result. If that result is 'depth-exceeded', the recursive call itself
223
+ // throws — the caller named something the engine cannot scope.
224
+ mergedNested = mergeIncludeWithAccessControl(callerEntry.include, accessEntry.nested, relatedFields, config, relatedConfig.listName, depth + 1);
200
225
  }
201
- else if (accessEntry?.include) {
226
+ else if (accessEntry.nested.kind === 'scoped') {
202
227
  // Caller selected the relation bare (no nested include); keep the
203
228
  // access-controlled nested include so deeper relations stay filtered.
204
- mergedNested = accessEntry.include;
229
+ mergedNested = toPrismaInclude(accessEntry.nested);
205
230
  }
231
+ // If the caller's entry has no nested include, a 'depth-exceeded' nested
232
+ // result is silent here too — nothing was asked for past this point.
206
233
  const entry = {};
207
234
  if (mergedWhere)
208
235
  entry.where = mergedWhere;
@@ -1 +1 @@
1
- {"version":3,"file":"access-filter.js","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"access-filter.js","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"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"}