@opensaas/stack-core 0.37.0 → 0.39.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 (226) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +191 -0
  3. package/CLAUDE.md +42 -3
  4. package/dist/access/access-filter.d.ts +70 -113
  5. package/dist/access/access-filter.d.ts.map +1 -1
  6. package/dist/access/access-filter.js +183 -210
  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 +62 -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/engine.d.ts +0 -20
  23. package/dist/access/engine.d.ts.map +1 -1
  24. package/dist/access/engine.js +0 -25
  25. package/dist/access/engine.js.map +1 -1
  26. package/dist/access/errors.d.ts +61 -8
  27. package/dist/access/errors.d.ts.map +1 -1
  28. package/dist/access/errors.js +91 -12
  29. package/dist/access/errors.js.map +1 -1
  30. package/dist/access/field-access.d.ts +39 -1
  31. package/dist/access/field-access.d.ts.map +1 -1
  32. package/dist/access/field-access.js +108 -42
  33. package/dist/access/field-access.js.map +1 -1
  34. package/dist/access/field-access.test.js +229 -2
  35. package/dist/access/field-access.test.js.map +1 -1
  36. package/dist/access/field-transforms.d.ts +0 -27
  37. package/dist/access/field-transforms.d.ts.map +1 -1
  38. package/dist/access/field-visibility.d.ts +2 -5
  39. package/dist/access/field-visibility.d.ts.map +1 -1
  40. package/dist/access/field-visibility.js +89 -41
  41. package/dist/access/field-visibility.js.map +1 -1
  42. package/dist/access/index.d.ts +6 -3
  43. package/dist/access/index.d.ts.map +1 -1
  44. package/dist/access/index.js +12 -2
  45. package/dist/access/index.js.map +1 -1
  46. package/dist/access/query-validation.d.ts +121 -0
  47. package/dist/access/query-validation.d.ts.map +1 -0
  48. package/dist/access/query-validation.js +292 -0
  49. package/dist/access/query-validation.js.map +1 -0
  50. package/dist/access/relationship-count.d.ts +1 -1
  51. package/dist/access/relationship-count.d.ts.map +1 -1
  52. package/dist/access/relationship-count.js +2 -10
  53. package/dist/access/relationship-count.js.map +1 -1
  54. package/dist/access/relationship-label-filter.d.ts +23 -23
  55. package/dist/access/relationship-label-filter.d.ts.map +1 -1
  56. package/dist/access/relationship-label-filter.js +5 -76
  57. package/dist/access/relationship-label-filter.js.map +1 -1
  58. package/dist/access/relationship-label-filter.test.js +19 -56
  59. package/dist/access/relationship-label-filter.test.js.map +1 -1
  60. package/dist/access/transaction-registry.d.ts +45 -0
  61. package/dist/access/transaction-registry.d.ts.map +1 -0
  62. package/dist/access/transaction-registry.js +37 -0
  63. package/dist/access/transaction-registry.js.map +1 -0
  64. package/dist/access/types.d.ts +55 -59
  65. package/dist/access/types.d.ts.map +1 -1
  66. package/dist/config/index.d.ts +9 -7
  67. package/dist/config/index.d.ts.map +1 -1
  68. package/dist/config/index.js +10 -18
  69. package/dist/config/index.js.map +1 -1
  70. package/dist/config/label.d.ts +6 -13
  71. package/dist/config/label.d.ts.map +1 -1
  72. package/dist/config/label.js +6 -13
  73. package/dist/config/label.js.map +1 -1
  74. package/dist/config/nav-count.d.ts +11 -19
  75. package/dist/config/nav-count.d.ts.map +1 -1
  76. package/dist/config/nav-count.js +11 -19
  77. package/dist/config/nav-count.js.map +1 -1
  78. package/dist/config/plugin-engine.d.ts +0 -10
  79. package/dist/config/plugin-engine.d.ts.map +1 -1
  80. package/dist/config/plugin-engine.js +1 -37
  81. package/dist/config/plugin-engine.js.map +1 -1
  82. package/dist/config/types.d.ts +94 -109
  83. package/dist/config/types.d.ts.map +1 -1
  84. package/dist/context/apply-defaults.d.ts +8 -31
  85. package/dist/context/apply-defaults.d.ts.map +1 -1
  86. package/dist/context/apply-defaults.js +12 -41
  87. package/dist/context/apply-defaults.js.map +1 -1
  88. package/dist/context/hook-pipeline.d.ts +4 -29
  89. package/dist/context/hook-pipeline.d.ts.map +1 -1
  90. package/dist/context/hook-pipeline.js +8 -41
  91. package/dist/context/hook-pipeline.js.map +1 -1
  92. package/dist/context/index.d.ts +4 -15
  93. package/dist/context/index.d.ts.map +1 -1
  94. package/dist/context/index.js +223 -215
  95. package/dist/context/index.js.map +1 -1
  96. package/dist/context/nested-operations.d.ts +7 -26
  97. package/dist/context/nested-operations.d.ts.map +1 -1
  98. package/dist/context/nested-operations.js +73 -171
  99. package/dist/context/nested-operations.js.map +1 -1
  100. package/dist/context/transaction-boundary.d.ts +36 -47
  101. package/dist/context/transaction-boundary.d.ts.map +1 -1
  102. package/dist/context/transaction-boundary.js +92 -79
  103. package/dist/context/transaction-boundary.js.map +1 -1
  104. package/dist/context/write-pipeline.d.ts +25 -64
  105. package/dist/context/write-pipeline.d.ts.map +1 -1
  106. package/dist/context/write-pipeline.js +81 -111
  107. package/dist/context/write-pipeline.js.map +1 -1
  108. package/dist/fields/format-prisma-default.d.ts.map +1 -1
  109. package/dist/fields/format-prisma-default.js +4 -7
  110. package/dist/fields/format-prisma-default.js.map +1 -1
  111. package/dist/fields/index.d.ts +17 -2
  112. package/dist/fields/index.d.ts.map +1 -1
  113. package/dist/fields/index.js +136 -147
  114. package/dist/fields/index.js.map +1 -1
  115. package/dist/filter/collect.d.ts +26 -3
  116. package/dist/filter/collect.d.ts.map +1 -1
  117. package/dist/filter/collect.js +30 -12
  118. package/dist/filter/collect.js.map +1 -1
  119. package/dist/filter/filter.test.js +98 -15
  120. package/dist/filter/filter.test.js.map +1 -1
  121. package/dist/filter/index.d.ts +1 -0
  122. package/dist/filter/index.d.ts.map +1 -1
  123. package/dist/filter/index.js.map +1 -1
  124. package/dist/filter/map.d.ts.map +1 -1
  125. package/dist/filter/map.js +0 -5
  126. package/dist/filter/map.js.map +1 -1
  127. package/dist/filter/parse.d.ts.map +1 -1
  128. package/dist/filter/parse.js +2 -9
  129. package/dist/filter/parse.js.map +1 -1
  130. package/dist/filter/serialize.d.ts.map +1 -1
  131. package/dist/filter/serialize.js +2 -4
  132. package/dist/filter/serialize.js.map +1 -1
  133. package/dist/hooks/index.d.ts +4 -42
  134. package/dist/hooks/index.d.ts.map +1 -1
  135. package/dist/hooks/index.js +5 -59
  136. package/dist/hooks/index.js.map +1 -1
  137. package/dist/index.d.ts +3 -1
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +20 -4
  140. package/dist/index.js.map +1 -1
  141. package/dist/internal.d.ts +1 -0
  142. package/dist/internal.d.ts.map +1 -1
  143. package/dist/internal.js +4 -0
  144. package/dist/internal.js.map +1 -1
  145. package/dist/lib/case-utils.d.ts +0 -32
  146. package/dist/lib/case-utils.d.ts.map +1 -1
  147. package/dist/lib/case-utils.js +0 -32
  148. package/dist/lib/case-utils.js.map +1 -1
  149. package/dist/mcp/handler.d.ts +0 -4
  150. package/dist/mcp/handler.d.ts.map +1 -1
  151. package/dist/mcp/handler.js +12 -69
  152. package/dist/mcp/handler.js.map +1 -1
  153. package/dist/query/index.d.ts +55 -227
  154. package/dist/query/index.d.ts.map +1 -1
  155. package/dist/query/index.js +41 -117
  156. package/dist/query/index.js.map +1 -1
  157. package/dist/query/relationship-options.d.ts +1 -1
  158. package/dist/query/relationship-options.js +1 -1
  159. package/dist/utils/password.d.ts +0 -68
  160. package/dist/utils/password.d.ts.map +1 -1
  161. package/dist/utils/password.js +7 -76
  162. package/dist/utils/password.js.map +1 -1
  163. package/dist/validation/field-config.d.ts.map +1 -1
  164. package/dist/validation/field-config.js +0 -6
  165. package/dist/validation/field-config.js.map +1 -1
  166. package/dist/validation/schema.d.ts +0 -7
  167. package/dist/validation/schema.d.ts.map +1 -1
  168. package/dist/validation/schema.js +0 -10
  169. package/dist/validation/schema.js.map +1 -1
  170. package/package.json +1 -1
  171. package/src/access/access-filter.test.ts +205 -275
  172. package/src/access/access-filter.ts +236 -272
  173. package/src/access/declared-dependencies.test.ts +277 -0
  174. package/src/access/declared-dependencies.ts +117 -37
  175. package/src/access/depth-limits.ts +8 -7
  176. package/src/access/engine.ts +0 -25
  177. package/src/access/errors.ts +97 -12
  178. package/src/access/field-access.test.ts +312 -2
  179. package/src/access/field-access.ts +118 -42
  180. package/src/access/field-transforms.ts +8 -39
  181. package/src/access/field-visibility.ts +99 -45
  182. package/src/access/index.ts +17 -5
  183. package/src/access/query-validation.ts +359 -0
  184. package/src/access/relationship-count.ts +3 -11
  185. package/src/access/relationship-label-filter.test.ts +19 -68
  186. package/src/access/relationship-label-filter.ts +27 -104
  187. package/src/access/transaction-registry.ts +58 -0
  188. package/src/access/types.ts +83 -86
  189. package/src/config/index.ts +10 -22
  190. package/src/config/label.ts +6 -13
  191. package/src/config/nav-count.ts +11 -19
  192. package/src/config/plugin-engine.ts +1 -37
  193. package/src/config/types.ts +95 -110
  194. package/src/context/apply-defaults.ts +12 -44
  195. package/src/context/hook-pipeline.ts +12 -67
  196. package/src/context/index.ts +311 -276
  197. package/src/context/nested-operations.ts +94 -226
  198. package/src/context/transaction-boundary.ts +113 -109
  199. package/src/context/write-pipeline.ts +99 -155
  200. package/src/fields/format-prisma-default.ts +4 -7
  201. package/src/fields/index.ts +155 -147
  202. package/src/filter/collect.ts +48 -12
  203. package/src/filter/filter.test.ts +153 -15
  204. package/src/filter/index.ts +1 -0
  205. package/src/filter/map.ts +0 -5
  206. package/src/filter/parse.ts +2 -9
  207. package/src/filter/serialize.ts +2 -4
  208. package/src/hooks/index.ts +5 -59
  209. package/src/index.ts +23 -4
  210. package/src/internal.ts +5 -0
  211. package/src/lib/case-utils.ts +0 -32
  212. package/src/mcp/handler.ts +13 -71
  213. package/src/query/index.ts +82 -235
  214. package/src/query/relationship-options.ts +1 -1
  215. package/src/utils/password.ts +7 -76
  216. package/src/validation/field-config.ts +0 -6
  217. package/src/validation/schema.ts +0 -10
  218. package/tests/access-relationships.test.ts +18 -16
  219. package/tests/computed-field-selective-evaluation.test.ts +418 -0
  220. package/tests/context.test.ts +727 -1
  221. package/tests/field-types.test.ts +222 -0
  222. package/tests/mcp-handler.test.ts +34 -0
  223. package/tests/needs-declared-dependencies.test.ts +7 -4
  224. package/tests/resolve-chain.test.ts +11 -11
  225. package/tests/transaction-boundary-outermost.test.ts +530 -0
  226. package/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,5 @@
