Skip to content

feat(core): a computed field runs only when it is going to be returned (ADR-0027) - #890

Merged
borisno2 merged 1 commit into
mainfrom
claude/funny-sagan-4ipkyo
Aug 4, 2026
Merged

feat(core): a computed field runs only when it is going to be returned (ADR-0027)#890
borisno2 merged 1 commit into
mainfrom
claude/funny-sagan-4ipkyo

Conversation

@borisno2

@borisno2 borisno2 commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

Implements ADR-0027, the follow-up scoped by #850/#852: a computed field (any field carrying a resolveOutput hook, virtual or not) is now computed — and its declared relations (needs, ADR-0025) fetched — if and only if the read is actually going to return it, rather than unconditionally on every read.

  • Projection-aware, not access-aware. Only a fragment query's own field selection restricts a level this way, applied recursively at every nesting level (a nested fragment selecting a subset computes only that subset; a nested include still computes every computed field there). A bare read or an include-based read is completely unaffected — every computed field on the list still computes, exactly as before.
  • A field the read won't return does no work at all — neither its field-level read access check nor its resolveOutput hook runs, and its declared relation is never folded into the include.
  • A computed field's hook never sees another computed field's resolved output, on any read path — only the row's stored columns and its own declared dependencies. This closes a real bug: previously a virtual field received the already-assembled, already-resolved object, so a virtual field could accidentally read an earlier-declared virtual's resolved value purely by declaration order (reordering two fields silently changed the result). A skipped or access-denied sibling's key is now absent from what a hook sees, never present holding its raw pre-hook value (load-bearing for e.g. a password() wrapper).
  • A hookless virtual field does no work at all, on any read — it can never produce output, so there's nothing to preserve read-access side effects for (reconciling a small inconsistency ADR-0027 called out).

Key files

  • packages/core/src/query/index.ts — new FieldSelectionScope type + buildFieldSelectionScope(), derived from a fragment's field selection.
  • packages/core/src/access/declared-dependencies.tsgetDeclaredRelationNames/foldDeclaredDependencies take an optional selection scope, folding needs only for fields the read is going to return.
  • packages/core/src/access/field-visibility.tsfilterReadableFields skips a field's access check + hook entirely when a selection scope excludes it; a virtual field's hook now reads from a sanitized computedFieldItem (stored columns, minus access-denied/selection-skipped keys, minus nothing declared-only) instead of the previously-resolved filtered object.
  • packages/core/src/context/index.tsresolveReadInclude builds and threads the selection scope through the fragment read path only; every other path (caller include, sudo, bare) passes no selection, unchanged.

This is a silent break — see the changeset

Two behaviors changed with no thrown error, documented in .changeset/silent-hooks-compute-once.md with detection guidance and a before/after example:

  1. A hook reading a sibling field that is itself computed (virtual-reads-virtual, or a stored field with its own resolveOutput) no longer sees that field's resolved value.
  2. A resolveOutput hook relied on for a side effect no longer runs on a fragment read that doesn't select that field.

Test plan

  • New test file packages/core/tests/computed-field-selective-evaluation.test.ts (10 tests) covering: unselected field runs neither access nor hook and folds no needs; selected field computes + fetches correctly; two fields sharing a declared relation dedup correctly when only one is selected; nested fragment vs. nested include selectivity; hook item never sees another computed field's resolved output (order-independence + explicit leak check); field-level access still gates a selected field; hookless virtual field never has its access evaluated; bare/include reads unaffected.
  • Full existing suite passes unmodified: pnpm test in packages/core — 49 files / 958 tests passing (958 = 948 pre-existing + 10 new).
  • pnpm build (root, all 11 packages) and pnpm build (core) both succeed.
  • pnpm lint — no new errors (3 pre-existing unrelated warnings).
  • pnpm manypkg fix / pnpm format run, no diffs beyond formatting the changeset itself.
  • Docs: added an "A Computed Field Runs Only When It Is Going To Be Returned (ADR-0027)" section to packages/core/CLAUDE.md and corrected the now-stale "computed on every read" line in the Virtual Fields section. CONTEXT.md/docs/adr/0027-*.md already existed from the prior triage doc PR and needed no changes.

Closes #855


Generated by Claude Code

#855, ADR-0027)

