Skip to content

feat(prisma): derive schemas from a datamodel - #841

Merged
tada5hi merged 2 commits into
feat/prisma-adapterfrom
feat/prisma-schema-derivation
Jul 26, 2026
Merged

tada5hi merged 2 commits into
feat/prisma-adapterfrom
feat/prisma-schema-derivation

Conversation

@tada5hi

@tada5hi tada5hi commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Stacked on #838. Two features: schema derivation from a datamodel, and client-bound adapter construction.

Bind the adapter from a model delegate

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

One argument. The model name, the datamodel (relations, cardinality, nullability, column types) and the active provider are read off the client the delegate belongs to, through its runtime backref. { client: prisma, model: 'User' } works with a plain name, provider/metadata remain overridable, and the fully explicit { provider, metadata } form stays as the private-API-free path.

Honesty note: the reads use private but long-stable client internals (_runtimeDataModel, _activeProvider, the delegate $name/$parent markers; Prisma has no public reflection API, prisma/orm#19392). Guarded accordingly:

  • every read fails typed instead of guessing (unresolvable client, unreadable provider),
  • pruned edge/wasm runtime datamodels (no cardinality/nullability) are rejected typed with the hand-written datamodel as the documented escape hatch,
  • the engine suite pins the shapes against a real generated client: the delegate-bound adapter must produce byte-identical arguments to the datamodel-bound one and select the same records through the engine, and the shapes are verified to survive $extends.

Schema derivation

const registry = defineSchemaRegistryWithDatamodel(prisma, {
    schemas: {
        user: {
            fields: { allowed: 'inherit' },       // the model's field names
            filters: { allowed: ['id', 'name'] }, // authorization stays yours
        },
    },
});

Mirrors @rapiq/typeorm's entity derivation contract: derivation supplies shape (schema name, relation allow-list, schemaMapping), authorization stays explicit, hand-written schemas take precedence, and name collisions or unmatched option keys fail loudly. assertSchemaMatchesModel turns schema/model drift into a boot-time SchemaModelMismatchError carrying every offending key. All entry points accept a client instance, a runtime datamodel or a DMMF datamodel.

Testing

33 new specs (client resolution, derivation, assert) plus engine-backed checks against the real generated client on SQLite and PostgreSQL. Acceptance test runs a derived registry through the full pipeline: wire input -> URL codec (relation traversal via derived schemaMapping) -> adapter -> args object.

Notes

  • change-case added as a dependency (same as @rapiq/typeorm, for the derived names).
  • Docs lead with the delegate-bound form; the explicit form is documented as the stable path with the private-API caveat spelled out.
  • Still deferred: scalar-list operators, _count ordering.

Adds the plan-017 analogue for prisma: defineSchemaWithModel,
defineSchemaRegistryWithDatamodel and assertSchemaMatchesModel,
mirroring the typeorm entity-derivation contract. Derivation supplies
shape only: the schema name (lower-camel model name), the relation
allow-list and the schemaMapping used for relation traversal, plus the
'inherit' sentinel expanding an allowed list to the model's scalar and
enum field names. Authorization stays explicit; a derived schema
without per-parameter options allows nothing.

Hand-written schemas already present in the registry take precedence,
derived-name collisions and unmatched schema option keys fail loudly,
and assertSchemaMatchesModel turns schema/model drift into a boot-time
SchemaModelMismatchError carrying every offending key (fields, filter
allow-lists and default condition trees, sort keys, relation keys).
Copilot AI review requested due to automatic review settings July 26, 2026 11:41

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

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b6df4e0c-1ba9-43dd-8a9f-13ced67ec22b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/prisma-schema-derivation

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.

A model delegate is now all it takes:

    new PrismaAdapter({ model: prisma.user })

The model name, the datamodel (relations, cardinality, nullability,
column types) and the active provider are read off the client the
delegate belongs to, through its runtime backref. The client-bound
shape also accepts `{ client, model }` with a plain model name, plus
`provider`/`metadata` overrides; the fully explicit form stays as the
private-API-free path.

The reads use private but long-stable client internals
(`_runtimeDataModel`, `_activeProvider`, the delegate `$name`/`$parent`
markers; prisma has no public reflection API, prisma/orm#19392).
Every read fails typed instead of guessing, pruned edge/wasm runtime
datamodels are rejected typed, and the engine suite pins the shapes
against a real generated client: the delegate-bound adapter must
produce byte-identical arguments to the datamodel-bound one and select
the same records through the engine.

defineMetadata and the schema derivation entry points accept the same
widened sources (client instance, runtime datamodel, DMMF datamodel).
@tada5hi
tada5hi force-pushed the feat/prisma-schema-derivation branch from ac65f00 to db29d51 Compare July 26, 2026 12:13
@tada5hi
tada5hi merged commit bc1d6e5 into feat/prisma-adapter Jul 26, 2026
1 check passed
tada5hi added a commit that referenced this pull request Jul 26, 2026
* feat(prisma): derive schemas from a datamodel

Adds the plan-017 analogue for prisma: defineSchemaWithModel,
defineSchemaRegistryWithDatamodel and assertSchemaMatchesModel,
mirroring the typeorm entity-derivation contract. Derivation supplies
shape only: the schema name (lower-camel model name), the relation
allow-list and the schemaMapping used for relation traversal, plus the
'inherit' sentinel expanding an allowed list to the model's scalar and
enum field names. Authorization stays explicit; a derived schema
without per-parameter options allows nothing.

Hand-written schemas already present in the registry take precedence,
derived-name collisions and unmatched schema option keys fail loudly,
and assertSchemaMatchesModel turns schema/model drift into a boot-time
SchemaModelMismatchError carrying every offending key (fields, filter
allow-lists and default condition trees, sort keys, relation keys).

* feat(prisma): bind the adapter from a model delegate

A model delegate is now all it takes:

    new PrismaAdapter({ model: prisma.user })

The model name, the datamodel (relations, cardinality, nullability,
column types) and the active provider are read off the client the
delegate belongs to, through its runtime backref. The client-bound
shape also accepts `{ client, model }` with a plain model name, plus
`provider`/`metadata` overrides; the fully explicit form stays as the
private-API-free path.

The reads use private but long-stable client internals
(`_runtimeDataModel`, `_activeProvider`, the delegate `$name`/`$parent`
markers; prisma has no public reflection API, prisma/orm#19392).
Every read fails typed instead of guessing, pruned edge/wasm runtime
datamodels are rejected typed, and the engine suite pins the shapes
against a real generated client: the delegate-bound adapter must
produce byte-identical arguments to the datamodel-bound one and select
the same records through the engine.

defineMetadata and the schema derivation entry points accept the same
widened sources (client instance, runtime datamodel, DMMF datamodel).
@github-actions github-actions Bot mentioned this pull request Jul 26, 2026
@tada5hi
tada5hi deleted the feat/prisma-schema-derivation 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