1
1
  import type { FieldConfig, OpenSaasConfig } from '../config/types.js';
2
+ import type { FieldSelectionScope } from '../query/index.js';
2
3
  /**
3
4
  * Declared Dependencies — folding a computed field's `needs` into a read's
4
5
  * `include` without widening what the caller receives (ADR-0025).
@@ -6,10 +7,9 @@ import type { FieldConfig, OpenSaasConfig } from '../config/types.js';
6
7
  * A field's `needs` declares immediate sibling relations its `resolveOutput`
7
8
  * hook cannot compute without. This module folds those relations into
8
9
  * whatever `include` a read is already building (caller-supplied, fragment-
9
- * derived, or none at all) BEFORE it reaches the existing access-scoping
10
- * pipeline (`buildIncludeWithAccessControl` / `mergeIncludeWithAccessControl`
11
- * in `access-filter.ts`) a declared relation is scoped exactly like a
12
- * caller-named one, never a bypass.
10
+ * derived, or none at all) BEFORE it reaches the access-scoping pipeline
11
+ * (`buildAccessScopedInclude` in `access-filter.ts`) — a declared relation is
12
+ * scoped exactly like a caller-named one, never a bypass.
13
13
  *
14
14
  * The fold also tracks provenance: which relation keys, at which nesting
15
15
  * level, were added ONLY to satisfy a declaration (as opposed to being named
@@ -17,25 +17,44 @@ import type { FieldConfig, OpenSaasConfig } from '../config/types.js';
17
17
  * from the result after `resolveOutput` hooks have had a chance to read them
18
18
  * — a declared dependency is private plumbing, not an implicit `include`.
19
19
  *
20
- * Reach beyond one hop: a relation added here to satisfy a declaration is
21
- * added BARE (`true`), never with an explicit nested include of its own.
22
- * `buildIncludeWithAccessControl` already auto-expands a bare relation's own
23
- * readable-relationship subtree to `READ_INCLUDE_MAX_DEPTH` (pre-ADR-0026),
24
- * so a chain of declarations rides that existing expansion for free the
25
- * related list's own declared needs are already present among what gets
26
- * auto-included beneath it. This is also why a declaration-driven cycle
27
- * (e.g. `Order.total` needs `lineItems`, `LineItem.orderRef` needs `order`)
28
- * can't recurse without bound here: it flows through
29
- * `buildIncludeWithAccessControl`'s existing `visitedLists` cycle guard
30
- * rather than through any recursion of this module's own (see ADR-0026's
31
- * note that this guard's remaining job, after that ADR lands, is defending
32
- * exactly this fold).
20
+ * **Every relation this module reaches is folded recursively**, whether it
21
+ * was added purely to satisfy a declaration or is already present for another
22
+ * reason (caller/fragment-named, bare or not). Since ADR-0026 removed the
23
+ * auto-expansion that used to carry a chain of declarations "for free" —
24
+ * naming a relation now fetches its own columns and stopsa field computed
25
+ * one hop down would otherwise lose its own declared dependencies the moment
26
+ * its list is reached any way OTHER than an explicit nested caller `include`.
27
+ * Declarations fold in "at every level a field is computed" (ADR-0025), not
28
+ * only where the caller happened to write one out.
33
29
  *
34
- * This module only recurses into EXPLICIT nested includes the caller wrote
35
- * (narrowing what's fetched below a relation) those cut off the free
36
- * auto-expansion, so a nested list's own declared needs must be folded in
37
- * explicitly. An explicit caller include is always a finite literal, so this
38
- * recursion terminates on its own without a separate depth/cycle guard.
30
+ * A branch added purely by this fold (`declaredOnly.keys`) is stripped
31
+ * wholesale by `filterReadableFields` regardless of what's nested inside it,
32
+ * so its own nested declared-only keys need no individual tracking only a
33
+ * branch present for another reason (caller/fragment-named) needs the
34
+ * fine-grained `declaredOnly.nested` tracking, so a declaration folded
35
+ * beneath IT can be stripped without removing the caller's own data.
36
+ *
37
+ * **Cycle guard, re-pointed (ADR-0026).** A caller-supplied `include` is
38
+ * always a finite literal and cannot cycle. Recursively following declared
39
+ * dependencies across lists CAN — list A declaring `needs` on a relation to
40
+ * list B, whose own field declares `needs` back to A — so `visitedLists`
41
+ * carries the list names already on this fold's own path and stops rather
42
+ * than recursing without bound. `pnpm generate`'s `validateNeedsClosureDepth`
43
+ * (`needs-closure.ts`) is the primary backstop — a cyclic `needs` closure
44
+ * fails generation and should never reach this code at runtime — this guard
45
+ * is defense in depth, not the mechanism relied on for correctness.
46
+ *
47
+ * The guard applies to DECLARATION-ADDED edges only, which is exactly where
48
+ * the unbounded recursion can come from: a branch added by this fold carries
49
+ * no caller include of its own, so everything beneath it is declaration-added
50
+ * too, and each such edge either reaches a list not yet on the path or stops
51
+ * — bounding that suffix by the number of lists. An edge the request itself
52
+ * named is bounded by the request's own finite literal instead. Applying the
53
+ * guard to those as well would stop the fold at a path that merely revisits a
54
+ * list (`Post → author → posts`, or a self-referential `parent`), silently
55
+ * leaving the revisited list's own declared dependencies unsatisfied — the
56
+ * `undefined`-compute ADR-0025 exists to prevent, at every level a field is
57
+ * computed.
39
58
  */