A computed field (any field with a resolveOutput hook, virtual or not) is now
computed, and its declared relations (needs) fetched, only when a fragment
`query` read is actually going to return it — projection-aware, applied
recursively at every nesting level. Bare and include-based reads are
unaffected. A computed field's hook also no longer sees another computed
field's resolved output as part of its item, on any read path, closing the
declaration-order dependency bug ADR-0025 left open.

Closes #855

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wgSw8qrPmMBy8d97LuGoh
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-docs Ready Ready Preview Aug 4, 2026 11:58am

@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 084ce6e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@opensaas/stack-core Minor
@opensaas/stack-auth Minor
@opensaas/stack-cli Minor
@opensaas/stack-rag Minor
@opensaas/stack-storage Minor
@opensaas/stack-tiptap Minor
@opensaas/stack-ui Minor
@opensaas/stack-storage-s3 Minor
@opensaas/stack-storage-vercel Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@borisno2
borisno2 enabled auto-merge (squash) August 4, 2026 12:04
@borisno2
borisno2 merged commit 17eb72f into main Aug 4, 2026
6 checks passed
@borisno2
borisno2 deleted the claude/funny-sagan-4ipkyo branch August 4, 2026 12:04
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Core Package Coverage (./packages/core)

Status Category Percentage Covered / Total
🟢 Lines 93.56% (🎯 65%) 1251 / 1337
🟢 Statements 92.03% (🎯 65%) 1352 / 1469
🟢 Functions 98.12% (🎯 62%) 209 / 213
🟢 Branches 83.74% (🎯 50%) 917 / 1095
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/core/src/access/declared-dependencies.ts 95.65% 96.15% 100% 100% 171, 181
packages/core/src/access/field-visibility.ts 98.92% 95.71% 100% 98.83% 345
Generated in workflow #1619 for commit 084ce6e by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for UI Package Coverage (./packages/ui)

Status Category Percentage Covered / Total
🔵 Lines 76.72% 244 / 318
🔵 Statements 76.29% 251 / 329
🔵 Functions 69.15% 74 / 107
🔵 Branches 64.25% 160 / 249
File CoverageNo changed files found.
Generated in workflow #1619 for commit 084ce6e by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for CLI Package Coverage (./packages/cli)

Status Category Percentage Covered / Total
🔵 Lines 79.16% 1539 / 1944
🔵 Statements 78.86% 1601 / 2030
🔵 Functions 85.94% 214 / 249
🔵 Branches 67.84% 690 / 1017
File CoverageNo changed files found.
Generated in workflow #1619 for commit 084ce6e by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Auth Package Coverage (./packages/auth)

Status Category Percentage Covered / Total
🔵 Lines 98.33% 118 / 120
🔵 Statements 98.37% 121 / 123
🔵 Functions 100% 38 / 38
🔵 Branches 94.44% 85 / 90
File CoverageNo changed files found.
Generated in workflow #1619 for commit 084ce6e by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Package Coverage (./packages/storage)

Status Category Percentage Covered / Total
🔵 Lines 78.57% 220 / 280
🔵 Statements 80.06% 245 / 306
🔵 Functions 86.07% 68 / 79
🔵 Branches 75.88% 214 / 282
File CoverageNo changed files found.
Generated in workflow #1619 for commit 084ce6e by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for RAG Package Coverage (./packages/rag)

Status Category Percentage Covered / Total
🔵 Lines 47.97% 355 / 740
🔵 Statements 48.14% 377 / 783
🔵 Functions 54.26% 70 / 129
🔵 Branches 42.55% 180 / 423
File CoverageNo changed files found.
Generated in workflow #1619 for commit 084ce6e by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)

Status Category Percentage Covered / Total
🔵 Lines 100% 40 / 40
🔵 Statements 100% 40 / 40
🔵 Functions 100% 9 / 9
🔵 Branches 100% 19 / 19
File CoverageNo changed files found.
Generated in workflow #1619 for commit 084ce6e by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)

Status Category Percentage Covered / Total
🔵 Lines 100% 68 / 68
🔵 Statements 100% 71 / 71
🔵 Functions 100% 15 / 15
🔵 Branches 97.87% 46 / 47
File CoverageNo changed files found.
Generated in workflow #1619 for commit 084ce6e by the Vitest Coverage Report Action

@github-actions github-actions Bot mentioned this pull request Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A computed field should run only when it is going to be returned

2 participants