@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,25 +1,29 @@
1
1
  import { READ_INCLUDE_MAX_DEPTH } from './depth-limits.js';
2
2
  /**
3
3
  * Thrown when a caller-supplied `include` names a relation nested deeper than
4
- * the Access Filter can scope (see `READ_INCLUDE_MAX_DEPTH`). Deliberately
5
- * distinct from `ValidationError`: this is not bad user input, it is the
6
- * engine refusing to return data it cannot prove is row/field scoped. Code
7
- * that catches `ValidationError` to report form errors must not silently
8
- * swallow this.
4
+ * `READ_INCLUDE_MAX_DEPTH`. Deliberately distinct from `ValidationError`: this
5
+ * is not bad user input, it is the engine declining to serve a tree this
6
+ * expensive. Code that catches `ValidationError` to report form errors must
7
+ * not silently swallow this.
9
8
  *
10
- * Only an explicit caller selection past the depth cap triggers this — the
11
- * auto-include silently stopping at the cap (no caller `include` involved)
12
- * never throws. See ADR-0022 and issue #830.
9
+ * Only an explicit request naming something at or past the cap triggers this
10
+ * a read that simply doesn't reach this deep never throws. Before
11
+ * ADR-0026 made the read pipeline caller-directed, this cap was the engine's
12
+ * last line of defense against returning a relation it could not prove was
13
+ * row/field scoped (ADR-0022, issue #830); a request naming anything at this
14
+ * depth is now scoped exactly like every other named relation; the cap
15
+ * exists solely to bound how deep a request may cost the engine to serve. See
16
+ * ADR-0026 and `docs/adr/0022-access-control-fails-closed-when-it-cannot-scope.md`.
13
17
  */
