Skip to content

feat(core,devtools): compose core extension Prisma models and migrations at build - #707

Open
seanspeaks wants to merge 3 commits into
claude/internal-mcp-08-proxy-hostsfrom
claude/internal-mcp-10-extension-schemas
Open

seanspeaks wants to merge 3 commits into
claude/internal-mcp-08-proxy-hostsfrom
claude/internal-mcp-10-extension-schemas

Conversation

@seanspeaks

@seanspeaks seanspeaks commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #690.

Problem

A core extension can declare a Prisma schema (coreExtensions[].schema), but nothing ever put its models into the generated client or created its tables. getExtensionSchemaPaths and composeSchemas existed and were never called. So:

  • every prisma.<model> an extension uses is undefined at runtime;
  • the extension's encrypted fields are registered for a model the client does not have.

For example, the db-credentials extension's /admin/oauth-credentials answers 500 on every request.

Change

core/extensions/prisma-composer.js handles each database the app uses:

  1. Base schema. It keeps the pristine core schema as schema.base.prisma. It then writes base + the extensions' models to prisma-<db>/schema.prisma.
    • Composing again appends nothing.
    • Removing an extension restores the base and regenerates.
    • The generated client, the migration runner and the Lambda layer all read that file, so they all see the composed schema.
  2. Name clashes. It refuses an extension model, enum or type that redeclares a core one or another extension's.
  3. Migrations (PostgreSQL only). A new option, coreExtensions[].migrations, takes a directory of <timestamp>_<name>/migration.sql. The composer copies them beside core's migrations, so prisma migrate deploy applies them in order with core's. It refuses a same-named migration with different SQL. Copied migrations are never removed, because an applied one must stay in the history.
  4. Client. It runs prisma generate when the schema changed or the generated client is stale.

coreExtensions[].schema may now be { postgresql, mongodb } paths, because a model written for one database does not compile for the other.

devtools runs the composer on the backend's installed core in three places:

  • the Netlify build (provider-build.js);
  • the AWS build (infrastructure-composer.js), which also invalidates a Prisma layer built from the old client;
  • frigg db:setup.

It is a no-op when no extension declares a schema.

The extension's encrypted fields need nothing new. loadExtensionEncryptionSchemas already registered them; with the model now in the client, they go through the same Prisma encryption extension as Credential.

Tests

  • core/extensions/__tests__/prisma-composer.test.js covers:
    • composition, idempotence, regeneration when the client is missing, and restoring the base;
    • per-database schemas;
    • refusal of a duplicate model and of a conflicting migration;
    • no migrations on MongoDB.
    • One test runs the real prisma generate and asserts the client exposes the extension model.
  • extension-loader.test.js adds validation of the new schema and migrations forms.
  • devtools/infrastructure/__tests__/compose-extension-prisma.test.js checks that composition runs on the backend's core, not devtools' copy, and that buildWithProvider composes before writing files and not after a failed validation.
  • Mutation checks:
    • dropping the extension blocks fails 7 tests;
    • removing the buildWithProvider call fails 1.
  • Run against frigg-internal's real db-credentials schema on a copy of its installed core: the regenerated client has oAuthAppCredential and the rhel engine.

The pre-existing failures in infrastructure-composer.test.js are unchanged: 44 failing on the base, 44 with this change.


Generated by Claude Code


Note

Medium Risk
Changes how Prisma schemas and migrations are assembled at build/deploy time; mistakes could break client generation or migration ordering, though guards exist for duplicate models and conflicting migration SQL.

Overview
Adds build-time Prisma composition so core extensions that declare coreExtensions[].schema and coreExtensions[].migrations actually land in the generated client and PostgreSQL migration history. New composeExtensionPrisma keeps a pristine schema.base.prisma, merges extension models (with clash checks), copies extension migration folders on PostgreSQL, and runs prisma generate when the schema or client is stale.

Extension config now accepts per-database schema paths ({ postgresql, mongodb }) and a migrations directory string, with validation in extension-loader. Devtools runs composition against the backend’s installed @friggframework/core during Netlify provider-build, AWS infrastructure-composer (invalidating a stale Prisma Lambda layer when the client changes), and frigg db:setup.

The db-credentials bootstrap now wraps OAuth env loaders for modules from appDefinition.entities as well as integrations, ignores inactive DB credentials (isActive === false), and still dedupes shared definitions.

Reviewed by Cursor Bugbot for commit df52379. Bugbot is set up for automated code reviews on this repo. Configure here.

📦 Published PR as canary version: 2.0.0--canary.707.df52379.0

✨ Test out this PR locally via:

