feat(prisma): add prisma adapter - #838
Conversation
|
Warning Review limit reached
Next review available in: 12 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (63)
📝 WalkthroughWalkthroughAdds the ChangesPrisma adapter implementation
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
49329d2 to
9242976
Compare
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (2)
packages/prisma/package.json (1)
18-22: 🗄️ Data Integrity & Integration | 🔵 TrivialConfirm Prisma 6.x is the intended test target.
packages/prisma/test/data/client.tsreadsgenerated.Prisma.dmmf.datamodel, which the Prisma 7prisma-clientgenerator no longer exposes; Prisma 7 would require a different/maintained DMMF source. Documenting why this package still targets Prisma 6.x would make this dependency/version choice clearer.🤖 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/package.json` around lines 18 - 22, Document in the Prisma package metadata why the devDependencies remain pinned to Prisma 6.x: packages/prisma/test/data/client.ts depends on generated.Prisma.dmmf.datamodel, which Prisma 7’s prisma-client generator does not expose. Keep the existing dependency versions unchanged and add a concise explanatory note using the package’s established metadata/documentation convention.packages/prisma/test/data/schema.ts (1)
57-63: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider typing
createAdapterOptionsagainst the real options type instead ofany.
overridesand the return value are untyped (Record<string, any>/as any). Since this helper feeds option overrides (provider,filters.caseSensitive,relations.toMany, etc.) into nearly every spec file in this package, a typo in an override key would silently no-op rather than fail to compile.♻️ Suggested typing (adjust import path/name to the actual exported options type)
-export function createAdapterOptions(overrides: Record<string, any> = {}) { +import type { PrismaAdapterOptions } from '../../src'; + +export function createAdapterOptions(overrides: Partial<PrismaAdapterOptions> = {}) : PrismaAdapterOptions { return { provider: 'postgresql', metadata: defineMetadata(datamodel, 'User'), ...overrides, - } as any; + }; }Please confirm the exact exported name/shape of the adapter constructor options type (e.g. in
packages/prisma/src/adapter/types.ts) before applying.🤖 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/data/schema.ts` around lines 57 - 63, Update createAdapterOptions to use the actual exported adapter constructor options type for both its overrides parameter and return value, locating and importing the canonical type from the adapter implementation. Remove Record<string, any> and the as any cast while preserving the existing defaults and spread behavior so invalid override keys are caught by TypeScript.
🤖 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 3-12: Update the cross-adapter semantics statement in the guide to
qualify parity as applying to supported features, and reference the documented
Prisma limitations if an existing limitations link is available. Preserve the
adapter overview and its current behavior description.
In `@packages/docs/packages/prisma.md`:
- Around line 171-174: Update the Prisma translation paths for to-many relation
ordering and to-one elemMatch to return typed AdapterErrors with code
FEATURE_UNSUPPORTED instead of emitting Prisma orderBy or some payloads. Locate
the relevant relation-ordering and elemMatch handling symbols, preserve
supported relation behavior, and ensure both unsupported cases follow the
existing adapter error contract before query execution.
- Around line 131-133: Update the PrismaAdapter provider example to include a
constructed metadata object alongside the provider option, matching the required
PrismaAdapterOptions shape and ensuring relation and filter adapters receive
metadata.
In `@packages/prisma/README.md`:
- Around line 98-106: Update the “Case sensitivity” introduction in the Prisma
README to narrow the cross-backend claim or explicitly exempt SQLite equality,
matching the documented SQLite behavior where equals remains case-sensitive.
Keep the provider table and its existing connector-specific details unchanged.
In `@packages/prisma/src/adapter/module.ts`:
- Around line 142-176: The applySelection method must preserve base.select as an
allow-list for both requested root fields and included relations. Intersect
selection.select with base.select, or reject entries outside the base
projection, instead of replacing the base projection; when merging
selection.include into base.select, do not let same-key query selections
overwrite more restrictive base selections. Continue removing conflicting
include/omit arguments after producing the constrained selection.
In `@packages/prisma/test/data/client.ts`:
- Line 136: Replace the `generated.Prisma.dmmf.datamodel` dependency in the
parity fixture with a generated `{ models }` datamodel obtained through a
supported metadata path, such as `@prisma/internals` `getDMMF` or emitted
`--datamodel-json` output. Update the surrounding setup to consume and store
that generated datamodel while preserving the existing parity behavior.
In `@packages/prisma/test/unit/engine.db.spec.ts`:
- Around line 50-107: Extract the duplicated parity records fixture into a
shared module and export it. In packages/prisma/test/unit/engine.db.spec.ts
lines 50-107 and packages/prisma/test/unit/complement.spec.ts lines 44-101,
remove each local records array and import the shared fixture instead,
preserving the existing records symbol and test behavior at both sites.
In `@packages/prisma/test/unit/fields.spec.ts`:
- Around line 79-86: Update the test case around build to avoid the invalid
items.realm metadata path: use a nested relation that exists in the test
datamodel, or revise the fixture and expected select accordingly. Preserve the
intent of verifying that deeper relations remain included for a wholly hydrated
relation without relying on unknown path segments.
---
Nitpick comments:
In `@packages/prisma/package.json`:
- Around line 18-22: Document in the Prisma package metadata why the
devDependencies remain pinned to Prisma 6.x: packages/prisma/test/data/client.ts
depends on generated.Prisma.dmmf.datamodel, which Prisma 7’s prisma-client
generator does not expose. Keep the existing dependency versions unchanged and
add a concise explanatory note using the package’s established
metadata/documentation convention.
In `@packages/prisma/test/data/schema.ts`:
- Around line 57-63: Update createAdapterOptions to use the actual exported
adapter constructor options type for both its overrides parameter and return
value, locating and importing the canonical type from the adapter
implementation. Remove Record<string, any> and the as any cast while preserving
the existing defaults and spread behavior so invalid override keys are caught by
TypeScript.
🪄 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: 89071e5a-65cd-44f3-9dc5-424dd893c0a3
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (62)
.agents/architecture.md.agents/structure.md.github/workflows/main.yml.release-please-manifest.jsonREADME.mdpackages/codec-url/README.mdpackages/core/README.mdpackages/docs/.vitepress/config.mjspackages/docs/.vitepress/theme/components/PackageLayers.vuepackages/docs/guide/executing-queries.mdpackages/docs/guide/installation.mdpackages/docs/packages/index.mdpackages/docs/packages/prisma.mdpackages/memory/README.mdpackages/parser-expression/README.mdpackages/parser-mongo/README.mdpackages/parser-simple/README.mdpackages/prisma/LICENSEpackages/prisma/README.mdpackages/prisma/package.jsonpackages/prisma/src/adapter/fields.tspackages/prisma/src/adapter/filters.tspackages/prisma/src/adapter/index.tspackages/prisma/src/adapter/interpreter.tspackages/prisma/src/adapter/module.tspackages/prisma/src/adapter/pagination.tspackages/prisma/src/adapter/relations.tspackages/prisma/src/adapter/sort.tspackages/prisma/src/adapter/types.tspackages/prisma/src/index.tspackages/prisma/src/metadata/index.tspackages/prisma/src/metadata/module.tspackages/prisma/src/metadata/types.tspackages/prisma/src/provider/constants.tspackages/prisma/src/provider/index.tspackages/prisma/src/provider/module.tspackages/prisma/src/provider/types.tspackages/prisma/test/data/client.tspackages/prisma/test/data/datamodel.tspackages/prisma/test/data/evaluate.tspackages/prisma/test/data/index.tspackages/prisma/test/data/schema.tspackages/prisma/test/data/type.tspackages/prisma/test/prisma/schema.postgres.prismapackages/prisma/test/prisma/schema.prismapackages/prisma/test/unit/acceptance.spec.tspackages/prisma/test/unit/complement.spec.tspackages/prisma/test/unit/engine.db.spec.tspackages/prisma/test/unit/fields.spec.tspackages/prisma/test/unit/filters.spec.tspackages/prisma/test/unit/metadata.spec.tspackages/prisma/test/unit/query.spec.tspackages/prisma/test/unit/relations.spec.tspackages/prisma/test/unit/sort.spec.tspackages/prisma/test/vitest.config.tspackages/prisma/test/vitest.db.config.tspackages/prisma/tsconfig.build.jsonpackages/prisma/tsconfig.jsonpackages/prisma/tsdown.config.tspackages/sql/README.mdpackages/typeorm/README.mdrelease-please-config.json
| A validated `Query` becomes results through an **adapter**. Four ship with rapiq; pick by where your data lives: | ||
|
|
||
| | Adapter | Target | Returns | | ||
| |---|---|---| | ||
| | [@rapiq/typeorm](/packages/typeorm) | TypeORM `SelectQueryBuilder` | mutates the builder in place | | ||
| | [@rapiq/prisma](/packages/prisma) | Prisma Client | a `findMany` argument object | | ||
| | [@rapiq/sql](/packages/sql) | any SQL driver | parameterized SQL fragments | | ||
| | [@rapiq/memory](/packages/memory) | plain objects & arrays | compiled functions / filtered data | | ||
|
|
||
| All three consume the same AST, and they agree on semantics: the records a query selects in memory are the records it selects in the database. | ||
| All four consume the same AST, and they agree on semantics: the records a query selects in memory are the records it selects in the database. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Qualify the cross-backend parity claim.
Line 12 says all four adapters agree on semantics, but packages/docs/packages/prisma.md documents known exceptions for to-many binding, unsupported relation operations, and SQLite case behavior. Say “for supported features” or link the limitations so users do not assume unconditional Prisma parity.
Suggested wording
-All four consume the same AST, and they agree on semantics: the records a query selects in memory are the records it selects in the database.
+All four consume the same AST and aim for matching semantics for supported features; backend-specific limitations are documented on each package page.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| A validated `Query` becomes results through an **adapter**. Four ship with rapiq; pick by where your data lives: | |
| | Adapter | Target | Returns | | |
| |---|---|---| | |
| | [@rapiq/typeorm](/packages/typeorm) | TypeORM `SelectQueryBuilder` | mutates the builder in place | | |
| | [@rapiq/prisma](/packages/prisma) | Prisma Client | a `findMany` argument object | | |
| | [@rapiq/sql](/packages/sql) | any SQL driver | parameterized SQL fragments | | |
| | [@rapiq/memory](/packages/memory) | plain objects & arrays | compiled functions / filtered data | | |
| All three consume the same AST, and they agree on semantics: the records a query selects in memory are the records it selects in the database. | |
| All four consume the same AST, and they agree on semantics: the records a query selects in memory are the records it selects in the database. | |
| A validated `Query` becomes results through an **adapter**. Four ship with rapiq; pick by where your data lives: | |
| | Adapter | Target | Returns | | |
| |---|---|---| | |
| | [`@rapiq/typeorm`](/packages/typeorm) | TypeORM `SelectQueryBuilder` | mutates the builder in place | | |
| | [`@rapiq/prisma`](/packages/prisma) | Prisma Client | a `findMany` argument object | | |
| | [`@rapiq/sql`](/packages/sql) | any SQL driver | parameterized SQL fragments | | |
| | [`@rapiq/memory`](/packages/memory) | plain objects & arrays | compiled functions / filtered data | | |
| All four consume the same AST and aim for matching semantics for supported features; backend-specific limitations are documented on each package page. |
🤖 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/docs/guide/executing-queries.md` around lines 3 - 12, Update the
cross-adapter semantics statement in the guide to qualify parity as applying to
supported features, and reference the documented Prisma limitations if an
existing limitations link is available. Preserve the adapter overview and its
current behavior description.
| /** | ||
| * Prisma rejects `select` next to `include` (and next to `omit`) on | ||
| * the same level, so exactly one of them survives. | ||
| * | ||
| * A baseline `select` is a deliberate projection restriction and is | ||
| * therefore never dropped: relations the query hydrates join it | ||
| * instead: widening a caller-owned projection would expose columns | ||
| * the application chose to withhold. | ||
| */ | ||
| protected applySelection(args: Args, base?: ARGS) : void { | ||
| const selection = this.fields.execute(); | ||
|
|
||
| if (selection.select) { | ||
| args.select = selection.select; | ||
| delete args.include; | ||
| delete (args as Record<string, unknown>).omit; | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| if (!selection.include) { | ||
| return; | ||
| } | ||
|
|
||
| if (base && base.select) { | ||
| args.select = { ...base.select, ...selection.include }; | ||
| delete args.include; | ||
| delete (args as Record<string, unknown>).omit; | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| args.include = selection.include; | ||
| delete args.select; | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg 'packages/prisma/src/adapter/module\.ts|packages/prisma/src'
echo "== target file outline/size =="
wc -l packages/prisma/src/adapter/module.ts
ast-grep outline packages/prisma/src/adapter/module.ts --view compact || true
echo "== relevant lines =="
sed -n '1,240p' packages/prisma/src/adapter/module.ts | nl -ba | sed -n '1,240p'
echo "== search applySelection usages =="
rg -n "applySelection|fields\.execute|applySelection" packages/prisma/src -SRepository: tada5hi/rapiq
Length of output: 1101
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== target file relevant content =="
sed -n '1,240p' packages/prisma/src/adapter/module.ts > /tmp/module_lines.txt
cat -n /tmp/module_lines.txt | sed -n '1,240p'
echo "== adapter sources outline/content snippets =="
for f in packages/prisma/src/adapter/fields.ts packages/prisma/src/adapter/types.ts packages/prisma/src/adapter/interpreter.ts packages/prisma/src/adapter/filters.ts packages/prisma/src/adapter/relations.ts packages/prisma/src/adapter/module.ts; do
echo "--- $f ($(wc -l < "$f") lines) ---"
if fd -p "$f" . >/dev/null 2>&1; then
ast-grep outline "$f" --view names || true
rg -n "execute|class|fields|selection|applySelection|omit|include|select" "$f" || true
else
echo "MISSING"
fi
done
echo "== read-only behavioral/static verifier for applySelection =="
python3 - <<'PY'
from pathlib import Path
p = Path('packages/prisma/src/adapter/module.ts')
text = p.read_text()
needles = [
'if (selection.select) {',
'args.select = selection.select;',
'if (base && base.select) {',
'args.select = { ...base.select, ...selection.include };',
]
for i, n in enumerate(needles, 1):
print(f"{i}: {n!r} {'FOUND' if n in text else 'MISSING'}")
print("contains delete args.select after include merge:", "delete args.select;" in text[lines:=text.find('if (base && base.select) {') if (lines:=text.find('if (base && base.select) {')) != -1 else 0:])
PYRepository: tada5hi/rapiq
Length of output: 13376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== FieldAdapter execute/buildSelection source =="
sed -n '80,220p' packages/prisma/src/adapter/fields.ts > /tmp/fields_lines.txt
cat -n /tmp/fields_lines.txt
echo "== fields.ts behavioral static extractor =="
python3 - <<'PY'
from pathlib import Path
p = Path('packages/prisma/src/adapter/fields.ts')
text = p.read_text()
start = text.index('execute() : { select?: Record<string, any>, include?: Record<string, any> }')
print(text[start:start+750])
lines = text.splitlines()
# print line numbers for key implementation lines
for idx, line in enumerate(lines, 1):
if f'function descend' in line or 'execute() :' in line or 'const select = this.buildSelect' in line or 'if (selected !== false)' in line:
print(f"{idx}: {line}")
PY
echo "== focused behavioral/static verifier for applySelection outcomes =="
python3 - <<'PY'
from pathlib import Path
module = Path('packages/prisma/src/adapter/module.ts').read_text()
# Model the exact branches used by applySelection; this matches the source's
# order: selection.select -> include-merge-with-base.select -> include-only.
def apply_selection(selection_select, selection_include, base):
args = {**(base or {})}
ignore = []
def delete(key, owner=None):
if key == 'select':
args.pop('select', None)
elif key == 'include':
args.pop('include', None)
elif key == 'omit':
args.pop('omit', None)
ignore.append(f'{owner or ""}{key}')
if selection_select:
args['select'] = selection_select
delete('include'); delete('omit')
return args
if not selection_include:
return args
if base and 'select' in base:
args['select'] = {**base.get('select', {}), **selection_include}
delete('include'); delete('omit')
return args
args['include'] = selection_include
delete('select')
return args
base = {'select': {'id': True}}
cases = [
("query requests email while base only allows id",
{'select': {'email': True}}, {'include': None}),
("include-only relation under select allowlist",
{'select': None}, {'include': {'posts': True}}),
("shallow include may overwrite nested base select",
{'select': None}, {'include': {'posts': True}}),
]
for name, sele, include in cases:
result = apply_selection(sele, include, base)
print(f"{name}: {result}")
PYRepository: tada5hi/rapiq
Length of output: 6640
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== static behavioral verifier for applySelection (source-shaped) =="
python3 - <<'PY'
from pathlib import Path
module = Path('packages/prisma/src/adapter/module.ts').read_text()
apply = module[module.index('protected applySelection(args: Args, base?: ARGS) : void {'):module.index('protected buildWhere(base?: Where')].replace('\n', ' ').expandtabs()
checks = {
'selection.select assigns args.select wholesale': 'if (selection.select) { ... args.select = selection.select; ... }' in apply,
'selection.select ignores base.select before assignment': '.base.select' not in apply[:apply.index('if (selection.select) { ')+40],
'selection.include merges into base.select': 'args.select = { ...base.select, ...selection.include };' in apply,
'selection.include deletes args.select': 'delete args.select;' in apply[apply.index('args.select = { ...base.select, ...selection.include };'):],
'selection.include deletes args.include': 'delete args.include;' in apply,
'selection.include deletes omit': 'delete (args as Record<string, unknown>).omit;' in apply,
}
for k, v in checks.items():
print(f"{k}: {v}")
def apply_selection(select_requested, include_requested, base):
args = {**(base or {})}
selection = {'select': select_requested, 'include': include_requested}
if selection['select']:
args['select'] = selection['select']
args.pop('include', None)
args.pop('omit', None)
return args
if not selection['include']:
return args
if base and 'select' in base:
args['select'] = {**base.get('select', {}), **selection['include']}
args.pop('include', None)
args.pop('omit', None)
return args
args['include'] = selection['include']
args.pop('select', None)
return args
cases = [
("base only allows id but query selects email",
{'email': True}, None, {'select': {'id': True}}),
("base only allows nested.id but query selects nested.name",
{'nested': {'name': True}}, None, {'select': {'nested': {'id': True}}}),
("include relation under root select allowlist",
None, {'posts': True}, {'select': {'id': True}}),
("spare include relation under root select allowlist",
None, {'posts': {'id': True, 'author': True}}, {'select': {'id': True}}),
("shallow include overwrites nested base select",
None, {'posts': True}, {'select': {'posts': {'title': True}}}),
]
for name, sel, inc, base in cases:
out = apply_selection(sel, inc, base)
base_select = {**(base or {}).pop('select') if base and isinstance(base.get('select'), dict) else {}}
out_select = out.get('select') or {}
def has_requested(key, tree=None):
if tree is None: tree = out_select
if tree is True: return True
if key in tree:
return True if (tree[key] is True) else tree[key] is True or has_requested(key, tree[key])
if isinstance(tree, dict):
return any(has_requested(key, v) for v in tree.values())
return False
print(f"{name}: out={out}; exposed_requested={'no' if has_requested('no') else 'yes'}")
PYRepository: tada5hi/rapiq
Length of output: 430
Intersect query selections with the base projection before emitting args.
applySelection replaces args.select with selection.select whenever the query requests fields, so fields outside the caller-owned base.select can be exposed. It should treat base.select as an allow-list for root select entries, or reject out-of-scoped field requests, instead of dropping that restriction. A parallel issue is the include merge: { ...base.select, ...selection.include } can overwrite a more restrictive same-key base selection with a query-selected relation.
🤖 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 142 - 176, The
applySelection method must preserve base.select as an allow-list for both
requested root fields and included relations. Intersect selection.select with
base.select, or reject entries outside the base projection, instead of replacing
the base projection; when merging selection.include into base.select, do not let
same-key query selections overwrite more restrictive base selections. Continue
removing conflicting include/omit arguments after producing the constrained
selection.
|
|
||
| return { | ||
| client, | ||
| datamodel: generated.Prisma.dmmf.datamodel, |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Does the "prisma-client-js" generator in Prisma 6.19 still expose Prisma.dmmf reliably?
💡 Result:
In Prisma 6.19, the Prisma.dmmf property is not reliably exposed in the generated client [1][2]. Prisma has been actively moving away from exposing the dmmf object as a public property, as it was originally an undocumented, internal API [3][2][4]. Key points regarding the current status of DMMF in modern Prisma versions: 1. Removal of Prisma.dmmf: The direct exposure of Prisma.dmmf has been intentionally phased out or removed in favor of more stable, documented reflection APIs [1][4]. Users upgrading from older versions to the newer prisma-client generator (which is intended to replace prisma-client-js) often find this property missing [1][5]. 2. Alternative Approaches: - Since Prisma.dmmf is no longer a supported or reliable interface, developers requiring schema metadata are encouraged to use @prisma/internals to generate the DMMF at runtime [1][2]. This involves using functions like getDMMF provided by that package [2][4]. - Prisma has communicated plans to introduce a stable, public-facing alternative, sometimes referred to as Schema Intermediate Representation (SIR), to replace the needs previously met by the undocumented DMMF [4]. 3. Engine Limitations: If you are using the newer engineType = "client" configuration, the DMMF structure—even if accessed via other means—may appear significantly simplified and lack critical metadata (such as relation information, constraints, and specific database types) compared to the default Rust-based engine [6]. Because Prisma.dmmf is not a stable or documented part of the public API, it should not be relied upon for production code [3][2]. Relying on it may lead to breakage in future versions as Prisma continues to optimize its client generation and internal architecture [3][4].
Citations:
- 1: missing DMMF in prisma-client generated output prisma/orm#28166
- 2: https://www.answeroverflow.com/m/1366812803817672816
- 3: Replace generated
Prisma.dmmfexport with public reflection API. prisma/orm#19392 - 4: [6.7.0] Get a list of model fields without
Prisma.dmmfprisma/orm#27028 - 5: Prisma ORM Roadmap: June - August 2025 prisma/orm#27503
- 6: DMMF simplified when using engineType = "client" (Prisma ORM without Rust engines) prisma/orm#28348
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Repo files matching client/data specs:"
git ls-files | rg '^(packages/prisma/test/data/client\.ts|packages/prisma/test/engine\.db\.spec\.ts|prisma\.schema|schema\.prisma|generated/prisma|node\.modules/prisma|package\.json)$' || true
echo
echo "Relevant snippets:"
for f in packages/prisma/test/data/client.ts packages/prisma/test/engine.db.spec.ts; do
if [ -f "$f" ]; then
echo "--- $f ($(wc -l < "$f") lines) ---"
sed -n '1,220p' "$f" | nl -ba | sed -n '1,240p'
fi
done
echo
echo "Search for generator/provider options and dmmf usage:"
rg -n --glob '!node_modules' --glob '!dist' --glob '!build' 'generator|engineType|provider|dmmf|Prisma\.dmmf|getDMMF|`@prisma/internals`' .Repository: tada5hi/rapiq
Length of output: 356
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Tracked candidate files:"
git ls-files | grep -E '(^|/)(package\.json|packages/prisma/test/data/client\.ts|packages/prisma/test/engine\.db\.spec\.ts|schemas?\.prisma|.*schema.*\.prisma)$' || true
echo
echo "packages/prisma/test/data/client.ts:"
sed -n '1,180p' packages/prisma/test/data/client.ts 2>/dev/null || true
echo
echo "packages/prisma/test/engine.db.spec.ts:"
sed -n '1,240p' packages/prisma/test/engine.db.spec.ts 2>/dev/null || true
echo
echo "Search metadata/prisma generator/dmmf usage:"
grep -R -n --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build \
-E '(generator|engineType|provider|dmmf|Prisma\.dmmf|getDMMF|`@prisma/internals`|sqlite|postgres|mysql)' . \
|| trueRepository: tada5hi/rapiq
Length of output: 42482
Avoid depending on Prisma.dmmf in this parity suite.
These generators use prisma-client-js, but Prisma treats the DMMF as an internal, non-stable API and the public client property can be missing or reshaped with generator/runtime changes. Generate/store the needed { models } datamodel instead by using a supported Prisma metadata API such as @prisma/internals’ getDMMF, or by emitting the datamodel with --datamodel-json.
🤖 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/data/client.ts` at line 136, Replace the
`generated.Prisma.dmmf.datamodel` dependency in the parity fixture with a
generated `{ models }` datamodel obtained through a supported metadata path,
such as `@prisma/internals` `getDMMF` or emitted `--datamodel-json` output.
Update the surrounding setup to consume and store that generated datamodel while
preserving the existing parity behavior.
| const records : User[] = [ | ||
| { | ||
| id: 1, | ||
| first_name: 'Caleb', | ||
| last_name: 'Barrows', | ||
| email: 'caleb.barrows@gmail.com', | ||
| age: 18, | ||
| address: 'Hogwarts', | ||
| realm_id: 1, | ||
| realm: { | ||
| id: 1, | ||
| name: 'master', | ||
| description: null, | ||
| }, | ||
| items: [{ | ||
| id: 1, | ||
| title: 'book', | ||
| color: 'red', | ||
| }], | ||
| }, | ||
| { | ||
| id: 2, | ||
| first_name: 'Aston', | ||
| last_name: 'Nel', | ||
| email: 'ashton.nel@gmail.com', | ||
| age: 60, | ||
| address: null, | ||
| realm_id: null, | ||
| realm: null, | ||
| items: [], | ||
| }, | ||
| { | ||
| id: 3, | ||
| first_name: 'Frodo', | ||
| last_name: 'Baggins', | ||
| email: 'frodo.baggins@gmail.com', | ||
| age: 33, | ||
| address: 'Mordor', | ||
| realm_id: 2, | ||
| realm: { | ||
| id: 2, | ||
| name: 'shire', | ||
| description: 'the shire', | ||
| }, | ||
| items: [ | ||
| { | ||
| id: 2, | ||
| title: 'ring', | ||
| color: null, | ||
| }, | ||
| { | ||
| id: 3, | ||
| title: 'book', | ||
| color: 'blue', | ||
| }, | ||
| ], | ||
| }, | ||
| ]; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Extract the shared parity records fixture instead of duplicating it verbatim. Both files hard-code the identical records array used to assert that Prisma's real engine, @rapiq/memory, and the in-test evaluator all agree — the entire point of this design is that all three compare against the same data, so two independently-maintained copies risk silent drift that would quietly break the parity guarantee without any test failure calling it out.
packages/prisma/test/unit/engine.db.spec.ts#L50-L107: move thisrecordsarray into a shared fixture (e.g.packages/prisma/test/data/records.ts) and import it here.packages/prisma/test/unit/complement.spec.ts#L44-L101: import the same shared fixture instead of redeclaring the array.
📍 Affects 2 files
packages/prisma/test/unit/engine.db.spec.ts#L50-L107(this comment)packages/prisma/test/unit/complement.spec.ts#L44-L101
🤖 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/engine.db.spec.ts` around lines 50 - 107, Extract
the duplicated parity records fixture into a shared module and export it. In
packages/prisma/test/unit/engine.db.spec.ts lines 50-107 and
packages/prisma/test/unit/complement.spec.ts lines 44-101, remove each local
records array and import the shared fixture instead, preserving the existing
records symbol and test behavior at both sites.
Eliminates group negation from a condition plan by pushing it down to the leaves: De Morgan over compounds, leaf twins toggled, not(ordering) rewritten as the dual operator OR a null check (the complement derived from the semantics table's compare ranges, keeping the table the single authority), and residual wrappers kept only around mod/size, which have no complement form. planCondition retains group negation because sql and memory render it two-valued cheaply (a CASE wrapper, a `!`). Backends without a two-valued NOT of their own, prisma first and the other structured-args ORMs of plan 023 next, consume this transform instead of re-deriving operator semantics locally. The transform encodes the settled negation contract: group negation is the two-valued complement per binding with the quantifier outermost, so it commutes through elemMatch (not(elemMatch(c)) selects records with an element failing c, not records without a matching element).
Serializes a parsed Query into a Prisma findMany argument object:
filters to `where`, fields to `select`, relations to `include`, sort to
`orderBy`, pagination to `take`/`skip`. The adapter is stateless and
pure: `execute` maps a value to a value, composition happens before
serialization (mergeQueries, filters.and()), and @prisma/client is
neither a runtime nor a peer dependency.
The `where` is produced by a pipeline of pure passes: planCondition,
core's distributeNegation, quantifier factoring, then a leaf-literal
table. Prisma's three-valued `not`/`NOT` is never relied upon.
Cross-backend semantics hold exactly: relation traversal quantifies
existentially with the quantifier outermost (a left join evaluated per
row), conditions sharing a to-many path within a conjunction are
factored into ONE `some` scope so they bind to the same element as in
sql/typeorm/memory (mixed trees are expanded distributively first,
capped with a typed error), and every quantifier gains its `none: {}`
or absence arm exactly when the interior holds at the all-null binding
an empty collection contributes.
`provider` and `metadata` are required: each metadata fact (relation,
cardinality, nullability, string-typedness) changes what a valid prisma
filter looks like, so a wrong guess is a validation error rather than
degradation.
Parity is measured rather than modelled: the engine suite runs the
matrix through a real prisma client (sqlite by default, postgres under
DB_TYPE=postgres) and cross-checks every condition against
@rapiq/memory and an in-test evaluator, including a mixed-element
record that pins same-element binding and the per-binding complement
of elemMatch.
All of the package's engine-backed specs need a generated prisma client, so they live behind `test:db` and the default `tests` job stays codegen-free. The postgres matrix entry is the only place where `mode: 'insensitive'` exists at all, so the case contract and the ILIKE-wildcard veto can only be measured there.
Adds the package page (parameter mapping, metadata contract, negation and same-element semantics, per-connector case behaviour) and the package README in the layout established by #836, lists the adapter in the shared "rapiq family" table of every sibling README, and references it from the overview, installation and executing-queries pages.
9242976 to
a114010
Compare
Adds
@rapiq/prisma, the first of the additional ORM adapters from the plan-023 direction doc, plus the core plan transform it (and the next adapters) build on.What it does
Serializes a parsed
Queryinto a PrismafindManyargument object. A pure, stateless serializer:executemaps a value to a value, and@prisma/clientis neither a runtime nor a peer dependency (devDependency only, for the engine tests). Queries compose before serialization (mergeQueries,filters.and()), so there is no accumulation API.filterswherefieldsselectrelationsinclude, or a nestedselectwhen fields are pickedsortorderBypaginationtake/skipArchitecture: pure passes, semantics stay in core
The
whereis produced by a pipeline:planCondition->distributeNegation(new in@rapiq/core) -> quantifier factoring -> a leaf-literal table.distributeNegationeliminates group negation by pushing it to the leaves: De Morgan over compounds, leaf twins toggled,not(ordering)rewritten as the dual operator OR a null check (complements derived from the semantics table, not hardcoded). Core keeps group negation for sql/memory, which render it two-valued cheaply; backends without a two-valued NOT (prisma now, Drizzle/MikroORM next per plan 023) consume the transform instead of re-deriving operator semantics locally. Prisma's three-valuednot/NOTis never relied upon.Cross-backend semantics hold exactly
Two contracts that are easy to get wrong, both pinned by probing
@rapiq/memoryand by the engine suite:filter[items.title]+filter[items.color]bind to the same joined row in sql/typeorm/memory. The renderer factors conditions sharing a to-many path into ONEsomescope; mixed root/relation trees are expanded distributively first (∃distributes over OR; capped at 64 conjuncts with a typed error). A record satisfying the conditions only on different elements does not match, on any backend.not(elemMatch(c))is per-binding. Group negation applies per binding with the quantifier outermost (sql wraps CASE per join row), so the complement issome(not c)plus the empty-collection arm, NOTnone(c). Everynone: {}/absence arm follows one rule: the interior evaluated at the all-null binding an empty collection contributes.providerandmetadataare required: each metadata fact (relation, cardinality, nullability, string-typedness) changes what a valid prisma filter looks like, so a wrong guess is a validation error rather than degradation.Parity is measured, not modelled
npm run test:dbruns the matrix through a real Prisma client and cross-checks every condition three ways: engine,@rapiq/memory, and an in-test evaluator. SQLite by default; PostgreSQL underDB_TYPE=postgresin the existingtests-dbjob (the only connector wheremode: 'insensitive'exists, so the case contract and the ILIKE-wildcard veto are measured there). The fixtures include a mixed-element record that would have caught both semantics bugs above; earlier fixtures were too weak to distinguish them.Measured on Postgres 18 / Prisma 6.19.3:
{ equals: 'a_b', mode: 'insensitive' }lowers to ILIKE and wildcards (a%bmatches every row), whilein/notInnever do, so the wildcard veto applies to the equality family only.Known limitations
Typed
AdapterErrorrather than an approximation:regex,mod,size, the$thismarker,elemMatchon a to-one relation, and filter trees beyond the expansion ceiling. To-manyorderByis not expressible in Prisma; SQLite compares case-sensitively (measured, documented).Notes