Skip to content

feat: provide the client in the computed field context - #2789

Open
evgenovalov wants to merge 2 commits into
zenstackhq:devfrom
evgenovalov:feat/computed-field-client-context
Open

feat: provide the client in the computed field context#2789
evgenovalov wants to merge 2 commits into
zenstackhq:devfrom
evgenovalov:feat/computed-field-client-context

Conversation

@evgenovalov

@evgenovalov evgenovalov commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Motivation

Computed field implementations receive (eb, { modelAlias }, args). There is no way to read per-client state — most notably the auth context set with $setAuth. A field like "is this row mine" currently needs a runtime plugin to inject the user id into the query. Custom function implementations already get the client through ZModelFunctionContext; this PR gives computed fields the same access.

Example

model Post {
    id Int @id @default(autoincrement())
    authorId Int
    isMine Boolean @computed
}
const db = new ZenStackClient(schema, {
    computedFields: {
        post: {
            isMine: (eb, { client }) => eb('authorId', '=', client?.$auth?.id ?? -1),
        },
    },
});

const userDb = db.$setAuth({ id: 1 });
await userDb.post.findMany({ where: { isMine: true } }); // posts authored by user 1

Changes

  • New ComputedFieldContext<Schema> type ({ modelAlias, client? }); ComputedFieldsOptions types the context parameter with it. Generated schemas are untouched, so existing implementations keep compiling.
  • BaseCrudDialect / getCrudDialect take an optional client, threaded from the CRUD operations, the query executor, and the name mapper. fieldRef passes it into the context.
  • client is optional in the context because a dialect can be constructed without one (e.g. ResultProcessor); every query issued through the client API has it.
  • Replaced the as unknown as ClientContract<SchemaDef> casts in client-impl.ts / zenstack-query-executor.ts with a single ClientImpl.$contract accessor — same object at runtime, one assertion at one documented boundary.
  • e2e test: the context client carries the auth set via $setAuth; the base client stays unaffected.

No breaking changes: the new parameters are optional and the context object only gains a property.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added client access to computed-field context, enabling use of authentication and other client state.
    • Added a typed $contract accessor for safer client interactions.
    • Improved filtering and querying with client-aware computed fields.
  • Bug Fixes

    • Preserved client immutability when changing authentication context.
  • Tests

    • Added coverage for authenticated and unauthenticated computed-field results and filtering.

Computed field implementations receive (eb, { modelAlias }, args) and
cannot read per-client state such as the auth context set via $setAuth.
Pass the executing client in the context, mirroring what custom function
implementations already get through ZModelFunctionContext.

Also replaces the scattered 'as unknown as ClientContract' casts with a
single ClientImpl.$contract accessor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9aaf461b-45db-4c2b-b8e6-67db215c162c

📥 Commits

Reviewing files that changed from the base of the PR and between eaafe95 and 771e216.

📒 Files selected for processing (1)
  • tests/e2e/orm/client-api/computed-fields.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/e2e/orm/client-api/computed-fields.test.ts

📝 Walkthrough

Walkthrough

The ORM exposes a typed $contract, propagates it through CRUD dialect construction, and provides it to computed fields. Transaction callbacks, executor hooks, connection-scoped clients, and end-to-end tests now use the contract.

Changes

Client-aware computed fields

Layer / File(s) Summary
Contract and computed-field context
packages/orm/src/client/options.ts, packages/orm/src/client/client-impl.ts
Adds ComputedFieldContext with an optional client. Adds the public $contract getter. Transaction callbacks now receive the contract.
Dialect client propagation
packages/orm/src/client/crud/dialects/*, packages/orm/src/client/crud/operations/base.ts, packages/orm/src/client/executor/name-mapper.ts, packages/orm/src/client/executor/zenstack-query-executor.ts
Passes the client contract through dialect constructors and computed-field execution contexts.
Executor integration and validation
packages/orm/src/client/executor/zenstack-query-executor.ts, tests/e2e/orm/client-api/computed-fields.test.ts
Uses the contract in hooks and connection-scoped clients. Tests authentication-aware computed fields, filtering, $setAuth, and client isolation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: ymc9

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: providing the client in computed field context.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…t test

A bare binary comparison embedded by the boolean where-filter renders as
a chained '=' — a syntax error on postgres (sqlite tolerates it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@evgenovalov

Copy link
Copy Markdown
Contributor Author

CI fix in 771e216: the new e2e test failed on the postgres matrix only. The computed field was written as a bare binary comparison (eb('authorId', '=', ...)); where: { isMine: true } embeds the computed expression as <expr> = $n, which rendered "authorId" = $2 = $3 — chained = is a syntax error on postgres (sqlite/mysql tolerate it). The test now parenthesizes the expression with eb.parens.

Side note for maintainers: this is reproducible on dev with any boolean computed field written as a bare comparison plus a boolean where filter — the dialect could parenthesize computed expressions when embedding them. Happy to file it as a separate issue.

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.

1 participant