npm install @friggframework/admin-scripts@2.0.0--canary.707.df52379.0
npm install @friggframework/core@2.0.0--canary.707.df52379.0
npm install @friggframework/devtools@2.0.0--canary.707.df52379.0
npm install @friggframework/eslint-config@2.0.0--canary.707.df52379.0
npm install @friggframework/extension-db-credentials@2.0.0--canary.707.df52379.0
npm install @friggframework/prettier-config@2.0.0--canary.707.df52379.0
npm install @friggframework/provider-aws@2.0.0--canary.707.df52379.0
npm install @friggframework/provider-netlify@2.0.0--canary.707.df52379.0
npm install @friggframework/schemas@2.0.0--canary.707.df52379.0
npm install @friggframework/serverless-plugin@2.0.0--canary.707.df52379.0
npm install @friggframework/test@2.0.0--canary.707.df52379.0
npm install @friggframework/ui@2.0.0--canary.707.df52379.0
# or 
yarn add @friggframework/admin-scripts@2.0.0--canary.707.df52379.0
yarn add @friggframework/core@2.0.0--canary.707.df52379.0
yarn add @friggframework/devtools@2.0.0--canary.707.df52379.0
yarn add @friggframework/eslint-config@2.0.0--canary.707.df52379.0
yarn add @friggframework/extension-db-credentials@2.0.0--canary.707.df52379.0
yarn add @friggframework/prettier-config@2.0.0--canary.707.df52379.0
yarn add @friggframework/provider-aws@2.0.0--canary.707.df52379.0
yarn add @friggframework/provider-netlify@2.0.0--canary.707.df52379.0
yarn add @friggframework/schemas@2.0.0--canary.707.df52379.0
yarn add @friggframework/serverless-plugin@2.0.0--canary.707.df52379.0
yarn add @friggframework/test@2.0.0--canary.707.df52379.0
yarn add @friggframework/ui@2.0.0--canary.707.df52379.0

…ons at build

A core extension could declare a Prisma schema (coreExtensions[].schema),
but nothing put its models into the generated client or created its
tables: getExtensionSchemaPaths and composeSchemas were never called. So
every prisma.<model> an extension used was undefined at runtime (the
db-credentials extension's /admin/oauth-credentials answered 500) and
its encrypted fields were registered for a model the client did not have.

core/extensions/prisma-composer.js, for each database the app uses:
- keeps the pristine core schema as schema.base.prisma and writes
  base + the extensions' models to prisma-<db>/schema.prisma, so a second
  run appends nothing and removing an extension restores the base;
- refuses an extension model that redeclares a core model or another
  extension's;
- PostgreSQL: copies coreExtensions[].migrations (new, a directory of
  <timestamp>_<name>/migration.sql) beside core's migrations, refusing a
  same-named migration with different SQL;
- runs prisma generate when the schema changed or the client is stale.

coreExtensions[].schema may now be { postgresql, mongodb } paths, since a
model written for one database does not compile for the other.

devtools runs it on the backend's installed core in the Netlify build
(provider-build.js), the AWS build (infrastructure-composer.js, which
also invalidates a Prisma layer built from the old client) and
`frigg db:setup`. With no extension declaring a schema it is a no-op.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
@seanspeaks seanspeaks added release Create a release when this pr is merged prerelease This change is available in a prerelease. labels Oct 24, 2026 — with Claude

@claude claude 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.

⚠️ Code review skipped — your organization has no extra usage available to pay for this review.

If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.

Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2702ff3. Configure here.

…hema

The composer skipped a database with no extension schema before copying
migrations, so an extension that ships only SQL (Bugbot on #707) never
had its migration folders put beside core's and its tables were never
created. Migrations are now copied first; the schema and client are
still left as shipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6

Copy link
Copy Markdown
Contributor Author

build (22.x) is red, but nothing in it is from this PR. The devtools suites fail exactly as on #690, this PR's base:

Failing suites Failing tests Passing tests
#690 33 119 1776
#707 33 119 1781

The 5 extra passing tests are this PR's new compose-extension-prisma.test.js. Every other package passes as on #690. The 33 failing suites are the stale tests and missing CI setup already explained on #686 and #690; this PR does not touch them.


Generated by Claude Code

The bootstrap only patched modules reached through
integrations[].Definition.modules, so a module declared only in
appDefinition.entities (e.g. google) never read its DB credentials.
It also used a record with isActive: false. Collect definitions from
both places (each wrapped once) and fall back to the static env for an
inactive record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

prerelease This change is available in a prerelease. release Create a release when this pr is merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants