Skip to content

feat(prisma): run queries through the bound model and merge args - #846

Merged
tada5hi merged 4 commits into
masterfrom
feat/prisma-run
Jul 26, 2026
Merged

tada5hi merged 4 commits into
masterfrom
feat/prisma-run

Conversation

@tada5hi

@tada5hi tada5hi commented Jul 26, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to #845, closing the gap against the typeorm adapter: a model-bound PrismaAdapter can now run what it serialized, and the args-merge rules become a public helper.

Runners

const adapter = new PrismaAdapter({ model: prisma.user });

const rows = await adapter.findMany(query, {
    base: { where: { realm_id: realmId } },
});
const total = await adapter.count(query);  // pre-pagination, for the meta block
  • findMany(query, options): execute() piped into the delegate.
  • count(query, options): the pre-pagination total (sees the where, including any baseline, never the page window).

There is deliberately no bundled rows-plus-total call: it would hide a second query on every request and, without a transaction, could pair mutually inconsistent results. The two primitives compose, and prisma's $transaction remains available when the pair must be consistent.

execute() stays the pure serializer; on an adapter constructed with explicit { provider, metadata } the runners reject with a typed error, since there is nothing to run against. Runner failures are always promise rejections, never synchronous throws, and an object passed as model without a callable findMany does not bind.

mergeArgs

Prisma ships no per-call args composition ($extends intercepts every call globally), so the merge the adapter always applied to its base option is now exported:

import { mergeArgs } from '@rapiq/prisma';

const args = mergeArgs(baseline, produced);

where conditions are conjoined (AND), an overriding include joins a baseline select instead of replacing it (a caller-owned projection is never widened), orderBy/take/skip follow the override, and unknown keys (cursor, distinct, ...) pass through. execute(query, { base }) is this merge applied to what the query produced, with the impossible-condition root form ({ OR: [] }) as the one special case, since a nested empty group would be stripped by prisma.

Testing

Recording-delegate unit tests (argument shapes per runner, baseline conjunction, typed rejections on unbound or non-runnable bindings) and mergeArgs semantics, plus engine-backed runs against the real client, including the rows-plus-total composition and a baseline where conjunction.

A model-bound adapter can now run what it serialized, the counterpart
of the typeorm adapter applying its state to the bound query builder:
`findMany(query, options)` pipes execute() into the delegate,
`count(query, options)` reports the pre-pagination total (the where,
never the page window), and `apply(query, options)` returns
`{ data, total, pagination }` in one call, shaped like
`@rapiq/memory`'s applyQuery. On an adapter constructed with explicit
`{ provider, metadata }` the runners raise a typed error; `execute()`
stays the pure serializer either way.

The base-merging rules move out of the adapter into an exported
`mergeArgs(base, override)` helper, since prisma ships no per-call
args composition of its own ($extends intercepts every call
globally): where conditions conjoin, an overriding include joins a
baseline select instead of replacing it, orderBy/take/skip follow the
override and unknown keys (cursor, distinct, ...) pass through.
`execute(query, { base })` is exactly this merge applied to what the
query produced; the impossible-condition root form stays a special
case because a nested empty OR group would be stripped by prisma.

The engine suite runs apply/findMany against the real client,
including a baseline where conjunction.
Copilot AI review requested due to automatic review settings July 26, 2026 13:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Prisma adapters now support model-bound findMany, count, and apply execution. A public mergeArgs helper combines baseline and generated arguments, while tests and documentation cover execution, pagination, filtering, projection, and unsupported adapter behavior.

Changes

Prisma query execution