14
18
  export class AccessScopeDepthExceededError extends Error {
15
19
  listKey;
16
20
  fieldKey;
17
21
  depth;
18
22
  constructor(listKey, fieldKey, depth) {
19
- super(`Cannot compute an access scope for "${listKey}.${fieldKey}" at include depth ${depth}: ` +
20
- `this exceeds the Access Filter's maximum read-include depth (${READ_INCLUDE_MAX_DEPTH}). ` +
21
- `A caller-supplied include this deep cannot be row- and field-scoped, so the read is denied ` +
22
- `rather than returned unscoped. Restructure the query to fetch this relation separately.`);
23
+ super(`Cannot include "${listKey}.${fieldKey}" at include depth ${depth}: this exceeds the read ` +
24
+ `pipeline's maximum include depth (${READ_INCLUDE_MAX_DEPTH}). This is a cost limit, not an ` +
25
+ `inability to scope the engine declines to serve a tree this deep rather than returning ` +
26
+ `it. Restructure the query to fetch this relation separately.`);
23
27
  this.name = 'AccessScopeDepthExceededError';
24
28
  this.listKey = listKey;
25
29
  this.fieldKey = fieldKey;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/access/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAE1D;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,6BAA8B,SAAQ,KAAK;IAC/C,OAAO,CAAQ;IACf,QAAQ,CAAQ;IAChB,KAAK,CAAQ;IAEpB,YAAY,OAAe,EAAE,QAAgB,EAAE,KAAa;QAC1D,KAAK,CACH,uCAAuC,OAAO,IAAI,QAAQ,sBAAsB,KAAK,IAAI;YACvF,gEAAgE,sBAAsB,KAAK;YAC3F,6FAA6F;YAC7F,yFAAyF,CAC5F,CAAA;QACD,IAAI,CAAC,IAAI,GAAG,+BAA+B,CAAA;QAC3C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACxB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACzC,KAAK,CAAkD;IAE9D,YAAY,KAAuD;QACjE,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAChF,KAAK,CACH,iCAAiC,IAAI,+CAA+C;YAClF,wFAAwF;YACxF,0FAA0F;YAC1F,iCAAiC,CACpC,CAAA;QACD,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAA;QACrC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;CACF"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/access/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAE1D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,6BAA8B,SAAQ,KAAK;IAC/C,OAAO,CAAQ;IACf,QAAQ,CAAQ;IAChB,KAAK,CAAQ;IAEpB,YAAY,OAAe,EAAE,QAAgB,EAAE,KAAa;QAC1D,KAAK,CACH,mBAAmB,OAAO,IAAI,QAAQ,sBAAsB,KAAK,0BAA0B;YACzF,qCAAqC,sBAAsB,kCAAkC;YAC7F,2FAA2F;YAC3F,8DAA8D,CACjE,CAAA;QACD,IAAI,CAAC,IAAI,GAAG,+BAA+B,CAAA;QAC3C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACxB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACzC,KAAK,CAAkD;IAE9D,YAAY,KAAuD;QACjE,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAChF,KAAK,CACH,iCAAiC,IAAI,+CAA+C;YAClF,wFAAwF;YACxF,0FAA0F;YAC1F,iCAAiC,CACpC,CAAA;QACD,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAA;QACrC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;CACF"}
@@ -1,6 +1,7 @@
1
1
  import type { Session, AccessContext } from './types.js';
2
2
  import type { OpenSaasConfig, FieldConfig } from '../config/types.js';
3
3
  import type { DeclaredOnlyTree } from './declared-dependencies.js';
4
+ import type { FieldSelectionScope } from '../query/index.js';
4
5
  /**
5
6
  * Filter fields from an object based on read access
6
7
  * Recursively applies access control to nested relationships
@@ -10,5 +11,5 @@ export declare function filterReadableFields<T extends Record<string, unknown>>(
10
11
  context: AccessContext & {
11
12
  _isSudo?: boolean;
12
13
  };
13
- }, config?: OpenSaasConfig, depth?: number, listKey?: string, declaredOnly?: DeclaredOnlyTree): Promise<Partial<T>>;
14
+ }, config?: OpenSaasConfig, depth?: number, listKey?: string, declaredOnly?: DeclaredOnlyTree, selection?: FieldSelectionScope): Promise<Partial<T>>;
14
15
  //# sourceMappingURL=field-visibility.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"field-visibility.d.ts","sourceRoot":"","sources":["../../src/access/field-visibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAKrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAqKlE;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,IAAI,EAAE,CAAC,EACP,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,IAAI,EAAE;IACJ,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,aAAa,GAAG;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAC/C,EACD,MAAM,CAAC,EAAE,cAAc,EACvB,KAAK,GAAE,MAAU,EACjB,OAAO,CAAC,EAAE,MAAM,EAMhB,YAAY,GAAE,gBAA0C,GACvD,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAiLrB"}
1
+ {"version":3,"file":"field-visibility.d.ts","sourceRoot":"","sources":["../../src/access/field-visibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAKrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAElE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AA+K5D;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,IAAI,EAAE,CAAC,EACP,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,IAAI,EAAE;IACJ,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,aAAa,GAAG;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAC/C,EACD,MAAM,CAAC,EAAE,cAAc,EACvB,KAAK,GAAE,MAAU,EACjB,OAAO,CAAC,EAAE,MAAM,EAMhB,YAAY,GAAE,gBAA0C,EAOxD,SAAS,CAAC,EAAE,mBAAmB,GAC9B,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAmPrB"}
@@ -54,8 +54,11 @@ function deriveResolveOutputContext(context, link, config) {
54
54
  * from the result.
55
55
  *
56
56
  * `accessItem` is the row used to evaluate field access; `hookItem` is the
57
- * object passed to the hook as `item` (these differ for virtual fields, which
58
- * see the already-filtered output so they can read sibling fields).
57
+ * object passed to the hook as `item`. For a stored field, both are
58
+ * `workingItem` (the row's own stored/fetched columns). For a virtual field,
59
+ * `hookItem` is `computedFieldItem` instead — the same stored columns with
60
+ * every skipped-or-denied key removed — so it never sees another computed
61
+ * field's resolved value (ADR-0027).
59
62
  */
60
63
  async function resolveReadableFieldValue(params) {
61
64
  const { fieldConfig, fieldName, value, accessItem, hookItem, listKey, args, config } = params;
@@ -114,7 +117,14 @@ export async function filterReadableFields(item, fieldConfigs, args, config, dep
114
117
  // caller-named branch. Stripped from `filtered` right before it is
115
118
  // returned — after resolveOutput has had a chance to read them — so a
116
119
  // declared dependency never widens what the caller receives.
117
- declaredOnly = emptyDeclaredOnlyTree()) {
120
+ declaredOnly = emptyDeclaredOnlyTree(),
121
+ // The fragment scope this level was reached under (ADR-0027), and the same
122
+ // tree one level down for each nested relation. `undefined` — the default,
123
+ // and what a bare/`include`-based read passes at every level — means
124
+ // unrestricted: every field on the list is computed, unchanged from
125
+ // before ADR-0027. Only a `query` fragment's own field selection ever
126
+ // restricts a level.
127
+ selection) {
118
128
  const filtered = {};
119
129
  // Multi-column fields (e.g. storage image()/file() in Keystone-parity mode)
120
130
  // back several physical columns rather than one. Before the per-field pass,
@@ -139,6 +149,14 @@ declaredOnly = emptyDeclaredOnlyTree()) {
139
149
  }
140
150
  workingItem[fieldName] = assembled;
141
151
  }
152
+ // Keys denied by field-level read access during the pass below — as opposed
153
+ // to a key merely skipped by `selection` or held back only for
154
+ // `declaredOnly` stripping. Tracked separately because a denied key must
155
+ // stay invisible to a computed field's hook (below), while a declared-only
156
+ // key must stay VISIBLE to one — that is the entire point of declaring it
157
+ // (ADR-0025) — even though `selection` above skipped adding it to
158
+ // `filtered` because the caller's fragment never asked for it.
159
+ const accessDeniedKeys = new Set();
142
160
  // Process existing fields from the database result
143
161
  for (const [fieldName, value] of Object.entries(workingItem)) {
144
162
  const fieldConfig = fieldConfigs[fieldName];
@@ -147,8 +165,18 @@ declaredOnly = emptyDeclaredOnlyTree()) {
147
165
  filtered[fieldName] = value;
148
166
  continue;
149
167
  }
168
+ // Projection-aware skip (ADR-0027): a fragment read that does not select
169
+ // this field does no work for it at all — no field-level read-access
170
+ // check, no resolveOutput, no recursion into a relation — because the
171
+ // read is never going to return it. `selection` is only ever restricted
172
+ // by a fragment's own field selection; a bare/`include`-based read, and a
173
+ // relation reached only to satisfy another field's `needs`, pass no
174
+ // selection at all and compute every field here, unchanged.
175
+ if (selection?.fields && !selection.fields.has(fieldName)) {
176
+ continue;
177
+ }
150
178
  // Handle relationship fields - recursively filter fields within related items
151
- // Note: Access control filtering is now done at database level via buildIncludeWithAccessControl
179
+ // Note: Access control filtering is now done at database level via buildAccessScopedInclude
152
180
  // This only handles field-level access (hiding sensitive fields)
153
181
  //
154
182
  // Deliberately uncapped: the row/relation scoping in access-filter.ts bounds
@@ -171,6 +199,7 @@ declaredOnly = emptyDeclaredOnlyTree()) {
171
199
  item: workingItem,
172
200
  });