40
59
  /** Which relation keys, at which nesting level, exist only to satisfy a `needs` declaration. */
41
60
  export type DeclaredOnlyTree = {
@@ -50,18 +69,39 @@ export declare function emptyDeclaredOnlyTree(): DeclaredOnlyTree;
50
69
  * list that have a `resolveOutput` hook. A `needs` entry on a field without
51
70
  * one is inert — there is no hook to feed it to — so it contributes nothing
52
71
  * to fetch.
72
+ *
73
+ * `selectedFields`, when given, restricts the union to fields the read is
74
+ * actually going to return (ADR-0027) — a field a fragment did not select is
75
+ * never computed, so its declared relation is never fetched for it either.
76
+ * `undefined` means unrestricted: every field with a hook contributes,
77
+ * matching a bare or `include`-based read, which always returns every
78
+ * computed field on the list.
53
79
  */
54
- export declare function getDeclaredRelationNames(fieldConfigs: Record<string, FieldConfig>): string[];
80
+ export declare function getDeclaredRelationNames(fieldConfigs: Record<string, FieldConfig>, selectedFields?: ReadonlySet<string>): string[];
55
81
  /**
56
82
  * Fold this list's declared dependencies into `rawInclude`, recursing into
57
- * any EXPLICIT nested include the caller wrote so a related list's own
58
- * declared needs are satisfied too (see module doc comment).
83
+ * EVERY relation reached from here whether it was just added to satisfy a
84
+ * declaration or was already present for another reason — so that list's own
85
+ * declared dependencies are satisfied too, wherever it's reached (see module
86
+ * doc comment).
59
87
  *
60
88
  * Returns `rawInclude` itself (same reference) when there is nothing to
61
89
  * fold, so a list with no `needs` fields and no caller include stays on the
62
90
  * exact bare-read path (ADR-0024) — untouched, not merely equivalent.
91
+ *
92
+ * `listKey` seeds the cycle guard at the root of a read; recursive calls
93
+ * extend it with each related list reached along THIS fold's own path.
94
+ *
95
+ * `selection`, when given, is the fragment scope this level was reached
96
+ * under (ADR-0027) — only fields it names contribute their `needs` (see
97
+ * `getDeclaredRelationNames`). It is `undefined` for a bare/`include`-based
98
+ * read (unrestricted: every field's `needs` folds in, unchanged from before
99
+ * ADR-0027) and for any branch reached only to satisfy a declaration — a
100
+ * relation added purely by this fold has no fragment scope of its own, so
101
+ * its own list folds unrestricted, exactly as it did before selectivity
102
+ * existed.
63
103
  */
64
- export declare function foldDeclaredDependencies(rawInclude: Record<string, unknown> | undefined, fieldConfigs: Record<string, FieldConfig>, config: OpenSaasConfig): {
104
+ export declare function foldDeclaredDependencies(rawInclude: Record<string, unknown> | undefined, fieldConfigs: Record<string, FieldConfig>, config: OpenSaasConfig, listKey: string, visitedLists?: readonly string[], selection?: FieldSelectionScope): {
65
105
  include: Record<string, unknown> | undefined;
66
106
  declaredOnly: DeclaredOnlyTree;
67
107
  };
@@ -1 +1 @@
1
- {"version":3,"file":"declared-dependencies.d.ts","sourceRoot":"","sources":["../../src/access/declared-dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAGrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,gGAAgG;AAChG,MAAM,MAAM,gBAAgB,GAAG;IAC7B,2EAA2E;IAC3E,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACjB,6JAA6J;IAC7J,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CACzC,CAAA;AAED,wBAAgB,qBAAqB,IAAI,gBAAgB,CAExD;AAiBD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,EAAE,CAS5F;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC/C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,MAAM,EAAE,cAAc,GACrB;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAAC,YAAY,EAAE,gBAAgB,CAAA;CAAE,CAwClF"}
1
+ {"version":3,"file":"declared-dependencies.d.ts","sourceRoot":"","sources":["../../src/access/declared-dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAErE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,gGAAgG;AAChG,MAAM,MAAM,gBAAgB,GAAG;IAC7B,2EAA2E;IAC3E,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACjB,6JAA6J;IAC7J,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CACzC,CAAA;AAED,wBAAgB,qBAAqB,IAAI,gBAAgB,CAExD;AA6BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GACnC,MAAM,EAAE,CAUV;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC/C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,MAAM,EACf,YAAY,GAAE,SAAS,MAAM,EAAc,EAC3C,SAAS,CAAC,EAAE,mBAAmB,GAC9B;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAAC,YAAY,EAAE,gBAAgB,CAAA;CAAE,CA6DlF"}
@@ -11,17 +11,37 @@ function isRelationshipFieldConfig(fieldConfig) {
11
11
  'ref' in fieldConfig &&
12
12
  !!fieldConfig.ref);
13
13
  }
14
+ /** A plain object — excludes `null` and arrays, which `typeof x === 'object'` alone would admit. */
15
+ function isPlainObject(value) {
16
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
17
+ }
18
+ /** The explicit nested `include` on an include entry, if the entry is a structured object naming one. */
19
+ function getExplicitInclude(value) {
20
+ if (!isPlainObject(value))
21
+ return undefined;
22
+ const { include } = value;
23
+ return isPlainObject(include) ? include : undefined;
24
+ }
14
25
  /**
15
26
  * The deduped set of relation names declared via `needs` by fields on this
16
27
  * list that have a `resolveOutput` hook. A `needs` entry on a field without
17
28
  * one is inert — there is no hook to feed it to — so it contributes nothing
18
29
  * to fetch.
30
+ *
31
+ * `selectedFields`, when given, restricts the union to fields the read is
32
+ * actually going to return (ADR-0027) — a field a fragment did not select is
33
+ * never computed, so its declared relation is never fetched for it either.
34
+ * `undefined` means unrestricted: every field with a hook contributes,
35
+ * matching a bare or `include`-based read, which always returns every
36
+ * computed field on the list.
19
37
  */
20
- export function getDeclaredRelationNames(fieldConfigs) {
38
+ export function getDeclaredRelationNames(fieldConfigs, selectedFields) {
21
39
  const names = new Set();
22
- for (const fieldConfig of Object.values(fieldConfigs)) {
40
+ for (const [fieldName, fieldConfig] of Object.entries(fieldConfigs)) {
23
41
  if (!fieldConfig?.hooks?.resolveOutput)
24
42
  continue;
43
+ if (selectedFields && !selectedFields.has(fieldName))
44
+ continue;
25
45
  for (const name of fieldConfig.needs ?? []) {
26
46
  names.add(name);
27
47
  }
@@ -30,15 +50,29 @@ export function getDeclaredRelationNames(fieldConfigs) {
30
50
  }
31
51
  /**
32
52
  * Fold this list's declared dependencies into `rawInclude`, recursing into
33
- * any EXPLICIT nested include the caller wrote so a related list's own
34
- * declared needs are satisfied too (see module doc comment).
53
+ * EVERY relation reached from here whether it was just added to satisfy a
54
+ * declaration or was already present for another reason — so that list's own
55
+ * declared dependencies are satisfied too, wherever it's reached (see module
56
+ * doc comment).
35
57
  *
36
58
  * Returns `rawInclude` itself (same reference) when there is nothing to
37
59
  * fold, so a list with no `needs` fields and no caller include stays on the
38
60
  * exact bare-read path (ADR-0024) — untouched, not merely equivalent.
61
+ *
62
+ * `listKey` seeds the cycle guard at the root of a read; recursive calls
63
+ * extend it with each related list reached along THIS fold's own path.
64
+ *
65
+ * `selection`, when given, is the fragment scope this level was reached
66
+ * under (ADR-0027) — only fields it names contribute their `needs` (see
67
+ * `getDeclaredRelationNames`). It is `undefined` for a bare/`include`-based
68
+ * read (unrestricted: every field's `needs` folds in, unchanged from before
69
+ * ADR-0027) and for any branch reached only to satisfy a declaration — a
70
+ * relation added purely by this fold has no fragment scope of its own, so
71
+ * its own list folds unrestricted, exactly as it did before selectivity
72
+ * existed.
39
73
  */
40
- export function foldDeclaredDependencies(rawInclude, fieldConfigs, config) {
41
- const declaredNames = getDeclaredRelationNames(fieldConfigs);
74
+ export function foldDeclaredDependencies(rawInclude, fieldConfigs, config, listKey, visitedLists = [listKey], selection) {
75
+ const declaredNames = getDeclaredRelationNames(fieldConfigs, selection?.fields);
42
76
  if (declaredNames.length === 0 && !rawInclude) {
43
77
  return { include: rawInclude, declaredOnly: emptyDeclaredOnlyTree() };
44
78
  }
@@ -56,21 +90,32 @@ export function foldDeclaredDependencies(rawInclude, fieldConfigs, config) {
56
90
  const fieldConfig = fieldConfigs[key];
57
91
  if (!isRelationshipFieldConfig(fieldConfig))
58
92
  continue;
59
- // A whole branch we just added is bare `true` — its own subtree auto-expands
60
- // (see module doc comment), so there is no explicit nested include to recurse into.
61
- if (declaredOnly.keys.has(key))
62
- continue;
63
- const entry = value;
64
- if (!entry || typeof entry !== 'object' || !entry.include)
65
- continue;
66
93
  const relatedConfig = getRelatedListConfig(fieldConfig.ref, config);
67
94
  if (!relatedConfig)
68
95
  continue;
69
- const nested = foldDeclaredDependencies(entry.include, relatedConfig.listConfig.fields, config);
70
- if (nested.include !== entry.include) {
71
- merged[key] = { ...entry, include: nested.include };
96
+ // Defensive cycle guard (see module doc comment) — a DECLARATION-ADDED
97
+ // edge into a list already on this path stops here; the value at `key`
98
+ // is left exactly as-is.
99
+ if (declaredOnly.keys.has(key) && visitedLists.includes(relatedConfig.listName))
100
+ continue;
101
+ // A branch added purely by the fold has no fragment scope of its own —
102
+ // it folds unrestricted, as before ADR-0027. A branch the request itself
103
+ // named (caller include or fragment) carries that name's own nested
104
+ // scope, if the fragment gave it one (a bare `true` selector leaves it
105
+ // `undefined` — also unrestricted, since the caller asked for
106
+ // "everything" there).
107
+ const nestedSelection = declaredOnly.keys.has(key) ? undefined : selection?.nested[key];
108
+ const explicitNested = getExplicitInclude(value);
109
+ const nested = foldDeclaredDependencies(explicitNested, relatedConfig.listConfig.fields, config, relatedConfig.listName, [...visitedLists, relatedConfig.listName], nestedSelection);
110
+ if (nested.include) {
111
+ merged[key] = {
112
+ ...(typeof value === 'object' && value ? value : {}),
113
+ include: nested.include,
114
+ };
72
115
  }
73
- if (!isDeclaredOnlyTreeEmpty(nested.declaredOnly)) {
116
+ // Declaration-added branches need no nested tracking of their own (see
117
+ // module doc comment) — only caller/fragment-named branches do.
118
+ if (!declaredOnly.keys.has(key) && !isDeclaredOnlyTreeEmpty(nested.declaredOnly)) {
74
119
  declaredOnly.nested[key] = nested.declaredOnly;
75
120
  }
76
121
  }
@@ -1 +1 @@
1
- {"version":3,"file":"declared-dependencies.js","sourceRoot":"","sources":["../../src/access/declared-dependencies.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"declared-dependencies.js","sourceRoot":"","sources":["../../src/access/declared-dependencies.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=declared-dependencies.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"declared-dependencies.test.d.ts","sourceRoot":"","sources":["../../src/access/declared-dependencies.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,226 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { foldDeclaredDependencies, emptyDeclaredOnlyTree } from './declared-dependencies.js';
3
+ /**
4
+ * Unit coverage for `foldDeclaredDependencies` (ADR-0025), focused on the
5
+ * behaviour ADR-0026 requires of it once the read pipeline stopped
6
+ * auto-expanding a named relation's own subtree: a declared dependency no
7
+ * longer rides that expansion "for free" and must be folded recursively —
8
+ * at every level a field is computed, however that level was reached
9
+ * (declaration-added, caller-named bare, or caller-named with its own nested
10
+ * include).
11
+ *
12
+ * End-to-end coverage of the fold feeding an actual read (mocked Prisma, real
13
+ * `resolveOutput` hooks) lives in `tests/needs-declared-dependencies.test.ts`;
14
+ * these tests exercise the fold function directly so a regression here fails
15
+ * fast with a small, precise reproduction.
16
+ */
17
+ function relField(ref, many = false) {
18
+ return { type: 'relationship', ref, many };
19
+ }
20
+ function virtualNeeds(needs) {
21
+ return {
22
+ type: 'virtual',
23
+ needs,
24
+ hooks: { resolveOutput: () => 'x' },
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- minimal field config for unit test
26
+ };
27
+ }
28
+ describe('foldDeclaredDependencies', () => {
29
+ it('returns the exact same reference when there is nothing to fold', () => {
30
+ const fields = { name: { type: 'text' } };
31
+ const config = { db: { provider: 'sqlite' }, lists: {} };
32
+ const result = foldDeclaredDependencies(undefined, fields, config, 'List');
33
+ expect(result.include).toBeUndefined();
34
+ expect(result.declaredOnly).toEqual(emptyDeclaredOnlyTree());
35
+ });
36
+ it('folds a declaration-added bare relation whose own related list has further needs', () => {
37
+ // Order.total needs lineItems (declaration-added, no caller include at
38
+ // all). LineItem.summary needs product. Without recursing into the
39
+ // declaration-added branch, `product` would never be fetched, and
40
+ // LineItem.summary would silently compute over `undefined`.
41
+ const config = {
42
+ db: { provider: 'sqlite' },
43
+ lists: {
44
+ Order: {
45
+ fields: {
46
+ lineItems: relField('LineItem.order', true),
47
+ total: virtualNeeds(['lineItems']),
48
+ },
49
+ },
50
+ LineItem: {
51
+ fields: { product: relField('Product'), summary: virtualNeeds(['product']) },
52
+ },
53
+ Product: { fields: { name: { type: 'text' } } },
54
+ },
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- minimal config for unit test
56
+ };
57
+ const result = foldDeclaredDependencies(undefined, config.lists.Order.fields, config, 'Order');
58
+ expect(result.include).toEqual({ lineItems: { include: { product: true } } });
59
+ // The whole `lineItems` branch is declaration-only at Order's level, so it
60
+ // needs no fine-grained nested tracking — the entire branch is stripped
61
+ // regardless of what's nested inside it.
62
+ expect(result.declaredOnly.keys.has('lineItems')).toBe(true);
63
+ expect(result.declaredOnly.nested.lineItems).toBeUndefined();
64
+ });
65
+ it('folds a caller-named BARE relation whose related list has its own needs, tracking the addition for fine-grained stripping', () => {
66
+ // The caller explicitly asked for `lineItems` (bare) — NOT declaration-only
67
+ // at Order's level — but LineItem's own `product` still has to be folded
68
+ // in for LineItem.summary's hook. Because the caller DID ask for
69
+ // `lineItems`, `product` must be tracked as declaration-only ONE LEVEL
70
+ // DOWN so it (and only it) gets stripped from each returned line item.
71
+ const config = {
72
+ db: { provider: 'sqlite' },
73
+ lists: {
74
+ Order: {
75
+ fields: {
76
+ lineItems: relField('LineItem.order', true),
77
+ total: virtualNeeds(['lineItems']),
78
+ },
79
+ },
80
+ LineItem: {
81
+ fields: {
82
+ product: relField('Product'),
83
+ tag: relField('Tag'),
84
+ summary: virtualNeeds(['product']),
85
+ },
86
+ },
87
+ Product: { fields: { name: { type: 'text' } } },
88
+ Tag: { fields: { name: { type: 'text' } } },
89
+ },
90
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- minimal config for unit test
91
+ };
92
+ const result = foldDeclaredDependencies({ lineItems: true }, config.lists.Order.fields, config, 'Order');
93
+ expect(result.include).toEqual({ lineItems: { include: { product: true } } });
94
+ // `lineItems` itself is caller-named, not declaration-only...
95
+ expect(result.declaredOnly.keys.has('lineItems')).toBe(false);
96
+ // ...but `product`, nested beneath it, IS — so it gets stripped from each
97
+ // line item while `lineItems` (and its other fields, e.g. `tag`) survive.
98
+ expect(result.declaredOnly.nested.lineItems?.keys.has('product')).toBe(true);
99
+ });
100
+ it("recurses into an explicit caller-nested include so the nested list's own needs are satisfied there too", () => {
101
+ const config = {
102
+ db: { provider: 'sqlite' },
103
+ lists: {
104
+ Order: { fields: { lineItems: relField('LineItem.order', true) } },
105
+ LineItem: {
106
+ fields: {
107
+ product: relField('Product'),
108
+ tag: relField('Tag'),
109
+ summary: virtualNeeds(['product']),
110
+ },
111
+ },
112
+ Product: { fields: { name: { type: 'text' } } },
113
+ Tag: { fields: { name: { type: 'text' } } },
114
+ },
115
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- minimal config for unit test
116
+ };
117
+ const result = foldDeclaredDependencies({ lineItems: { include: { tag: true } } }, config.lists.Order.fields, config, 'Order');
118
+ expect(result.include).toEqual({ lineItems: { include: { tag: true, product: true } } });
119
+ expect(result.declaredOnly.nested.lineItems?.keys.has('product')).toBe(true);
120
+ expect(result.declaredOnly.nested.lineItems?.keys.has('tag')).toBe(false);
121
+ });
122
+ it('does not fold anything beneath a relation whose related list has no needs of its own', () => {
123
+ const config = {
124
+ db: { provider: 'sqlite' },
125
+ lists: {
126
+ Order: { fields: { customer: relField('Customer') } },
127
+ Customer: { fields: { name: { type: 'text' } } },
128
+ },
129
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- minimal config for unit test
130
+ };
131
+ const result = foldDeclaredDependencies({ customer: true }, config.lists.Order.fields, config, 'Order');
132
+ // Untouched — no needs anywhere in reach, so the bare relation stays bare.
133
+ expect(result.include).toEqual({ customer: true });
134
+ expect(result.declaredOnly).toEqual(emptyDeclaredOnlyTree());
135
+ });
136
+ it("folds a revisited list's own needs beneath a relation the request named", () => {
137
+ // The request names Post → author → posts, revisiting Post. Nothing here
138
+ // can loop — the include is a finite literal the caller wrote out — so the
139
+ // fold must keep going and satisfy Post.blurb's `needs` at THAT level too.
140
+ // Guarding a caller-named edge on list identity would stop the fold at
141
+ // `posts` and leave `blurb` computing over an undefined `tags`.
142
+ const config = {
143
+ db: { provider: 'sqlite' },
144
+ lists: {
145
+ Post: {
146
+ fields: {
147
+ author: relField('User.posts'),
148
+ comments: relField('Comment', true),
149
+ tags: relField('Tag', true),
150
+ blurb: virtualNeeds(['tags']),
151
+ },
152
+ },
153
+ User: { fields: { posts: relField('Post.author', true) } },
154
+ Comment: { fields: { body: { type: 'text' } } },
155
+ Tag: { fields: { label: { type: 'text' } } },
156
+ },
157
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- minimal config for unit test
158
+ };
159
+ const result = foldDeclaredDependencies({ author: { include: { posts: { include: { comments: true } } } } }, config.lists.Post.fields, config, 'Post');
160
+ expect(result.include).toEqual({
161
+ tags: true,
162
+ author: { include: { posts: { include: { comments: true, tags: true } } } },
163
+ });
164
+ // Only the nested `tags` needs fine-grained tracking: the root-level one is
165
+ // declaration-only at this level (stripped wholesale), while the nested one
166
+ // sits inside branches the caller named and must be stripped individually.
167
+ expect(result.declaredOnly.keys.has('tags')).toBe(true);
168
+ expect(result.declaredOnly.nested.author?.nested.posts?.keys.has('tags')).toBe(true);
169
+ expect(result.declaredOnly.nested.author?.nested.posts?.keys.has('comments')).toBe(false);
170
+ });
171
+ it("folds a self-referential relation's own needs when the request names it", () => {
172
+ // `parent` points back at Category itself. The request named it, so the
173
+ // fold must satisfy Category's own `needs` beneath it — `depth` is computed
174
+ // on the parent row too.
175
+ const config = {
176
+ db: { provider: 'sqlite' },
177
+ lists: {
178
+ Category: {
179
+ fields: {
180
+ parent: relField('Category.children'),
181
+ children: relField('Category.parent', true),
182
+ depth: virtualNeeds(['children']),
183
+ },
184
+ },
185
+ },
186
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- minimal config for unit test
187
+ };
188
+ const result = foldDeclaredDependencies({ parent: true }, config.lists.Category.fields, config, 'Category');
189
+ // `children` folds in at the root AND beneath the caller-named `parent`.
190
+ // The fold beneath `parent` stops there: `children` is declaration-added,
191
+ // so the guard applies to its own onward edge back into Category.
192
+ expect(result.include).toEqual({
193
+ parent: { include: { children: true } },
194
+ children: true,
195
+ });
196
+ expect(result.declaredOnly.keys.has('children')).toBe(true);
197
+ expect(result.declaredOnly.nested.parent?.keys.has('children')).toBe(true);
198
+ });
199
+ it('defensively terminates a two-list mutual `needs` cycle instead of recursing without bound', () => {
200
+ // Order.total needs lineItems; LineItem.orderRef needs order — a cycle
201
+ // `validateNeedsClosureDepth` (needs-closure.ts) rejects at generate time.
202
+ // This constructs it directly to exercise the fold's OWN defensive guard,
203
+ // independent of that generate-time backstop.
204
+ const config = {
205
+ db: { provider: 'sqlite' },
206
+ lists: {
207
+ Order: {
208
+ fields: {
209
+ lineItems: relField('LineItem.order', true),
210
+ total: virtualNeeds(['lineItems']),
211
+ },
212
+ },
213
+ LineItem: {
214
+ fields: { order: relField('Order.lineItems'), orderRef: virtualNeeds(['order']) },
215
+ },
216
+ },
217
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- minimal config for unit test
218
+ };
219
+ const result = foldDeclaredDependencies(undefined, config.lists.Order.fields, config, 'Order');
220
+ // Terminates (would hang/stack-overflow without the guard) and still
221
+ // produces a usable, finite include: `lineItems` folds in, but `order`
222
+ // beneath it stops at a flat fetch rather than looping back to `lineItems` again.
223
+ expect(result.include).toEqual({ lineItems: { include: { order: true } } });
224
+ });
225
+ });
226
+ //# sourceMappingURL=declared-dependencies.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"declared-dependencies.test.js","sourceRoot":"","sources":["../../src/access/declared-dependencies.test.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,13 @@
1
1
  /**
2
- * Maximum nesting depth of relation `include`s that the Access Filter
3
- * (`buildIncludeWithAccessControl`) will auto-scope on a read.
2
+ * Maximum nesting depth of relation `include`s the Access Filter
3
+ * (`buildAccessScopedInclude`) will scope on a read.
4
4
  *
5
- * Security implication: this is an access-control boundary, not just a cost
6
- * bound. Past this depth the engine cannot compute a row/field scope for a
7
- * relation, so a caller-supplied `include` naming a relation at or beyond it
8
- * must be treated as a denial (see `AccessScopeDepthExceededError`) rather
9
- * than passed through unscoped — see ADR-0022 and issue #830.
5
+ * Since ADR-0026 made the read pipeline caller-directed, this is a COST
6
+ * limit, not an access-control boundary: nothing walks the relationship
7
+ * graph unprompted anymore, so there is no unscoped tree to fail open on. A
8
+ * request naming a relation at or beyond this depth still throws
9
+ * `AccessScopeDepthExceededError` (ADR-0022, issue #830) — the engine
10
+ * declines to serve a tree this expensive, not because it cannot scope one.
10
11
  */
11
12
  export declare const READ_INCLUDE_MAX_DEPTH = 5;
12
13
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"depth-limits.d.ts","sourceRoot":"","sources":["../../src/access/depth-limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAA;AAEvC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAA"}
1
+ {"version":3,"file":"depth-limits.d.ts","sourceRoot":"","sources":["../../src/access/depth-limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAA;AAEvC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAA"}
@@ -1,12 +1,13 @@
1
1
  /**
2
- * Maximum nesting depth of relation `include`s that the Access Filter
3
- * (`buildIncludeWithAccessControl`) will auto-scope on a read.
2
+ * Maximum nesting depth of relation `include`s the Access Filter
3
+ * (`buildAccessScopedInclude`) will scope on a read.
4
4
  *
5
- * Security implication: this is an access-control boundary, not just a cost
6
- * bound. Past this depth the engine cannot compute a row/field scope for a
7
- * relation, so a caller-supplied `include` naming a relation at or beyond it
8
- * must be treated as a denial (see `AccessScopeDepthExceededError`) rather
9
- * than passed through unscoped — see ADR-0022 and issue #830.
5
+ * Since ADR-0026 made the read pipeline caller-directed, this is a COST
6
+ * limit, not an access-control boundary: nothing walks the relationship
7
+ * graph unprompted anymore, so there is no unscoped tree to fail open on. A
8
+ * request naming a relation at or beyond this depth still throws
9
+ * `AccessScopeDepthExceededError` (ADR-0022, issue #830) — the engine
10
+ * declines to serve a tree this expensive, not because it cannot scope one.
10
11
  */
11
12
  export const READ_INCLUDE_MAX_DEPTH = 5;
12
13
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"depth-limits.js","sourceRoot":"","sources":["../../src/access/depth-limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAA;AAEvC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAA"}
1
+ {"version":3,"file":"depth-limits.js","sourceRoot":"","sources":["../../src/access/depth-limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAA;AAEvC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAA"}
@@ -14,36 +14,16 @@ import type { OpenSaasConfig, ListConfig } from '../config/types.js';
14
14
  * (`checkFieldAccess`). See `docs/adr/0001-access-control-is-a-two-phase-read.md`
15
15
  * and the access-control glossary in `CONTEXT.md`.
16
16
  */
17
- /**
18
- * Check if access control result is a boolean
19
- */
20
17
  export declare function isBoolean(value: unknown): value is boolean;
21
- /**
22
- * Check if access control result is a Prisma filter
23
- */
24
18
  export declare function isPrismaFilter(value: unknown): value is PrismaFilter;
25
- /**
26
- * Parse a relationship ref and get the related list configuration
27
- * Relationship refs are in the format "ListName.fieldName"
28
- *
29
- * @param relationshipRef - The ref string (e.g., "Post.author")
30
- * @param config - The OpenSaas configuration
31
- * @returns The related list name and config, or null if not found
32
- */
33
19
  export declare function getRelatedListConfig(relationshipRef: string, config: OpenSaasConfig): {
34
20
  listName: string;
35
21
  listConfig: ListConfig<any>;
36
22
  } | null;
37
- /**
38
- * Execute an access control function
39
- */
40
23
  export declare function checkAccess<T = Record<string, unknown>>(accessControl: AccessControl<T> | undefined, args: {
41
24
  session: Session | null;
42
25
  item?: T;
43
26
  context: AccessContext;
44
27
  }): Promise<boolean | PrismaFilter<T>>;
45
- /**
46
- * Merge user filter with access control filter
47
- */
48
28
  export declare function mergeFilters(userFilter: PrismaFilter | undefined, accessFilter: boolean | PrismaFilter): PrismaFilter | null;
49
29
  //# sourceMappingURL=engine.d.ts.map