Layer / File(s) Summary
Argument merging contract
packages/prisma/src/adapter/merge.ts, packages/prisma/src/adapter/index.ts
Adds and exports mergeArgs with Prisma-specific where, projection, pagination, ordering, and passthrough semantics.
Model-bound execution flow
packages/prisma/src/adapter/module.ts, packages/prisma/src/adapter/types.ts
Resolves model delegates, merges generated and baseline arguments, and adds findMany, count, and combined apply execution with typed output.
Execution and merge validation
packages/prisma/test/unit/run.spec.ts, packages/prisma/test/unit/engine.db.spec.ts
Tests serialized runner arguments, delegate resolution, baseline filters, merged arguments, unsupported adapters, and paginated apply results.
Public usage documentation
packages/docs/packages/prisma.md, packages/prisma/README.md, packages/docs/guide/executing-queries.md
Documents model-bound execution, apply results, separate runners, baseline arguments, and mergeArgs.

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant PrismaAdapter
  participant PrismaDelegate
  Caller->>PrismaAdapter: apply(query, options)
  PrismaAdapter->>PrismaDelegate: findMany(serialized args)
  PrismaAdapter->>PrismaDelegate: count(baseline where)
  PrismaDelegate-->>PrismaAdapter: rows and total
  PrismaAdapter-->>Caller: data, total, pagination
Loading

Possibly related PRs

  • tada5hi/rapiq#838: Introduces the PrismaAdapter serializer that this model-bound execution refactor extends.

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding bound-model query execution and Prisma arg merging.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/prisma-run

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/prisma/src/adapter/module.ts (1)

149-194: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Returned pagination doesn't reflect baseline-supplied take/skip.

pagination: { limit, offset } (line 193) is built from the query's own pagination, but the actual args sent to Prisma may carry a different take/skip inherited from base via mergeArgs (which preserves base.take/base.skip whenever the query itself doesn't set them). This contradicts the documented contract on PrismaAdapterOutput.pagination/ApplyOutput.pagination: "the pagination actually applied, e.g. for the response meta block". A caller relying on base to supply a default page size will get a metadata block that silently disagrees with what was actually queried.

🐛 Proposed fix
         return {
             args: args as ARGS,
-            pagination: { limit, offset },
+            pagination: { limit: args.take, offset: args.skip },
         };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/prisma/src/adapter/module.ts` around lines 149 - 194, Update the
returned pagination metadata in the query-building flow around mergeArgs so it
reflects the take/skip values actually present in the merged args, including
values inherited from base when query.pagination omits them. Preserve explicit
query pagination, including 0, and derive the metadata consistently from the
final args sent to Prisma.
🧹 Nitpick comments (2)
packages/prisma/test/unit/run.spec.ts (1)

120-129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unbound-adapter coverage only exercises findMany.

Given the sync-throw-vs-async-rejection inconsistency flagged in module.ts (findMany/count throw synchronously, apply rejects), consider adding equivalent unbound-adapter tests for count and apply (the latter via .rejects) so both error-surfacing paths are locked in by tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/prisma/test/unit/run.spec.ts` around lines 120 - 129, Extend the
unbound-adapter test coverage around PrismaAdapter to include equivalent
assertions for count and apply, using the existing FEATURE_UNSUPPORTED error
expectation. Keep count’s synchronous throw assertion consistent with findMany,
and verify apply through an async rejection assertion with rejects.
packages/prisma/src/adapter/merge.ts (1)

27-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Args typing doesn't support the passthrough contract this function advertises.

The docstring promises pass-through of arbitrary keys (cursor, distinct, ...), but override: Args (and T extends Args) has no index signature, so rest types down to essentially nothing extra. Callers must cast to any to exercise this documented behavior — as seen in the accompanying test ({ cursor: { id: 5 }, distinct: ['email'] } as any). Since mergeArgs is now exported publicly, consider widening Args (e.g. an index signature for unknown keys) so consumers get type-safe passthrough without an escape hatch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/prisma/src/adapter/merge.ts` around lines 27 - 38, Update the Args
typing used by mergeArgs so arbitrary passthrough keys such as cursor and
distinct are accepted without casts. Add an appropriate index signature or
equivalent widening to Args, while preserving the existing mergeArgs generic
return behavior and known argument fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/docs/guide/executing-queries.md`:
- Around line 51-56: Update the model-bound adapter example around adapter.apply
so the adapter used for the request is constructed with model: prisma.user
instead of only metadata. Keep the existing query and base scope unchanged, or
introduce a separate model-bound adapter before invoking apply.