173
201
  if (!canRead) {
202
+ accessDeniedKeys.add(fieldName);
174
203
  continue;
175
204
  }
176
205
  const relatedConfig = getRelatedListConfig(fieldConfig.ref, config);
@@ -179,16 +208,22 @@ declaredOnly = emptyDeclaredOnlyTree()) {
179
208
  // back to an empty tree when this relation isn't declaration-related at
180
209
  // all — the common case.
181
210
  const nestedDeclaredOnly = declaredOnly.nested[fieldName] ?? emptyDeclaredOnlyTree();
211
+ // This relation's own fragment scope, if the caller's fragment named it
212
+ // with a nested Fragment/RelationSelector. `undefined` (a bare `true`
213
+ // selector, or no `selection` at all) means the nested list computes
214
+ // unrestricted — matching what naming a relation without narrowing it
215
+ // further has always meant.
216
+ const nestedSelection = selection?.nested[fieldName];
182
217
  if (relatedConfig) {
183
218
  // For many relationships (arrays) - recursively filter fields in each item
184
219
  // The recursive call already handles applying resolveOutput hooks
185
220
  if (Array.isArray(value)) {
186
- filtered[fieldName] = await Promise.all(value.map((relatedItem) => filterReadableFields(relatedItem, relatedConfig.listConfig.fields, args, config, depth + 1, relatedConfig.listName, nestedDeclaredOnly)));
221
+ filtered[fieldName] = await Promise.all(value.map((relatedItem) => filterReadableFields(relatedItem, relatedConfig.listConfig.fields, args, config, depth + 1, relatedConfig.listName, nestedDeclaredOnly, nestedSelection)));
187
222
  }
188
223
  // For single relationships (objects) - recursively filter fields
189
224
  // The recursive call already handles applying resolveOutput hooks
190
225
  else if (typeof value === 'object') {
191
- filtered[fieldName] = await filterReadableFields(value, relatedConfig.listConfig.fields, args, config, depth + 1, relatedConfig.listName, nestedDeclaredOnly);
226
+ filtered[fieldName] = await filterReadableFields(value, relatedConfig.listConfig.fields, args, config, depth + 1, relatedConfig.listName, nestedDeclaredOnly, nestedSelection);
192
227
  }
193
228
  }
194
229
  else {
@@ -212,6 +247,38 @@ declaredOnly = emptyDeclaredOnlyTree()) {
212
247
  if (result.readable) {
213
248
  filtered[fieldName] = result.value;
214
249
  }
250
+ else {
251
+ accessDeniedKeys.add(fieldName);
252
+ }
253
+ }
254
+ // The item a virtual field's hook sees: stored columns and fetched
255
+ // relations (from `workingItem`, never a resolved value — no hook's output
256
+ // is ever written back into `workingItem`). A key is visible here if it
257
+ // either survived into `filtered` (selected and allowed) OR exists only to
258
+ // satisfy a `needs` declaration (`declaredOnly` — that IS the point of
259
+ // declaring it: fetched for a hook, never for the caller, ADR-0025).
260
+ // Everything else — field-level denied, or skipped by `selection` and
261
+ // declared by no one — is deleted. A computed field reaches for exactly its
262
+ // own declared dependencies and nothing another field's hook produced
263
+ // (ADR-0027): reaching for a sibling that was denied or skipped-and-
264
+ // undeclared finds nothing there, the same as reaching for one never
265
+ // declared at all, and reaching for a sibling that DID survive finds its
266
+ // raw stored form, never another hook's resolved value — a virtual field
267
+ // computed earlier in declaration order is exactly as invisible as one
268
+ // computed later.
269
+ const computedFieldItem = { ...workingItem };
270
+ for (const key of Object.keys(workingItem)) {
271
+ if (['id', 'createdAt', 'updatedAt'].includes(key))
272
+ continue;
273
+ if (accessDeniedKeys.has(key)) {
274
+ delete computedFieldItem[key];
275
+ continue;
276
+ }
277
+ if (key in filtered)
278
+ continue;
279
+ if (declaredOnly.keys.has(key))
280
+ continue;
281
+ delete computedFieldItem[key];
215
282
  }
216
283
  // Process virtual fields - compute values from other fields
217
284
  // Virtual fields don't exist in the database result, so we need to compute them separately
@@ -224,21 +291,27 @@ declaredOnly = emptyDeclaredOnlyTree()) {
224
291
  if (!fieldConfig.virtual) {
225
292
  continue;
226
293
  }
227
- // Virtual fields must have a resolveOutput hook to compute their value;
228
- // without one there is nothing to add to the result.
294
+ // Projection-aware skip (ADR-0027): same rule as the stored-field pass
295
+ // above a fragment that does not select this virtual field does no
296
+ // work for it at all.
297
+ if (selection?.fields && !selection.fields.has(fieldName)) {
298
+ continue;
299
+ }
300
+ // A virtual field with no resolveOutput hook can never produce a value
301
+ // on ANY read — there is nothing to compute, so there is nothing to do,
302
+ // including evaluating its read access (ADR-0027 reconciles the
303
+ // access-only evaluation this branch used to preserve: a field that
304
+ // never has output has no side effect worth preserving access for).
229
305
  if (!(fieldConfig.hooks?.resolveOutput && listKey)) {
230
- // Still evaluate read access to preserve any access-fn side effects.
231
- await checkFieldAccess(fieldConfig.access, 'read', { ...args, item: workingItem });
232
306
  continue;
233
307
  }
234
- // Check read access and compute the value via the shared helper. Virtual
235
- // fields see the already-filtered item so they can read sibling fields.
308
+ // Check read access and compute the value via the shared helper.
236
309
  const result = await resolveReadableFieldValue({
237
310
  fieldConfig,
238
311
  fieldName,
239
312
  value: undefined, // Virtual fields don't have a database value
240
313
  accessItem: workingItem,
241
- hookItem: filtered,
314
+ hookItem: computedFieldItem,
242
315
  listKey,
243
316
  args,
244
317
  config,
@@ -248,10 +321,11 @@ declaredOnly = emptyDeclaredOnlyTree()) {
248
321
  }
249
322
  }
250
323
  // Strip relations that were fetched ONLY to satisfy a `needs` declaration
251
- // (ADR-0025), now that every resolveOutput hook at this level including
252
- // virtual fields, which read the assembled `filtered` object above has
253
- // had the chance to see them. A declared dependency is private plumbing,
254
- // not an implicit `include`: it never widens what the caller receives.
324
+ // (ADR-0025), now that every resolveOutput hook at this level has had the
325
+ // chance to see them (via `computedFieldItem`, never `filtered` itselfa
326
+ // declared dependency is read from stored columns, not from another
327
+ // field's resolved output). A declared dependency is private plumbing, not
328
+ // an implicit `include`: it never widens what the caller receives.
255
329
  for (const key of declaredOnly.keys) {
256
330
  delete filtered[key];
257
331
  }
@@ -1 +1 @@
1
- {"version":3,"file":"field-visibility.js","sourceRoot":"","sources":["../../src/access/field-visibility.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"field-visibility.js","sourceRoot":"","sources":["../../src/access/field-visibility.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,7 @@
1
1
  export type { AccessControl, FieldAccess, Session, AccessContext, PrismaFilter, AccessControlledDB, PrismaClientLike, StorageUtils, AugmentedFindMany, AugmentedFindUnique, FindManyQueryArgs, } from './types.js';
2
2
  export { checkAccess, mergeFilters, isBoolean, isPrismaFilter, getRelatedListConfig, } from './engine.js';
3
3
  export { checkFieldAccess, filterWritableFields } from './field-access.js';
4
- export { buildIncludeWithAccessControl, mergeIncludeWithAccessControl, stripVirtualFieldsFromInclude, toPrismaInclude, } from './access-filter.js';
5
- export type { AccessIncludeResult } from './access-filter.js';
4
+ export { buildAccessScopedInclude, stripVirtualFieldsFromInclude } from './access-filter.js';
6
5
  export { filterReadableFields } from './field-visibility.js';
7
6
  export { foldDeclaredDependencies, getDeclaredRelationNames, emptyDeclaredOnlyTree, } from './declared-dependencies.js';
8
7
  export type { DeclaredOnlyTree } from './declared-dependencies.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/access/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,aAAa,EACb,WAAW,EACX,OAAO,EACP,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,WAAW,EACX,YAAY,EACZ,SAAS,EACT,cAAc,EACd,oBAAoB,GACrB,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE1E,OAAO,EACL,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,eAAe,GAChB,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAE7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAG5D,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,4BAA4B,CAAA;AACnC,YAAY,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAElE,OAAO,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAA;AAE3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/access/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,aAAa,EACb,WAAW,EACX,OAAO,EACP,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,WAAW,EACX,YAAY,EACZ,SAAS,EACT,cAAc,EACd,oBAAoB,GACrB,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE1E,OAAO,EAAE,wBAAwB,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAA;AAE5F,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAG5D,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,4BAA4B,CAAA;AACnC,YAAY,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAElE,OAAO,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAA;AAE3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA"}
@@ -3,7 +3,7 @@ export { checkAccess, mergeFilters, isBoolean, isPrismaFilter, getRelatedListCon
3
3
  // Canonical field-level access evaluation (shared by read and write paths).
4
4
  export { checkFieldAccess, filterWritableFields } from './field-access.js';
5
5
  // Phase 1 — Access Filter (pre-query row/relation scoping).
6
- export { buildIncludeWithAccessControl, mergeIncludeWithAccessControl, stripVirtualFieldsFromInclude, toPrismaInclude, } from './access-filter.js';
6
+ export { buildAccessScopedInclude, stripVirtualFieldsFromInclude } from './access-filter.js';
7
7
  // Phase 2 — Field Visibility (post-query field stripping + resolveOutput).
8
8
  export { filterReadableFields } from './field-visibility.js';
9
9
  // Declared Dependencies — folding `needs` into an include without widening
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/access/index.ts"],"names":[],"mappings":"AAaA,mEAAmE;AACnE,OAAO,EACL,WAAW,EACX,YAAY,EACZ,SAAS,EACT,cAAc,EACd,oBAAoB,GACrB,MAAM,aAAa,CAAA;AACpB,4EAA4E;AAC5E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC1E,4DAA4D;AAC5D,OAAO,EACL,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,eAAe,GAChB,MAAM,oBAAoB,CAAA;AAE3B,2EAA2E;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAC5D,2EAA2E;AAC3E,yBAAyB;AACzB,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,4BAA4B,CAAA;AAEnC,mFAAmF;AACnF,OAAO,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAA;AAC3D,gFAAgF;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/access/index.ts"],"names":[],"mappings":"AAaA,mEAAmE;AACnE,OAAO,EACL,WAAW,EACX,YAAY,EACZ,SAAS,EACT,cAAc,EACd,oBAAoB,GACrB,MAAM,aAAa,CAAA;AACpB,4EAA4E;AAC5E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC1E,4DAA4D;AAC5D,OAAO,EAAE,wBAAwB,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAA;AAC5F,2EAA2E;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAC5D,2EAA2E;AAC3E,yBAAyB;AACzB,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,4BAA4B,CAAA;AAEnC,mFAAmF;AACnF,OAAO,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAA;AAC3D,gFAAgF;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA"}
@@ -10,7 +10,7 @@ import type { OpenSaasConfig, ListConfig, FieldConfig } from '../config/types.js
10
10
  * per-row query is issued and the count can never include rows the session
11
11
  * cannot read. This module is the single place the related list's
12
12
  * operation-level `query` access is folded into that `_count`, mirroring how
13
- * `buildIncludeWithAccessControl` folds it into relation includes.
13
+ * `buildAccessScopedInclude` folds it into relation includes.
14
14
  *
15
15
  * It also resolves the count Filter spec's markers: Prisma cannot compare a
16
16
  * relation count in a `where`, so a to-many relationship's Filter spec emits a
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { checkAccess, mergeFilters, filterReadableFields, buildIncludeWithAccessControl, mergeIncludeWithAccessControl, stripVirtualFieldsFromInclude, foldDeclaredDependencies, } from '../access/index.js';
1
+ import { checkAccess, mergeFilters, filterReadableFields, buildAccessScopedInclude, stripVirtualFieldsFromInclude, foldDeclaredDependencies, } from '../access/index.js';
2
2
  import { ValidationError, DatabaseError } from '../hooks/index.js';
3
3
  import { getDbKey } from '../lib/case-utils.js';
4
- import { buildInclude, pickFields, isFragment } from '../query/index.js';
4
+ import { buildInclude, pickFields, isFragment, buildFieldSelectionScope } from '../query/index.js';
5
5
  import { getRelationshipOptions } from '../query/relationship-options.js';
6
6
  import { runWritePipeline, createWriteStrategy, updateWriteStrategy, deleteWriteStrategy, } from './write-pipeline.js';
7
7
  /**
@@ -619,32 +619,39 @@ export function buildDbDelegate(config, prisma, context) {
619
619
  *
620
620
  * A fragment's own `include` and a sudo caller's `include` are folded and
621
621
  * used as-is, matching their existing (unmerged) treatment. A non-sudo
622
- * caller include is folded and then merged through the same
623
- * access-scoping pipeline as before. A bare read stays on the exact
624
- * ADR-0024 path `include: undefined`, no related `query` access
625
- * evaluated unless folding actually added something, which only happens
626
- * when a field on this list declares `needs`.
622
+ * caller include is folded and then scoped by `buildAccessScopedInclude`
623
+ * (ADR-0026) caller-directed, so a relation named nowhere in the folded
624
+ * tree never has its list's `query` access evaluated at all. A bare read
625
+ * stays on the exact ADR-0024 path `include: undefined`, no related
626
+ * `query` access evaluated unless folding actually added something, which
627
+ * only happens when a field on this list declares `needs`.
628
+ *
629
+ * Also returns the `FieldSelectionScope` a fragment's own field selection
630
+ * produces (ADR-0027), so the caller can pass it to `filterReadableFields`
631
+ * and make computation itself projection-aware, not only the fold above.
632
+ * `undefined` for every non-fragment path: a caller `include` (sudo or not)
633
+ * and a bare read both mean "compute every field," matching what they
634
+ * already fetch.
627
635
  */
628
636
  async function resolveReadInclude(callerInclude, fragmentFields, listName,
629
637
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
630
638
  listConfig, context, config) {
631
639
  if (fragmentFields !== undefined) {
632
640
  const fragmentInclude = buildInclude(fragmentFields) ?? undefined;
633
- return foldDeclaredDependencies(fragmentInclude, listConfig.fields, config);
641
+ const selection = buildFieldSelectionScope(fragmentFields);
642
+ const folded = foldDeclaredDependencies(fragmentInclude, listConfig.fields, config, listName, [listName], selection);
643
+ return { ...folded, selection };
634
644
  }
635
645
  if (context._isSudo) {
636
- return foldDeclaredDependencies(callerInclude, listConfig.fields, config);
646
+ const folded = foldDeclaredDependencies(callerInclude, listConfig.fields, config, listName);
647
+ return { ...folded, selection: undefined };
637
648
  }
638
- const folded = foldDeclaredDependencies(callerInclude, listConfig.fields, config);
649
+ const folded = foldDeclaredDependencies(callerInclude, listConfig.fields, config, listName);
639
650
  if (!folded.include) {
640
- return folded;
651
+ return { ...folded, selection: undefined };
641
652
  }
642
- const accessControlledInclude = await buildIncludeWithAccessControl(listConfig.fields, { session: context.session, context }, config, 0,
643
- // Seed the cycle guard with the root list so a relationship cycle back
644
- // to it (self-referential or longer) stops re-descending.
645
- [listName]);
646
- const include = mergeIncludeWithAccessControl(folded.include, accessControlledInclude, listConfig.fields, config, listName);
647
- return { include, declaredOnly: folded.declaredOnly };
653
+ const include = await buildAccessScopedInclude(folded.include, listConfig.fields, { session: context.session, context }, config, listName);
654
+ return { include, declaredOnly: folded.declaredOnly, selection: undefined };
648
655
  }
649
656
  /**
650
657
  * Create findUnique operation with access control
@@ -688,12 +695,14 @@ listConfig, prisma, context, config) {
688
695
  // Resolve `include`, folding any declared dependencies (`needs`,
689
696
  // ADR-0025) in alongside whatever the fragment/caller/sudo/bare path
690
697
  // already produces — see `resolveReadInclude`'s doc comment.
691
- let { include, declaredOnly } = await resolveReadInclude(args.include, fragment ? fragment._fields : undefined, listName, listConfig, context, config);
698
+ let { include, declaredOnly, selection } = await resolveReadInclude(args.include, fragment ? fragment._fields : undefined, listName, listConfig, context, config);
692
699
  // Virtual fields have no database column. Whichever path produced
693
700
  // `include` (fragment, access-controlled merge, or sudo passthrough), a
694
701
  // virtual key must never reach Prisma — it would throw "Unknown field"
695
- // (#628). The virtual value is still computed unconditionally below by
696
- // `filterReadableFields`, independent of what was requested here.
702
+ // (#628). Below, `filterReadableFields` computes a virtual field's value
703
+ // exactly when `selection` says the read is going to return it (ADR-0027)
704
+ // — every one of them for a bare/`include`-based read (`selection` is
705
+ // `undefined`), only the ones a fragment named otherwise.
697
706
  include = stripVirtualFieldsFromInclude(include, listConfig.fields, config);
698
707
  // Execute query with optimized includes
699
708
  // Access Prisma model dynamically - required because model names are generated at runtime
@@ -711,7 +720,7 @@ listConfig, prisma, context, config) {
711
720
  const filtered = await filterReadableFields(item, listConfig.fields, {
712
721
  session: context.session,
713
722
  context: { ...context, _isSudo: context._isSudo },
714
- }, config, 0, listName, declaredOnly);
723
+ }, config, 0, listName, declaredOnly, selection);
715
724
  // When a fragment is provided, pick only the requested fields from the result
716
725
  if (fragment) {
717
726
  return pickFields(filtered, fragment._fields);
@@ -755,12 +764,14 @@ listConfig, prisma, context, config) {
755
764
  // Resolve `include`, folding any declared dependencies (`needs`,
756
765
  // ADR-0025) in alongside whatever the fragment/caller/sudo/bare path
757
766
  // already produces — see `resolveReadInclude`'s doc comment.
758
- let { include, declaredOnly } = await resolveReadInclude(args?.include, fragment ? fragment._fields : undefined, listName, listConfig, context, config);
767
+ let { include, declaredOnly, selection } = await resolveReadInclude(args?.include, fragment ? fragment._fields : undefined, listName, listConfig, context, config);
759
768
  // Virtual fields have no database column. Whichever path produced
760
769
  // `include` (fragment, access-controlled merge, or sudo passthrough), a
761
770
  // virtual key must never reach Prisma — it would throw "Unknown field"
762
- // (#628). The virtual value is still computed unconditionally below by
763
- // `filterReadableFields`, independent of what was requested here.
771
+ // (#628). Below, `filterReadableFields` computes a virtual field's value
772
+ // exactly when `selection` says the read is going to return it (ADR-0027)
773
+ // — every one of them for a bare/`include`-based read (`selection` is
774
+ // `undefined`), only the ones a fragment named otherwise.
764
775
  include = stripVirtualFieldsFromInclude(include, listConfig.fields, config);
765
776
  // Execute query with optimized includes
766
777
  // Access Prisma model dynamically - required because model names are generated at runtime
@@ -778,7 +789,7 @@ listConfig, prisma, context, config) {
778
789
  const filtered = await Promise.all(items.map((item) => filterReadableFields(item, listConfig.fields, {
779
790
  session: context.session,
780
791
  context: { ...context, _isSudo: context._isSudo },
781
- }, config, 0, listName, declaredOnly)));
792
+ }, config, 0, listName, declaredOnly, selection)));
782
793
  // When a fragment is provided, pick only the requested fields from each result
783
794
  if (fragment) {
784
795
  return filtered.map((item) => pickFields(item, fragment._fields));
@@ -976,7 +987,7 @@ createFn) {
976
987
  // Resolve `include`, folding any declared dependencies (`needs`,
977
988
  // ADR-0025) in alongside whatever the fragment/caller/sudo/bare path
978
989
  // already produces — see `resolveReadInclude`'s doc comment.
979
- let { include, declaredOnly } = await resolveReadInclude(args?.include, fragment ? fragment._fields : undefined, listName, listConfig, context, config);
990
+ let { include, declaredOnly, selection } = await resolveReadInclude(args?.include, fragment ? fragment._fields : undefined, listName, listConfig, context, config);
980
991
  // Virtual fields have no database column and must never reach Prisma (#628).
981
992
  include = stripVirtualFieldsFromInclude(include, listConfig.fields, config);
982
993
  // Try to find the record
@@ -990,7 +1001,7 @@ createFn) {
990
1001
  const filtered = await filterReadableFields(item, listConfig.fields, {
991
1002
  session: context.session,
992
1003
  context: { ...context, _isSudo: context._isSudo },
993
- }, config, 0, listName, declaredOnly);
1004
+ }, config, 0, listName, declaredOnly, selection);
994
1005
  // When a fragment is provided, pick only the requested fields from the result
995
1006
  if (fragment) {
996
1007
  return pickFields(filtered, fragment._fields);