In `@packages/docs/packages/prisma.md`:
- Line 56: Update the Prisma documentation sentence describing execute(query, {
base }) and mergeArgs to qualify that they are equivalent for normal argument
merging, except that execute applies additional handling for impossible filters
afterward; replace the unqualified “exactly this merge” wording without changing
the documented merge behavior.

In `@packages/prisma/src/adapter/module.ts`:
- Around line 62-80: Update resolveDelegate so object-form options.model is
accepted only when it exposes a callable findMany method, matching the existing
property-lookup validation; otherwise return undefined so the caller produces
its standard typed AdapterError for binding failures.
- Around line 203-265: Mark the model-bound adapter methods findMany and count
as async so synchronous errors from delegate() become promise rejections,
matching apply’s behavior. Update the unbound-adapter tests for these methods to
assert rejection with .rejects rather than expecting synchronous throws.

---

Outside diff comments:
In `@packages/prisma/src/adapter/module.ts`:
- Around line 149-194: Update the returned pagination metadata in the
query-building flow around mergeArgs so it reflects the take/skip values
actually present in the merged args, including values inherited from base when
query.pagination omits them. Preserve explicit query pagination, including 0,
and derive the metadata consistently from the final args sent to Prisma.

---

Nitpick comments:
In `@packages/prisma/src/adapter/merge.ts`:
- Around line 27-38: Update the Args typing used by mergeArgs so arbitrary
passthrough keys such as cursor and distinct are accepted without casts. Add an
appropriate index signature or equivalent widening to Args, while preserving the
existing mergeArgs generic return behavior and known argument fields.

In `@packages/prisma/test/unit/run.spec.ts`:
- Around line 120-129: Extend the unbound-adapter test coverage around
PrismaAdapter to include equivalent assertions for count and apply, using the
existing FEATURE_UNSUPPORTED error expectation. Keep count’s synchronous throw
assertion consistent with findMany, and verify apply through an async rejection
assertion with rejects.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cf759faa-4532-4d4f-b54f-feba14ebd590

📥 Commits

Reviewing files that changed from the base of the PR and between 1f8cad8 and fbcf620.

📒 Files selected for processing (9)
  • packages/docs/guide/executing-queries.md
  • packages/docs/packages/prisma.md
  • packages/prisma/README.md
  • packages/prisma/src/adapter/index.ts
  • packages/prisma/src/adapter/merge.ts
  • packages/prisma/src/adapter/module.ts
  • packages/prisma/src/adapter/types.ts
  • packages/prisma/test/unit/engine.db.spec.ts
  • packages/prisma/test/unit/run.spec.ts

Comment thread packages/docs/guide/executing-queries.md Outdated
Comment thread packages/docs/packages/prisma.md Outdated
Comment thread packages/prisma/src/adapter/module.ts
Comment thread packages/prisma/src/adapter/module.ts
tada5hi added 3 commits July 26, 2026 16:05
Runner errors now reject consistently: findMany and count are async so
an unbound adapter surfaces the typed error as a promise rejection
(apply already did), never as a synchronous throw a .catch() would
miss. An object passed as `model` without a callable findMany no
longer binds silently; the runners keep their typed error instead of a
raw TypeError from inside the delegate.

Docs: the executing-queries example constructs a model-bound adapter
(the previous one was unbound and missing its provider, so apply()
could never run), and the mergeArgs equivalence on the package page
now names the impossible-condition root form as the one exception.
A bundled rows-plus-total call hides a second query (a count on every
request, wanted or not) and pairs the two results without a
transaction, so they can be mutually inconsistent under concurrent
writes. findMany and count stay as the primitives; an endpoint that
wants both composes them, and prisma's own $transaction remains
available when the pair must be consistent.
@tada5hi
tada5hi merged commit 5d1e3de into master Jul 26, 2026
9 checks passed
@github-actions github-actions Bot mentioned this pull request Jul 26, 2026
@tada5hi
tada5hi deleted the feat/prisma-run branch July 27, 2026 07:53
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.

2 participants