diff --git a/.gitignore b/.gitignore index fce6340f4117..d92626bd53c3 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,8 @@ CLAUDE.local.md # `scripts/sync-package-skills.ts` (run from their `prepack`). The # canonical source of truth is `skills/` at the repository root. packages/9-public/@prisma/*/skills/ +packages/9-public/@prisma/*/skills.staging-*/ +packages/9-public/@prisma/*/skills.trash-*/ # Per-machine install lockfile written by `skills add`. Local only; # our canonical source is `skills-contrib/` (for contributors) and the diff --git a/README.md b/README.md index 988b94fa3068..48c84df9a055 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,13 @@ Just describe what you want. For example: > *"Add a `posts` model with a relation to `users`, then write a query that loads each user's three most recent posts."* -The agent loads the `prisma-8` skill, opens its contract and queries references, then drives the change end-to-end. +The agent loads the `prisma-orm-core-concepts` skill, opens its contract and queries references, then drives the change end-to-end. For the full catalogue and what each skill covers, see [`skills/README.md`](./skills/README.md). ## Found a bug, missing a feature, or have a question for the team? -Ask your agent. The `prisma-8` skill's feedback flow drafts a structured GitHub issue or hands you a Prisma Discord link for live Q&A. You can review and confirm before anything is submitted. +Ask your agent. The `prisma-orm-core-concepts` skill's feedback flow drafts a structured GitHub issue or hands you a Prisma Discord link for live Q&A. You can review and confirm before anything is submitted. ## For extension authors diff --git a/docs/oss/versioning.md b/docs/oss/versioning.md index 1cd3e8094214..3ea66728841e 100644 --- a/docs/oss/versioning.md +++ b/docs/oss/versioning.md @@ -21,7 +21,7 @@ Every workspace package — publishable, private, the workspace root, and exampl This invariant has consequences that ecosystem participants need to plan for: -- **Agent skills, the upgrade instructions, and any other tooling we ship alongside the framework version in lockstep with it.** A skill installed at the same time as `@internal/postgres@0.8.0` is a `0.8.0` skill and reasons about a `0.8.0` contract. There is no separate skill-version axis to track. This is now physical rather than conventional: the `prisma-8` skill ships inside the `@prisma/orm-postgres`, `@prisma/orm-sqlite` and `@prisma/orm-mongo` tarballs (copied in by each package's `prepack`), and `scripts/set-version.ts` stamps its `metadata.library_version` frontmatter with the version being published. Getting the skill and getting the code are one install, and a consumer can compare the stamp on its synced copy against its installed packages. +- **Agent skills, the upgrade instructions, and any other tooling we ship alongside the framework version in lockstep with it.** A skill installed at the same time as `@prisma/orm-postgres@0.8.0` is a `0.8.0` skill and reasons about a `0.8.0` contract. There is no separate skill-version axis to track. This is now physical rather than conventional: the `prisma-orm-core-concepts` and `prisma-orm-migrations` skills ship inside the `@prisma/orm-postgres`, `@prisma/orm-sqlite` and `@prisma/orm-mongo` tarballs (copied in by each package's `prepack`), and `scripts/set-version.ts` stamps each skill's `metadata.library_version` frontmatter with the version being published. Getting the skill and getting the code are one install, and a consumer can compare the stamp on its synced copy against its installed packages. - **Extension authors that depend on internal framework packages must pin those dependencies to the framework version their consumers will use.** If your extension depends on `@internal/sql-core` (an internal framework package), publish each version of your extension targeting one specific Prisma Next minor and pin to it exactly (`"@internal/sql-core": "0.8.0"`, not `"^0.8.0"`). Internal packages do not promise inter-minor compatibility — `0.8.x` and `0.9.x` may have incompatible internals even when the user-visible surface looks similar. The extension's published version range communicates which framework minor it targets. - **Internal packages are never published, but they still version in lockstep** so a contributor cloning the repo at any commit sees one consistent answer to "what version is this code?" The `private: true` flag means `pnpm publish` skips them. diff --git a/package.json b/package.json index f3148500dca0..b26248d11760 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "lint:docs": "node scripts/validate-package-readmes.mjs", "lint:manifests": "node scripts/validate-package-manifests.mjs && node scripts/validate-typescript-peer.mjs", "lint:workflows": "node scripts/lint-workflow-triggers.mjs", - "test:scripts": "node --test scripts/coverage-config.test.mjs scripts/coverage-report.test.mjs scripts/lint-workflow-triggers.test.mjs scripts/validate-skills.test.mjs scripts/determine-version-utils.test.ts scripts/check-upgrade-coverage.test.mjs scripts/check-release-notes.test.mjs scripts/set-version-utils.test.ts scripts/check-publish-deps.test.mjs scripts/check-conformance.test.mjs scripts/check-publish-deps-pn-pins.test.mjs scripts/check-publish-deps-declarations.test.mjs scripts/validate-package-manifests.test.mjs scripts/validate-package-readmes.test.mjs scripts/publish-packages-utils.test.mjs scripts/check-clean-tree.test.mjs scripts/lint-casts.test.mjs scripts/lint-throws.test.mjs scripts/list-error-codes.test.mjs scripts/lint-framework-vocabulary.test.mjs scripts/lint-single-import-root.test.mjs scripts/lint-legacy-name.test.mjs scripts/lint-consumer-internal-imports.test.mjs scripts/sync-agent-rules.test.mjs scripts/validate-typescript-peer.test.mjs scripts/run-logged.test.mjs scripts/migrate-migrations-layout.test.mjs skills-contrib/review-fetch-phase/scripts/guard-review-artifacts-ignored.test.mjs skills-contrib/review-fetch-phase/scripts/render-review-state.test.mjs skills-contrib/review-triage-phase/scripts/render-review-actions.test.mjs", + "test:scripts": "node --test scripts/coverage-config.test.mjs scripts/sync-package-skills.test.mjs scripts/coverage-report.test.mjs scripts/lint-workflow-triggers.test.mjs scripts/validate-skills.test.mjs scripts/determine-version-utils.test.ts scripts/check-upgrade-coverage.test.mjs scripts/check-release-notes.test.mjs scripts/set-version-utils.test.ts scripts/check-publish-deps.test.mjs scripts/check-conformance.test.mjs scripts/check-publish-deps-pn-pins.test.mjs scripts/check-publish-deps-declarations.test.mjs scripts/validate-package-manifests.test.mjs scripts/validate-package-readmes.test.mjs scripts/publish-packages-utils.test.mjs scripts/check-clean-tree.test.mjs scripts/lint-casts.test.mjs scripts/lint-throws.test.mjs scripts/list-error-codes.test.mjs scripts/lint-framework-vocabulary.test.mjs scripts/lint-single-import-root.test.mjs scripts/lint-legacy-name.test.mjs scripts/lint-consumer-internal-imports.test.mjs scripts/sync-agent-rules.test.mjs scripts/validate-typescript-peer.test.mjs scripts/run-logged.test.mjs scripts/migrate-migrations-layout.test.mjs skills-contrib/review-fetch-phase/scripts/guard-review-artifacts-ignored.test.mjs skills-contrib/review-fetch-phase/scripts/render-review-state.test.mjs skills-contrib/review-triage-phase/scripts/render-review-actions.test.mjs", "bump-version": "node scripts/bump-version.ts", "check:publish-deps": "node scripts/check-publish-deps.mjs", "check:conformance": "node scripts/check-conformance.mjs", diff --git a/packages/0-shared/extension-author-tools/README.md b/packages/0-shared/extension-author-tools/README.md index d928160f3835..076492830c89 100644 --- a/packages/0-shared/extension-author-tools/README.md +++ b/packages/0-shared/extension-author-tools/README.md @@ -1,8 +1,8 @@ # @internal/extension-author-tools -CLI tools that pair with the extension-upgrade branch of the [`prisma-8`](../../../skills/prisma-8/references/upgrade-extension.md) agent skill. Today this package ships one tool; future tools for extension authors using the upgrade-skill flow will land here. +CLI tools that pair with the extension-upgrade branch of the [`prisma-orm-core-concepts`](../../../skills/prisma-orm-core-concepts/references/upgrade-extension.md) agent skill. Today this package ships one tool; future tools for extension authors using the upgrade-skill flow will land here. -The agent-readable upgrade procedure itself (the flow in [`references/upgrade-extension.md`](../../../skills/prisma-8/references/upgrade-extension.md) and the `upgrades/-to-/instructions.md` set under [`upgrading/extension/`](../../../skills/prisma-8/upgrading/extension/)) ships inside the `@prisma/orm-*` tarballs. This package is the npm-published companion that supplies the CI bin the skill drives. +The agent-readable upgrade procedure itself (the flow in [`references/upgrade-extension.md`](../../../skills/prisma-orm-core-concepts/references/upgrade-extension.md) and the `upgrades/-to-/instructions.md` set under [`upgrading/extension/`](../../../skills/prisma-orm-core-concepts/upgrading/extension/)) ships inside the `@prisma/orm-*` tarballs. This package is the npm-published companion that supplies the CI bin the skill drives. ## Installation @@ -18,7 +18,7 @@ npm install --save-dev @internal/extension-author-tools CI guard for extension packages. Asserts that every `@internal/*` entry under the package's `peerDependencies` (and, optionally, `dependencies`) is pinned to an exact version, not a range. -This is the invariant the [extension-upgrade flow](../../../skills/prisma-8/references/upgrade-extension.md) relies on at upgrade time: extension authors pin every `@internal/*` peer to a single exact version per release of their extension, so the skill can mechanically advance both the framework deps and the extension's published version in lockstep. +This is the invariant the [extension-upgrade flow](../../../skills/prisma-orm-core-concepts/references/upgrade-extension.md) relies on at upgrade time: extension authors pin every `@internal/*` peer to a single exact version per release of their extension, so the skill can mechanically advance both the framework deps and the extension's published version in lockstep. Run from the extension's repository root: diff --git a/packages/0-shared/publish-surface/test/package-skills.test.ts b/packages/0-shared/publish-surface/test/package-skills.test.ts index d17681d5ef09..72174f90b493 100644 --- a/packages/0-shared/publish-surface/test/package-skills.test.ts +++ b/packages/0-shared/publish-surface/test/package-skills.test.ts @@ -1,6 +1,7 @@ /** - * The `prisma-8` agent skill ships inside the tarball of every package an - * application depends on directly, so the skill a user has always describes + * The Prisma Next agent skills (`prisma-orm-core-concepts`, + * `prisma-orm-migrations`) ship inside the tarball of every package an + * application depends on directly, so the skills a user has always describe * the version they installed. * * That claim is only worth as much as the artifact that proves it, and every @@ -26,7 +27,7 @@ import { publicShells, type ShellName } from '../src/shells'; const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..'); const facades: ShellName[] = ['@prisma/orm-postgres', '@prisma/orm-sqlite', '@prisma/orm-mongo']; -const SKILL_NAME = 'prisma-8'; +const SKILL_NAMES = ['prisma-orm-core-concepts', 'prisma-orm-migrations'] as const; interface Manifest { readonly version: string; @@ -93,10 +94,10 @@ function packAndUnpack(facade: ShellName): string { return join(work, 'package'); } -describe('the skill source in the repository', () => { - it('carries the stamp the version sweep maintains', () => { +describe('the skill sources in the repository', () => { + it.each(SKILL_NAMES)('%s carries the stamp the version sweep maintains', (skillName) => { const rootVersion = manifestAt(repoRoot).version; - const source = readFileSync(join(repoRoot, 'skills', SKILL_NAME, 'SKILL.md'), 'utf8'); + const source = readFileSync(join(repoRoot, 'skills', skillName, 'SKILL.md'), 'utf8'); expect(metadataValue(source, 'library_version')).toBe(rootVersion); }); }); @@ -112,30 +113,32 @@ describe.each(facades)('%s', (facade) => { ); }); - it('carries the whole skill tree in its tarball, stamped with what shipped it', () => { + it('carries every skill tree in its tarball, stamped with what shipped it', () => { const packedRoot = packAndUnpack(facade); - const packedSkillDir = join(packedRoot, 'skills', SKILL_NAME); - - expect( - existsSync(join(packedSkillDir, 'SKILL.md')), - `the ${facade} tarball has no skills/${SKILL_NAME}/SKILL.md`, - ).toBe(true); - - const packedSkill = readFileSync(join(packedSkillDir, 'SKILL.md'), 'utf8'); - expect(metadataValue(packedSkill, 'library')).toBe(facade); - expect(metadataValue(packedSkill, 'library_version')).toBe(manifestAt(packedRoot).version); - - // The tarball and the repository's tracked tree must serve the same - // instructions: the only difference is the package each copy names. - const sourceDir = join(repoRoot, 'skills', SKILL_NAME); - expect(filesUnder(packedSkillDir)).toEqual(filesUnder(sourceDir)); - for (const file of filesUnder(sourceDir)) { - if (file === 'SKILL.md') continue; - expect(readFileSync(join(packedSkillDir, file), 'utf8')).toBe( - readFileSync(join(sourceDir, file), 'utf8'), - ); + for (const skillName of SKILL_NAMES) { + const packedSkillDir = join(packedRoot, 'skills', skillName); + + expect( + existsSync(join(packedSkillDir, 'SKILL.md')), + `the ${facade} tarball has no skills/${skillName}/SKILL.md`, + ).toBe(true); + + const packedSkill = readFileSync(join(packedSkillDir, 'SKILL.md'), 'utf8'); + expect(metadataValue(packedSkill, 'library')).toBe(facade); + expect(metadataValue(packedSkill, 'library_version')).toBe(manifestAt(packedRoot).version); + + // The tarball and the repository's tracked tree must serve the same + // instructions: the only difference is the package each copy names. + const sourceDir = join(repoRoot, 'skills', skillName); + expect(filesUnder(packedSkillDir)).toEqual(filesUnder(sourceDir)); + for (const file of filesUnder(sourceDir)) { + if (file === 'SKILL.md') continue; + expect(readFileSync(join(packedSkillDir, file), 'utf8')).toBe( + readFileSync(join(sourceDir, file), 'utf8'), + ); + } + const sourceSkill = readFileSync(join(sourceDir, 'SKILL.md'), 'utf8'); + expect(packedSkill).toBe(sourceSkill.replace(/^(\s+)library:.*$/m, `$1library: '${facade}'`)); } - const sourceSkill = readFileSync(join(sourceDir, 'SKILL.md'), 'utf8'); - expect(packedSkill).toBe(sourceSkill.replace(/^(\s+)library:.*$/m, `$1library: '${facade}'`)); }, 60_000); }); diff --git a/packages/1-framework/3-tooling/cli/src/commands/init/skill-sources.ts b/packages/1-framework/3-tooling/cli/src/commands/init/skill-sources.ts index f5c616ebbeb8..7802a38da381 100644 --- a/packages/1-framework/3-tooling/cli/src/commands/init/skill-sources.ts +++ b/packages/1-framework/3-tooling/cli/src/commands/init/skill-sources.ts @@ -1,6 +1,6 @@ -// The agent skills ship inside the packages they describe — the `prisma-8` -// skill travels in the `@prisma/orm-*` tarball a project installs — so init -// no longer fetches or installs them from anywhere. Skills setup belongs to +// The agent skills ship inside the packages they describe — the +// `prisma-orm-*` skills travel in the `@prisma/orm-*` tarball a project +// installs — so init no longer fetches or installs them from anywhere. Skills setup belongs to // the family-level `prisma init` command; the only skill work left in // `orm init` is deleting the retired directories below. @@ -9,17 +9,19 @@ // ------------------------------------------------------------------- /** - * Skill directories that predate the consolidated `prisma-8` skill: the - * per-workflow usage cluster (including the renamed - * `prisma-8-migration-review` spelling it briefly shipped under), the - * pre-rename spellings of the consolidated skill and the extension-author - * upgrade skill, any hand-rolled `prisma-next` stub, and the two standalone - * upgrade skills that folded into the `prisma-8` router. Projects initialised - * before those changes carry these as sibling directories in each agent's - * install root; left in place they compete with the current skill for - * activation, so init removes them on every run. + * Skill directories that predate the current `prisma-orm-*` skill set: the + * consolidated `prisma-8` router it replaced, the per-workflow usage cluster + * that preceded the router (including the renamed `prisma-8-migration-review` + * spelling it briefly shipped under), the pre-rename spellings of the + * consolidated skill and the extension-author upgrade skill, any hand-rolled + * `prisma-next` stub, and the two standalone upgrade skills that folded into + * the `prisma-8` router. Projects initialised before those changes carry + * these as sibling directories in each agent's install root; left in place + * they compete with the current skills for activation, so init removes them + * on every run. */ export const RETIRED_SKILL_NAMES = [ + 'prisma-8', 'prisma-next', 'prisma-next-quickstart', 'prisma-next-contract', diff --git a/packages/1-framework/3-tooling/cli/test/commands/init/skill-sources.test.ts b/packages/1-framework/3-tooling/cli/test/commands/init/skill-sources.test.ts index f730e29024ba..a0eeaf722be7 100644 --- a/packages/1-framework/3-tooling/cli/test/commands/init/skill-sources.test.ts +++ b/packages/1-framework/3-tooling/cli/test/commands/init/skill-sources.test.ts @@ -20,10 +20,14 @@ describe('legacy skill cleanup', () => { expect(RETIRED_SKILL_NAMES).toContain('prisma-8-extension-upgrade'); }); + it('retires the consolidated router the prisma-orm-* skills replaced', () => { + expect(RETIRED_SKILL_NAMES).toContain('prisma-8'); + }); + it('names one directory per harness root and retired skill', () => { const dirs = legacySkillDirs(); expect(dirs).toHaveLength(AGENT_SKILL_ROOTS.length * RETIRED_SKILL_NAMES.length); expect(dirs).toContain('.cursor/skills/prisma-next-upgrade'); - expect(dirs).not.toContain('.claude/skills/prisma-8'); + expect(dirs).toContain('.claude/skills/prisma-8'); }); }); diff --git a/packages/1-framework/3-tooling/cli/test/orm/init-scaffold.test.ts b/packages/1-framework/3-tooling/cli/test/orm/init-scaffold.test.ts index 9abcbe05c6b8..6b062b32bb02 100644 --- a/packages/1-framework/3-tooling/cli/test/orm/init-scaffold.test.ts +++ b/packages/1-framework/3-tooling/cli/test/orm/init-scaffold.test.ts @@ -162,7 +162,7 @@ describe('init scaffold', () => { expect(run.exitCode).toBe(0); expect(manifest.scripts?.postinstall).toBeUndefined(); - expect(gitignore).not.toContain('skills/prisma-8/'); + expect(gitignore).not.toContain('skills/prisma-'); }, timeouts.coldTransformImport, ); @@ -175,7 +175,7 @@ describe('init scaffold', () => { const retired = join(projectDir, '.claude/skills/prisma-next-queries'); mkdirSync(retired, { recursive: true }); writeFileSync(join(retired, 'SKILL.md'), '# stale\n', 'utf-8'); - const installed = join(projectDir, '.agents/skills/prisma-8'); + const installed = join(projectDir, '.agents/skills/prisma-orm-core-concepts'); mkdirSync(installed, { recursive: true }); writeFileSync(join(installed, 'SKILL.md'), '# installed\n', 'utf-8'); diff --git a/scripts/check-upgrade-coverage.mjs b/scripts/check-upgrade-coverage.mjs index c22d6b8133f3..b3682ad50bff 100644 --- a/scripts/check-upgrade-coverage.mjs +++ b/scripts/check-upgrade-coverage.mjs @@ -127,8 +127,8 @@ export function parseChangesFrontmatter(src) { return { ok: false, reason: 'changes key absent' }; } -const USER_SKILL_PKG = 'skills/prisma-8/upgrading/app'; -const EXT_SKILL_PKG = 'skills/prisma-8/upgrading/extension'; +const USER_SKILL_PKG = 'skills/prisma-orm-core-concepts/upgrading/app'; +const EXT_SKILL_PKG = 'skills/prisma-orm-core-concepts/upgrading/extension'; /** * Substrates covered by the gate. Each entry pairs a diff pathspec @@ -284,7 +284,7 @@ const TRANSITION_PATH = new RegExp( * Parse a path under `/upgrades//...` and return * the transition segment, or null if the path does not match. * - * Example: `skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/foo.ts` + * Example: `skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/foo.ts` * → `'0.7-to-0.8'` */ export function parseTransitionFromPath(path) { diff --git a/scripts/check-upgrade-coverage.test.mjs b/scripts/check-upgrade-coverage.test.mjs index 8f7c61ff8629..55b29bcaf5de 100644 --- a/scripts/check-upgrade-coverage.test.mjs +++ b/scripts/check-upgrade-coverage.test.mjs @@ -65,21 +65,29 @@ afterEach(() => { describe('parseTransitionFromPath', () => { it('extracts the transition segment for the user skill', () => { assert.equal( - parseTransitionFromPath('skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/foo.ts'), + parseTransitionFromPath( + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/foo.ts', + ), '0.7-to-0.8', ); }); it('extracts the transition segment for the extension skill', () => { assert.equal( parseTransitionFromPath( - 'skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.7-to-0.8/instructions.md', ), '0.7-to-0.8', ); }); it('returns null for paths outside an upgrades// subdirectory', () => { - assert.equal(parseTransitionFromPath('skills/prisma-8/upgrading/app/SKILL.md'), null); - assert.equal(parseTransitionFromPath('skills/prisma-8/upgrading/app/upgrades/'), null); + assert.equal( + parseTransitionFromPath('skills/prisma-orm-core-concepts/upgrading/app/SKILL.md'), + null, + ); + assert.equal( + parseTransitionFromPath('skills/prisma-orm-core-concepts/upgrading/app/upgrades/'), + null, + ); assert.equal(parseTransitionFromPath('examples/foo/bar.ts'), null); }); }); @@ -264,7 +272,10 @@ describe('check-upgrade-coverage — coverage rule (publish style: prev.minor < const result = runScript(['--prev', prev, '--head', 'HEAD']); assert.notEqual(result.status, 0); assert.match(result.stderr, /coverage/); - assert.match(result.stderr, /skills\/prisma-8\/upgrading\/app\/upgrades\/0\.6-to-0\.7/); + assert.match( + result.stderr, + /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades\/0\.6-to-0\.7/, + ); assert.match(result.stderr, /examples\/demo\/src\/main\.ts/); }); @@ -278,7 +289,10 @@ describe('check-upgrade-coverage — coverage rule (publish style: prev.minor < commitAll('head'); const result = runScript(['--prev', prev, '--head', 'HEAD']); assert.notEqual(result.status, 0); - assert.match(result.stderr, /skills\/prisma-8\/upgrading\/extension\/upgrades\/0\.6-to-0\.7/); + assert.match( + result.stderr, + /skills\/prisma-orm-core-concepts\/upgrading\/extension\/upgrades\/0\.6-to-0\.7/, + ); }); it('requires both directories when both substrates change; passes once both are present', () => { @@ -295,15 +309,18 @@ describe('check-upgrade-coverage — coverage rule (publish style: prev.minor < // Neither directory present → both missing. const missingBoth = runScript(['--prev', prev, '--head', 'HEAD']); assert.notEqual(missingBoth.status, 0); - assert.match(missingBoth.stderr, /skills\/prisma-8\/upgrading\/app\/upgrades\/0\.6-to-0\.7/); assert.match( missingBoth.stderr, - /skills\/prisma-8\/upgrading\/extension\/upgrades\/0\.6-to-0\.7/, + /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades\/0\.6-to-0\.7/, + ); + assert.match( + missingBoth.stderr, + /skills\/prisma-orm-core-concepts\/upgrading\/extension\/upgrades\/0\.6-to-0\.7/, ); // Add only the user-skill directory; extension-skill still missing. writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.6-to-0.7/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.6-to-0.7/instructions.md', '---\nfrom: "0.6"\nto: "0.7"\nchanges: []\n---\n', ); commitAll('add user-skill dir'); @@ -311,16 +328,16 @@ describe('check-upgrade-coverage — coverage rule (publish style: prev.minor < assert.notEqual(missingExt.status, 0); assert.match( missingExt.stderr, - /skills\/prisma-8\/upgrading\/extension\/upgrades\/0\.6-to-0\.7/, + /skills\/prisma-orm-core-concepts\/upgrading\/extension\/upgrades\/0\.6-to-0\.7/, ); assert.doesNotMatch( missingExt.stderr, - /skills\/prisma-8\/upgrading\/app\/upgrades\/0\.6-to-0\.7/, + /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades\/0\.6-to-0\.7/, ); // Add the extension-skill directory; both present → pass. writeRepoFile( - 'skills/prisma-8/upgrading/extension/upgrades/0.6-to-0.7/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.6-to-0.7/instructions.md', '---\nfrom: "0.6"\nto: "0.7"\nchanges: []\n---\n', ); commitAll('add ext-skill dir'); @@ -338,7 +355,10 @@ describe('check-upgrade-coverage — coverage rule (publish style: prev.minor < commitAll('head'); const result = runScript(['--mode', 'publish', '--head', 'HEAD']); assert.notEqual(result.status, 0); - assert.match(result.stderr, /skills\/prisma-8\/upgrading\/app\/upgrades\/0\.6-to-0\.7/); + assert.match( + result.stderr, + /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades\/0\.6-to-0\.7/, + ); }); it('publish mode: default --prev skips pre-release tags and picks the last stable v[0-9]* tag', () => { @@ -357,11 +377,11 @@ describe('check-upgrade-coverage — coverage rule (publish style: prev.minor < // both authored mid-cycle. writeRepoFile('examples/demo/src/main.ts', 'b\n'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.6-to-0.7/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.6-to-0.7/instructions.md', '---\nfrom: "0.6"\nto: "0.7"\nchanges: []\n---\n', ); writeRepoFile( - 'skills/prisma-8/upgrading/extension/upgrades/0.6-to-0.7/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.6-to-0.7/instructions.md', '---\nfrom: "0.6"\nto: "0.7"\nchanges: []\n---\n', ); commitAll('feature with upgrade entry'); @@ -391,7 +411,10 @@ describe('check-upgrade-coverage — coverage rule (PR style: prev.minor === hea commitAll('head'); const result = runScript(['--prev', prev, '--head', 'HEAD']); assert.notEqual(result.status, 0); - assert.match(result.stderr, /skills\/prisma-8\/upgrading\/app\/upgrades\/0\.7-to-0\.8/); + assert.match( + result.stderr, + /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades\/0\.7-to-0\.8/, + ); assert.doesNotMatch(result.stderr, /upgrades\/0\.6-to-0\.7/); }); @@ -401,7 +424,7 @@ describe('check-upgrade-coverage — coverage rule (PR style: prev.minor === hea const prev = git('rev-parse', 'HEAD'); writeRepoFile('examples/demo/src/main.ts', 'export const a = 2;\n'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\n', ); commitAll('head'); @@ -463,14 +486,14 @@ describe('check-upgrade-coverage — new-entries rule', () => { // (allowed transitions are 0.7-to-0.8 and 0.8-to-0.9). writePackageJson('0.7.0'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.6-to-0.7/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.6-to-0.7/instructions.md', '---\nfrom: "0.6"\nto: "0.7"\nchanges: []\n---\n', ); commitAll('prev'); const prev = git('rev-parse', 'HEAD'); writePackageJson('0.8.0'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.6-to-0.7/new-script.ts', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.6-to-0.7/new-script.ts', 'export const x = 1;\n', ); commitAll('head'); @@ -481,8 +504,8 @@ describe('check-upgrade-coverage — new-entries rule', () => { // Either of the allowed transitions should be mentioned. assert.match(result.stderr, /0\.7-to-0\.8|0\.8-to-0\.9/); // The "move the new file under" diagnostic should name both cluster paths. - assert.match(result.stderr, /skills\/prisma-8\/upgrading\/app\/upgrades/); - assert.match(result.stderr, /skills\/prisma-8\/upgrading\/extension\/upgrades/); + assert.match(result.stderr, /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades/); + assert.match(result.stderr, /skills\/prisma-orm-core-concepts\/upgrading\/extension\/upgrades/); }); it('publish mode: accepts an added file under either prev→head or head→head+1', () => { @@ -493,11 +516,11 @@ describe('check-upgrade-coverage — new-entries rule', () => { const prev = git('rev-parse', 'HEAD'); writePackageJson('0.8.0'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\n', ); writeRepoFile( - 'skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.8-to-0.9/instructions.md', '---\nfrom: "0.8"\nto: "0.9"\nchanges: []\n---\n', ); commitAll('head'); @@ -514,7 +537,7 @@ describe('check-upgrade-coverage — new-entries rule', () => { const prev = git('rev-parse', 'HEAD'); writePackageJson('0.7.0'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\n', ); commitAll('head'); @@ -530,7 +553,7 @@ describe('check-upgrade-coverage — new-entries rule', () => { const prev = git('rev-parse', 'HEAD'); writePackageJson('0.7.0'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.6-to-0.7/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.6-to-0.7/instructions.md', '---\nfrom: "0.6"\nto: "0.7"\nchanges: []\n---\n', ); commitAll('head'); @@ -539,8 +562,8 @@ describe('check-upgrade-coverage — new-entries rule', () => { assert.match(result.stderr, /new-entries-stale-transition/); assert.match(result.stderr, /0\.7-to-0\.8/); // The "move the new file under" diagnostic should name both cluster paths. - assert.match(result.stderr, /skills\/prisma-8\/upgrading\/app\/upgrades/); - assert.match(result.stderr, /skills\/prisma-8\/upgrading\/extension\/upgrades/); + assert.match(result.stderr, /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades/); + assert.match(result.stderr, /skills\/prisma-orm-core-concepts\/upgrading\/extension\/upgrades/); }); it('treats a git mv from outside the upgrades tree into a valid transition directory as a move, not an addition', () => { @@ -560,11 +583,11 @@ describe('check-upgrade-coverage — new-entries rule', () => { // Simulate `git mv` by writing the file at the new path (same content) // and removing the old path. Git's rename detection (-M) infers the move. writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.6-to-0.7/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.6-to-0.7/instructions.md', '---\nfrom: "0.6"\nto: "0.7"\nchanges: []\n---\n', ); git('rm', 'packages/0-shared/upgrade-skill/upgrades/0.6-to-0.7/instructions.md'); - git('add', 'skills/prisma-8/upgrading/app/upgrades/0.6-to-0.7/instructions.md'); + git('add', 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.6-to-0.7/instructions.md'); git('commit', '-m', 'move upgrade instructions to new cluster'); const result = runScript(['--prev', prev, '--head', 'HEAD']); @@ -575,7 +598,7 @@ describe('check-upgrade-coverage — new-entries rule', () => { it('accepts a modification to an existing file in a stale transition directory', () => { writePackageJson('0.7.0'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.6-to-0.7/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.6-to-0.7/instructions.md', '---\nfrom: "0.6"\nto: "0.7"\nchanges: []\n---\n# v1\n', ); commitAll('prev'); @@ -583,7 +606,7 @@ describe('check-upgrade-coverage — new-entries rule', () => { writePackageJson('0.8.0'); // Same path — modification, not add. writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.6-to-0.7/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.6-to-0.7/instructions.md', '---\nfrom: "0.6"\nto: "0.7"\nchanges: []\n---\n# v2 — bug fix\n', ); commitAll('head'); @@ -608,25 +631,34 @@ describe('check-upgrade-coverage — skip-publish chain (head.minor > prev.minor const missingBoth = runScript(['--prev', prev, '--head', 'HEAD']); assert.notEqual(missingBoth.status, 0); - assert.match(missingBoth.stderr, /skills\/prisma-8\/upgrading\/app\/upgrades\/0\.7-to-0\.8/); - assert.match(missingBoth.stderr, /skills\/prisma-8\/upgrading\/app\/upgrades\/0\.8-to-0\.9/); + assert.match( + missingBoth.stderr, + /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades\/0\.7-to-0\.8/, + ); + assert.match( + missingBoth.stderr, + /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades\/0\.8-to-0\.9/, + ); assert.doesNotMatch(missingBoth.stderr, /upgrades\/0\.7-to-0\.9/); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\n', ); commitAll('add 0.7-to-0.8'); const missingSecond = runScript(['--prev', prev, '--head', 'HEAD']); assert.notEqual(missingSecond.status, 0); - assert.match(missingSecond.stderr, /skills\/prisma-8\/upgrading\/app\/upgrades\/0\.8-to-0\.9/); + assert.match( + missingSecond.stderr, + /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades\/0\.8-to-0\.9/, + ); assert.doesNotMatch( missingSecond.stderr, - /skills\/prisma-8\/upgrading\/app\/upgrades\/0\.7-to-0\.8[^/]*$/m, + /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades\/0\.7-to-0\.8[^/]*$/m, ); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.8-to-0.9/instructions.md', '---\nfrom: "0.8"\nto: "0.9"\nchanges: []\n---\n', ); commitAll('add 0.8-to-0.9'); @@ -640,23 +672,23 @@ describe('check-upgrade-coverage — skip-publish chain (head.minor > prev.minor const prev = git('rev-parse', 'HEAD'); writePackageJson('0.9.0'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\n', ); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.8-to-0.9/instructions.md', '---\nfrom: "0.8"\nto: "0.9"\nchanges: []\n---\n', ); writeRepoFile( - 'skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\n', ); writeRepoFile( - 'skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.8-to-0.9/instructions.md', '---\nfrom: "0.8"\nto: "0.9"\nchanges: []\n---\n', ); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.9-to-0.10/instructions.md', '---\nfrom: "0.9"\nto: "0.10"\nchanges: []\n---\n', ); commitAll('head'); @@ -671,15 +703,15 @@ describe('check-upgrade-coverage — skip-publish chain (head.minor > prev.minor writePackageJson('0.9.0'); // Coverage directories for the chain so coverage isn't the failure mode. writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\n', ); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.8-to-0.9/instructions.md', '---\nfrom: "0.8"\nto: "0.9"\nchanges: []\n---\n', ); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.6-to-0.7/new-script.ts', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.6-to-0.7/new-script.ts', 'export const x = 1;\n', ); commitAll('head'); @@ -708,8 +740,8 @@ describe('check-upgrade-coverage — skip-publish chain (head.minor > prev.minor .filter((v) => v.rule === 'coverage' && v.substrate === 'examples/') .map((v) => v.requiredDir); assert.deepEqual(coverageDirs.sort(), [ - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8', - 'skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.8-to-0.9', ]); }); }); @@ -734,12 +766,12 @@ describe('check-upgrade-coverage — release-candidate release PR', () => { assert.notEqual(missing.status, 0); assert.match( missing.stderr, - /skills\/prisma-8\/upgrading\/app\/upgrades\/8\.0\.0-rc\.1-to-8\.0\.0-rc\.2/, + /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades\/8\.0\.0-rc\.1-to-8\.0\.0-rc\.2/, ); assert.doesNotMatch(missing.stderr, /upgrades\/8\.0-to-8\.1/); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md', '---\nfrom: "8.0.0-rc.1"\nto: "8.0.0-rc.2"\nchanges: []\n---\n', ); commitAll('record the rc.1 → rc.2 entry'); @@ -753,7 +785,7 @@ describe('check-upgrade-coverage — release-candidate release PR', () => { const prev = git('rev-parse', 'HEAD'); writeRepoFile('examples/demo/src/main.ts', 'export const a = 2;\n'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md', '---\nfrom: "8.0.0-rc.1"\nto: "8.0.0-rc.2"\nchanges: []\n---\n', ); commitAll('head'); @@ -850,7 +882,7 @@ describe('check-upgrade-coverage — per-PR correspondence rule', () => { it('substrate touched + in-flight instructions.md NOT in diff → violation', () => { writePackageJson('0.7.0'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\n', ); commitAll('prev — directory already exists'); @@ -862,7 +894,7 @@ describe('check-upgrade-coverage — per-PR correspondence rule', () => { assert.match(result.stderr, /per-pr-declaration/); assert.match( result.stderr, - /skills\/prisma-8\/upgrading\/app\/upgrades\/0\.7-to-0\.8\/instructions\.md/, + /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades\/0\.7-to-0\.8\/instructions\.md/, ); }); @@ -872,7 +904,7 @@ describe('check-upgrade-coverage — per-PR correspondence rule', () => { const prev = git('rev-parse', 'HEAD'); writeRepoFile('examples/demo/src/main.ts', 'export const a = 2;\n'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges:\n - id: my-change\n summary: Some migration step.\n---\n', ); commitAll('head'); @@ -886,7 +918,7 @@ describe('check-upgrade-coverage — per-PR correspondence rule', () => { const prev = git('rev-parse', 'HEAD'); writeRepoFile('examples/demo/src/main.ts', 'export const a = 2;\n'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\n', ); commitAll('head'); @@ -910,11 +942,11 @@ describe('check-upgrade-coverage — per-PR correspondence rule', () => { // correspondence check then fires independently for each cluster. writePackageJson('0.7.0'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\n', ); writeRepoFile( - 'skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\n', ); commitAll('prev — both directories already exist'); @@ -924,7 +956,7 @@ describe('check-upgrade-coverage — per-PR correspondence rule', () => { writeRepoFile('examples/demo/src/main.ts', 'b\n'); writeRepoFile('packages/3-extensions/pgvector/src/main.ts', 'b\n'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\nupdated\n', ); commitAll('head — only user-skill instructions.md updated'); @@ -933,15 +965,15 @@ describe('check-upgrade-coverage — per-PR correspondence rule', () => { assert.match(missingExt.stderr, /per-pr-declaration/); assert.match( missingExt.stderr, - /skills\/prisma-8\/upgrading\/extension\/upgrades\/0\.7-to-0\.8\/instructions\.md/, + /skills\/prisma-orm-core-concepts\/upgrading\/extension\/upgrades\/0\.7-to-0\.8\/instructions\.md/, ); assert.doesNotMatch( missingExt.stderr, - /skills\/prisma-8\/upgrading\/app\/upgrades\/0\.7-to-0\.8\/instructions\.md/, + /skills\/prisma-orm-core-concepts\/upgrading\/app\/upgrades\/0\.7-to-0\.8\/instructions\.md/, ); writeRepoFile( - 'skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\nupdated\n', ); commitAll('head — both instructions.md updated'); @@ -955,7 +987,7 @@ describe('check-upgrade-coverage — per-PR correspondence rule', () => { const prev = git('rev-parse', 'HEAD'); writeRepoFile('examples/demo/src/main.ts', 'export const a = 2;\n'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\n---\n', ); commitAll('head — instructions.md missing changes key'); @@ -969,11 +1001,11 @@ describe('check-upgrade-coverage — translation-irrelevant substrate diffs need function seedTransitionDir() { writePackageJson('0.7.0'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\n', ); writeRepoFile( - 'skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.7-to-0.8/instructions.md', '---\nfrom: "0.7"\nto: "0.8"\nchanges: []\n---\n', ); } @@ -1233,7 +1265,7 @@ describe('check-upgrade-coverage — release sweep per-PR declaration', () => { writePackageJson('8.0.0-rc.3'); writeRepoFile('examples/demo/package.json', '{"name":"demo","version":"8.0.0-rc.3"}\n'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md', '---\nfrom: "8.0.0-rc.3"\nto: "8.0.0-rc.4"\nchanges: []\n---\n', ); commitAll('prev, directory already recorded by an earlier PR'); @@ -1252,7 +1284,7 @@ describe('check-upgrade-coverage — release sweep per-PR declaration', () => { writeRepoFile('examples/demo/package.json', '{"name":"demo","version":"8.0.0-rc.3"}\n'); writeRepoFile('examples/demo/src/main.ts', 'export const a = 1;\n'); writeRepoFile( - 'skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md', + 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md', '---\nfrom: "8.0.0-rc.3"\nto: "8.0.0-rc.4"\nchanges: []\n---\n', ); commitAll('prev'); diff --git a/scripts/lint-throws.test.mjs b/scripts/lint-throws.test.mjs index 5811de4e37c2..42b8605b99f7 100644 --- a/scripts/lint-throws.test.mjs +++ b/scripts/lint-throws.test.mjs @@ -89,7 +89,9 @@ describe('filterNoBareThrowDiags', () => { { category: 'plugin', message: msg, - location: { path: 'skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/codemod.ts' }, + location: { + path: 'skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.9-to-0.10/codemod.ts', + }, }, { category: 'plugin', message: msg, location: { path: 'packages/x/src/scripts.ts' } }, { category: 'plugin', message: msg, location: { path: 'packages/x/src/thing.ts' } }, diff --git a/scripts/set-version.ts b/scripts/set-version.ts index f5b492114732..abb3d8e15497 100755 --- a/scripts/set-version.ts +++ b/scripts/set-version.ts @@ -10,6 +10,7 @@ import { rewriteWorkspaceDeps, stampSkillMetadata, } from './set-version-utils.ts'; +import { SKILL_NAMES } from './sync-package-skills.ts'; const rootDir = path.dirname(path.dirname(fileURLToPath(import.meta.url))); @@ -92,12 +93,14 @@ for (const manifestPath of trackedManifests) { // The user-facing skills ship inside the `@prisma/orm-*` tarballs and carry // the version they were published with in their frontmatter, so a consumer -// can tell whether its synced copy still matches its installed packages. -const skillPath = path.join(rootDir, 'skills', 'prisma-8', 'SKILL.md'); -await fs.writeFile( - skillPath, - stampSkillMetadata(await fs.readFile(skillPath, 'utf-8'), 'library_version', version), -); -console.log(`Stamped ${path.relative(rootDir, skillPath)} with library_version ${version}`); +// can tell whether its synced copies still match its installed packages. +for (const skillName of SKILL_NAMES) { + const skillPath = path.join(rootDir, 'skills', skillName, 'SKILL.md'); + await fs.writeFile( + skillPath, + stampSkillMetadata(await fs.readFile(skillPath, 'utf-8'), 'library_version', version), + ); + console.log(`Stamped ${path.relative(rootDir, skillPath)} with library_version ${version}`); +} console.log(`\nDone! Updated ${updatedCount} packages.`); diff --git a/scripts/sync-package-skills.test.mjs b/scripts/sync-package-skills.test.mjs new file mode 100644 index 000000000000..72dbb3a26393 --- /dev/null +++ b/scripts/sync-package-skills.test.mjs @@ -0,0 +1,102 @@ +import assert from 'node:assert/strict'; +import { execFile } from 'node:child_process'; +import { readdirSync, readFileSync, statSync } from 'node:fs'; +import { readdir, readFile, rm, stat } from 'node:fs/promises'; +import { dirname, join, relative } from 'node:path'; +import { test } from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { promisify } from 'node:util'; + +import { SKILL_ANCHOR_PACKAGES, SKILL_NAMES, syncPackageSkills } from './sync-package-skills.ts'; + +const execFileAsync = promisify(execFile); +const repoRoot = dirname(dirname(fileURLToPath(import.meta.url))); +const scriptPath = join(repoRoot, 'scripts', 'sync-package-skills.ts'); + +const PACKAGE_NAME = '@prisma/orm-postgres'; +const packageDir = join(repoRoot, SKILL_ANCHOR_PACKAGES.get(PACKAGE_NAME)); +const skillsDir = join(packageDir, 'skills'); + +function filesUnder(dir) { + return readdirSync(dir, { recursive: true, encoding: 'utf-8' }) + .filter((entry) => statSync(join(dir, entry)).isFile()) + .sort(); +} + +function assertSyncedTreeMatchesSource() { + for (const skillName of SKILL_NAMES) { + const sourceDir = join(repoRoot, 'skills', skillName); + const destDir = join(skillsDir, skillName); + assert.deepEqual(filesUnder(destDir), filesUnder(sourceDir), `${skillName} file list differs`); + for (const file of filesUnder(sourceDir)) { + if (file === 'SKILL.md') continue; + assert.equal( + readFileSync(join(destDir, file), 'utf-8'), + readFileSync(join(sourceDir, file), 'utf-8'), + `${skillName}/${file} differs from source`, + ); + } + } +} + +function assertNoWorkdirLeftovers() { + const leftovers = readdirSync(packageDir).filter( + (entry) => entry.startsWith('skills.staging-') || entry.startsWith('skills.trash-'), + ); + assert.deepEqual(leftovers, [], 'staging/trash directories left behind'); +} + +test('concurrent syncs of one package all succeed and leave an intact tree', async () => { + const rounds = 4; + for (let round = 0; round < rounds; round += 1) { + await Promise.all( + Array.from({ length: 3 }, () => execFileAsync('node', [scriptPath, PACKAGE_NAME])), + ); + } + assertNoWorkdirLeftovers(); + assertSyncedTreeMatchesSource(); +}); + +test('concurrent in-process syncs of one package all succeed', async () => { + // Start cold so every call takes the swap path rather than the no-op path. + await rm(skillsDir, { recursive: true, force: true }); + await Promise.all(Array.from({ length: 4 }, () => syncPackageSkills(PACKAGE_NAME))); + assertNoWorkdirLeftovers(); + assertSyncedTreeMatchesSource(); +}); + +test('a re-sync with unchanged content leaves the directory untouched', async () => { + await syncPackageSkills(PACKAGE_NAME); + const before = await stat(skillsDir); + await syncPackageSkills(PACKAGE_NAME); + const after = await stat(skillsDir); + assert.equal(after.ino, before.ino, 'no-op sync must not replace the skills directory'); + assertNoWorkdirLeftovers(); +}); + +test('a reader never observes an absent file while warm syncs run', async () => { + await syncPackageSkills(PACKAGE_NAME); + const files = (await readdir(skillsDir, { recursive: true, withFileTypes: true })) + .filter((entry) => entry.isFile()) + .map((entry) => relative(skillsDir, join(entry.parentPath, entry.name))); + assert.ok(files.length > 0, 'expected a populated skills tree'); + + let stopped = false; + const reader = (async () => { + while (!stopped) { + // A pack's tar phase walks paths exactly like this; every read must + // succeed while warm re-syncs run concurrently. + await Promise.all(files.map((file) => readFile(join(skillsDir, file)))); + } + })(); + + try { + for (let i = 0; i < 5; i += 1) { + await execFileAsync('node', [scriptPath, PACKAGE_NAME]); + } + } finally { + stopped = true; + } + await reader; + assertNoWorkdirLeftovers(); +}); diff --git a/scripts/sync-package-skills.ts b/scripts/sync-package-skills.ts index 17c0e7a65fe6..63b20d6106d8 100644 --- a/scripts/sync-package-skills.ts +++ b/scripts/sync-package-skills.ts @@ -1,13 +1,13 @@ #!/usr/bin/env node /** - * Copies the user-facing `skills/prisma-8/` tree into the packages that ship - * it, stamping each copy with the package it now belongs to. + * Copies the user-facing `skills/prisma-orm-*` trees into the packages that + * ship them, stamping each copy with the package it now belongs to. * * Usage: node scripts/sync-package-skills.ts [...] * * Run from each shipping package's `prepack`, so the tarball always carries - * the skill tree that matches the code beside it. The copies are build + * the skill trees that match the code beside it. The copies are build * output: they are gitignored, and `files` carries them into the tarball. */ @@ -31,36 +31,115 @@ export const SKILL_ANCHOR_PACKAGES: ReadonlyMap = new Map([ ['@prisma/orm-mongo', 'packages/9-public/@prisma/orm-mongo'], ]); -export const SKILL_NAME = 'prisma-8'; +export const SKILL_NAMES = ['prisma-orm-core-concepts', 'prisma-orm-migrations'] as const; -export async function syncPackageSkills(packageName: string): Promise { +/** Every file under `dir` as sorted relative paths, or null when `dir` is absent. */ +async function listFiles(dir: string): Promise { + try { + const entries = await fs.readdir(dir, { recursive: true, withFileTypes: true }); + return entries + .filter((entry) => entry.isFile()) + .map((entry) => path.relative(dir, path.join(entry.parentPath, entry.name))) + .sort(); + } catch { + return null; + } +} + +/** Whether `actualDir` already holds exactly the tree `expectedDir` holds. */ +async function treesMatch(expectedDir: string, actualDir: string): Promise { + const [expected, actual] = await Promise.all([listFiles(expectedDir), listFiles(actualDir)]); + if (expected === null || actual === null) return false; + if (expected.length !== actual.length) return false; + if (expected.some((file, index) => file !== actual[index])) return false; + for (const file of expected) { + const [expectedContent, actualContent] = await Promise.all([ + fs.readFile(path.join(expectedDir, file)), + fs.readFile(path.join(actualDir, file)).catch(() => null), + ]); + if (actualContent === null || !expectedContent.equals(actualContent)) return false; + } + return true; +} + +/** + * Concurrent in-process calls must not share work directories either, so the + * pid is paired with a per-invocation counter. + */ +let invocation = 0; + +export async function syncPackageSkills(packageName: string): Promise { + invocation += 1; + const workId = `${process.pid}-${invocation}`; const packageDir = SKILL_ANCHOR_PACKAGES.get(packageName); if (packageDir === undefined) { const shipping = [...SKILL_ANCHOR_PACKAGES.keys()].join(', '); - throw new Error(`${packageName} does not ship the ${SKILL_NAME} skill; expected ${shipping}`); + throw new Error(`${packageName} does not ship the Prisma skills; expected ${shipping}`); } - const source = path.join(rootDir, 'skills', SKILL_NAME); - const destination = path.join(rootDir, packageDir, 'skills', SKILL_NAME); - await fs.rm(destination, { recursive: true, force: true }); - await fs.cp(source, destination, { recursive: true }); + // Concurrent packs of the same package (tarball tests run in parallel and + // each pack re-runs this prepack) must never observe a half-copied tree, so + // the copies are staged in a temporary sibling and swapped in with renames. + const skillsDir = path.join(rootDir, packageDir, 'skills'); + const stagingDir = `${skillsDir}.staging-${workId}`; + const results = SKILL_NAMES.map((skillName) => path.join(skillsDir, skillName)); + await fs.rm(stagingDir, { recursive: true, force: true }); + for (const skillName of SKILL_NAMES) { + const source = path.join(rootDir, 'skills', skillName); + const staged = path.join(stagingDir, skillName); + await fs.cp(source, staged, { recursive: true }); + + // The source tree names one canonical package; each copy names its own, + // so a consumer reading the copy sees the package it resolved it from. + const skillMd = path.join(staged, 'SKILL.md'); + await fs.writeFile( + skillMd, + stampSkillMetadata(await fs.readFile(skillMd, 'utf-8'), 'library', packageName), + ); + } - // The source tree names one canonical package; each copy names its own, so - // a consumer reading the copy sees the package it resolved it from. - const skillMd = path.join(destination, 'SKILL.md'); - await fs.writeFile( - skillMd, - stampSkillMetadata(await fs.readFile(skillMd, 'utf-8'), 'library', packageName), - ); + // A concurrent pack's tar phase may be reading `skills/` right now, after + // its own prepack returned. When the staged tree is already what is on + // disk — every pack after the first — leave the directory untouched so that + // reader can never observe an absent or partial tree. + if (await treesMatch(stagingDir, skillsDir)) { + await fs.rm(stagingDir, { recursive: true, force: true }); + return results; + } + + // The swap itself can collide with a concurrent prepack landing its own + // rename first. Every copy carries identical content, so retrying the whole + // swap converges instead of failing the pack. Retiring the old tree via + // rename (not a progressive recursive delete) keeps the path's absence down + // to the instant between the two renames. + const trashDir = `${skillsDir}.trash-${workId}`; + for (let attempt = 1; ; attempt += 1) { + try { + await fs.rm(trashDir, { recursive: true, force: true }); + await fs.rename(skillsDir, trashDir).catch((error: NodeJS.ErrnoException) => { + if (error.code !== 'ENOENT') throw error; + }); + await fs.rename(stagingDir, skillsDir); + break; + } catch (error) { + if (attempt >= 5) { + await fs.rm(stagingDir, { recursive: true, force: true }); + await fs.rm(trashDir, { recursive: true, force: true }); + throw error; + } + } + } + await fs.rm(trashDir, { recursive: true, force: true }); - return destination; + return results; } if (import.meta.url === `file://${process.argv[1]}`) { const requested = process.argv.slice(2); const targets = requested.length > 0 ? requested : [...SKILL_ANCHOR_PACKAGES.keys()]; for (const packageName of targets) { - const destination = await syncPackageSkills(packageName); - console.log(`Copied skills/${SKILL_NAME} to ${path.relative(rootDir, destination)}`); + for (const destination of await syncPackageSkills(packageName)) { + console.log(`Copied ${path.relative(rootDir, destination)}`); + } } } diff --git a/skills-contrib/draft-release-notes/SKILL.md b/skills-contrib/draft-release-notes/SKILL.md index e8f6ac110c01..8141d3defc6a 100644 --- a/skills-contrib/draft-release-notes/SKILL.md +++ b/skills-contrib/draft-release-notes/SKILL.md @@ -129,8 +129,8 @@ A breaking change shipping in this release has a matching upgrade-instructions d **Recipe links must be absolute, tag-pinned URLs** — `https://github.com/prisma/orm/blob/v$NEXT/...`. The notes file becomes the GitHub Release body via `--notes-file`, and the Release page does **not** reliably resolve repo-relative links, so a relative recipe path would publish as a dead migration link. Pinning to the release tag (`/blob/v$NEXT/`) means the link always resolves and never rots as the recipe tree evolves on `main`: -- User-facing migrations: `https://github.com/prisma/orm/blob/v$NEXT/skills/prisma-8/upgrading/app/upgrades//` -- Extension-author migrations: `https://github.com/prisma/orm/blob/v$NEXT/skills/prisma-8/upgrading/extension/upgrades//` +- User-facing migrations: `https://github.com/prisma/orm/blob/v$NEXT/skills/prisma-orm-core-concepts/upgrading/app/upgrades//` +- Extension-author migrations: `https://github.com/prisma/orm/blob/v$NEXT/skills/prisma-orm-core-concepts/upgrading/extension/upgrades//` A breaking change can affect one or both audiences — link whichever recipe directories exist. @@ -251,7 +251,7 @@ Cutting `v0.12.0` from `origin/main` (previous stable tag `v0.11.0`). 3. PR #1240's title is `TML-2536: contract deserializer seam`. Read TML-2536 in Linear → the user-facing outcome is "contract deserialization now goes through an explicit adapter seam". Write that outcome in public words; cite #1240, not TML-2536. 4. Triage: #1240 changes the contract format → **always-include, breaking**. A CI-cache tweak (#1237) and a test-only refactor (#1239) → **default-exclude**, dropped silently. A new `includeMany` capability (#1234) → feature. A null-handling bug fix (#1242) → fix. First-time contributor @somebody on #1238. 5. Categorize: Breaking changes (#1240) → Features (#1234) → Fixes (#1242) → New contributors (@somebody, #1238). -6. The breaking change's transition is `0.11-to-0.12`. The recipe dir `skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/` exists in the checkout → the breaking note links it as a tag-pinned URL, `https://github.com/prisma/orm/blob/v0.12.0/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/`. (If it were absent, the note would describe the required adapter migration inline instead.) +6. The breaking change's transition is `0.11-to-0.12`. The recipe dir `skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/` exists in the checkout → the breaking note links it as a tag-pinned URL, `https://github.com/prisma/orm/blob/v0.12.0/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/`. (If it were absent, the note would describe the required adapter migration inline instead.) 7. #1240 is a code-visible contract-shape/runtime change, so it earns a before/after example — lifted from the `0.11-to-0.12` recipe (a TS runtime change, so a `ts` fence). @somebody's contributor line, with absolute links: `- [@somebody](https://github.com/somebody) made their first contribution in [#1238](https://github.com/prisma/orm/pull/1238)`. 8. Write `docs/releases/v0.12.0.md` (every PR ref + handle an absolute link; the breaking entry carries a before/after): @@ -262,7 +262,7 @@ Contract deserialization gains an explicit adapter seam, and queries can now eag ## Breaking changes -- **Contract deserialization requires an adapter seam** — deserialization now goes through an explicit seam adapter; existing code must register one. See the [0.11-to-0.12 upgrade recipe](https://github.com/prisma/orm/blob/v0.12.0/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/). ([#1240](https://github.com/prisma/orm/pull/1240)) +- **Contract deserialization requires an adapter seam** — deserialization now goes through an explicit seam adapter; existing code must register one. See the [0.11-to-0.12 upgrade recipe](https://github.com/prisma/orm/blob/v0.12.0/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/). ([#1240](https://github.com/prisma/orm/pull/1240)) Before: diff --git a/skills-contrib/record-upgrade-instructions/SKILL.md b/skills-contrib/record-upgrade-instructions/SKILL.md index 62a622f1bfe6..0e3082f495b7 100644 --- a/skills-contrib/record-upgrade-instructions/SKILL.md +++ b/skills-contrib/record-upgrade-instructions/SKILL.md @@ -19,8 +19,8 @@ This skill fires on PRs **inside this repo** that make a breaking change to Pris The published skills you will be authoring entries into: -- `skills/prisma-8/upgrading/app/` — the upgrading branch of the `prisma-8` skill, shipped inside the `@prisma/orm-*` tarballs. **Audience: users of Prisma Next** (consumers of the public package API: `@internal/postgres`, `@internal/mongo`, the contract files in `prisma/`, on-disk migration shape). -- `skills/prisma-8/upgrading/extension/` — the extension-author half of the same branch. **Audience: authors of Prisma Next extensions** (consumers of the framework SPI: `@internal/contract`, `@internal/framework-components`, `@internal/migration-tools`, etc.). +- `skills/prisma-orm-core-concepts/upgrading/app/` — the upgrading branch of the `prisma-orm-core-concepts` skill, shipped inside the `@prisma/orm-*` tarballs. **Audience: users of Prisma Next** (consumers of the public package API: `@internal/postgres`, `@internal/mongo`, the contract files in `prisma/`, on-disk migration shape). +- `skills/prisma-orm-core-concepts/upgrading/extension/` — the extension-author half of the same branch. **Audience: authors of Prisma Next extensions** (consumers of the framework SPI: `@internal/contract`, `@internal/framework-components`, `@internal/migration-tools`, etc.). The two skill clusters are independent (no shared content). Cross-audience breaking changes — where the same on-disk transformation applies to both substrates — are recorded *separately* in each cluster, including duplicated colocated scripts. @@ -44,8 +44,8 @@ Two mechanical signals, each tied to one destination package: | Substrate touched by the PR | Destination skill | | ------------------------------ | -------------------------------------------------------------- | -| `examples/` | `skills/prisma-8/upgrading/app/` | -| `packages/3-extensions/` | `skills/prisma-8/upgrading/extension/` | +| `examples/` | `skills/prisma-orm-core-concepts/upgrading/app/` | +| `packages/3-extensions/` | `skills/prisma-orm-core-concepts/upgrading/extension/` | | Both | Both — duplicated entries (see below) | The substrate diff is the signal that an entry is required. The agent fixing the red tests in those substrates sees the signal directly; the reviewer sees the same diff. The release-pipeline check (`pnpm check:upgrade-coverage`) enforces the outcome — a substrate diff without the matching directory fails the PR. @@ -67,7 +67,7 @@ For each PR that hits one or both signals, walk these steps in order. 2. **Identify the touched substrate(s).** Compute `git diff origin/..HEAD` restricted to `examples/` and to `packages/3-extensions/`. Each non-empty substrate corresponds to one destination package per the routing table above. The "both" case is normal — the rare PR (e.g. a structural on-disk migration shape change) touches both. -3. **Find or create the directory in each destination.** For each destination, the directory is `/upgrades//` from step 1 (so e.g. `skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/` for the user-skill). If the directory already exists (an earlier PR on the same transition created it, or the placeholder shipped with the initial mechanism PR is still there), **do not create a duplicate** — append a new entry to the existing `instructions.md`'s `changes[]` array. +3. **Find or create the directory in each destination.** For each destination, the directory is `/upgrades//` from step 1 (so e.g. `skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/` for the user-skill). If the directory already exists (an earlier PR on the same transition created it, or the placeholder shipped with the initial mechanism PR is still there), **do not create a duplicate** — append a new entry to the existing `instructions.md`'s `changes[]` array. 4. **Write the entry into `instructions.md`.** Each `changes[]` entry carries an `id` (kebab-case, unique within the transition), a one-line `summary`, an optional `detection` block (glob + content predicate the consumer's agent runs to know whether the change applies to that consumer's project), and an optional `script:` reference (relative path to a colocated script next to `instructions.md`). For changes that need agent reasoning across the codebase rather than a deterministic script, the entry omits `script:` and the agent follows the prose body of `instructions.md` instead. @@ -147,7 +147,7 @@ The PR that introduces the breaking change must contain, in addition to the fram - **The new entry directory in each affected skill** — `/upgrades//instructions.md` plus any colocated scripts (the in-flight transition being the one determined in step 1 of the authoring workflow). - **The post-instructions state of every affected substrate** — these substrates would have been left broken without the entry; the entry's effect on the substrate *is* the diff that brings them back to green. The `` substrate state and the validation-by-execution output state must be identical outside the substrate's `test/` directories, which the entry neither writes nor updates. -- **A reference in the PR description naming each entry directory** (e.g. *"Adds entries to `skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/` and `skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/`."*). +- **A reference in the PR description naming each entry directory** (e.g. *"Adds entries to `skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/` and `skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.7-to-0.8/`."*). The human reviewer + the CI gate (`pnpm check:upgrade-coverage`) both check this shape, but the gate is **necessary-but-not-sufficient** — it only asserts that the in-flight transition *directory* exists, not that *this PR's* substrate diff has a matching `changes[]` entry. So a PR can have a real substrate diff, contribute no entry, and still pass the gate green whenever an earlier PR already created the transition directory. (This is exactly how a breaking change can ship undocumented: the directory was already there, so the gate stayed green.) The gap is load-bearing for the reviewer: **the human reviewer must verify that every substrate diff in the PR has a corresponding entry** — the gate will not catch a missing entry once the directory exists. The reviewer also catches the semantic case (entry exists but its prose / scripts don't match the framework change). @@ -183,7 +183,7 @@ Two corollaries of a release cut: ## Out of scope -This skill records **upgrade instructions** — code-translation entries the published skills will replay against consumer projects. It does **not** add the per-step bump-install-instructions-validate-commit loop to entry bodies. That flow is general content carried in the published `SKILL.md` files (`skills/prisma-8/references/upgrade-app.md` and `skills/prisma-8/references/upgrade-extension.md`) and runs around your entry. Your entry only contains the code-translation work specific to the transition. +This skill records **upgrade instructions** — code-translation entries the published skills will replay against consumer projects. It does **not** add the per-step bump-install-instructions-validate-commit loop to entry bodies. That flow is general content carried in the published `SKILL.md` files (`skills/prisma-orm-core-concepts/references/upgrade-app.md` and `skills/prisma-orm-core-concepts/references/upgrade-extension.md`) and runs around your entry. Your entry only contains the code-translation work specific to the transition. This skill also does not enforce the exact-pin rule for extensions — that is `prisma-8-check-pins` (a `bin` of `@internal/extension-author-tools`), and it runs in extension authors' own CI plus in the extension-upgrade skill's per-step flow. @@ -198,9 +198,9 @@ Both substrates are touched → both skill packages need entries. 1. Read root `package.json` on the PR branch → `version: "0.7.0"`. Currently-published minor is `0.7`, so the in-flight transition is `0.7 → 0.8`. Directory is `upgrades/0.7-to-0.8/` in each skill package. 2. Both substrates touched. -3. `skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md` already exists (placeholder shipped with the initial mechanism PR). Append a `changes[]` entry — call it `migration-metadata-shape-update`. Same for `skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md`. +3. `skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md` already exists (placeholder shipped with the initial mechanism PR). Append a `changes[]` entry — call it `migration-metadata-shape-update`. Same for `skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.7-to-0.8/instructions.md`. 4. The user-skill entry may be prose-only (e.g. "rename the imported type from `MigrationMetadata` to `MigrationManifest` in any consumer code"), since the user-facing fix is a simple rename. -5. The extension-skill entry needs more work — the SPI changed shape, not just name. Author `skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/update-migration-tools-imports.ts` and reference it from the entry's `script:` field. If the same transformation also applies to the example, copy the script into the user-skill cluster's directory too. +5. The extension-skill entry needs more work — the SPI changed shape, not just name. Author `skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.7-to-0.8/update-migration-tools-imports.ts` and reference it from the entry's `script:` field. If the same transformation also applies to the example, copy the script into the user-skill cluster's directory too. 6. Validate by execution: revert `packages/3-extensions/` to pre-PR → run the extension-skill entry → verify `pnpm test --filter='./packages/3-extensions/*'` green, the non-test paths matching ``, and the test paths still at ``. Then revert `examples/` to pre-PR → run the user-skill entry → verify `pnpm --filter test` green for the touched example, with the same two path checks. 7. Commit on the PR branch with both entry directories, the colocated script(s), and the matching substrate post-state. @@ -208,4 +208,4 @@ Both substrates are touched → both skill packages need entries. - Mechanism Linear ticket: [TML-2519](https://linear.app/prisma-company/issue/TML-2519). - Coverage gate script: `scripts/check-upgrade-coverage.mjs` (invoked as `pnpm check:upgrade-coverage`). -- Published skills whose entries you are authoring: `skills/prisma-8/upgrading/app/`, `skills/prisma-8/upgrading/extension/`. +- Published skills whose entries you are authoring: `skills/prisma-orm-core-concepts/upgrading/app/`, `skills/prisma-orm-core-concepts/upgrading/extension/`. diff --git a/skills/DEVELOPING.md b/skills/DEVELOPING.md index 8ff404ea2199..8943ec98b262 100644 --- a/skills/DEVELOPING.md +++ b/skills/DEVELOPING.md @@ -4,37 +4,42 @@ Contributor guide for the Prisma Next skills cluster. If you are *using* the ski ## What this tree is -Skills that teach an LLM agent how to operate Prisma Next end-to-end. The usage surface is one consolidated skill: [`skills/prisma-8/SKILL.md`](./prisma-8/SKILL.md) is the runtime-matched entry point (its `description:` frontmatter fires on any Prisma Next work) and routes via its routing table into workflow-scoped reference files under [`skills/prisma-8/references/`](./prisma-8/references/) — one user goal per reference file. Upgrading is part of the same skill: [`references/upgrade-app.md`](./prisma-8/references/upgrade-app.md) and [`references/upgrade-extension.md`](./prisma-8/references/upgrade-extension.md) carry the two flows, and the per-transition instructions they replay live under [`prisma-8/upgrading/app/upgrades/`](./prisma-8/upgrading/app/upgrades/) and [`prisma-8/upgrading/extension/upgrades/`](./prisma-8/upgrading/extension/upgrades/). +Skills that teach an LLM agent how to operate Prisma Next end-to-end. The usage surface is two skills with fixed trigger territories: + +- [`prisma-orm-core-concepts`](./prisma-orm-core-concepts/SKILL.md) — everything except migrations: the mental model, structured-error diagnosis, and the development workflows (quickstart, contract authoring, queries, runtime, build, Supabase, feedback, upgrades). Fires whenever the agent works with Prisma ORM. +- [`prisma-orm-migrations`](./prisma-orm-migrations/SKILL.md) — migration authoring, the graph/refs model, deploy review. Fires on migration work. + +Each `SKILL.md` is the runtime-matched entry point for its territory and routes via its routing table into workflow-scoped reference files under that skill's `references/` — one user goal per reference file. Upgrading is part of the core-concepts skill: [`references/upgrade-app.md`](./prisma-orm-core-concepts/references/upgrade-app.md) and [`references/upgrade-extension.md`](./prisma-orm-core-concepts/references/upgrade-extension.md) carry the two flows, and the per-transition instructions they replay live under [`prisma-orm-core-concepts/upgrading/app/upgrades/`](./prisma-orm-core-concepts/upgrading/app/upgrades/) and [`prisma-orm-core-concepts/upgrading/extension/upgrades/`](./prisma-orm-core-concepts/upgrading/extension/upgrades/). ## Design principles -The consolidated shape is deliberate. These principles govern every change to the published skills; a change that regresses one of them needs an explicit reason in the PR. +The two-skill shape is deliberate. These principles govern every change to the published skills; a change that regresses one of them needs an explicit reason in the PR. -### One skill, not a cluster +### Two skills, fixed territories -The usage surface is exactly one installable skill. Agent runtimes match skills against the user's prompt by `description:` — a cluster of sibling skills forces each description to carve out its own trigger territory, and the boundaries drift, overlap, and misfire as the cluster grows. One skill means one activation decision ("is this Prisma Next work?") followed by an explicit routing step the skill itself controls. +The usage surface is exactly two installable skills — core-concepts and migrations — and the set is closed. Agent runtimes match skills against the user's prompt by `description:`, and sibling descriptions carve trigger territory that drifts, overlaps, and misfires as a cluster grows (the pre-consolidation per-workflow cluster failed exactly this way; its names live on in the CLI's retired-skills cleanup list). The boundary is migrations-vs-everything-else: `prisma-orm-core-concepts` fires whenever the agent works with Prisma ORM at all, `prisma-orm-migrations` on migration work specifically, and each `SKILL.md` carries a *Related skills* section that hands misrouted tasks to the sibling. -**A new top-level skill needs a structural reason, not a topical one.** The upgrade flows used to be two sibling skills because their install ref policy differed (always-`main` vs version-pinned); now that the skill ships inside the packages, every copy is version-matched by construction and that reason is gone. A new workflow, feature area, or extension is a new reference file plus a routing-table row, never a new sibling skill. +**A new top-level skill needs a structural reason, not a topical one.** A new workflow, feature area, or extension is a new reference file plus a routing-table row in the skill that owns its territory — never a new sibling skill. When a task genuinely straddles both territories (a migration triggered by a contract edit, an error envelope raised mid-migration), the answer is cross-routing between the existing two, not a third skill. ### Progressive disclosure -`SKILL.md` is the only always-loaded content, so it must earn its context budget. It carries three things: the activation description, the routing table, and the canonical mental model — nothing else. Everything workflow-specific lives in a reference file that is loaded only when its routing-table row matches. API detail, worked examples, pitfalls, and capability gaps all belong at the reference layer. +Each skill's `SKILL.md` is the only always-loaded content for its territory, so it must earn its context budget. It carries three things: the activation description, the routing table, and the canonical mental model — nothing else. Everything workflow-specific lives in a reference file that is loaded only when its routing-table row matches. API detail, worked examples, pitfalls, and capability gaps all belong at the reference layer. The test for placement: *would every Prisma Next task benefit from the agent having read this?* If yes, it may live in `SKILL.md`. If only some tasks would, it goes in a reference file. -**The exception: cross-cutting gotchas.** A fact that defies a reasonable assumption — and that the agent has no obvious trigger to look up before it acts — needs to be read *before* the agent hits the situation, not after. A reference file only loads once its routing-table row matches, so a surprising fact scoped to one reference is fine there (its own *Common Pitfalls* section covers it). A surprising fact that cuts across workflows — the kind where an agent already committed to a plan under a wrong assumption has no reason to go back and check a reference it never routed to — belongs in `SKILL.md` itself. The Mongo ORM addressing rule (`db.orm.` uses storage names, not PSL model names) is the existing example: it lives in `SKILL.md`'s canonical-model paragraph, not buried in `references/queries.md`, because an agent that already assumed model-name addressing has no reason to open the queries reference to find out it's wrong. Keep this tier small — it is competing for the same ~150-line budget as everything else in `SKILL.md`. +**The exception: cross-cutting gotchas.** A fact that defies a reasonable assumption — and that the agent has no obvious trigger to look up before it acts — needs to be read *before* the agent hits the situation, not after. A reference file only loads once its routing-table row matches, so a surprising fact scoped to one reference is fine there (its own *Common Pitfalls* section covers it). A surprising fact that cuts across workflows — the kind where an agent already committed to a plan under a wrong assumption has no reason to go back and check a reference it never routed to — belongs in the owning skill's `SKILL.md` itself. Two standing examples: the Mongo ORM addressing rule (`db.orm.` uses storage names, not PSL model names) lives in the core-concepts `SKILL.md`'s canonical-model paragraph, not buried in `references/queries.md`; the plan-origin rule (`migration plan` never chains from the newest migration on disk) lives in the migrations `SKILL.md`. Keep this tier small — it is competing for the same ~150-line budget as everything else in a `SKILL.md`. ### Length budgets -- **`SKILL.md`: ~150 lines.** It is an index and a mental model, not a manual. If it is growing, content is leaking up from the reference layer — push it back down. +- **Each `SKILL.md`: ~150 lines.** It is an index and a mental model, not a manual. If it is growing, content is leaking up from the reference layer — push it back down. - **Reference files: ~200–350 lines.** Below that range, consider whether the file earns its routing-table row or should merge into a sibling. Above it, split into a companion reference (the `queries.md` → `queries-postgres.md` / `queries-mongo.md` split is the template) and link the companions from the parent reference's routing row. -- **`description:` frontmatter: one activation trigger, not a keyword dump.** The 1024-character registry limit is a ceiling, not a target. The description answers "does this skill apply to the current work?"; the per-workflow trigger phrases (CLI flags, error codes, feature vocabulary) live in the routing table's *Triggers* column, where there is room to be exhaustive. +- **`description:` frontmatter: the skill's trigger territory, not a keyword dump.** The 1024-character registry limit is a ceiling, not a target. Each description answers "does this skill — as opposed to its sibling — apply to the current work?"; the per-workflow trigger phrases (CLI flags, error codes, feature vocabulary) live in the routing table's *Triggers* column, where there is room to be exhaustive. ### Point at the source of truth instead of copying it Where a fact can be *queried* — from the framework source, the installed packages, or the CLI itself — a reference file should teach the query, not transcribe the answer. Transcribed API detail goes stale silently; a lookup procedure stays correct as the framework moves. This is the same principle as *concepts-over-procedures* below, applied to content: prefer *"ask the system with `command --flag`"* over a table of memorised outputs, and prefer `--help` pointers over restating flag lists. -The long-term direction is for versioned API documentation to ship inside the published `@internal/*` packages, with reference files shrinking toward routing plus lookup method. Until that lands, reference files still carry API content inline — which is why the lockstep rule below (skill updates ship in the same PR as framework-surface changes) is load-bearing. +The long-term direction is for versioned API documentation to ship inside the published `@prisma/orm-*` packages, with reference files shrinking toward routing plus lookup method. Until that lands, reference files still carry API content inline — which is why the lockstep rule below (skill updates ship in the same PR as framework-surface changes) is load-bearing. ## Authoring rules @@ -84,7 +89,7 @@ Procedural workflow sections — *"step 1: run X; step 2: read Y; step 3: if Z, #### Worked example — `references/migration-review.md` -The pilot rewrite of [`skills/prisma-8/references/migration-review.md`](./prisma-next/references/migration-review.md) is the canonical worked example for this principle in this cluster. Before that rewrite, the skill contained: +The pilot rewrite of [`skills/prisma-orm-migrations/references/migration-review.md`](./prisma-orm-migrations/references/migration-review.md) is the canonical worked example for this principle in this cluster. Before that rewrite, the skill contained: - A five-step *"diamond convergence procedure"* for resolving concurrent migrations. - A four-step *"detect that main advanced"* workflow. @@ -97,40 +102,42 @@ Read the diff if you want a before/after; read the rewrite itself if you want th ### Show façade-only imports in user-authored code -**The principle: every import a user types in their own source files comes from `@internal//` or `@internal/extension-/`. A user's `package.json` lists exactly one façade per target plus one façade per extension. They never see `@internal/cli/*`, `@internal/family-*`, `@internal/target-*`, `@internal/adapter-*`, `@internal/driver-*`, `@internal/sql-contract-*`, or `@internal/mongo-contract-*` in a file they own.** +**The principle: every import a user types in their own source files comes from `@prisma/orm-/` or `@prisma/orm-extension-/` — the published names. The `@internal/*` workspace scope is not published (ADR 242 — Public npm surface), so any `@internal/` import in a user-authored example is a defect, full stop. A user's `package.json` lists exactly one façade per target plus one façade per extension (plus the `@prisma/cli-engine` / `@prisma/cli` toolchain).** -The façade packages exist for this reason. `@internal/postgres/config` exposes a `defineConfig({ contract, db, extensions, migrations })` that bakes in `family`/`target`/`adapter`/`driver` and auto-routes `.prisma` vs `.ts` contract paths — so the user writes two imports instead of seven. `@internal/postgres/contract-builder` re-exports the TS-builder surface. `@internal/postgres/control` exposes `createPostgresControlClient({ connection, extensions })` instead of asking the user to compose a `createControlClient` call from five internal pieces. `@internal/postgres/runtime` does the same for the runtime client. +The façade packages exist for this reason. `@prisma/orm-postgres/config` exposes a `defineConfig({ contract, db, extensions, migrations })` that bakes in `family`/`target`/`adapter`/`driver` and auto-routes `.prisma` vs `.ts` contract paths — so the user writes two imports instead of seven. `@prisma/orm-postgres/contract-builder` re-exports the TS-builder surface. `@prisma/orm-postgres/control` exposes `createPostgresControlClient({ connection, extensions })` instead of asking the user to compose a `createControlClient` call from five internal pieces. `@prisma/orm-postgres/runtime` does the same for the runtime client. -A skill that teaches the verbose form has handed the agent a worse mental model than the API is actually capable of. When the user follows the skill's example into their own code, their `package.json` grows seven `@internal/*` entries instead of one. Upgrades are now seven-way coordinated instead of one-line. The drift compounds. +A skill that teaches the verbose form has handed the agent a worse mental model than the API is actually capable of. When the user follows the skill's example into their own code, their `package.json` grows entries that don't resolve on npm, or seven-way coordinated deep subpaths instead of one façade. The drift compounds. **Verify each user-authored import:** ```bash -rg "from '@internal/" skills/prisma-8/references/.md \ - | rg -v '@internal/(postgres|mongo|sqlite|extension-|[a-z]+-plugin-)' \ - | rg -v 'framework-rendered' +# Published-surface positive check — user examples import @prisma/orm-* (or @prisma/cli-engine): +rg "from '@prisma/orm-" skills/*/references/.md + +# Defect check — any @internal/ import in a user-authored example is a defect: +rg "from '@internal/" skills/prisma-orm-*/ ``` -Anything that prints is a likely defect: a user-authored example is importing from an internal package. Either rewrite it onto the façade, or annotate the surrounding prose so it reads as framework-rendered rather than user-typed. +Anything the second command prints is a defect (outside the historical `upgrading/` instruction sets): the `@internal/*` scope is unpublished, so a user cannot install it. Rewrite the example onto the published façade name. -The exclusion list covers the three sanctioned sources of user-authored `@internal/*` imports: target façades (`postgres`, `mongo`, `sqlite`), extension façades (`extension-`), and build-tool plugin packages (`-plugin-`, e.g. `@internal/vite-plugin-contract-emit`). Build-tool plugins are themselves one-package-per-integration façades — they ship their own public surface and are not internal to a target package. +The sanctioned sources of user-authored imports are: target façades (`@prisma/orm-postgres`, `@prisma/orm-mongo`, `@prisma/orm-sqlite`), extension façades (`@prisma/orm-extension-`), and the config/CLI toolchain (`@prisma/cli-engine` for `definePrismaConfig`). Build-tool plugins ship as façade subpaths (e.g. `@prisma/orm-postgres/vite-plugin-contract-emit`), not separate packages. -**The framework-rendered exception.** Some files in a user's project are written *by* the framework, not by the user — chiefly `migrations///migration.ts`, which `prisma migration plan` renders. Those files import from `@internal/postgres/migration` (or `@internal/sqlite/migration` for SQLite). A skill describing those files should: +**The framework-rendered exception.** Some files in a user's project are written *by* the framework, not by the user — chiefly `migrations///migration.ts`, which `prisma migration plan` renders. Those files import from `@prisma/orm-postgres/migration` (or `@prisma/orm-sqlite/migration` for SQLite; `@prisma/orm-mongo/family/migration` + `@prisma/orm-mongo/target/migration` on Mongo). A skill describing those files should: 1. Make explicit that the imports are framework-managed. 2. Not show those imports as if the user typed them. The framework-rendered migration scaffold uses the target façade's `/migration` subpath — the same façade-only convention as the rest of the project. -**Worked example — the contract skill re-audit.** Commit `e41f02c1b` rewrote every user-authored example in `references/contract.md` against the façade. The `prisma.config.ts` example went from seven imports across `@internal/{cli,adapter-postgres,driver-postgres,family-sql,target-postgres,sql-contract-psl}` to two imports from `@internal/{postgres/config, extension-pgvector/control}`. The TS builder example moved off `@internal/sql-contract-ts/contract-builder` onto `@internal/postgres/contract-builder`, and uses `@internal/postgres/family` and `@internal/postgres/target` as the `family`/`target` packs (a less-obvious façade subpath worth knowing about). Read the diff for a before/after. +**Worked example — the contract skill re-audit.** Commit `e41f02c1b` (predating the `@prisma/orm-*` publish rename, so its diff still shows workspace `@internal/*` names) rewrote every user-authored example in `references/contract.md` against the façade: the `prisma.config.ts` example went from seven low-level imports to two façade imports, and the TS builder example moved onto the façade's `/contract-builder`, `/family`, and `/target` subpaths. Read the diff for a before/after of the altitude change; today's spellings are the published `@prisma/orm-*` names. -Commit `bf742221c` (`examples: migrate to @internal/ façade imports`) does the same migration across nine example apps in `examples/`. Those apps are the canonical worked references; cite them when a skill needs a concrete example to point at. +Commit `bf742221c` (same pre-rename era) does the same migration across nine example apps in `examples/`. Those apps are the canonical worked references — now on the published names; cite them when a skill needs a concrete example to point at. ### Other authoring rules These are well-trodden but worth listing in one place: -- **`description:` frontmatter is a runtime matcher, not marketing prose.** Only the consolidated `SKILL.md` carries frontmatter; its description fires on any Prisma Next work. Per-workflow trigger phrases — CLI flags, error codes, feature names, foreign-tool vocabulary a user would type — live in the routing table's *Triggers* column, and a new reference file must add its row there. +- **`description:` frontmatter is a runtime matcher, not marketing prose.** Only the two `SKILL.md` files carry frontmatter; each description fires on its skill's territory. Per-workflow trigger phrases — CLI flags, error codes, feature names, foreign-tool vocabulary a user would type — live in the owning skill's routing table's *Triggers* column, and a new reference file must add its row there. - **One workflow per reference file.** File size is bounded by the per-file line ceiling. If a workflow grows past it, split into a companion reference (the queries → queries-postgres/queries-mongo split is the template) — don't sprawl. - **Provide a default, not a menu.** When more than one tool or approach would work (PSL vs. the TS builder, `db update` vs. `migration plan`, which query lane for a given target), commit to the one that's the recommended path for the common case and state it first. Mention the alternative briefly, as an escape hatch with the condition under which it applies — don't present both as equally-weighted options and leave the choice to the agent. An agent handed a menu without a default either guesses or asks; a stated default lets it proceed. - **Omit what the agent already knows.** Every sentence should teach something the agent wouldn't get right without it: a Prisma Next-specific convention, a non-obvious constraint, the actual verified tool surface. Don't explain what a foreign-key constraint is, what a connection pool does, or other general engineering or database knowledge the agent already has — that's editorial padding that pushes genuinely load-bearing content further from the top of the file and erodes the length budgets above. When rewriting or extending a reference file, apply the test explicitly: *would the agent get this wrong without this sentence?* If no, cut it. @@ -142,7 +149,7 @@ These are well-trodden but worth listing in one place: 1. Read [`README.md`](./README.md) for the user-facing scope of the skills. 2. Read the [`skill-specialist` persona](https://github.com/prisma/ignite/blob/main/skills/.curated/drive-agent-personas/personas/skill-specialist.md) in the Ignite persona library — it's the canonical lens for skill work. -3. Read [`skills/prisma-8/references/migration-review.md`](./prisma-next/references/migration-review.md) for the worked example of concepts-over-procedures. +3. Read [`skills/prisma-orm-migrations/references/migration-review.md`](./prisma-orm-migrations/references/migration-review.md) for the worked example of concepts-over-procedures. 4. Draft the reference file, **verifying each tool-surface claim against the framework source as you write it** (see *Verify the tool surface as you author* above for the ripgrep commands). The shape: - A routing-table row in `SKILL.md` as the matcher (CLI flags, error codes, feature names — all verified). - Preamble + canonical mental-model headline. diff --git a/skills/README.md b/skills/README.md index 80bd65b8c642..47ec07dd491b 100644 --- a/skills/README.md +++ b/skills/README.md @@ -4,23 +4,26 @@ Agent skills for [Prisma Next](https://github.com/prisma/prisma) (Prisma 8) — > **Edit your data contract. Prisma handles the rest.** > -> **You get the skill by installing the packages.** `prisma-8` ships inside the `@prisma/orm-postgres`, `@prisma/orm-sqlite` and `@prisma/orm-mongo` tarballs, so the skill in your project always describes the version you installed. +> **You get the skills by installing the packages.** The `prisma-orm-*` skills ship inside the `@prisma/orm-postgres`, `@prisma/orm-sqlite` and `@prisma/orm-mongo` tarballs, so the skills in your project always describe the version you installed. ## What's in the box -One installable skill: +Two installable skills, split by trigger territory: | Skill | Scope | |---|---| -| [`prisma-8`](./prisma-8/) | `SKILL.md` is a router: a description trigger that fires on any Prisma Next work, a routing table, and progressive disclosure into [`prisma-8/references/`](./prisma-8/references/) — adoption/quickstart, contract authoring, migration authoring, migration review on deploy, queries (with Postgres/SQLite and Mongo companions), runtime wiring, build integration, Supabase/RLS, structured-error debugging, feedback routing, and upgrading (both an application and an extension package). | +| [`prisma-orm-core-concepts`](./prisma-orm-core-concepts/) | Everything except migrations. The mental model (contract vs schema, emitting, hashes and the marker, plans, the query APIs, façade layering, capabilities, codecs, extensions, middleware, the migration graph, offline-vs-online CLI commands), structured-error diagnosis, and the development workflows: adoption/quickstart, contract authoring, queries (with Postgres/SQLite and Mongo companions), runtime wiring, build integration, Supabase/RLS, feedback routing, and upgrading (both an application and an extension package, with the per-transition instructions under [`prisma-orm-core-concepts/upgrading/`](./prisma-orm-core-concepts/upgrading/)). Fires whenever the agent works with Prisma ORM. | +| [`prisma-orm-migrations`](./prisma-orm-migrations/) | Migration authoring, the migration graph / refs / plan-origin model, and deploy-time migration review. Fires on migration planning, applying, and review. | + +Each `SKILL.md` is a router: a description trigger that carves that skill's territory, a routing table, and progressive disclosure into the skill's `references/` files. The skills cross-reference each other by name (they install as a set), and each carries the shared version-freshness preamble. The task-oriented reference files follow a shared shape: preamble + canonical mental-model headline, *When to Use* / *When Not to Use*, *Key Concepts*, *Workflow*, *Common Pitfalls*, **What Prisma Next doesn't do yet**, and *Checklist*. The two upgrade references are procedures rather than reference material, so they carry their own step-by-step shape instead. -Upgrading is a branch of the same skill rather than a separate one. [`references/upgrade-app.md`](./prisma-8/references/upgrade-app.md) and [`references/upgrade-extension.md`](./prisma-8/references/upgrade-extension.md) carry the two flows; the per-transition instructions and codemods they replay live under [`prisma-8/upgrading/app/upgrades/-to-/`](./prisma-8/upgrading/app/upgrades/) and [`prisma-8/upgrading/extension/upgrades/-to-/`](./prisma-8/upgrading/extension/upgrades/). The version you upgrade *to* carries the instructions for the transitions leading to it. +Upgrading is a branch of the core-concepts skill. [`prisma-orm-core-concepts/references/upgrade-app.md`](./prisma-orm-core-concepts/references/upgrade-app.md) and [`prisma-orm-core-concepts/references/upgrade-extension.md`](./prisma-orm-core-concepts/references/upgrade-extension.md) carry the two flows; the per-transition instructions and codemods they replay live under [`prisma-orm-core-concepts/upgrading/app/upgrades/-to-/`](./prisma-orm-core-concepts/upgrading/app/upgrades/) and [`prisma-orm-core-concepts/upgrading/extension/upgrades/-to-/`](./prisma-orm-core-concepts/upgrading/extension/upgrades/). The version you upgrade *to* carries the instructions for the transitions leading to it. ## Install -The skill arrives with the packages, and `prisma skills sync` copies it from the installed package into the agent directories at your project root (`.claude/skills/`, `.cursor/skills/`, `.agents/skills/`, `.windsurf/skills/`). The family-level `prisma init` command sets the skills up for you; `prisma orm init` does not touch them. After adding an agent runtime, or to refresh the copies, run the sync directly: +The skills arrive with the packages, and `prisma skills sync` copies them from the installed package into the agent directories at your project root (`.claude/skills/`, `.cursor/skills/`, `.agents/skills/`, `.windsurf/skills/`). The family-level `prisma init` command sets the skills up for you; `prisma orm init` does not touch them. After adding an agent runtime, or to refresh the copies, run the sync directly: ```bash pnpm exec prisma skills sync @@ -30,27 +33,27 @@ Every `prisma` command also checks the synced copies against the installed packa ### Manual fallback — install from GitHub -If you want the skill without installing the packages, the `skills` CLI can still read this directory. Pin the ref to your Prisma Next version so the skill surface matches your runtime surface: +If you want the skills without installing the packages, the `skills` CLI can still read this directory. Pin the ref to your Prisma Next version so the skill surface matches your runtime surface. Install both — they route to each other and are meant to travel together: ```bash -pnpm dlx skills add prisma/prisma/skills#v --skill prisma-8 -y +pnpm dlx skills add prisma/prisma/skills#v --all ``` This is interoperability, not the recommended path: nothing keeps a copy installed this way up to date. -The skills are always installed at the **project level** — there is no host-wide / global install path. The skill's surface (commands it references, exit codes it expects, capability claims it makes) tracks the project's `@internal/*` version, and a global install would have to pick a single version for every project on the host. Per-project keeps the skill, CLI, runtime, and extension packs coherent on every project the user works in. +The skills are always installed at the **project level** — there is no host-wide / global install path. Each skill's surface (commands it references, exit codes it expects, capability claims it makes) tracks the project's `@internal/*` version, and a global install would have to pick a single version for every project on the host. Per-project keeps the skills, CLI, runtime, and extension packs coherent on every project the user works in. To limit a `skills add` install to one agent runtime, add `-a ` (e.g. `-a claude-code`, `-a cursor`, `-a codex`). The `skills` CLI's `--help` lists the supported agent ids. ## Capability-gap honesty -Prisma Next is in early access (`0.x`). Each reference file carries a *What Prisma Next doesn't do yet* section that names features the framework doesn't implement (model validations, lifecycle callbacks, Studio, runtime-apply migrations, `EXPLAIN`, prepared statements, `db.batch()`, multi-database routing, Next.js plugin, …) along with the workaround and a route to [`prisma-8/references/feedback.md`](./prisma-8/references/feedback.md) so the request becomes a tracked issue instead of a one-line URL. +Prisma Next is in early access. Each reference file carries a *What Prisma Next doesn't do yet* section that names features the framework doesn't implement (model validations, lifecycle callbacks, Studio, runtime-apply migrations, `EXPLAIN`, prepared statements, `db.batch()`, multi-database routing, Next.js plugin, …) along with the workaround and a route to [`prisma-orm-core-concepts/references/feedback.md`](./prisma-orm-core-concepts/references/feedback.md) so the request becomes a tracked issue instead of a one-line URL. The pattern is deliberate: it gives the agent something concrete to say when a user asks about an unbuilt feature, instead of confabulating a plausible-looking API call against something that doesn't exist. ## Versioning -The skill is versioned with the rest of Prisma Next and ships inside the tarballs, so there is no separate skill-version axis to track ([`docs/oss/versioning.md`](../docs/oss/versioning.md)). Each published copy carries `metadata.library` (the package it shipped in) and `metadata.library_version` (the version it shipped at) in its frontmatter — `metadata` is where the [Agent Skills spec](https://agentskills.io) puts keys beyond `name` and `description`; `prisma skills sync` compares that stamp against the installed package to decide whether your copy is current. +The skills are versioned with the rest of Prisma Next and ship inside the tarballs, so there is no separate skill-version axis to track ([`docs/oss/versioning.md`](../docs/oss/versioning.md)). Each published copy carries `metadata.library` (the package it shipped in) and `metadata.library_version` (the version it shipped at) in its frontmatter — `metadata` is where the [Agent Skills spec](https://agentskills.io) puts keys beyond `name` and `description`; `prisma skills sync` compares that stamp against the installed package to decide whether your copies are current. ## Contributing / authoring diff --git a/skills/journey-tests/01-onboarding-first-query.md b/skills/journey-tests/01-onboarding-first-query.md index 55d2154d5324..3df3916c6f06 100644 --- a/skills/journey-tests/01-onboarding-first-query.md +++ b/skills/journey-tests/01-onboarding-first-query.md @@ -1,6 +1,6 @@ # Journey 01 — Onboarding to first query -**Skills under test:** `prisma-next-quickstart`, `prisma-next-contract`, `prisma-next-migrations`, `prisma-next-queries`. +**Skills under test:** `prisma-orm-core-concepts` (quickstart, contract, queries), `prisma-orm-migrations` (migrations). **Example app:** A fresh directory scaffolded with `pnpm dlx @prisma/cli orm init` (run inside the new directory — `init` operates in `cwd` and does not take a project-name argument), or `examples/prisma-8-demo` with the User model deleted. @@ -17,7 +17,7 @@ - [ ] Adds a `User` model with `id` (auto-increment PK) and `email` (string, unique). - [ ] Runs `pnpm prisma contract emit` (or the project-pm equivalent). - [ ] Plans + applies a migration (or uses `db update` for dev). -- [ ] Writes a query handler that calls `db.orm.User.select(...).all()` (the ORM lane is the default; the SQL builder and raw lanes are alternatives the `prisma-next-queries` skill covers). +- [ ] Writes a query handler that calls `db.orm.User.select(...).all()` (the ORM lane is the default; the SQL builder and raw lanes are alternatives the `prisma-orm-core-concepts` (queries) skill covers). - [ ] Runs the handler and observes the empty array (or rows if seeded). ## Success criteria diff --git a/skills/journey-tests/02a-add-relation.md b/skills/journey-tests/02a-add-relation.md index e9f4bf74e73b..d3fd838f76f1 100644 --- a/skills/journey-tests/02a-add-relation.md +++ b/skills/journey-tests/02a-add-relation.md @@ -1,6 +1,6 @@ # Journey 02a — Add a relation, apply, query -**Skills under test:** `prisma-next-contract`, `prisma-next-migrations`, `prisma-next-queries`. +**Skills under test:** `prisma-orm-core-concepts` (contract), `prisma-orm-migrations` (migrations), `prisma-orm-core-concepts` (queries). **Example app:** A project that already has a `User` model. diff --git a/skills/journey-tests/02b-rename-with-hint.md b/skills/journey-tests/02b-rename-with-hint.md index 23d7cd49a0fc..3f50429403c8 100644 --- a/skills/journey-tests/02b-rename-with-hint.md +++ b/skills/journey-tests/02b-rename-with-hint.md @@ -1,6 +1,6 @@ # Journey 02b — Rename a column (capability gap: no in-contract hint) -**Skills under test:** `prisma-next-contract`, `prisma-next-migrations`, `prisma-next-feedback`. +**Skills under test:** `prisma-orm-core-concepts` (contract, feedback), `prisma-orm-migrations` (migrations). ## Prompt @@ -14,7 +14,7 @@ - [ ] Runs `migration plan --name rename-user-email`. - [ ] Runs `migration show ` and confirms the plan is a `DROP COLUMN` + `ADD COLUMN` — the destructive shape the user was warned about. - [ ] Walks the user through hand-editing `migration.ts` to rewrite the destructive op as a `RENAME COLUMN`, then `node migrations/app//migration.ts` to self-emit and `db migrate`. -- [ ] Offers to route a feature request for a first-class rename hint via `prisma-next-feedback`. +- [ ] Offers to route a feature request for a first-class rename hint via `prisma-orm-core-concepts` (feedback). ## Success criteria diff --git a/skills/journey-tests/02c-data-transform-placeholder.md b/skills/journey-tests/02c-data-transform-placeholder.md index b15ab3eca06f..1510d89d38df 100644 --- a/skills/journey-tests/02c-data-transform-placeholder.md +++ b/skills/journey-tests/02c-data-transform-placeholder.md @@ -1,6 +1,6 @@ # Journey 02c — Fill a placeholder data transform -**Skills under test:** `prisma-next-migrations`. +**Skills under test:** `prisma-orm-migrations` (migrations). **Acceptance criterion:** AC5c. diff --git a/skills/journey-tests/02d-capability-gate.md b/skills/journey-tests/02d-capability-gate.md index e8c05f85dce6..15cd24285837 100644 --- a/skills/journey-tests/02d-capability-gate.md +++ b/skills/journey-tests/02d-capability-gate.md @@ -1,6 +1,6 @@ # Journey 02d — Capability-gated `returning()` -**Skills under test:** `prisma-next-queries`, `prisma-next-contract`. +**Skills under test:** `prisma-orm-core-concepts` (queries, contract). **Acceptance criterion:** AC5d. diff --git a/skills/journey-tests/02e-hash-mismatch.md b/skills/journey-tests/02e-hash-mismatch.md index 21f9c67180e6..cae48b9fb77c 100644 --- a/skills/journey-tests/02e-hash-mismatch.md +++ b/skills/journey-tests/02e-hash-mismatch.md @@ -1,6 +1,6 @@ # Journey 02e — Recover from `MIGRATION.HASH_MISMATCH` -**Skills under test:** `prisma-next-debug`, `prisma-next-migrations`. +**Skills under test:** `prisma-orm-core-concepts` (failure-modes), `prisma-orm-migrations` (migrations). **Acceptance criterion:** AC5e. diff --git a/skills/journey-tests/02f-merge-preview.md b/skills/journey-tests/02f-merge-preview.md index 8af10a128ef2..590b26236dbb 100644 --- a/skills/journey-tests/02f-merge-preview.md +++ b/skills/journey-tests/02f-merge-preview.md @@ -1,6 +1,6 @@ # Journey 02f — "What's about to run on merge?" -**Skills under test:** `prisma-8-migration-review`. +**Skills under test:** `prisma-orm-migrations` (migration-review). **Acceptance criterion:** AC5f. diff --git a/skills/journey-tests/02g-diamond-convergence.md b/skills/journey-tests/02g-diamond-convergence.md index a9b918f201bd..49c7e7a1a3f1 100644 --- a/skills/journey-tests/02g-diamond-convergence.md +++ b/skills/journey-tests/02g-diamond-convergence.md @@ -1,6 +1,6 @@ # Journey 02g — Resolve a diamond-convergence conflict -**Skills under test:** `prisma-8-migration-review`, `prisma-next-migrations`. +**Skills under test:** `prisma-orm-migrations` (migration-review), `prisma-orm-migrations` (migrations). **Acceptance criterion:** AC5g. diff --git a/skills/journey-tests/02h-query-interface.md b/skills/journey-tests/02h-query-interface.md index e58f5356742b..4233f3a55fec 100644 --- a/skills/journey-tests/02h-query-interface.md +++ b/skills/journey-tests/02h-query-interface.md @@ -1,6 +1,6 @@ # Journey 02h — Pick the right query interface -**Skills under test:** `prisma-next-queries`. +**Skills under test:** `prisma-orm-core-concepts` (queries). **Acceptance criterion:** AC5h. diff --git a/skills/journey-tests/02i-greenfield-trap.md b/skills/journey-tests/02i-greenfield-trap.md index 2a5a6f6c7f44..3a9096b52dba 100644 --- a/skills/journey-tests/02i-greenfield-trap.md +++ b/skills/journey-tests/02i-greenfield-trap.md @@ -1,6 +1,6 @@ # Journey 02i — Second plan in a project with no `db` ref -**Skills under test:** `prisma-8` (migration-model, migrations). +**Skills under test:** `prisma-orm-migrations` (migration-model, migrations). **Acceptance criterion:** — (added with the migration mental-model rework). diff --git a/skills/journey-tests/03-capability-gaps.md b/skills/journey-tests/03-capability-gaps.md index 2507612e2f0b..aead3da5877b 100644 --- a/skills/journey-tests/03-capability-gaps.md +++ b/skills/journey-tests/03-capability-gaps.md @@ -1,10 +1,10 @@ # Journey 03 — Capability-gap honesty -**Skills under test:** `prisma-next-contract`, `prisma-next-migrations`, `prisma-next-queries`, `prisma-next-build`, `prisma-next-debug`, `prisma-next-feedback`. +**Skills under test:** `prisma-orm-core-concepts` (contract, queries, build, failure-modes, feedback), `prisma-orm-migrations` (migrations). -**Acceptance criterion:** The agent names the gap, names the workaround, and routes to `prisma-next-feedback`; it does not fabricate an API call. +**Acceptance criterion:** The agent names the gap, names the workaround, and routes to `prisma-orm-core-concepts` (feedback); it does not fabricate an API call. -The point: when the user asks about a feature Prisma Next doesn't have yet, the agent must NOT confabulate an API. It must name the gap, suggest the workaround, and route to `prisma-next-feedback` so the request becomes a tracked issue. +The point: when the user asks about a feature Prisma Next doesn't have yet, the agent must NOT confabulate an API. It must name the gap, suggest the workaround, and route to `prisma-orm-core-concepts` (feedback) so the request becomes a tracked issue. ## Prompts and expected responses @@ -14,15 +14,15 @@ The point: when the user asks about a feature Prisma Next doesn't have yet, the - [ ] Agent names the gap: validations not first-class in PN. - [ ] Agent suggests app-side validation with arktype or zod. -- [ ] Agent routes to `prisma-next-feedback` for the feature request. +- [ ] Agent routes to `prisma-orm-core-concepts` (feedback) for the feature request. ### 03b — Lifecycle callbacks > Run a `beforeSave` hook on User to lowercase the email. - [ ] Agent names the gap: lifecycle callbacks not first-class. -- [ ] Agent suggests middleware (per `prisma-next-runtime`) or app code. -- [ ] Agent routes to `prisma-next-feedback` for the feature request. +- [ ] Agent suggests middleware (per `prisma-orm-core-concepts` (runtime)) or app code. +- [ ] Agent routes to `prisma-orm-core-concepts` (feedback) for the feature request. ### 03c — Studio @@ -30,7 +30,7 @@ The point: when the user asks about a feature Prisma Next doesn't have yet, the - [ ] Agent names the gap: Studio not shipped. - [ ] Agent suggests `prisma db schema` for CLI tree output. -- [ ] Agent routes to `prisma-next-feedback` for the feature request. +- [ ] Agent routes to `prisma-orm-core-concepts` (feedback) for the feature request. ### 03d — EXPLAIN @@ -38,7 +38,7 @@ The point: when the user asks about a feature Prisma Next doesn't have yet, the - [ ] Agent names the gap: no `.explain()` first-class method. - [ ] Agent suggests ``db.raw.sql`EXPLAIN ANALYZE ${...}` ``. -- [ ] Agent routes to `prisma-next-feedback` for the feature request. +- [ ] Agent routes to `prisma-orm-core-concepts` (feedback) for the feature request. ### 03e — Runtime-apply migrations @@ -46,18 +46,18 @@ The point: when the user asks about a feature Prisma Next doesn't have yet, the - [ ] Agent names the gap: no runtime-apply migrations API. - [ ] Agent suggests `prisma db migrate` from the deploy pipeline. -- [ ] Agent routes to `prisma-next-feedback` for the feature request. +- [ ] Agent routes to `prisma-orm-core-concepts` (feedback) for the feature request. ### 03f — Next.js plugin > Set up Prisma Next contract auto-emit in my Next.js project. -- [ ] Agent loads `prisma-next-build`. +- [ ] Agent loads `prisma-orm-core-concepts` (build). - [ ] Agent names the gap: no first-party Next.js plugin yet. - [ ] Agent suggests the `prebuild` script workaround. -- [ ] Agent routes to `prisma-next-feedback` if the user wants the gap closed. +- [ ] Agent routes to `prisma-orm-core-concepts` (feedback) if the user wants the gap closed. ## Success criteria -- [ ] For each prompt, the agent named the gap, named the workaround, and routed the user to `prisma-next-feedback` (not just a bare URL). +- [ ] For each prompt, the agent named the gap, named the workaround, and routed the user to `prisma-orm-core-concepts` (feedback) (not just a bare URL). - [ ] The agent did NOT fabricate an API call against a non-existent surface (`User.validates(...)`, `db.studio()`, `query.explain()`, `db.applyMigrations()`, `@internal/next-plugin-contract-emit`). diff --git a/skills/journey-tests/05-build-vite.md b/skills/journey-tests/05-build-vite.md index 518b242991de..63c497de5572 100644 --- a/skills/journey-tests/05-build-vite.md +++ b/skills/journey-tests/05-build-vite.md @@ -1,6 +1,6 @@ # Journey 05 — Vite plugin happy path -**Skill under test:** `prisma-next-build`. +**Skill under test:** `prisma-orm-core-concepts` (build). **Acceptance criterion:** The build workflow routes to the Vite plugin path and avoids unsupported package names. @@ -22,7 +22,7 @@ The agent runtime has Prisma Next skills registered (project level, as `init` in ## Expected agent behaviour -- [ ] Skill matcher fires on `prisma-next-build` (description contains "vite plugin", "vite.config.ts", "contract emit on save"). +- [ ] Skill matcher fires on `prisma-orm-core-concepts` (build) (description contains "vite plugin", "vite.config.ts", "contract emit on save"). - [ ] Agent installs `@internal/vite-plugin-contract-emit` as a devDependency via the project's package manager. - [ ] Agent edits `vite.config.ts` to register `prismaVitePlugin('prisma.config.ts')` (note: the argument is the *config path*, not the schema path). - [ ] Agent starts `vite dev` (or instructs the user to) and waits for the initial emit log line. diff --git a/skills/journey-tests/05b-build-nextjs-gap.md b/skills/journey-tests/05b-build-nextjs-gap.md index 0ea338c18a6a..c0dfbb681904 100644 --- a/skills/journey-tests/05b-build-nextjs-gap.md +++ b/skills/journey-tests/05b-build-nextjs-gap.md @@ -1,6 +1,6 @@ # Journey 05b — Next.js gap (no first-party plugin yet) -**Skills under test:** `prisma-next-build`, `prisma-next-feedback`. +**Skills under test:** `prisma-orm-core-concepts` (build, feedback). **Acceptance criterion:** AC8b (Next.js prompt) from `specs/usage-skill.spec.md`. @@ -16,16 +16,16 @@ A fresh Next.js project (`pnpm create next-app`) into which `prisma orm init` ha ## Expected agent behaviour -- [ ] Skill matcher fires on `prisma-next-build` (description contains "next plugin", "next.js plugin", "withPrismaNext"). +- [ ] Skill matcher fires on `prisma-orm-core-concepts` (build) (description contains "next plugin", "next.js plugin", "withPrismaNext"). - [ ] Agent surfaces the *What PN doesn't do yet* entry: no first-party Next.js plugin exists. - [ ] Agent does NOT fabricate `@internal/next` or `withPrismaNext` — those packages / exports do not exist. - [ ] Agent recommends the workaround: 1. Add a `prebuild` script to `package.json` running `prisma contract emit`. 2. Run `prisma contract emit` manually during development when the contract source changes, or wire a `tsx --watch` script. -- [ ] Agent asks the user if they want to file a feature request and, if yes, routes to the `prisma-next-feedback` skill (does NOT open an issue without explicit user confirmation). +- [ ] Agent asks the user if they want to file a feature request and, if yes, routes to the `prisma-orm-core-concepts` (feedback) skill (does NOT open an issue without explicit user confirmation). ## Success criteria - [ ] No fabricated `@internal/next` / `withPrismaNext` / `@internal/next-plugin-contract-emit` imports in any file the agent touches. - [ ] `package.json` contains a `prebuild` script that runs `prisma contract emit`. -- [ ] The user is offered the `prisma-next-feedback` route for filing a request. +- [ ] The user is offered the `prisma-orm-core-concepts` (feedback) route for filing a request. diff --git a/skills/journey-tests/06-feedback-bug.md b/skills/journey-tests/06-feedback-bug.md index 1a0a9a61e96e..9a977cfda97d 100644 --- a/skills/journey-tests/06-feedback-bug.md +++ b/skills/journey-tests/06-feedback-bug.md @@ -1,6 +1,6 @@ # Journey 06 — Feedback skill: bug report -**Skill under test:** `prisma-next-feedback`. +**Skill under test:** `prisma-orm-core-concepts` (feedback). **Acceptance criterion:** AC8c (bug report path) from `specs/usage-skill.spec.md`. @@ -14,7 +14,7 @@ A `prisma orm init`-scaffolded project (any target). ## Expected agent behaviour -- [ ] Skill matcher fires on `prisma-next-feedback`. +- [ ] Skill matcher fires on `prisma-orm-core-concepts` (feedback). - [ ] Agent classifies as a **bug report** (the CLI exit code is arguably wrong vs. documented behaviour). Not a feature request. - [ ] Agent produces a minimal reproduction: - A small `schema.psl` excerpt (renamed to neutral domain names like `User`, `Post`). diff --git a/skills/journey-tests/06b-feedback-feature.md b/skills/journey-tests/06b-feedback-feature.md index a7b2fcd8477c..f1cd1146b26a 100644 --- a/skills/journey-tests/06b-feedback-feature.md +++ b/skills/journey-tests/06b-feedback-feature.md @@ -1,6 +1,6 @@ # Journey 06b — Feedback skill: feature request via capability-gap route -**Skills under test:** `prisma-next-contract`, `prisma-next-feedback`. +**Skills under test:** `prisma-orm-core-concepts` (contract, feedback). **Acceptance criterion:** AC8c (feature request path) from `specs/usage-skill.spec.md`. @@ -14,7 +14,7 @@ First prompt (originates the route): > add a validation: email must contain '@' -Expected: `prisma-next-contract` fires, names the validations capability gap, names the arktype / zod workaround, and routes the user to `prisma-next-feedback` for the feature request. +Expected: `prisma-orm-core-concepts` (contract) fires, names the validations capability gap, names the arktype / zod workaround, and routes the user to `prisma-orm-core-concepts` (feedback) for the feature request. Second prompt (after the user says yes to the route): @@ -22,12 +22,12 @@ Second prompt (after the user says yes to the route): ## Expected agent behaviour -- [ ] On the first prompt, `prisma-next-contract` activates and does not confabulate a `@validates` PSL attribute. -- [ ] On the routing offer, the agent surfaces the *What PN doesn't do yet* entry plus the offer to file via `prisma-next-feedback`. -- [ ] On the second prompt, `prisma-next-feedback` fires. +- [ ] On the first prompt, `prisma-orm-core-concepts` (contract) activates and does not confabulate a `@validates` PSL attribute. +- [ ] On the routing offer, the agent surfaces the *What PN doesn't do yet* entry plus the offer to file via `prisma-orm-core-concepts` (feedback). +- [ ] On the second prompt, `prisma-orm-core-concepts` (feedback) fires. - [ ] Agent classifies as **feature request** (not a bug). - [ ] Agent produces the body using the feature-request fields from `.github/ISSUE_TEMPLATE/feature_request.yml`: *What problem are you trying to solve?* / *Proposed solution* / *Alternatives considered* / *Scope and impact*. -- [ ] *Alternatives considered* references back to `prisma-next-contract`'s *What PN doesn't do yet* entry on validations (arktype / zod workaround). +- [ ] *Alternatives considered* references back to `prisma-orm-core-concepts` (contract)'s *What PN doesn't do yet* entry on validations (arktype / zod workaround). - [ ] Title in `feat(area): summary` form, e.g. `feat(contract): first-class field-level validations in PSL`. - [ ] User is prompted for confirmation before submission. - [ ] On user confirmation, submitted via `gh issue create` or via the prefilled new-issue URL. @@ -36,5 +36,5 @@ Second prompt (after the user says yes to the route): - [ ] No fabricated `@validates` API in any prose or code the agent produces. - [ ] Body's *Alternatives considered* references arktype / zod (matching the source skill's gap entry). -- [ ] Body's *What problem are you trying to solve?* references the source skill (`prisma-next-contract`). +- [ ] Body's *What problem are you trying to solve?* references the source skill (`prisma-orm-core-concepts` (contract)). - [ ] User confirmation step happened before submission. diff --git a/skills/journey-tests/07-first-touch-orientation.md b/skills/journey-tests/07-first-touch-orientation.md index f0284e583ae3..4ccddd64274f 100644 --- a/skills/journey-tests/07-first-touch-orientation.md +++ b/skills/journey-tests/07-first-touch-orientation.md @@ -1,6 +1,6 @@ # Journey 07 — First-touch orientation -**Skills under test:** `prisma-next-quickstart` (First-touch orientation path), with hand-offs to `prisma-next-queries` and optionally `prisma-next-contract`. +**Skills under test:** `prisma-orm-core-concepts` (quickstart) (First-touch orientation path), with hand-offs to `prisma-orm-core-concepts` (queries) and optionally `prisma-orm-core-concepts` (contract, runtime, build). **Example app:** A Prisma Next project the user is encountering for the first time. Three flavours of this initial state should all pass: @@ -32,10 +32,10 @@ The journey should pass on each of: - [ ] Runs the snippet and confirms it round-trips data. - [ ] Surfaces CLI commands (`db init`, optionally `db update`, `contract emit`) **only when the user's current move requires them** — not as a pre-emptive tour. The *Commands you'll use day-to-day* table is offered as a reference once the first round-trip works, not recited beforehand. - [ ] Asks the user what they want to build next and routes: - - More queries → `prisma-next-queries`. - - Schema changes → `prisma-next-contract`. - - Runtime config / middleware → `prisma-next-runtime`. - - Dev-server integration → `prisma-next-build`. + - More queries → `prisma-orm-core-concepts` (queries). + - Schema changes → `prisma-orm-core-concepts` (contract). + - Runtime config / middleware → `prisma-orm-core-concepts` (runtime). + - Dev-server integration → `prisma-orm-core-concepts` (build). ## Success criteria diff --git a/skills/journey-tests/08-supabase-rls.md b/skills/journey-tests/08-supabase-rls.md index a69d64874109..930ae80453be 100644 --- a/skills/journey-tests/08-supabase-rls.md +++ b/skills/journey-tests/08-supabase-rls.md @@ -1,8 +1,8 @@ # Journey 08 — Supabase: RLS-scoped profile listing -**Skills under test:** `prisma-next-supabase`, `prisma-next-contract`, `prisma-next-queries`. +**Skills under test:** `prisma-orm-core-concepts` (supabase), `prisma-orm-core-concepts` (contract), `prisma-orm-core-concepts` (queries). -**Example app:** `examples/supabase` (the canonical Supabase walking skeleton), or a fresh project wired per the `prisma-next-supabase` config workflow against a disposable Supabase project. +**Example app:** `examples/supabase` (the canonical Supabase walking skeleton), or a fresh project wired per the `prisma-orm-core-concepts` (supabase) config workflow against a disposable Supabase project. ## Prompt diff --git a/skills/journey-tests/README.md b/skills/journey-tests/README.md index a91bac93a491..4eaa97853a6e 100644 --- a/skills/journey-tests/README.md +++ b/skills/journey-tests/README.md @@ -32,21 +32,23 @@ Cross-runtime automation against a moving model surface is its own research proj ## Journey index -| File | Skill(s) under test | Acceptance criterion | +The *Skill (references) under test* column names the installed skill and, in parentheses, the reference files inside it the journey exercises. + +| File | Skill (references) under test | Acceptance criterion | |---|---|---| -| [`01-onboarding-first-query.md`](01-onboarding-first-query.md) | quickstart, contract, migrations, queries | AC4 | -| [`02a-add-relation.md`](02a-add-relation.md) | contract, migrations, queries | AC5a | -| [`02b-rename-with-hint.md`](02b-rename-with-hint.md) | contract, migrations | AC5b | -| [`02c-data-transform-placeholder.md`](02c-data-transform-placeholder.md) | migrations | AC5c | -| [`02d-capability-gate.md`](02d-capability-gate.md) | queries, contract | AC5d | -| [`02e-hash-mismatch.md`](02e-hash-mismatch.md) | debug, migrations | AC5e | -| [`02f-merge-preview.md`](02f-merge-preview.md) | migration-review | AC5f | -| [`02g-diamond-convergence.md`](02g-diamond-convergence.md) | migration-review, migrations | AC5g | -| [`02h-query-interface.md`](02h-query-interface.md) | queries | AC5h | -| [`02i-greenfield-trap.md`](02i-greenfield-trap.md) | migration-model, migrations | — | -| [`03-capability-gaps.md`](03-capability-gaps.md) | contract, migrations, queries, build, debug, feedback | AC6 | -| [`05-build-vite.md`](05-build-vite.md) | build | AC8b | -| [`05b-build-nextjs-gap.md`](05b-build-nextjs-gap.md) | build, feedback | AC8b (Next.js path) | -| [`06-feedback-bug.md`](06-feedback-bug.md) | feedback | AC8c (bug path) | -| [`06b-feedback-feature.md`](06b-feedback-feature.md) | contract, feedback | AC8c (feature path) | -| [`07-first-touch-orientation.md`](07-first-touch-orientation.md) | quickstart (first-touch path), queries, contract | AC4 (orientation entry point) | +| [`01-onboarding-first-query.md`](01-onboarding-first-query.md) | prisma-orm-core-concepts (quickstart, contract, queries), prisma-orm-migrations (migrations) | AC4 | +| [`02a-add-relation.md`](02a-add-relation.md) | prisma-orm-core-concepts (contract, queries), prisma-orm-migrations (migrations) | AC5a | +| [`02b-rename-with-hint.md`](02b-rename-with-hint.md) | prisma-orm-core-concepts (contract), prisma-orm-migrations (migrations) | AC5b | +| [`02c-data-transform-placeholder.md`](02c-data-transform-placeholder.md) | prisma-orm-migrations (migrations) | AC5c | +| [`02d-capability-gate.md`](02d-capability-gate.md) | prisma-orm-core-concepts (queries, contract) | AC5d | +| [`02e-hash-mismatch.md`](02e-hash-mismatch.md) | prisma-orm-core-concepts (failure-modes), prisma-orm-migrations (migrations) | AC5e | +| [`02f-merge-preview.md`](02f-merge-preview.md) | prisma-orm-migrations (migration-review) | AC5f | +| [`02g-diamond-convergence.md`](02g-diamond-convergence.md) | prisma-orm-migrations (migration-review, migrations) | AC5g | +| [`02h-query-interface.md`](02h-query-interface.md) | prisma-orm-core-concepts (queries) | AC5h | +| [`02i-greenfield-trap.md`](02i-greenfield-trap.md) | prisma-orm-migrations (migration-model, migrations) | — | +| [`03-capability-gaps.md`](03-capability-gaps.md) | prisma-orm-core-concepts (contract, queries, build, failure-modes, feedback), prisma-orm-migrations (migrations) | AC6 | +| [`05-build-vite.md`](05-build-vite.md) | prisma-orm-core-concepts (build) | AC8b | +| [`05b-build-nextjs-gap.md`](05b-build-nextjs-gap.md) | prisma-orm-core-concepts (build, feedback) | AC8b (Next.js path) | +| [`06-feedback-bug.md`](06-feedback-bug.md) | prisma-orm-core-concepts (feedback) | AC8c (bug path) | +| [`06b-feedback-feature.md`](06b-feedback-feature.md) | prisma-orm-core-concepts (contract, feedback) | AC8c (feature path) | +| [`07-first-touch-orientation.md`](07-first-touch-orientation.md) | prisma-orm-core-concepts (quickstart first-touch path, queries, contract, runtime, build) | AC4 (orientation entry point) | diff --git a/skills/prisma-8/SKILL.md b/skills/prisma-orm-core-concepts/SKILL.md similarity index 51% rename from skills/prisma-8/SKILL.md rename to skills/prisma-orm-core-concepts/SKILL.md index d24bc563a7d4..899637b4d8a1 100644 --- a/skills/prisma-8/SKILL.md +++ b/skills/prisma-orm-core-concepts/SKILL.md @@ -1,20 +1,10 @@ --- -name: prisma-8 +name: prisma-orm-core-concepts description: >- - Comprehensive guide for building with Prisma 8 (Prisma Next), the - contract-first data layer. Use whenever working on Prisma code in a project - that uses it — authoring or editing the data contract (contract.prisma, PSL, - TypeScript builders), migrations, queries (db.orm / db.sql), runtime wiring - (db.ts, middleware, DATABASE_URL), build-tool integration, Supabase / RLS, - reading PN-* structured errors, or filing feedback — and for orientation - questions like "what is Prisma Next" or comparisons to other ORMs. Signals - that this skill applies: @internal/* imports, prisma.config.ts, - contract.prisma / contract.json / contract.d.ts, the prisma-next CLI, - PN-* error codes. Also covers upgrading Prisma in a project — "upgrade - Prisma 8", "bump Prisma Next", "move to Prisma Next X.Y", or dealing - with an @internal/* version bump, in a consumer app or in an extension - package. Does not apply to Prisma ORM 7 or earlier - (schema.prisma + @prisma/client projects). + Use whenever working with Prisma 8 (Prisma Next) — writing queries, editing + the schema / data contract, running prisma CLI commands, wiring the runtime, + debugging errors, or otherwise developing against the database. Does not + apply to Prisma ORM 7 or earlier (schema.prisma + @prisma/client projects). metadata: library: '@prisma/orm-postgres' library_version: '8.0.0-rc.8' @@ -24,23 +14,21 @@ metadata: > **Edit your data contract. Prisma handles the rest.** -Prisma 8 moves fast, and your training data about it is very likely outdated. This skill ships inside the installed Prisma packages, so it describes the exact version this project has — treat it and its reference files as the source of truth, over anything you remember about Prisma. The `metadata.library_version` in this file's frontmatter is the version it was published with; if it does not match the project's installed Prisma packages, run `prisma skills sync` and re-read. +Prisma 8 moves fast, and your training data about it is very likely outdated. This skill ships inside the installed Prisma packages, so it describes the exact version this project has — treat it and its reference files as the source of truth, over anything you remember about Prisma. If `metadata.library_version` in this file's frontmatter does not match the project's installed Prisma packages, run `prisma skills sync` and re-read. Additional documentation for the wider Prisma platform lives at . -Prisma Next is a contract-first data layer. This skill routes every Prisma Next task to the right reference file — open the reference before writing code; do not answer from this file alone. +This skill routes every Prisma Next task to the right reference file — open the reference before writing code; do not answer from this file alone. The references teach concepts, structures, and workflows, not the full CLI surface: for flag-level detail on any individual command, run it with `--help`. ## The canonical model (one paragraph) -You author a **data contract** (a `contract.prisma` file, or a TypeScript builder). The framework emits machine-readable artifacts (`contract.json`, `contract.d.ts`) and gives you two runtime surfaces on SQL targets: a typed SQL query builder (`db.sql..`) and a typed ORM client (`db.orm..`). On MongoDB targets only the ORM lane exists, and its keys are collection storage names (`db.orm.users`) rather than PSL model names — [`references/queries.md`](references/queries.md) § *MongoDB ORM addressing* covers the rule. Migrations are planned from the contract diff; you review them, optionally edit the `migration.ts` for data transforms, and apply. +You author a **data contract** (a `contract.prisma` file, or a TypeScript builder). The framework emits machine-readable artifacts (`contract.json`, `contract.d.ts`) and gives you two runtime surfaces on SQL targets: a typed SQL query builder (`db.sql..
`) and a typed ORM client (`db.orm..`). On MongoDB targets only the ORM lane exists, and its keys are collection storage names (`db.orm.users`) rather than PSL model names — [`references/queries.md`](references/queries.md) § *MongoDB ORM addressing* covers the rule. Every query compiles to a **plan** before execution, and the database carries a **marker** naming the contract hash it satisfies. Migrations are planned from the contract diff; the `prisma-orm-migrations` skill owns that flow. Three steps the user does: 1. **Edit your data contract.** ([`references/contract.md`](references/contract.md)) -2. **The system plans the migrations for you.** ([`references/migrations.md`](references/migrations.md)) -3. **If you need data migrations, you edit `migration.ts` and execute it.** ([`references/migrations.md`](references/migrations.md)) +2. **The system plans the migrations for you.** (`prisma-orm-migrations` skill) +3. **If you need data migrations, you edit `migration.ts` and execute it.** (`prisma-orm-migrations` skill) -Everything else — queries, runtime wiring, build integration, debugging, feedback — sits on top of those three. - -One cross-cutting migration fact: `migration plan` does **not** chain from the newest migration on disk. Its origin is `--from`, else the `db` ref, else an empty database — so a project with no ref keeps planning from scratch. Over existing migrations the CLI refuses that (`MIGRATION.PLAN_ORIGIN_UNKNOWN`) instead of writing a full-create package; choose the exit that matches your intent rather than reflexively passing `--from @empty`. [`references/migration-model.md`](references/migration-model.md) § *The trap* explains which to choose. +Everything else — queries, runtime wiring, build integration, upgrades, debugging, feedback — sits on top of those three. ## Routing table @@ -48,18 +36,16 @@ Open the reference whose triggers match the task. If more than one matches, open | Task | Reference | Triggers | | --- | --- | --- | +| Understand the system | [`references/concepts.md`](references/concepts.md) | "what is Prisma Next / Prisma 8", "how does it work", comparisons to Drizzle / Kysely / TypeORM / classic Prisma, contract vs schema, emit / artifacts, hashes, marker / database signature, plans, query lanes, façade / family / target / adapter / driver layering, capabilities, codecs, extensions, middleware, migration-graph analogy, which commands are offline vs touch the database | +| Diagnose a failure | [`references/failure-modes.md`](references/failure-modes.md) | any structured error envelope (code, domain, severity, why, fix, meta), emit failed, query won't typecheck, query crashed, migration won't apply, `BUDGET.ROWS_EXCEEDED`, `BUDGET.TIME_EXCEEDED`, `RUNTIME.ABORTED`, `PLAN.HASH_MISMATCH`, `CONTRACT.MARKER_MISSING`, PN-RUN-* / PN-MIG-* / PN-CLI-* / PN-SCHEMA-* codes, drift, capability missing, planner conflict, EXPLAIN, query log, script won't exit / close connection | | Adopt / set up / first steps | [`references/quickstart.md`](references/quickstart.md) | new project, existing database, "what can I do with Prisma Next", "where do I start", "just ran createprisma", `npx create-prisma`, first steps, first query, `prisma orm init` greenfield setup, `contract infer` + `db sign` brownfield adoption, connect-write-read first arc, day-to-day commands (`contract emit`, `db init`, `db update`, `migration plan`, `db migrate`, `db schema`, `db verify`), flags `--target` / `--authoring` / `--schema-path` / `--probe-db` / `--output` | -| Edit the data contract | [`references/contract.md`](references/contract.md) | schema, models, fields, attributes, relations, indexes, enums, value objects (composite types), type aliases, namespaces (Postgres schemas), cross-contract foreign keys (cross-space FK), polymorphic types (`@@discriminator` / `@@base`), extension namespaces (`pgvector.Vector(...)`, `cipherstash.EncryptedString(...)`), `prisma.config.ts` / `defineConfig`, `prisma contract emit`, PSL, `contract.prisma`, `contract.ts`, `contract.json`, `contract.d.ts`, `@internal/postgres/config`, `@internal/postgres/contract-builder`, `@internal/mongo/config`, `extensions:`, pgvector, cipherstash, postgis, paradedb, `@@control`, control policy (managed / tolerated / external / observed), soft delete, validations, callbacks | -| Author migrations | [`references/migrations.md`](references/migrations.md) | `db update` vs `migration plan`, `db migrate`, `migration new`, `migration show`, `db update --dry-run`, `db verify`, `db sign`, data migration, `dataTransform`, placeholder sentinels in framework-rendered `migration.ts`, `MIGRATION.HASH_MISMATCH`, PN-MIG-2001 unfilled placeholder, schema drift | -| Migration graph, refs, plan origin | [`references/migration-model.md`](references/migration-model.md) | migration graph, refs, `migration ref set` / `list` / `delete`, the `db` ref, `--advance-ref`, `migration plan --from`, `from: (baseline)` in plan output, greenfield / from-scratch plan, baseline, first migration before deploy (Composer / CD-managed databases), chaining migrations, retrofitting migrations onto an existing database, `MIGRATION.HASH_NOT_IN_GRAPH`, `MIGRATION.PATH_UNREACHABLE` at plan/chain time | -| Review migrations on deploy | [`references/migration-review.md`](references/migration-review.md) | "what migrations are going to run", "what runs on deploy / merge", merge conflict, diamond convergence, concurrent migrations, migration status, ref management for CI, staging / production environment refs, `MIGRATION.DIVERGED`, `MIGRATION.NO_MARKER`, `MIGRATION.MARKER_NOT_IN_HISTORY`, `db migrate status`, `db migrate diff`, `db migrate resolve` | +| Edit the data contract | [`references/contract.md`](references/contract.md) | schema, models, fields, attributes, relations, indexes, enums, value objects (composite types), type aliases, namespaces (Postgres schemas), cross-contract foreign keys (cross-space FK), polymorphic types (`@@discriminator` / `@@base`), extension namespaces (`pgvector.Vector(...)`, `cipherstash.EncryptedString(...)`), `prisma.config.ts` / `defineConfig`, `prisma contract emit`, PSL, `contract.prisma`, `contract.ts`, `contract.json`, `contract.d.ts`, `@prisma/orm-postgres/config`, `@prisma/orm-postgres/contract-builder`, `@prisma/orm-mongo/config`, `extensions:`, pgvector, cipherstash, postgis, paradedb, `@@control`, control policy (managed / tolerated / external / observed), soft delete, validations, callbacks | | Write queries | [`references/queries.md`](references/queries.md) | query, where, select, project, orderBy, limit, offset, take, skip, include, lookup, first, all, count, aggregate, groupBy, create, update, delete, upsert, returning, transaction, `db.orm`, `db.sql`, `db.query.from(...)` (Mongo pipeline), namespace-aware accessors, `.all()` Thenable, single-use iterators (`RUNTIME.ITERATOR_CONSUMED`), target-declared aggregate types (`count`, integer `sum`, and integer `avg` are `number`; `count` and integer `sum` throw outside ±(2^53 − 1) rather than round, while `avg` is a fraction already and carries no guard; `countBigInt` / `sumBigInt` / `avgDecimal` are the lossless forms, `avgDecimal` on PostgreSQL only), drizzle-style, kysely-style. Postgres/SQLite specifics: [`references/queries-postgres.md`](references/queries-postgres.md); Mongo specifics: [`references/queries-mongo.md`](references/queries-mongo.md) | | Wire the runtime | [`references/runtime.md`](references/runtime.md) | `db.ts`, `postgres(...)` / `sqlite(...)` / `mongo(...)` façades, middleware composition (telemetry, lints, budgets), `DATABASE_URL`, `.env`, connection pool / `poolOptions`, dev vs prod config, transactions, read replicas, multi-database, script won't exit / hangs, `db.close` / `pool.end`, `await using` / `[Symbol.asyncDispose]` | -| Build-tool integration | [`references/build.md`](references/build.md) | Vite plugin (`@internal/vite-plugin-contract-emit`, Vite 7/8), `vite.config.ts`, contract emit on save, HMR / dev server, Next.js / Webpack / esbuild / Rollup / Turbopack (named gaps, not fabricated) | -| Supabase | [`references/supabase.md`](references/supabase.md) | `@internal/extension-supabase`, RLS, row level security, policies (`policy_select` / `policy_update` / `@@rls`, `auth.uid()`), role binding (`asUser(jwt)` / `asAnon()` / `asServiceRole()`), `auth.users`, cross-space FKs to `supabase:auth.AuthUser`, JWT / JWKS (`SUPABASE_JWKS_URL`, `SUPABASE_JWT_SECRET`), `SUPABASE.JWT_INVALID`, `SUPABASE.CONFIG_INVALID`, `RoleBoundDb`, session pooler | -| Debug an error | [`references/debug.md`](references/debug.md) | any structured error envelope (code, domain, severity, why, fix, meta), emit failed, query won't typecheck, query crashed, migration won't apply, `MIGRATION.HASH_MISMATCH`, `BUDGET.ROWS_EXCEEDED`, `BUDGET.TIME_EXCEEDED`, `RUNTIME.ABORTED`, `PLAN.HASH_MISMATCH`, `CONTRACT.MARKER_MISSING`, PN-RUN-*/ PN-MIG-* / PN-CLI-*/ PN-SCHEMA-* codes, drift, capability missing, planner conflict, EXPLAIN, query log, script won't exit / close connection | -| Upgrade Prisma in an app | [`references/upgrade-app.md`](references/upgrade-app.md) | "upgrade Prisma", "upgrade Prisma Next", "bump Prisma Next", "move to Prisma Next X.Y", `@internal/*` version bump in an application, per-transition upgrade instructions in [`upgrading/app/upgrades/`](upgrading/app/upgrades/), extension-pin pre-flight, `PN-UPGRADE-*` | -| Upgrade Prisma in an extension | [`references/upgrade-extension.md`](references/upgrade-extension.md) | the same request in a package that *is* a Prisma extension (`@internal/contract` / SPI dependency, `^@.*/extension-` name), `prisma-8-check-pins`, exact-pin rule, per-transition instructions in [`upgrading/extension/upgrades/`](upgrading/extension/upgrades/) | +| Build-tool integration | [`references/build.md`](references/build.md) | Vite plugin (`@prisma/orm-postgres/vite-plugin-contract-emit`, Vite 7/8), `vite.config.ts`, contract emit on save, HMR / dev server, Next.js / Webpack / esbuild / Rollup / Turbopack (named gaps, not fabricated) | +| Supabase | [`references/supabase.md`](references/supabase.md) | `@prisma/orm-extension-supabase`, RLS, row level security, policies (`policy_select` / `policy_update` / `@@rls`, `auth.uid()`), role binding (`asUser(jwt)` / `asAnon()` / `asServiceRole()`), `auth.users`, cross-space FKs to `supabase:auth.AuthUser`, JWT / JWKS (`SUPABASE_JWKS_URL`, `SUPABASE_JWT_SECRET`), `SUPABASE.JWT_INVALID`, `SUPABASE.CONFIG_INVALID`, `RoleBoundDb`, session pooler | +| Upgrade Prisma in an app | [`references/upgrade-app.md`](references/upgrade-app.md) | "upgrade Prisma", "upgrade Prisma Next", "bump Prisma Next", "move to Prisma Next X.Y", `@prisma/orm-*` version bump in an application, per-transition upgrade instructions in [`upgrading/app/upgrades/`](upgrading/app/upgrades/), extension-pin pre-flight, `PN-UPGRADE-*` | +| Upgrade Prisma in an extension | [`references/upgrade-extension.md`](references/upgrade-extension.md) | the same request in a package that *is* a Prisma extension (framework SPI dependency, `^@.*/extension-` name), `prisma-8-check-pins`, exact-pin rule, per-transition instructions in [`upgrading/extension/upgrades/`](upgrading/extension/upgrades/) | | File feedback / ask the team | [`references/feedback.md`](references/feedback.md) | bug report, file an issue, feature request, missing feature, capability gap, "this is broken", surprising behaviour, Q&A / design discussion, ask the Prisma team, Prisma Discord (pris.ly/discord), extension-author questions | ## Routing rules @@ -71,17 +57,21 @@ For a vague prompt, ask **one** disambiguating question. Pick from: - *"Are you new to Prisma Next and asking what you can do with it, or where to start?"* → [`references/quickstart.md`](references/quickstart.md) (first-touch orientation path). - *"Do you want to set up a new Prisma Next project, or wire it into an existing database?"* → [`references/quickstart.md`](references/quickstart.md). - *"Do you want to edit your data contract (add a model / field / relation), or work with the database (migrations, queries)?"* → [`references/contract.md`](references/contract.md) vs the others. -- *"Is this about authoring a migration, or about reviewing what's going to run on deploy?"* → [`references/migrations.md`](references/migrations.md) vs [`references/migration-review.md`](references/migration-review.md). If it's about where a plan starts, refs, or an unexpected from-scratch plan → [`references/migration-model.md`](references/migration-model.md). - *"Is this about wiring Prisma Next into your build tool (Vite / Next.js / …), or about wiring `db.ts` and middleware at runtime?"* → [`references/build.md`](references/build.md) vs [`references/runtime.md`](references/runtime.md). -- *"What error or symptom are you seeing?"* → [`references/debug.md`](references/debug.md). +- *"What error or symptom are you seeing?"* → [`references/failure-modes.md`](references/failure-modes.md). - *"Do you want to report this as a bug to the Prisma Next team, or is this a feature request?"* → [`references/feedback.md`](references/feedback.md). - *"Is the project you want to upgrade an application, or a Prisma extension package?"* → [`references/upgrade-app.md`](references/upgrade-app.md) vs [`references/upgrade-extension.md`](references/upgrade-extension.md). If you still can't tell which reference applies, ask the user what they want to do. Do not guess. +## Related skills + +Planning, authoring, reviewing, or applying migrations — `db update`, `migration plan`, `db migrate`, refs, deploy review, `MIGRATION.*` codes — is owned by the sibling `prisma-orm-migrations` skill; the two install together. + ## Checklist - [ ] If the task matches a routing-table row, open that reference before writing code. - [ ] If the prompt is vague, ask one disambiguating question. - [ ] Do not attempt to answer from this file alone — the references carry the verified tool surface. - [ ] If the user describes a missing feature or a misbehaviour they want fixed, open [`references/feedback.md`](references/feedback.md). +- [ ] If the task is migration planning / applying / review, route to `prisma-orm-migrations`. diff --git a/skills/prisma-8/references/build.md b/skills/prisma-orm-core-concepts/references/build.md similarity index 77% rename from skills/prisma-8/references/build.md rename to skills/prisma-orm-core-concepts/references/build.md index f0bfa8a51c80..ed90c0f43fff 100644 --- a/skills/prisma-8/references/build.md +++ b/skills/prisma-orm-core-concepts/references/build.md @@ -3,7 +3,7 @@ > **Edit your data contract. Prisma handles the rest.** -This skill covers Prisma Next's build-tool plugins — the dev-server / build-system integrations that re-emit contract artifacts automatically as the user edits the contract source. Today that's [`@internal/vite-plugin-contract-emit`](https://github.com/prisma/prisma/blob/main/packages/1-framework/3-tooling/vite-plugin-contract-emit/README.md) for Vite 7 and Vite 8. Next.js, Webpack, esbuild, Rollup, and Turbopack plugins are documented under *What Prisma Next doesn't do yet* with the workaround. +This skill covers Prisma Next's build-tool plugins — the dev-server / build-system integrations that re-emit contract artifacts automatically as the user edits the contract source. Today that's the Vite plugin published as the [`@prisma/orm-postgres/vite-plugin-contract-emit`](https://github.com/prisma/prisma/blob/main/packages/1-framework/3-tooling/vite-plugin-contract-emit/README.md) façade subpath (the same subpath exists on `@prisma/orm-sqlite` and `@prisma/orm-mongo` for those targets), for Vite 7 and Vite 8. Next.js, Webpack, esbuild, Rollup, and Turbopack plugins are documented under *What Prisma Next doesn't do yet* with the workaround. **If the project is using Vite and consuming the contract, install the plugin.** There's no good reason not to — manual `prisma contract emit` during dev is friction the plugin eliminates. The agent should proactively offer the plugin whenever it sees a `vite.config.ts` in the project; the user doesn't need to ask. @@ -24,25 +24,21 @@ This skill covers Prisma Next's build-tool plugins — the dev-server / build-sy - **The plugin's job is `contract emit`, on a schedule the bundler knows about.** It is *not* a runtime concern — at runtime, the application reads `contract.json` / `contract.d.ts` the same way whether the plugin emitted them or a script did. The plugin saves you the manual command during development. - **Vite 7 and Vite 8 only.** Peer range `^7.0.0 || ^8.0.0`. Vite 6 is not on the support matrix. -- **`executeContractEmit` is the canonical publish path.** Custom plugins for other bundlers must also call it — never re-implement the load → emit → publish dance. The atomic-rename invariant (`contract.d.ts` renamed before `contract.json`) and the per-output FIFO queue live in `@internal/cli/control-api`. +- **`executeContractEmit` is the canonical publish path.** Custom plugins for other bundlers must also call it — never re-implement the load → emit → publish dance. The atomic-rename invariant (`contract.d.ts` renamed before `contract.json`) and the per-output FIFO queue live in the CLI's control API (framework-internal; the Vite plugin source is the reference consumer). - **No build-time / production emission.** The Vite plugin runs in `vite dev` only. For `vite build` / production, run `prisma contract emit` from a `prebuild` script. ## Workflow — Vite (the supported path) -### 1. Install the plugin +### 1. No extra install -```bash -pnpm add -D @internal/vite-plugin-contract-emit -``` - -(Or `npm install --save-dev`, `yarn add -D`, `bun add -d` — use what the project's package manager is.) +The plugin ships as a subpath of the target façade the project already has — `@prisma/orm-postgres/vite-plugin-contract-emit` (or the same subpath on `@prisma/orm-sqlite` / `@prisma/orm-mongo`). There is no separate plugin package to add. ### 2. Wire `vite.config.ts` ```typescript // vite.config.ts import { defineConfig } from 'vite'; -import { prismaVitePlugin } from '@internal/vite-plugin-contract-emit'; +import { prismaVitePlugin } from '@prisma/orm-postgres/vite-plugin-contract-emit'; export default defineConfig({ plugins: [prismaVitePlugin('prisma.config.ts')], @@ -96,7 +92,7 @@ The Vite plugin is compatible with `@react-router/dev/vite`. Both plugins are li ```typescript import { reactRouter } from '@react-router/dev/vite'; -import { prismaVitePlugin } from '@internal/vite-plugin-contract-emit'; +import { prismaVitePlugin } from '@prisma/orm-postgres/vite-plugin-contract-emit'; export default defineConfig({ plugins: [ @@ -114,13 +110,13 @@ See [`examples/react-router-demo`](https://github.com/prisma/prisma/tree/main/ex 2. **Vite 6 or earlier.** Not supported. Upgrade Vite to 7 or 8. 3. **The plugin warns: *"watching only the config; loader resolved inputs unavailable."*** The plugin couldn't resolve `contract.source.inputs` from the loader. The fallback watches only `prisma.config.ts` itself, so contract edits won't re-emit. Causes: the config file throws during loading; the contract source path resolves outside the Vite root. Fix the config error first, then check that the contract source path in the config is relative to (or inside) the Vite root. 4. **Expecting `vite build` to re-emit.** It doesn't. Add a `prebuild` script. -5. **Emit errors during dev**: the plugin surfaces them via Vite's error overlay. Read the overlay; the underlying cause is a contract authoring problem — chain to `references/debug.md` for resolution (PSL syntax, missing namespace, conflicting extensions). +5. **Emit errors during dev**: the plugin surfaces them via Vite's error overlay. Read the overlay; the underlying cause is a contract authoring problem — chain to `references/failure-modes.md` for resolution (PSL syntax, missing namespace, conflicting extensions). 6. **Re-installing dependencies without the plugin's peer-range move.** When PN bumps the plugin's peer range, you must re-run `pnpm install` so the lockfile picks up the new range. A stale lockfile keeps the old plugin and produces confusing version mismatch warnings. ## What Prisma Next doesn't do yet -- **Next.js plugin.** No first-party `@internal/next-plugin-*` exists. Workaround: run `prisma contract emit` from a `prebuild` script in `package.json` and run it manually during development when the contract changes. Many Next.js projects also run a dev-time `tsx --watch` against a small script that calls the CLI on contract-source change. If you want a first-party Next.js plugin, file a feature request via the `references/feedback.md` skill. -- **Webpack, esbuild, Rollup, Turbopack plugins.** None exist yet as first-party. Workaround: the canonical `executeContractEmit` surface lives in `@internal/cli/control-api` — a small per-bundler plugin can call it from the bundler's prebuild hook, but PN doesn't ship one for you. The `vite-plugin-contract-emit` source is the reference implementation if you want to write one yourself. If you want a first-party plugin for your bundler, file a feature request via the `references/feedback.md` skill. +- **Next.js plugin.** No first-party Next.js contract-emit plugin exists. Workaround: run `prisma contract emit` from a `prebuild` script in `package.json` and run it manually during development when the contract changes. Many Next.js projects also run a dev-time `tsx --watch` against a small script that calls the CLI on contract-source change. If you want a first-party Next.js plugin, file a feature request via the `references/feedback.md` skill. +- **Webpack, esbuild, Rollup, Turbopack plugins.** None exist yet as first-party. Workaround: run `prisma contract emit` from the bundler's prebuild hook; the Vite plugin's source (`packages/1-framework/3-tooling/vite-plugin-contract-emit`) is the reference implementation of the canonical `executeContractEmit` publish path if you want to write a bundler plugin yourself. If you want a first-party plugin for your bundler, file a feature request via the `references/feedback.md` skill. - **`vite build` integration.** The plugin runs in `vite dev` only. Workaround: a `prebuild` script that runs `prisma contract emit`. If you want the plugin to also run during `vite build`, file a feature request via the `references/feedback.md` skill. - **Vite 6 or earlier.** Not on the support matrix. Workaround: upgrade Vite to 7 or 8. If you have a hard reason to stay on Vite 6, file a feature request via the `references/feedback.md` skill. @@ -139,4 +135,4 @@ See [`examples/react-router-demo`](https://github.com/prisma/prisma/tree/main/ex - [ ] `prebuild` script (or equivalent) runs `prisma contract emit` for CI / production builds. - [ ] No `vite build` expectation that the plugin will run. - [ ] For non-Vite bundlers: surfaced the *What PN doesn't do yet* entry and routed the user to `references/feedback.md` if they want first-party support. -- [ ] Did NOT confabulate a `@internal/next-plugin-contract-emit` package or any other bundler-specific plugin that doesn't exist. +- [ ] Did NOT confabulate a Next.js contract-emit plugin or any other bundler-specific plugin that doesn't exist. diff --git a/skills/prisma-orm-core-concepts/references/concepts.md b/skills/prisma-orm-core-concepts/references/concepts.md new file mode 100644 index 000000000000..1c238a87c899 --- /dev/null +++ b/skills/prisma-orm-core-concepts/references/concepts.md @@ -0,0 +1,151 @@ + +# Prisma Next — Core Concepts + +> **Edit your data contract. Prisma handles the rest.** + +This reference carries the mental model behind Prisma 8 (Prisma Next): the structures, hierarchies, relationships, and workflows the CLI, the runtime, and the sibling skills all assume. It is not a CLI reference — commands appear only where a workflow needs them, and for flag-level detail on any individual command the authoritative source is the command itself, run with `--help`. Read the section the question is about; read the whole file when the user is orienting ("what is Prisma Next", "how does it compare to X"). + +## When to Use + +- User asks *"what is Prisma Next / Prisma 8"*, *"how does it work"*, or compares it to another ORM (Drizzle, Kysely, TypeORM, classic Prisma). +- User asks what a contract, plan, marker, ref, capability, codec, extension, or middleware *is*. +- User asks why two files (`contract.json`, `contract.d.ts`) exist, or what emitting produces. +- User asks which CLI commands touch the database and which are offline. + +## When Not to Use + +- User wants to *do* something — edit the contract, write a query, wire `db.ts`, set up a project → the workflow references in this skill's routing table. +- User wants to plan, apply, or review a migration → the `prisma-orm-migrations` skill. +- User pasted an error envelope → [`failure-modes.md`](failure-modes.md). + +## The contract and the schema + +You author a **contract**: the description of the data your application needs — models, fields, relations, and their mapping onto tables or collections. It lives in `contract.prisma` (PSL, the canonical surface) or a TypeScript builder file. The **schema** is what the database actually has right now — the live tables and indexes. In Prisma 8, you author a contract, and the schema is what the database has: + +- Queries are typed against the **contract**. +- Migrations move the **schema** toward the contract. +- Verification confirms the schema satisfies the contract. + +The distinction is load-bearing: the contract can be ahead of the schema (you edited but haven't migrated), behind it (someone changed the database out-of-band), or in agreement. Every CLI diagnostic about "drift" is a statement about the two disagreeing. + +## Emitting: from source to artifacts + +**Emitting** is the build step: the CLI compiles the contract source into two plain files colocated with it: + +- `contract.json` — the canonical, content-hashed Contract IR. Read by the migration planner, the runtime, and verification. +- `contract.d.ts` — the precise TypeScript types the runtime and query lanes propagate. + +Every other part of the toolchain reads these artifacts, not your source file. Emission is deterministic — the same source produces the same artifacts — so both are committed to version control. The pair works like `package.json` and a lockfile: the source is what you asked for; the artifacts are the exact resolved result. Edit the source, never the artifacts. + +## Hashes and the database marker + +Every emitted contract has a content **hash** — a fingerprint that names that exact contract state. The database carries the complementary half: a **marker** (the docs also call it the database signature) — a small record stored in the database itself naming the contract hash the database currently satisfies. On Postgres it is a row in `prisma_contract.marker`; on Mongo, a document in `_prisma_migrations`. Signing writes the marker (after a schema verification passes); applying migrations or updates advances it. + +The two sides verify each other: the runtime compares the contract hash against the marker before executing queries, and the migration runner checks the marker matches a migration's `from` hash before applying it. When contract and marker disagree, that state is **drift**, and verification is the diagnostic that reveals it. + +## Queries compile to plans + +A **plan** is the compiled form of a query: a plain data object holding the statement to run, its parameters, and metadata about what the query touches. Every query — whichever API authored it — becomes a plan before execution. The SQL builder shows this explicitly: + +```typescript +const plan = db.sql.public.user + .select('id', 'name') + .where((f, fns) => fns.eq(f.active, true)) + .limit(10) + .build(); +const rows = await db.runtime().execute(plan); +``` + +Plans matter for two reasons: every query goes through the same execution pipeline, and a plan is *data* — it exists as an object before anything reaches the database, which is what lets middleware inspect, veto, or record it. Raw queries are still plans, so middleware and telemetry see them like any other query. + +## The query APIs + +All query surfaces are typed against the contract and all produce plans: + +- **ORM client** (`db.orm`) — model-shaped queries and mutations; operations like `.include(...)` coordinate multiple queries. The default lane. +- **SQL builder** (`db.sql`, Postgres/SQLite) — composable joins, grouping, computed projections; raw SQL (`db.raw.sql`) as the escape hatch. +- **Pipeline builder** (`db.query.from(...)`, Mongo) — typed aggregation pipelines; raw commands as the escape hatch. + +Raw results bypass codec decoding — values arrive as the driver returns them. Lane selection guidance lives in this skill's `references/queries.md`. + +## The stack behind one package + +A project installs **one façade package per target** — `@prisma/orm-postgres`, `@prisma/orm-sqlite`, or `@prisma/orm-mongo` — and user code imports only the façade subpaths (`@prisma/orm-postgres/config`, `@prisma/orm-postgres/runtime`, …). Behind the façade sit the layers: the database **family** (SQL or document), the **target** dialect (Postgres), the **adapter** (translates plans to the dialect), and the **driver** (holds the connection). The layering exists for extensibility — Prisma 8's core is small, and everything around it, Postgres support included, plugs in through the same public interfaces. Supporting a new database means new target, adapter, and driver implementations, not a fork of the core. + +## Capabilities + +A **capability** is a specific feature a database may or may not support — `RETURNING` clauses, lateral joins, vector indexes. The active adapter advertises its capabilities and they become part of the emitted contract; the typed query surface gates on them at authoring time, so a method that needs a missing capability (e.g. `.returning(...)` without the `returning` capability) does not typecheck. Extensions can add capabilities; they are enabled through `extensions: [...]` in `prisma.config.ts`, not by hand-editing a capability list. + +## Codecs + +A **codec** converts values between JavaScript and the database's wire format, in both directions. Every column type in the contract has one — a Postgres `timestamptz` column produces a JavaScript `Date` on read and encodes it back on write. Picking a column type in PSL is also picking the codec that handles every value the column carries. Extensions introduce codecs for new types; raw query results bypass codecs entirely. + +## Extensions + +An **extension** is an installable package that adds capability to the whole toolchain: new column types with their codecs, query operations, index kinds. Registered once in `prisma.config.ts`: + +```typescript +import { definePrismaConfig } from '@prisma/cli-engine'; +import pgvector from '@prisma/orm-extension-pgvector/control'; +import { defineConfig as ormConfig } from '@prisma/orm-postgres/config'; + +export default definePrismaConfig({ + orm: ormConfig({ + contract: './src/prisma/contract.prisma', + extensions: [pgvector], + db: { connection: process.env.DATABASE_URL }, + }), +}); +``` + +After registration the extension's types appear in the contract language (`pgvector.Vector(length: 1536)`), the emitted types, the query builders, and the planned migrations. Extension authoring and the available packs are covered in this skill's `references/contract.md`. + +## Middleware + +A **middleware** is a plain object with a name and one or more hooks that run around every query. Registered once in `db.ts`, it sees the structured plan object — so it can log, enforce limits, or reject before anything executes. Built-in middleware includes `lints` (blocks risky query shapes) and `budgets` (row/latency caps); a cache middleware ships as an extension package (`@prisma/orm-extension-middleware-cache`). Telemetry/logging is a small custom middleware you write yourself. Composition and custom middleware are covered in this skill's `references/runtime.md`. + +## Migrations: a graph of contracts + +A **migration** records how to move the schema between two contract states. Each is a package on disk: an editable `migration.ts`, compiled operations (`ops.json`), and a manifest recording the `from` and `to` contract hashes. Together they form a directed **graph** — contracts (by hash) are nodes, migrations are edges. A **ref** is a named pointer at a contract (`production`, `staging`), stored as a small committed file in the repository. + +The git analogy holds up well: + +| Git | Prisma 8 | +| --- | --- | +| Commit | Contract (by hash) | +| Patch | Migration | +| Branch / tag | Ref | +| HEAD | Database marker | +| `git checkout` | `db migrate --to` | + +The full model — plan origins, refs, deploy review — is the `prisma-orm-migrations` skill. + +## How CLI commands combine + +One division governs the CLI: **`db ...` commands connect to a live database and can change it; `contract ...` and `migration ...` commands work on the files in your repository.** The one exception is `contract infer`, which reads a live database without modifying it. + +The workflows compose from that division: + +1. **Development** — `contract emit` → `migration plan --name ` → `db migrate`. +2. **Prototyping** — `contract emit` → `db update --dry-run` → `db update` (no migration files; dev databases only). +3. **Adoption** — `contract infer` → review → `contract emit` → `db sign`. +4. **CI/CD** — `migration check` (offline artifact/graph integrity) → `migration status --to --db $URL` (read-only gate) → `db migrate --to --db $URL`. + +The commands appear here only to show how they combine; each command's flags, modes, and exit codes are documented by the command itself — run it with `--help`. + +## What Prisma Next doesn't do yet + +Concept-level gaps a user orienting on the system tends to ask about — each with today's workaround, detailed in [`failure-modes.md`](failure-modes.md) § *What Prisma Next doesn't do yet*: + +- **Studio / GUI database browser** — use the CLI's live-schema view or a third-party client. +- **`EXPLAIN` integration** — write the `EXPLAIN` as a raw query. +- **First-class query logger middleware** — write a small custom middleware. + +To request any of these, route to this skill's `references/feedback.md`. + +## Checklist + +- [ ] Answered the orientation question from this file's model, then routed the *doing* to the matching workflow reference (or the `prisma-orm-migrations` skill). +- [ ] Kept the contract/schema distinction straight: contract = authored intent, schema = live database state. +- [ ] Did not present emitted artifacts (`contract.json`, `contract.d.ts`) as editable. +- [ ] Did not confabulate a capability toggle, Studio, or EXPLAIN API — named the gap and the workaround instead. diff --git a/skills/prisma-8/references/contract.md b/skills/prisma-orm-core-concepts/references/contract.md similarity index 75% rename from skills/prisma-8/references/contract.md rename to skills/prisma-orm-core-concepts/references/contract.md index 28dce731df79..ac1d2ea62ef8 100644 --- a/skills/prisma-8/references/contract.md +++ b/skills/prisma-orm-core-concepts/references/contract.md @@ -6,8 +6,8 @@ The data contract is the single source of truth for your data layer. You edit a contract source — `contract.prisma` (PSL, the canonical surface) or `contract.ts` (TypeScript builder) — and the framework derives types, migrations, and runtime configuration from it. The three-step user model: 1. **You edit your data contract.** -2. **The system plans the migrations for you.** (`references/migrations.md`) -3. **If you need data migrations, you edit `migration.ts` and execute it.** (`references/migrations.md`) +2. **The system plans the migrations for you.** (`prisma-orm-migrations/references/migrations.md`) +3. **If you need data migrations, you edit `migration.ts` and execute it.** (`prisma-orm-migrations/references/migrations.md`) Behind step 1 the agent runs `prisma contract emit` after every contract edit (or installs the Vite plugin so the bundler runs it on save — see `references/build.md`). Emit reads the contract source through the provider the façade picks based on the file extension of `contract:` in `prisma.config.ts`, then writes two artefacts colocated with the source: @@ -23,32 +23,47 @@ Both files are **emitted artefacts**. Edit the source; never the JSON or `.d.ts` - User wants to add a namespace block (Postgres schema) or a cross-contract foreign key. - User wants to set `@@control` on a model or configure `defaultControlPolicy`. - User wants to use a custom type from an extension (`pgvector.Vector(length: 1536)`, `cipherstash.EncryptedString({...})`). -- User wants to install or configure an extension via `extensions: [...]` in `prisma.config.ts`, including `@internal/extension-supabase`. +- User wants to install or configure an extension via `extensions: [...]` in `prisma.config.ts`, including `@prisma/orm-extension-supabase`. - User is migrating between authoring sources (PSL ↔ TypeScript builder). - User received `PN-CLI-4002`, `PN-CLI-4003`, or `PN-CLI-4011` from `contract emit`. -- User mentions: *schema, fields, models, attributes, prisma schema, PSL, contract.prisma, contract.ts, contract.json, contract.d.ts, contract emit, façade imports, `@internal/postgres/config`, `@internal/postgres/contract-builder`, extensions, pgvector, cipherstash, postgis, paradedb, supabase, namespaces, cross-space FK, `@@control`, enums, check constraints, `@@check`, value objects, validations, callbacks, soft delete, paranoid, scopes*. (The last cluster routes to *What Prisma Next doesn't do yet* below.) +- User mentions: *schema, fields, models, attributes, prisma schema, PSL, contract.prisma, contract.ts, contract.json, contract.d.ts, contract emit, façade imports, `@prisma/orm-postgres/config`, `@prisma/orm-postgres/contract-builder`, extensions, pgvector, cipherstash, postgis, paradedb, supabase, namespaces, cross-space FK, `@@control`, enums, check constraints, `@@check`, value objects, validations, callbacks, soft delete, paranoid, scopes*. (The last cluster routes to *What Prisma Next doesn't do yet* below.) ## When Not to Use -- User wants to apply a contract change to the DB → `references/migrations.md`. +- User wants to apply a contract change to the DB → `prisma-orm-migrations/references/migrations.md`. - User wants to write a query against the contract → `references/queries.md`. - User wants to wire `db.ts` (runtime entry point, middleware, env config) → `references/runtime.md`. - User wants the Vite / bundler integration → `references/build.md`. - User wants to set up Prisma Next for the first time → `references/quickstart.md`. -- User wants a deeper read of a single structured error envelope → `references/debug.md`. +- User wants a deeper read of a single structured error envelope → `references/failure-modes.md`. - User wants to file a missing-feature request → `references/feedback.md`. ## Key Concepts -- **The `@internal/` façade is the only surface user-authored code imports from.** For a Postgres app: `@internal/postgres/config`, `@internal/postgres/contract-builder`, `@internal/postgres/control`, `@internal/postgres/runtime`. Mongo has the same layout (`@internal/mongo/config`, `@internal/mongo/contract-builder`, `@internal/mongo/runtime`). Each extension publishes its own façade — `@internal/extension-pgvector/control`, `@internal/extension-postgis/control`, `@internal/extension-paradedb/control`. **Never reach into `@internal/cli/*`, `@internal/family-*`, `@internal/target-*`, `@internal/adapter-*`, `@internal/driver-*`, or `@internal/sql-contract-*` from user code.** The façade bakes the family / target / adapter / driver wiring in. See *Common Pitfalls* #4. +- **The `@prisma/orm-` façade is the only package user-authored code imports from.** For a Postgres app: `@prisma/orm-postgres/config`, `@prisma/orm-postgres/contract-builder`, `@prisma/orm-postgres/control`, `@prisma/orm-postgres/runtime`. Mongo has the same layout (`@prisma/orm-mongo/config`, `@prisma/orm-mongo/contract-builder`, `@prisma/orm-mongo/runtime`). Each extension publishes its own façade — `@prisma/orm-extension-pgvector/control`, `@prisma/orm-extension-postgis/control`, `@prisma/orm-extension-paradedb/control`. **Stick to those user-facing subpaths.** The façade packages also publish deeper subpaths (`/family*`, `/target*`, `/adapter*`, `/driver*`, `/components*`) for framework-rendered files and extension SPI — don't import those from app code; the façade bakes the family / target / adapter / driver wiring in. See *Common Pitfalls* #4. - **Contract source.** A file the framework reads and lowers to the canonical Contract IR. Two flavours, both first-class: - **`contract.prisma` (PSL)** — schema-flavoured DSL. Canonical for typical apps and brownfield Prisma users. Wired by `contract: './/contract.prisma'` — the `defineConfig` façade detects the `.prisma` extension and routes through the PSL provider. - - **`contract.ts` (TypeScript builder)** — programmatic authoring with `defineContract({...}, ({ field, model, rel, type }) => ({...}))` from `@internal/postgres/contract-builder` (or `@internal/mongo/contract-builder`). Wired by `contract: './/contract.ts'` — the façade detects the `.ts` extension and routes through the TS provider. Use when you need programmatic composition (per-tenant variants, generated fields) or constructs PSL doesn't yet express (e.g. registering a parameterised extension type — see pgvector's contract). -- **`prisma.config.ts`.** Wires the contract source, the database connection, the migrations directory, and any installed extensions. Use `defineConfig({...})` from `@internal/postgres/config` (or `@internal/mongo/config`). The four fields the façade accepts: `contract` (path string — `.prisma` or `.ts`), `db` (`{ connection?: string }`), `extensions` (array of control descriptors), `migrations` (`{ dir?: string }`). The output path for `contract.json` is auto-derived from `contract` (e.g. `./src/prisma/contract.prisma` → `./src/prisma/contract.json`). + - **`contract.ts` (TypeScript builder)** — programmatic authoring with `defineContract({...}, ({ field, model, rel, type }) => ({...}))` from `@prisma/orm-postgres/contract-builder` (or `@prisma/orm-mongo/contract-builder`). Wired by `contract: './/contract.ts'` — the façade detects the `.ts` extension and routes through the TS provider. Use when you need programmatic composition (per-tenant variants, generated fields) or constructs PSL doesn't yet express (e.g. registering a parameterised extension type — see pgvector's contract). +- **`prisma.config.ts`.** Wires the contract source, the database connection, the migrations directory, and any installed extensions. The file composes two layers: `definePrismaConfig({ orm: ... })` from `@prisma/cli-engine` is the default export, and its `orm:` section takes the result of the façade's `defineConfig({...})` from `@prisma/orm-postgres/config` (or `@prisma/orm-mongo/config`), conventionally imported as `ormConfig`. The façade `defineConfig(...)` result is never the default export by itself. The four fields the façade accepts: `contract` (path string — `.prisma` or `.ts`), `db` (`{ connection?: string }`), `extensions` (array of control descriptors), `migrations` (`{ dir?: string }`). The output path for `contract.json` is auto-derived from `contract` (e.g. `./src/prisma/contract.prisma` → `./src/prisma/contract.json`). The canonical shape: + + ```typescript + import 'dotenv/config'; + import { definePrismaConfig } from '@prisma/cli-engine'; + import { defineConfig as ormConfig } from '@prisma/orm-postgres/config'; + + export default definePrismaConfig({ + orm: ormConfig({ + contract: './src/prisma/contract.prisma', + db: { + connection: process.env['DATABASE_URL']!, + }, + }), + }); + ``` - **Emit pipeline.** `prisma contract emit --config ?` reads `prisma.config.ts`, calls the provider the façade picked, validates the resulting Contract, then atomically writes `contract.json` + `contract.d.ts` colocated with the source. - **Extension namespaces.** Extensions contribute namespaced constructors (`pgvector.Vector(length: 1536)`, `cipherstash.EncryptedString({equality: true})`) and helper presets. Install them by adding the descriptor to **two** places — both fields are named `extensions`, but the two surfaces consume two different descriptor types and shapes: - - **In the config (façade and core):** `extensions: [pgvector]` — array of *control* descriptors imported from `@internal/extension-/control`. - - **In the TS builder's `defineContract` (only when authoring `contract.ts`):** `extensions: { pgvector }` — record of *pack* descriptors imported from `@internal/extension-/pack`. + - **In the config (façade and core):** `extensions: [pgvector]` — array of *control* descriptors imported from `@prisma/orm-extension-/control`. + - **In the TS builder's `defineContract` (only when authoring `contract.ts`):** `extensions: { pgvector }` — record of *pack* descriptors imported from `@prisma/orm-extension-/pack`. - **Contract space.** Every package that emits a contract owns its own *contract space* — a `prisma.config.ts` at package root, a contract source, the colocated emitted artefacts, and a `migrations/` directory. **There are two intentional on-disk layouts**, picked by whether the contract space is the consuming application or a contract-space package (an extension, an internal aggregate-root package, etc.): - **Application layout** (what you use when building an *app*). `prisma.config.ts` at repo root; `src/prisma/contract.{prisma,ts}`; `src/prisma/contract.{json,d.ts}` colocated; `src/prisma/db.ts` colocated; migrations under `migrations/app/_/`. The `app/` segment is the consuming application's space-id; extension space-ids land in sibling `migrations//` directories that the extension packages manage. This is what `examples/prisma-8-demo` uses. `prisma orm init` currently scaffolds something different (`prisma/...` at repo root) — that's a defect (TML-2532); the canonical layout is what every command actually expects to see. - **Contract-space-package layout** (what you use when *publishing* a contract-space package — extensions, internal monorepo packages). `prisma.config.ts` at package root; `src/contract.{prisma,ts}` directly (no `prisma/` subdir); `src/contract.{json,d.ts}` colocated; `migrations/_/` directly under `migrations/` (no `` segment — the package *is* a single space). Documented in `.cursor/rules/contract-space-package-layout.mdc` and ADR 212. @@ -61,9 +76,9 @@ Both files are **emitted artefacts**. Edit the source; never the JSON or `.d.ts` | Code | Meaning | Next move | |---|---|---| -| `PN-CLI-4002` *Contract configuration missing* | `contract` not set in `prisma.config.ts`. | Add `contract: './src/prisma/contract.prisma'` (app layout) or `'./src/contract.prisma'` (contract-space-package layout) — likewise for `.ts` sources — to `defineConfig({...})` from `@internal/postgres/config`. | +| `PN-CLI-4002` *Contract configuration missing* | `contract` not set in `prisma.config.ts`. | Add `contract: './src/prisma/contract.prisma'` (app layout) or `'./src/contract.prisma'` (contract-space-package layout) — likewise for `.ts` sources — to `defineConfig({...})` from `@prisma/orm-postgres/config`. | | `PN-CLI-4003` *Contract validation failed* | Source loaded but the Contract IR failed structural validation. | Read `meta.diagnostics` / `meta.issues` for the offending model/field, fix the source, re-emit. | -| `PN-CLI-4011` *Missing extension packs in config* | The contract uses a namespaced constructor (e.g. `pgvector.Vector(...)`) but `extensions` in the config does not list a matching descriptor. `meta.missingExtensions` names them. | Install the package, import its control descriptor (`import pgvector from '@internal/extension-pgvector/control'`), add it to `extensions: [...]` in `prisma.config.ts`. | +| `PN-CLI-4011` *Missing extension packs in config* | The contract uses a namespaced constructor (e.g. `pgvector.Vector(...)`) but `extensions` in the config does not list a matching descriptor. `meta.missingExtensions` names them. | Install the package, import its control descriptor (`import pgvector from '@prisma/orm-extension-pgvector/control'`), add it to `extensions: [...]` in `prisma.config.ts`. | ## Workflow — Read the contract source of truth @@ -120,16 +135,18 @@ model User { } ``` +> **`DateTime` columns are Temporal-typed.** The Postgres `DateTime` / `timestamptz` codec (`pg/timestamptz-temporal@1`) reads and writes `Temporal` instants — a JS `Date` is an encode error, and decoding throws `RUNTIME.TEMPORAL_UNAVAILABLE` on runtimes without a global `Temporal` (stock Node 24, Bun 1.3). Either add `import 'temporal-polyfill/global'` at the server entry before any query runs, or use a string-typed column alternative (e.g. `TimestamptzString`) when a Temporal global is unavailable. + Note: scalar lists (e.g. `String[]`) and implicit Prisma-ORM many-to-many (list nav on both sides without a join model) are rejected by the SQL interpreter — use a join model. Composite/embeddable types (`type Address { ... }` with `address Address` on a model) are supported: the interpreter lowers them to `valueObjects` in the domain and stores them as `jsonb` columns. See *Workflow — Value objects* below. ## Workflow — Edit a model / field / relation (TS builder) -The concept: same model, different authoring surface. The façade re-exports `defineContract`, `field`, `model`, `rel`, plus the `family`/`target` packs as default exports of `@internal/postgres/family` and `@internal/postgres/target`. Use the callback overload (`defineContract({...}, ({ field, model, rel, type }) => ({...}))`) to get the higher-level helpers (`field.text()`, `field.id.uuidv7String()`, `field.temporal.createdAt()`, `type.sql.String(35)`). +The concept: same model, different authoring surface. The façade re-exports `defineContract`, `field`, `model`, `rel`, plus the `family`/`target` packs as default exports of `@prisma/orm-postgres/family` and `@prisma/orm-postgres/target`. Use the callback overload (`defineContract({...}, ({ field, model, rel, type }) => ({...}))`) to get the higher-level helpers (`field.text()`, `field.id.uuidv7String()`, `field.temporal.createdAt()`, `type.sql.String(35)`). ```typescript -import sqlFamily from '@internal/postgres/family'; -import { defineContract } from '@internal/postgres/contract-builder'; -import postgresPack from '@internal/postgres/target'; +import sqlFamily from '@prisma/orm-postgres/family'; +import { defineContract } from '@prisma/orm-postgres/contract-builder'; +import postgresPack from '@prisma/orm-postgres/target'; export const contract = defineContract( { @@ -152,7 +169,7 @@ export const contract = defineContract( Then `pnpm prisma contract emit`. The `field.()` helpers are only available inside the callback overload; outside the callback only `field.column(...)`, `field.generated(...)`, `field.namedType(...)` exist. -For Mongo, swap every `@internal/postgres/*` import for `@internal/mongo/*`. The Mongo builder also exposes `index` and `valueObject`. +For Mongo, swap every `@prisma/orm-postgres/*` import for `@prisma/orm-mongo/*`. The Mongo builder also exposes `index` and `valueObject`. ## Workflow — Add an extension-typed scalar (pgvector) @@ -161,12 +178,15 @@ The concept: an extension contributes a namespace (`pgvector.*`) plus two descri `prisma.config.ts`: ```typescript -import pgvector from '@internal/extension-pgvector/control'; -import { defineConfig } from '@internal/postgres/config'; - -export default defineConfig({ - contract: './src/prisma/contract.prisma', - extensions: [pgvector], +import { definePrismaConfig } from '@prisma/cli-engine'; +import pgvector from '@prisma/orm-extension-pgvector/control'; +import { defineConfig as ormConfig } from '@prisma/orm-postgres/config'; + +export default definePrismaConfig({ + orm: ormConfig({ + contract: './src/prisma/contract.prisma', + extensions: [pgvector], + }), }); ``` @@ -218,7 +238,7 @@ model Feature { Verify the polymorphism syntax against the interpreter tests if in doubt: `packages/2-sql/2-authoring/contract-psl/test/interpreter.polymorphism.test.ts`. -Mongo has no schema layer, so polymorphism on Mongo is modelled by an explicit `discriminator` field on the model in the TS builder (see `@internal/mongo/contract-builder`); `@@base` / `@@discriminator` PSL attributes are SQL-only. +Mongo has no schema layer, so polymorphism on Mongo is modelled by an explicit `discriminator` field on the model in the TS builder (see `@prisma/orm-mongo/contract-builder`); `@@base` / `@@discriminator` PSL attributes are SQL-only. Querying the variants is a runtime concern — see `references/queries.md`. @@ -326,7 +346,7 @@ namespace public { } ``` -`supabase:auth.AuthUser` means: model `AuthUser` in namespace `auth` of contract space `supabase`. The target space is provided by a registered extension pack (here `@internal/extension-supabase/pack`). +`supabase:auth.AuthUser` means: model `AuthUser` in namespace `auth` of contract space `supabase`. The target space is provided by a registered extension pack (here `@prisma/orm-extension-supabase/pack`). Canonical worked example: `examples/supabase/src/contract.prisma`. @@ -343,28 +363,31 @@ model AuditLog { } ``` -A contract-level default can be set via `defaultControlPolicy` on `prismaContract(path, { defaultControlPolicy })`. See `references/migrations.md` for how control policies affect DDL planning. +A contract-level default can be set via `defaultControlPolicy` on `prismaContract(path, { defaultControlPolicy })`. See `prisma-orm-migrations/references/migrations.md` for how control policies affect DDL planning. -## Workflow — `@internal/extension-supabase` +## Workflow — `@prisma/orm-extension-supabase` -The concept: the Supabase extension provides the `supabase` contract space (the `auth` / `storage` schemas as `external` tables, plus the platform roles) and its own role-first runtime factory. It does not expose a `/control` subpath so it cannot be registered via the user-facing `defineConfig({ extensions: [...] })` façade — it is wired via `extensions` in the low-level config. See `examples/supabase` for the full working pattern. +The concept: the Supabase extension provides the `supabase` contract space (the `auth` / `storage` schemas as `external` tables, plus the platform roles) and its own role-first runtime factory. It publishes no `/control` subpath — register its `/pack` descriptor in the façade config's `extensions: [...]` instead. See `examples/supabase` for the full working pattern. `prisma.config.ts` (mirrors the example): ```typescript -import supabasePack from '@internal/extension-supabase/pack'; -import { defineConfig } from '@internal/cli/config-types'; -// ... other low-level imports - -export default defineConfig({ - // ... - extensions: [supabasePack], +import { definePrismaConfig } from '@prisma/cli-engine'; +import supabasePack from '@prisma/orm-extension-supabase/pack'; +import { defineConfig as ormConfig } from '@prisma/orm-postgres/config'; + +export default definePrismaConfig({ + orm: ormConfig({ + contract: './src/contract.prisma', + extensions: [supabasePack], + migrations: { dir: 'migrations' }, + }), }); ``` -`db.ts` does **not** use the stock `postgres()` factory — a Supabase app builds its client with the `supabase()` factory from `@internal/extension-supabase/runtime` (role-first: `asUser(jwt)` / `asAnon()` / `asServiceRole()`, JWT validation, RLS). That runtime — and RLS policy authoring (`policy_select` / `@@rls`) — is covered by **`references/supabase.md`**; load it for anything past the config wiring. +`db.ts` does **not** use the stock `postgres()` factory — a Supabase app builds its client with the `supabase()` factory from `@prisma/orm-extension-supabase/runtime` (role-first: `asUser(jwt)` / `asAnon()` / `asServiceRole()`, JWT validation, RLS). That runtime — and RLS policy authoring (`policy_select` / `@@rls`) — is covered by **`references/supabase.md`**; load it for anything past the config wiring. -Export subpaths: `@internal/extension-supabase/pack`, `@internal/extension-supabase/runtime`, `@internal/extension-supabase/contract`. Canonical worked example: `examples/supabase`. +Export subpaths: `@prisma/orm-extension-supabase/pack`, `@prisma/orm-extension-supabase/runtime`, `@prisma/orm-extension-supabase/contract`. Canonical worked example: `examples/supabase`. ## Workflow — Brownfield introspection @@ -381,10 +404,10 @@ Infer captures indexes at full fidelity — expression, partial (`where:`), uniq 1. **Forgetting to re-emit after an edit.** `contract.json` and `contract.d.ts` go stale; downstream typecheck and `migration plan` see the old shape. Re-emit, or install the Vite plugin (`references/build.md`). 2. **Editing the emitted artefacts.** `contract.json` and `contract.d.ts` are emitted; edits there round-trip away on the next emit. Edit the source. -3. **Wrong factory/import path for the TS builder.** `defineContract`, `field`, `model`, `rel` come from `@internal/postgres/contract-builder` (or `@internal/mongo/contract-builder`). Outside the callback overload, the available field constructors are `field.column(...)`, `field.generated(...)`, `field.namedType(...)`. -4. **Reaching into internal packages from user code.** User-authored files (`prisma.config.ts`, `contract.ts`, `db.ts`, control clients) import only from `@internal//` and `@internal/extension-/`. Imports from `@internal/cli/*`, `@internal/family-*`, `@internal/target-*`, `@internal/adapter-*`, `@internal/driver-*`, or `@internal/sql-contract-*` are framework-internal — the façade composes them for you. If a façade subpath you need is missing for your target, see *What Prisma Next doesn't do yet* and route to `references/feedback.md`. The canonical worked examples are `examples/multi-extension-monorepo/app/prisma.config.ts` and `examples/prisma-8-postgis-demo/prisma.config.ts`. -5. **Confusing the config `extensions` with the TS builder's `extensions`.** Same packs, two surfaces, one field name but two shapes: `defineConfig({ extensions: [pgvector] })` (array of *control* descriptors from `@internal/extension-/control`) versus `defineContract({ extensions: { pgvector } })` (record of *pack* descriptors from `@internal/extension-/pack`). -6. **Renaming a field and expecting the planner to detect it.** Prisma Next has no in-contract rename hint; the planner sees a destructive drop+add. Hand-edit `migration.ts` after `migration plan` (see `references/migrations.md`), or use the keep-then-drop two-migration pattern. +3. **Wrong factory/import path for the TS builder.** `defineContract`, `field`, `model`, `rel` come from `@prisma/orm-postgres/contract-builder` (or `@prisma/orm-mongo/contract-builder`). Outside the callback overload, the available field constructors are `field.column(...)`, `field.generated(...)`, `field.namedType(...)`. +4. **Reaching past the user-facing façade subpaths.** User-authored files (`prisma.config.ts`, `contract.ts`, `db.ts`, control clients) import only from `@prisma/orm-/` (config, contract-builder, control, runtime) and `@prisma/orm-extension-/` — plus `@prisma/cli-engine` for `definePrismaConfig`. The façades' deeper subpaths (`/family*`, `/target*`, `/adapter*`, `/driver*`, `/components*`) are framework plumbing — the façade composes them for you. If a façade subpath you need is missing for your target, see *What Prisma Next doesn't do yet* and route to `references/feedback.md`. The canonical worked examples are `examples/multi-extension-monorepo/app/prisma.config.ts` and `examples/prisma-8-postgis-demo/prisma.config.ts`. +5. **Confusing the config `extensions` with the TS builder's `extensions`.** Same packs, two surfaces, one field name but two shapes: `defineConfig({ extensions: [pgvector] })` (array of *control* descriptors from `@prisma/orm-extension-/control`) versus `defineContract({ extensions: { pgvector } })` (record of *pack* descriptors from `@prisma/orm-extension-/pack`). +6. **Renaming a field and expecting the planner to detect it.** Prisma Next has no in-contract rename hint; the planner sees a destructive drop+add. Hand-edit `migration.ts` after `migration plan` (see `prisma-orm-migrations/references/migrations.md`), or use the keep-then-drop two-migration pattern. ## What Prisma Next doesn't do yet @@ -407,9 +430,9 @@ Infer captures indexes at full fidelity — expression, partial (`where:`), uniq ## Checklist - [ ] Read `prisma.config.ts` and identified the contract source (path string ending in `.prisma` or `.ts`) and the installed `extensions: [...]`. -- [ ] All user-authored imports resolve to `@internal//` (e.g. `@internal/postgres/config`) or `@internal/extension-/`. No imports from `@internal/cli/*`, `@internal/family-*`, `@internal/target-*`, `@internal/adapter-*`, `@internal/driver-*`, or `@internal/sql-contract-*` in user files. +- [ ] All user-authored imports resolve to `@prisma/orm-/` (e.g. `@prisma/orm-postgres/config`), `@prisma/orm-extension-/`, or `@prisma/cli-engine`. No imports from the façades' deeper plumbing subpaths (`/family*`, `/target*`, `/adapter*`, `/driver*`, `/components*`) in user files. - [ ] Edited the contract source (`contract.prisma` or `contract.ts`), not an emitted artefact. -- [ ] For new extension namespaces: added the package, imported its control descriptor (`@internal/extension-/control`), added it to `extensions: [...]` in `defineConfig({...})` (and the matching pack descriptor to `defineContract({extensions: {...}})` if using the TS builder). +- [ ] For new extension namespaces: added the package, imported its control descriptor (`@prisma/orm-extension-/control`), added it to `extensions: [...]` in `defineConfig({...})` (and the matching pack descriptor to `defineContract({extensions: {...}})` if using the TS builder). - [ ] For renames: hand-edited `migration.ts` after `migration plan` (or used the keep-then-drop two-migration pattern) — Prisma Next has no rename hint today. - [ ] Ran `pnpm prisma contract emit` after the edit (or let the Vite plugin re-emit on save). - [ ] Confirmed `contract.json` and `contract.d.ts` updated next to the source. diff --git a/skills/prisma-8/references/debug.md b/skills/prisma-orm-core-concepts/references/failure-modes.md similarity index 87% rename from skills/prisma-8/references/debug.md rename to skills/prisma-orm-core-concepts/references/failure-modes.md index 3a4e2cf68b31..6165bb57a1f9 100644 --- a/skills/prisma-8/references/debug.md +++ b/skills/prisma-orm-core-concepts/references/failure-modes.md @@ -1,5 +1,5 @@ -# Prisma Next — Debug +# Prisma Next — Failure Modes (Structured Errors) > **Edit your data contract. Prisma handles the rest.** @@ -79,35 +79,35 @@ The single source of truth: read the envelope, find the row by `code` (or `meta. |---|---|---| | `PN-CLI-4001` *Config file not found* | Most `prisma` commands | Run `prisma orm init`, or pass `--config `. | | `PN-CLI-4002` *Contract configuration missing* | `contract emit`, `db *` | Add `contract: { ... }` to `prisma.config.ts`. See `references/contract.md`. | -| `PN-CLI-4003` *Contract validation failed* | `contract emit`, `db *` | Re-run `pnpm prisma-cli contract emit` after fixing the contract source named in `where.path`. See `references/contract.md`. | +| `PN-CLI-4003` *Contract validation failed* | `contract emit`, `db *` | Re-run `pnpm prisma contract emit` after fixing the contract source named in `where.path`. See `references/contract.md`. | | `PN-CLI-4005` *Database connection is required* | `db *`, `db migrate`, `migration status` | Pass `--db ` or set `db.connection` in `prisma.config.ts`. | | `PN-CLI-4011` *Missing extension packs in config* | `contract emit` (e.g. contract uses `pgvector.Vector(...)` but config does not list the pgvector pack) | Add the descriptors named in `meta.missingExtensions` to `extensions` in `prisma.config.ts`. See `references/contract.md`. | -| `PN-CLI-4020` *Migration planning failed* | `db init`, `db update` | Inspect `meta.conflicts`. Recovery is per-conflict — chain to `references/migrations.md`. | +| `PN-CLI-4020` *Migration planning failed* | `db init`, `db update` | Inspect `meta.conflicts`. Recovery is per-conflict — chain to `prisma-orm-migrations/references/migrations.md`. | | `PN-CLI-5002/5003/5004/…` *Init errors* | `prisma orm init` | Re-run with the missing/invalid flags listed in `meta.missingFlags` or `meta.allowed`. | -| `PN-MIG-2001` *Unfilled migration placeholder* | `node migrations/app//migration.ts` (self-emit) or `db migrate` | Edit `migration.ts`, replace the named `placeholder("")` with a real query closure, self-emit. See `references/migrations.md`. | +| `PN-MIG-2001` *Unfilled migration placeholder* | `node migrations/app//migration.ts` (self-emit) or `db migrate` | Edit `migration.ts`, replace the named `placeholder("")` with a real query closure, self-emit. See `prisma-orm-migrations/references/migrations.md`. | | `PN-MIG-2002` *migration.ts not found* | Reading a migration package | Restore from version control or scaffold a fresh package with `migration plan`. | -| `PN-MIG-2003` *Invalid default export* | Loading `migration.ts` | Use `export default class extends Migration { ... }` (or factory `() => ({ ... })`). See `references/migrations.md`. | +| `PN-MIG-2003` *Invalid default export* | Loading `migration.ts` | Use `export default class extends Migration { ... }` (or factory `() => ({ ... })`). See `prisma-orm-migrations/references/migrations.md`. | | `PN-MIG-2005` *dataTransform contract mismatch* | Building a data-transform query plan | Pass the same `endContract` reference to both `dataTransform(endContract, …)` and the query-builder context. | | `PN-RUN-3001` *Database not signed* | `db verify`, runtime startup | DB has no marker yet. Run `prisma db init --db ` (baseline empty DB) or `db update --db ` (apply contract directly). | -| `PN-RUN-3002` *Hash mismatch* | `db verify`, runtime startup | Marker disagrees with contract hash. Either migrate forward (`db migrate` / `db update`), or — if the DB is correct after a manual fix-up — `db sign`. See `references/migrations.md`. | +| `PN-RUN-3002` *Hash mismatch* | `db verify`, runtime startup | Marker disagrees with contract hash. Either migrate forward (`db migrate` / `db update`), or — if the DB is correct after a manual fix-up — `db sign`. See `prisma-orm-migrations/references/migrations.md`. | | `PN-RUN-3003` *Target mismatch* | Runtime startup | Contract target ≠ config target; align them (see `meta.expected` / `meta.actual`). | | `PN-RUN-3004` *Schema verification failed* | `db verify` (full mode) | Inspect `meta.verificationResult`. Run `db update` to reconcile, or adjust contract. | | `PN-RUN-3010` *Schema verification failed (CLI surface)* | `db verify` schema-only | Same as 3004. | | `PN-RUN-3020` *Migration runner failed* | `db migrate`, `db update`, `db init` | Inspect `meta` for the conflict; reconcile schema drift, then re-run. Previously applied migrations are preserved. | | `PN-RUN-3030` *Destructive changes require confirmation* | `db update` (interactive prompt fires; non-interactive returns this code) | Re-run with `-y` (or `--yes`) to apply, or `--dry-run` to preview. **Only `db update` has this flow** — `db migrate` does not gate destructive ops on a flag. | -| `PN-RUN-3000` *(wrapper)* | `db migrate`, others wrapping `MigrationToolsError` | Read `meta.code`. Cases: `MIGRATION.HASH_MISMATCH` (re-emit: `node migrations/app//migration.ts`); `MIGRATION.AMBIGUOUS_TARGET` (concurrent migrations — `references/migration-review.md`); `MIGRATION.STALE_CONTRACT_BOOKENDS` (re-run `migration plan`); `MIGRATION.NO_INVARIANT_PATH` / `MIGRATION.UNKNOWN_INVARIANT` (`references/migration-review.md`); `MIGRATION.PATH_UNREACHABLE` / `MIGRATION.MARKER_MISMATCH` (run `db migrate --show --db $URL` to inspect the path, then `migration plan --from --to ` or `migration list` to audit the graph — see `references/migration-review.md`). | +| `PN-RUN-3000` *(wrapper)* | `db migrate`, others wrapping `MigrationToolsError` | Read `meta.code`. Cases: `MIGRATION.HASH_MISMATCH` (re-emit: `node migrations/app//migration.ts`); `MIGRATION.AMBIGUOUS_TARGET` (concurrent migrations — `prisma-orm-migrations/references/migration-review.md`); `MIGRATION.STALE_CONTRACT_BOOKENDS` (re-run `migration plan`); `MIGRATION.NO_INVARIANT_PATH` / `MIGRATION.UNKNOWN_INVARIANT` (`prisma-orm-migrations/references/migration-review.md`); `MIGRATION.PATH_UNREACHABLE` / `MIGRATION.MARKER_MISMATCH` (run `db migrate --show --db $URL` to inspect the path, then `migration plan --from --to ` or `migration list` to audit the graph — see `prisma-orm-migrations/references/migration-review.md`). | | `PN-SCHEMA-0001` | `db verify` schema check | Live schema does not satisfy contract. `meta.verificationResult` has the diff. Run `db update` or adjust the contract. | -| `MIGRATION.UP_TO_DATE` / `.DATABASE_BEHIND` | `migration status` `info` diagnostics | Informational; exit 0. See `references/migration-review.md`. | -| `MIGRATION.MISSING_INVARIANTS` | `migration status` `info` diagnostic | The live marker reached the destination hash structurally but doesn't carry all invariants the target ref requires. Run `db migrate --to --db $URL` to take a path that covers the missing invariants. See `references/migration-review.md`. | -| `MIGRATION.NO_MARKER` / `.MARKER_NOT_IN_HISTORY` / `.DIVERGED` / `CONTRACT.AHEAD` / `CONTRACT.UNREADABLE` | `migration status` `warn` diagnostics (exit 0; CI gates parse `--json`) | Read `severity` *and* `code`. `references/migration-review.md` covers the diamond/diverged/marker-out-of-history flows. | +| `MIGRATION.UP_TO_DATE` / `.DATABASE_BEHIND` | `migration status` `info` diagnostics | Informational; exit 0. See `prisma-orm-migrations/references/migration-review.md`. | +| `MIGRATION.MISSING_INVARIANTS` | `migration status` `info` diagnostic | The live marker reached the destination hash structurally but doesn't carry all invariants the target ref requires. Run `db migrate --to --db $URL` to take a path that covers the missing invariants. See `prisma-orm-migrations/references/migration-review.md`. | +| `MIGRATION.NO_MARKER` / `.MARKER_NOT_IN_HISTORY` / `.DIVERGED` / `CONTRACT.AHEAD` / `CONTRACT.UNREADABLE` | `migration status` `warn` diagnostics (exit 0; CI gates parse `--json`) | Read `severity` *and* `code`. `prisma-orm-migrations/references/migration-review.md` covers the diamond/diverged/marker-out-of-history flows. | | `BUDGET.ROWS_EXCEEDED` / `BUDGET.TIME_EXCEEDED` | Runtime, when the `budgets` middleware is active | Tune `budgets({ maxRows, maxLatencyMs, ... })` or rewrite the query. See `references/runtime.md`. | | `LINT.SELECT_STAR` / `LINT.NO_LIMIT` / `LINT.DELETE_WITHOUT_WHERE` / `LINT.UPDATE_WITHOUT_WHERE` / `LINT.READ_ONLY_MUTATION` | Runtime, when the `lints` middleware is active | Fix the query (add a `WHERE` / `LIMIT` / explicit columns), or relax the lint config. See `references/runtime.md`. | | `PLAN.HASH_MISMATCH` | Runtime, executing a precompiled plan | The contract the plan was built against does not match the runtime contract. Re-emit, rebuild, redeploy. | | `CONTRACT.MARKER_MISSING` / `CONTRACT.MARKER_MISMATCH` | Runtime, marker check before executing | Same family as `PN-RUN-3001` / `PN-RUN-3002` but raised in-process by the runtime rather than a CLI. Recovery is the same. | | `RUNTIME.ABORTED` (`details.phase` = `encode\|decode\|stream\|beforeExecute\|afterExecute\|onRow`) | Runtime, when an `AbortSignal` fires mid-execute | Cancellation, not a bug; surface to the caller. | | `SqlQueryError` (no `PN-` code) | Raw-SQL paths surfacing a driver error | Inspect `sqlState` + `constraint` + `table` + `column`. Postgres `23505` = unique violation, `23503` = foreign-key violation, etc. Fix the data or the schema. | -| TypeScript error mentioning a capability (e.g. `returning()` not on the type, `include` of a many-relation off a many-load) | Authoring-time, before any envelope fires | Capability gates are declared in the **contract** (`capabilities` block, namespaced by target/family), not in `prisma.config.ts`. Route to `references/contract.md` for capability declaration and to `references/queries.md` for which method gates on which capability. Re-emit (`pnpm prisma-cli contract emit`) after enabling. | -| TypeScript error mentioning a missing field/method on `db.orm.` or a stale `Contract` shape | Authoring-time | Re-emit (`pnpm prisma-cli contract emit`); confirm `db.ts` instantiates with `postgres(...)` (the type parameters propagate the contract types). See `references/runtime.md` and `references/contract.md`. | +| TypeScript error mentioning a capability (e.g. `returning()` not on the type, `include` of a many-relation off a many-load) | Authoring-time, before any envelope fires | Capability gates are declared in the **contract** (`capabilities` block, namespaced by target/family), not in `prisma.config.ts`. Route to `references/contract.md` for capability declaration and to `references/queries.md` for which method gates on which capability. Re-emit (`pnpm prisma contract emit`) after enabling. | +| TypeScript error mentioning a missing field/method on `db.orm..` or a stale `Contract` shape | Authoring-time | Re-emit (`pnpm prisma contract emit`); confirm `db.ts` instantiates with `postgres(...)` (the single type parameter propagates the contract types), and that model access goes through the namespace coordinate (`db.orm.public.User`, not `db.orm.User`). See `references/runtime.md` and `references/contract.md`. | If the envelope's `code` is not in this table, follow the envelope's `fix` field literally — it's the framework's first-party next move. If `fix` is empty or unhelpful, escalate via `references/feedback.md`. @@ -124,7 +124,7 @@ If the envelope's `code` is not in this table, follow the envelope's `fix` field - **Studio / GUI database browser.** No first-party Studio. Workaround: `prisma db schema` for a CLI tree of the live schema, or use a third-party tool (TablePlus, DataGrip, `psql`) against your `DATABASE_URL`. If you need a built-in GUI, file a feature request via `references/feedback.md`. - **First-class query logger middleware.** No built-in "log every query" middleware ships with the framework. Workaround: write a small custom middleware that wraps each operation (see `references/runtime.md` for middleware composition). If you need a built-in query log, file a feature request via `references/feedback.md`. - **`EXPLAIN` integration.** No first-class `.explain()` on plans. Workaround: write the EXPLAIN as a raw query (``db.raw.sql`EXPLAIN ANALYZE ...` ``; see `references/queries.md`). If you need first-class EXPLAIN, file a feature request via `references/feedback.md`. -- **Prepared-statement caching as a user-facing surface.** Adapters prepare under the hood for parameterized queries, but you cannot pre-prepare and re-execute a statement by name. Workaround: use TypedSQL (see `references/queries.md`). If you need prepared statements as a first-class API, file a feature request via `references/feedback.md`. +- **Prepared-statement caching as a user-facing surface.** Adapters prepare under the hood for parameterized queries, but you cannot pre-prepare and re-execute a statement by name. Workaround: extract a function that returns the built plan and re-execute it via `db.runtime().execute(plan)` (see `references/queries.md`). If you need prepared statements as a first-class API, file a feature request via `references/feedback.md`. ## Asking for help when the envelope doesn't route diff --git a/skills/prisma-8/references/feedback.md b/skills/prisma-orm-core-concepts/references/feedback.md similarity index 97% rename from skills/prisma-8/references/feedback.md rename to skills/prisma-orm-core-concepts/references/feedback.md index a46c2c874267..ec5044554722 100644 --- a/skills/prisma-8/references/feedback.md +++ b/skills/prisma-orm-core-concepts/references/feedback.md @@ -14,7 +14,7 @@ Canonical channels: ## When to Use -- A capability-gap entry from another `prisma-next-*` skill fired and the user said *"yes, file the feature request"*. +- A capability-gap entry from another Prisma Next skill (`prisma-orm-*`) fired and the user said *"yes, file the feature request"*. - User says *"this is a bug"*, *"file this"*, *"report this"*, *"file an issue against PN"*, *"send feedback"*, *"this should be a feature"*. - User describes an unexpected behaviour — wrong exit code, error message that didn't match what happened, type signature that doesn't match runtime behaviour, planner refused a migration that looked safe — and wants it on the framework team's radar. - User asks *"can I ask the Prisma team about this?"*, *"is there somewhere I can talk to the team?"*, *"is this the intended way to do X?"*, *"how should I integrate with PN?"*, or any other open-ended Q&A or design-feedback prompt — including extension authors asking integration questions. @@ -42,7 +42,7 @@ The user wants to hand something off to the team. Which channel? - The user describes a concrete bug (see *Classify* below for the bug-vs-feature split). - The user has a concrete feature request — a named capability, a specific API shape, a specific CLI flag — that they want on the backlog. -- A capability-gap entry from another `prisma-next-*` skill routed them here for a feature request. +- A capability-gap entry from another Prisma Next skill (`prisma-orm-*`) routed them here for a feature request. **Prisma Discord** () if any of: @@ -80,7 +80,7 @@ If both — a bug *and* the user wants a related feature — file two separate i For **either** kind: -- **Prisma Next version**: `pnpm ls @internal/postgres` (or `@internal/mongo`). If the project uses a target package, that version is canonical. +- **Prisma Next version**: `pnpm ls @prisma/orm-postgres` (or `@prisma/orm-mongo`). If the project uses a target package, that version is canonical. - **Node version**: `node -v`. - **Package manager**: `pnpm` / `npm` / `yarn` / `bun` / `deno`. - **OS**: `darwin` / `linux` / `win32` and the version string is enough. @@ -110,7 +110,7 @@ Bug-report body shape (fields named to match `.github/ISSUE_TEMPLATE/bug_report. ~~~markdown ## Package and version - + ## What happened? @@ -209,7 +209,7 @@ When step 1 picked the Discord channel (steps 2–7 do not apply): - Extension-author / partner-integration / breaking-change-coordination questions → the public extension-authors channel, or the user can ping a maintainer directly once they're in the server. 2. **Help draft the opening message.** Prisma's Discord is searchable; a well-framed opening message gets a faster, more useful answer. The agent drafts a short message with: - One-sentence summary of what the user is trying to do. - - The Prisma Next version (`pnpm ls @internal/postgres` or equivalent). + - The Prisma Next version (`pnpm ls @prisma/orm-postgres` or equivalent). - A short code snippet (PSL excerpt, query, config file) where relevant — redacted the same way as a GitHub issue body (no `DATABASE_URL`, no customer schema names). - The specific question the user wants answered. 3. **Do not auto-post.** The agent surfaces the drafted message to the user — *"here's an opening message you can paste into Discord; want to adjust before sending?"* — and lets the user decide whether to paste it as-is, edit it, or pick a different framing. diff --git a/skills/prisma-8/references/queries-mongo.md b/skills/prisma-orm-core-concepts/references/queries-mongo.md similarity index 88% rename from skills/prisma-8/references/queries-mongo.md rename to skills/prisma-orm-core-concepts/references/queries-mongo.md index 07c914e1437b..c3c2e050b886 100644 --- a/skills/prisma-8/references/queries-mongo.md +++ b/skills/prisma-orm-core-concepts/references/queries-mongo.md @@ -1,12 +1,12 @@ # Prisma Next — Queries (Mongo) -> Load this guide when `db.ts` imports from `@internal/mongo/runtime`. +> Load this guide when `db.ts` imports from `@prisma/orm-mongo/runtime`. Shared concepts (result consumption, script teardown, cross-target pitfalls, capability gaps) live in [`queries.md`](./queries.md). ## Key Concepts -**Mongo** (`mongo(...)` from `@internal/mongo/runtime`): +**Mongo** (`mongo(...)` from `@prisma/orm-mongo/runtime`): - **`db.orm.`** — ORM, lowercased plural contract root (`db.orm.users`, `db.orm.posts`). Same fluent chaining; `.where({ field: value })` object equality is the idiomatic filter form. - **`db.query`** — typed aggregation-pipeline builder. Start with `db.query.from('')`, chain `.match(...)` / `.project(...)` / `.group(...)` / `.lookup(...)`, terminal with `.build()`. Execute via `(await db.runtime()).execute(plan)`. @@ -49,7 +49,7 @@ const recent = await db.orm.posts **`.where(...)`** accepts a plain object whose keys are model field names and values are compared with equality (codec-aware — `ObjectId` fields accept string ids from the contract). Chain multiple `.where({ ... })` calls to AND-compose filters. -For operators the object form doesn't cover (`.in([...])`, range comparisons, nested logic), pass a `MongoFilterExpr` — today that means importing filter helpers from `@internal/mongo-query-ast/execution` (a façade-completeness gap; see *What Prisma Next doesn't do yet* in [`queries.md`](./queries.md)). Prefer the object form whenever equality suffices. +For operators the object form doesn't cover (`.in([...])`, range comparisons, nested logic), pass a `MongoFilterExpr` — today that means importing filter helpers from `@prisma/orm-mongo/query-ast/execution` (a façade-completeness gap; see *What Prisma Next doesn't do yet* in [`queries.md`](./queries.md)). Prefer the object form whenever equality suffices. **Polymorphic roots.** When the contract declares variants on a model, narrow before querying: @@ -80,7 +80,7 @@ Relation names match the contract's `@relation` field names. Nested includes fol ## Workflow — ORM writes -Mongo mutations require a preceding `.where(...)` filter (except `.create` / `.createAll`). Updates accept either a partial document or a field-accessor callback for Mongo operators: +Mongo mutations require a preceding `.where(...)` filter (except `.create` / `.createAll`). As on the SQL lane, `.update(...)` and `.delete()` operate on a **single** matching document (update goes through `findOneAndUpdate`) and return it (`Row | null`); the every-matching-document forms are `.updateAll(...)` / `.deleteAll()` (return the documents) and the `*AndCount` terminals (return the count). Updates accept either a partial document or a field-accessor callback for Mongo operators: ```typescript // Create — returns the row with server-assigned `_id`. @@ -123,7 +123,7 @@ await db.orm.users.where({ email: 'alice@example.com' }).upsert({ The Mongo ORM does not expose `.aggregate(...)` / `.groupBy(...)`. Express aggregations through **`db.query`** — the pipeline builder — with `.group(...)` and accumulator helpers: ```typescript -import { acc } from '@internal/mongo-query-builder'; +import { acc } from '@prisma/orm-mongo/query-builder'; const runtime = await db.runtime(); const plan = db.query @@ -140,7 +140,7 @@ const plan = db.query const byKind = await runtime.execute(plan); ``` -Import `acc` and expression helpers (`fn`) from `@internal/mongo-query-builder` when building computed pipeline stages. +Import `acc` and expression helpers (`fn`) from `@prisma/orm-mongo/query-builder` when building computed pipeline stages. ## Workflow — Query builder (`db.query`) @@ -148,7 +148,7 @@ The concept: `db.query.from('')` starts a typed aggregation-pipeline chain ```typescript // src/queries/analytics.ts -import { acc, fn } from '@internal/mongo-query-builder'; +import { acc, fn } from '@prisma/orm-mongo/query-builder'; import { db } from '../prisma/db'; const runtime = await db.runtime(); @@ -217,10 +217,11 @@ Update callbacks return arrays of field operations (`.set`, `.inc`, `.push`, `.p 7. **Trying to use `db.sql`.** There is no `db.sql` on Mongo. 8. **Trying to `db.execute(plan)` directly.** Execute query-builder plans via `(await db.runtime()).execute(plan)`. 9. **Expecting ORM `.aggregate(...)` / `.groupBy(...)`.** Use `db.query.from(...).group(...).build()` instead. +10. **Expecting `.delete()` / `.update(...)` to affect every matching document.** They affect one document (the first match) and return it. Use `.deleteAll()` / `.updateAll(...)` or the `*AndCount` terminals for many-document writes. ## Reference Files -- Example queries under [`examples/mongo-demo/src/server.ts`](examples/mongo-demo/src/server.ts) — ORM reads, `.include`, `.variant`, and pipeline DSL via `db.query`. +- Example queries under [`examples/mongo-demo/src/server.ts`](../../../examples/mongo-demo/src/server.ts) — ORM reads, `.include`, `.variant`, and pipeline DSL via `db.query`. - Integration tests under `examples/mongo-demo/test/` (`blog.test.ts`, `crud-lifecycle.test.ts`, `query-builder-writes.test.ts`). - Query builder README under `packages/2-mongo-family/5-query-builders/query-builder/README.md`. - ORM collection surface under `packages/2-mongo-family/5-query-builders/orm/src/collection.ts`. diff --git a/skills/prisma-8/references/queries-postgres.md b/skills/prisma-orm-core-concepts/references/queries-postgres.md similarity index 70% rename from skills/prisma-8/references/queries-postgres.md rename to skills/prisma-orm-core-concepts/references/queries-postgres.md index aab647ab8df2..368cb6030823 100644 --- a/skills/prisma-8/references/queries-postgres.md +++ b/skills/prisma-orm-core-concepts/references/queries-postgres.md @@ -1,15 +1,15 @@ # Prisma Next — Queries (Postgres) -> Load this guide when `db.ts` imports from `@internal/postgres/runtime`. +> Load this guide when `db.ts` imports from `@prisma/orm-postgres/runtime`. Shared concepts (result consumption, script teardown, cross-target pitfalls, capability gaps) live in [`queries.md`](./queries.md). ## Key Concepts -**Postgres** (`postgres(...)` from `@internal/postgres/runtime`): +**Postgres** (`postgres(...)` from `@prisma/orm-postgres/runtime`): -- **`db.orm.`** — ORM, PascalCase model name (`db.orm.User`). Fluent `.where(...).select(...).orderBy(...).all()`, fully typed against `Contract`. Default lane for CRUD with relations. -- **`db.sql.
`** — SQL builder, lowercase storage name (`db.sql.user`). Produces a *plan* executed via `db.runtime().execute(plan)`. Use when the ORM is too high-level — explicit `JOIN`, computed projections, set operations, window functions. +- **`db.orm..`** — ORM, namespace then PascalCase model name (`db.orm.public.User`). Fluent `.where(...).select(...).orderBy(...).all()`, fully typed against `Contract`. Default lane for CRUD with relations. +- **`db.sql..
`** — SQL builder, namespace then lowercase storage name (`db.sql.public.user`). Produces a *plan* executed via `db.runtime().execute(plan)`. Use when the ORM is too high-level — explicit `JOIN`, computed projections, set operations, window functions. Reach for the ORM first; drop to `db.sql` when the ORM can't express the shape. Lane choice is local — one query function picks one lane, not the whole app. @@ -17,33 +17,33 @@ Reach for the ORM first; drop to `db.sql` when the ORM can't express the shape. | Need | Choose | Why | | --- | --- | --- | -| Standard CRUD with relations | **ORM (`db.orm.`)** | Highest ergonomics; fully typed; model-shaped. | +| Standard CRUD with relations | **ORM (`db.orm..`)** | Highest ergonomics; fully typed; model-shaped. | | Eager-load related records | **ORM `.include(...)`** | Composes with `.where` / `.select` / `.orderBy` / `.limit` per branch. | | Aggregate (count, sum, avg) | **ORM `.aggregate(...)`** | Typed result; works with grouping (`.groupBy(...).aggregate(...)`). | -| `INSERT ... RETURNING` / `UPDATE ... RETURNING` typed result | **ORM mutations** (returns updated rows) or **`db.sql..insert(...).returning(...)`** | ORM returns inserted/updated rows; SQL builder exposes `.returning(...)` explicitly. | -| Computed projection (e.g. `ST_DistanceSphere(location, point) AS meters`) alongside model fields | **SQL builder (`db.sql.`)** | The ORM projects model fields; arbitrary expression projection is the SQL builder's seam. | +| `INSERT ... RETURNING` / `UPDATE ... RETURNING` typed result | **ORM mutations** (returns updated rows) or **`db.sql...insert(...).returning(...)`** | ORM returns inserted/updated rows; SQL builder exposes `.returning(...)` explicitly. | +| Computed projection (e.g. `ST_DistanceSphere(location, point) AS meters`) alongside model fields | **SQL builder (`db.sql..`)** | The ORM projects model fields; arbitrary expression projection is the SQL builder's seam. | | Complex `JOIN`, set operation, window function | **SQL builder** | The ORM doesn't express arbitrary joins. | | Postgres-specific feature (`LATERAL`, `FILTER`, custom aggregates) | **SQL builder**, falling back to extension operators when the extension provides them | DSL first; extensions can contribute operators (`postgis`, `pgvector`, `cipherstash`). | ## Workflow — ORM reads -The concept: `db.orm.` returns a *collection* you compose method-by-method. Each call returns a new collection (immutable chaining); the terminal verb (`.all()` / `.first()` / `.count()` / `.aggregate(...)`) issues the query. Predicates are lambdas over a field proxy: `u.field.(value)`. +The concept: `db.orm..` returns a *collection* you compose method-by-method. Each call returns a new collection (immutable chaining); the terminal verb (`.all()` / `.first()` / `.aggregate(...)`) issues the query. There is no bare `.count()` terminal — count rows with `.aggregate((a) => ({ total: a.count() }))` (see *Workflow — Aggregates*). Predicates are lambdas over a field proxy: `u.field.(value)`. ```typescript // src/queries/users.ts — one directory deep under src/, so the import is '../prisma/db' import { db } from '../prisma/db'; // Find one record by primary key shorthand. -const user = await db.orm.User.first({ id: userId }); +const user = await db.orm.public.User.first({ id: userId }); // Returns the full row or `null`. // Find one matching a predicate. -const alice = await db.orm.User +const alice = await db.orm.public.User .where((u) => u.email.eq('alice@example.com')) .first(); // Find many with projection, sort, and limit. -const recentUsers = await db.orm.User +const recentUsers = await db.orm.public.User .select('id', 'email', 'createdAt') .orderBy((u) => u.createdAt.desc()) .limit(10) @@ -54,10 +54,10 @@ const recentUsers = await db.orm.User ```typescript // Lambda form — full expression power. -db.orm.User.where((u) => u.email.eq('alice@example.com')); +db.orm.public.User.where((u) => u.email.eq('alice@example.com')); // Shorthand object form — equality on the named fields. -db.orm.User.where({ kind: 'admin' }); +db.orm.public.User.where({ kind: 'admin' }); ``` Operators on the field proxy include `.eq`, `.neq`, `.lt`, `.lte`, `.gt`, `.gte`, `.like`, `.ilike`, `.in([...])`, `.isNull()`, `.isNotNull()`. Extensions add target-specific operators on extension-typed columns (`pgvector`'s `.cosineDistance(...)`, `postgis`'s `.within(...)` / `.intersectsBbox(...)` / `.distanceSphere(...)`, `cipherstash`'s `.cipherstashEq(...)` / `.cipherstashGt(...)` / …). @@ -66,26 +66,26 @@ Operators on the field proxy include `.eq`, `.neq`, `.lt`, `.lte`, `.gt`, `.gte` ```typescript // Chained .where() — each clause AND-composes with the previous one. -await db.orm.Sale +await db.orm.public.Sale .where((s) => s.day.gte(start)) .where((s) => s.day.lte(end)) .all(); // Equivalent with an explicit `and(...)` inside one clause. -import { and } from '@internal/sql-orm-client'; // façade re-export pending — see *What PN doesn't do yet* in queries.md -await db.orm.Sale +import { and } from '@prisma/orm-postgres/orm-client'; +await db.orm.public.Sale .where((s) => and(s.day.gte(start), s.day.lte(end))) .all(); ``` The two forms emit the same SQL. Pick chained `.where()` when each clause adds a separate condition that reads as its own thought; pick `and(...)` when one logical predicate happens to have two parts and you want the visual grouping. Don't reach for a `between` helper — there isn't one. -**Combinators** (`and`, `or`, `not`) compose predicates, and **relation predicates** (`.some(...)`, `.none(...)`, `.every(...)`) recurse into a relation. These currently come from the internal `@internal/sql-orm-client` package — see *What Prisma Next doesn't do yet* in [`queries.md`](./queries.md): +**Combinators** (`and`, `or`, `not`) compose predicates, and **relation predicates** (`.some(...)`, `.none(...)`, `.every(...)`) recurse into a relation. The combinators import from the façade's `/orm-client` subpath, not `/runtime` — see *What Prisma Next doesn't do yet* in [`queries.md`](./queries.md): ```typescript -import { and, or, not } from '@internal/sql-orm-client'; +import { and, or, not } from '@prisma/orm-postgres/orm-client'; -await db.orm.User +await db.orm.public.User .where((u) => and( or(u.kind.eq('admin'), u.email.ilike('%@example.com')), @@ -98,7 +98,7 @@ await db.orm.User **Sorting and pagination.** `.orderBy(...)` accepts a single lambda or an array of lambdas (each calling `.asc()` / `.desc()` on a field). `.limit(n)` limits; `.offset(n)` offsets. ```typescript -await db.orm.Post +await db.orm.public.Post .where((p) => p.authorId.eq(userId)) .orderBy([(p) => p.createdAt.desc(), (p) => p.id.desc()]) .limit(20) @@ -108,13 +108,13 @@ await db.orm.Post **Cursor pagination.** Call `.cursor({ field: lastValue })` after `.orderBy(...)` to resume from a known position. The cursor requires a prior `orderBy` — the type system enforces this. Direction (forward or backward) follows the sort: ascending order means "greater than the cursor value", descending means "less than". ```typescript -const page1 = await db.orm.Post +const page1 = await db.orm.public.Post .orderBy((p) => p.createdAt.desc()) .limit(20) .all(); const last = page1[page1.length - 1]!; -const page2 = await db.orm.Post +const page2 = await db.orm.public.Post .orderBy((p) => p.createdAt.desc()) .cursor({ createdAt: last.createdAt }) .limit(20) @@ -130,7 +130,7 @@ Cursor keys must match fields in the active `orderBy`. For a composite `orderBy` The concept: `.include('', (branch) => branch.)` adds a relation branch to the parent query. The branch is its own collection — compose `.where` / `.select` / `.orderBy` / `.limit` on it just like the parent. ```typescript -await db.orm.User +await db.orm.public.User .select('id', 'email') .include('posts', (post) => post @@ -146,25 +146,25 @@ await db.orm.User **Reduce a to-many relation to a scalar.** A refinement callback may return a *reducer* — `count()`, `sum(field)`, `avg(field)`, `min(field)`, `max(field)`, plus the lossless `countBigInt()`, `sumBigInt(field)`, and `avgDecimal(field)` — instead of a collection. The parent's relation field then carries that one value rather than an array. Reducers exist only inside an `include(...)` callback; calling one elsewhere throws. ```typescript -await db.orm.User.include('posts', (posts) => posts.count()).all(); +await db.orm.public.User.include('posts', (posts) => posts.count()).all(); // → Array<{ ...user, posts: number }> — a parent with no posts reads 0 -await db.orm.User.include('posts', (posts) => posts.sum('views')).all(); +await db.orm.public.User.include('posts', (posts) => posts.sum('views')).all(); // → Array<{ ...user, posts: number | null }> -await db.orm.User.include('posts', (posts) => posts.avg('views')).all(); +await db.orm.public.User.include('posts', (posts) => posts.avg('views')).all(); // → Array<{ ...user, posts: number | null }> -await db.orm.User.include('posts', (posts) => posts.min('views')).all(); -await db.orm.User.include('posts', (posts) => posts.max('views')).all(); +await db.orm.public.User.include('posts', (posts) => posts.min('views')).all(); +await db.orm.public.User.include('posts', (posts) => posts.max('views')).all(); // → Array<{ ...user, posts: number | null }> // The lossless form, for a total that may outgrow a JS number: -await db.orm.User.include('posts', (posts) => posts.sumBigInt('views')).all(); +await db.orm.public.User.include('posts', (posts) => posts.sumBigInt('views')).all(); // → Array<{ ...user, posts: bigint | null }> // Several sub-views of one relation at once: -await db.orm.User.include('posts', (posts) => +await db.orm.public.User.include('posts', (posts) => posts.combine({ recent: posts.limit(3), total: posts.count() }), ).all(); // → Array<{ ...user, posts: { recent: Post[]; total: number } }> @@ -176,53 +176,76 @@ Nested `1:N → 1:N` includes (e.g. `User → posts → comments`) require the c ## Workflow — ORM writes +**`.update(data)` and `.delete()` write a single row** — the first row the filter matches — and return it (`Row | null`). Both require a prior `.where(...)`; an unfiltered call is a type error. For every-matching-row semantics, use the `*All` / `*AndCount` forms below. + ```typescript // Create — returns the inserted row. -const user = await db.orm.User.create({ id, email, displayName, kind, createdAt }); +const user = await db.orm.public.User.create({ id, email, displayName, kind, createdAt }); // Create with selected return — narrows the return shape. -const summary = await db.orm.User +const summary = await db.orm.public.User .select('id', 'email', 'kind') .create({ id, email, displayName, kind, createdAt }); -// Update by predicate. -await db.orm.User.where({ id }).update({ email: newEmail }); +// Update ONE row — the first match — and return it (Row | null). +await db.orm.public.User.where({ id }).update({ email: newEmail }); // Update with selected return. -await db.orm.User +await db.orm.public.User .where({ id }) .select('id', 'email', 'kind') .update({ email: newEmail }); -// Delete by predicate. -await db.orm.User.where({ id }).delete(); +// Delete ONE row — the first match — and return it (Row | null). +await db.orm.public.User.where({ id }).delete(); -// Upsert — typed by the create branch's shape. -await db.orm.User +// Upsert — typed by the create branch's shape; returns the row. +await db.orm.public.User .select('id', 'email', 'kind', 'createdAt') .upsert({ - create: { id, email, displayName, kind, createdAt: new Date() }, + create: { id, email, displayName, kind, createdAt: Temporal.Now.instant() }, update: { email, displayName, kind }, }); ``` +> **`DateTime` columns are Temporal-typed.** The Postgres `DateTime` / `timestamptz` codec encodes and decodes through the `Temporal` API: writes take Temporal instants (a JS `Date` is an encode error), and decoding throws `RUNTIME.TEMPORAL_UNAVAILABLE` on runtimes without a global `Temporal` (stock Node 24, Bun 1.3). Either add `import 'temporal-polyfill/global'` at the server entry before any query runs, or author the column with a string-typed alternative (e.g. `TimestamptzString`). See `references/contract.md`. + +### Operating on many rows + +The single-row `.update(data)` / `.delete()` terminals have every-matching-row counterparts: + +- **`.updateAll(data)` / `.deleteAll()` / `.createAll([...])`** — operate on every matching row (or insert every element) and return the affected rows. The result is awaitable and also async-iterable (`for await`). These require the target's `returning` capability — the Postgres adapter advertises it by default. +- **`.updateAndCount(data)` / `.deleteAndCount()` / `.createAndCount([...])`** — operate on every matching row and return only the affected-row count (`number`). No `returning` capability needed. + +```typescript +// Archive every matching post and get the rows back. +const archived = await db.orm.public.Post + .where((p) => p.createdAt.lt(cutoff)) + .updateAll({ status: 'archived' }); + +// Delete every matching row; only the count comes back. +const removed = await db.orm.public.Post + .where({ status: 'archived' }) + .deleteAndCount(); +``` + The ORM returns inserted / updated rows by default. The `.returning(...)` selector lives on the SQL builder (next section), where you build a plan and execute it explicitly. ## Workflow — Aggregates ```typescript -const totals = await db.orm.User.aggregate((aggregate) => ({ +const totals = await db.orm.public.User.aggregate((aggregate) => ({ totalUsers: aggregate.count(), })); -const adminTotals = await db.orm.User +const adminTotals = await db.orm.public.User .where({ kind: 'admin' }) .aggregate((aggregate) => ({ adminUsers: aggregate.count(), })); // Group-by + aggregate. -const byKind = await db.orm.User +const byKind = await db.orm.public.User .groupBy('kind') .having((having) => having.count().gte(minUsers)) .aggregate((aggregate) => ({ @@ -261,7 +284,7 @@ const byKind = await db.orm.User Nullability isn't a typing bug — it's faithful to what the database returns. Coalesce client-side when you want zero-fill: ```typescript -const revenue = await db.orm.Sale +const revenue = await db.orm.public.Sale .where((s) => s.day.gte(start)) .aggregate((a) => ({ total: a.sum('amount') })); // revenue.total: number | null @@ -271,16 +294,16 @@ const safe = revenue.total ?? 0; // ← apply at the consumption site, not in If `?? 0` is showing up on every aggregate, that's a signal you're calling `sum` (or peers) over potentially-empty filters — which is exactly when SQL returns NULL. The pattern is correct; the typing is honest. -## Workflow — SQL builder (`db.sql.
`) +## Workflow — SQL builder (`db.sql..
`) -The concept: `db.sql.
` is a table-shaped builder that produces a *plan*. The plan is a serialisable description of the query (AST + parameters); you execute it through the runtime with `db.runtime().execute(plan)`. The builder gives you the lanes the ORM doesn't express — explicit `JOIN`, arbitrary expression projection, target-specific operations through extension helpers — without dropping to raw SQL. +The concept: `db.sql..
` is a table-shaped builder that produces a *plan*. The plan is a serialisable description of the query (AST + parameters); you execute it through the runtime with `db.runtime().execute(plan)`. The builder gives you the lanes the ORM doesn't express — explicit `JOIN`, arbitrary expression projection, target-specific operations through extension helpers — without dropping to raw SQL. ```typescript // src/queries/posts.ts — adjust the relative import to match file depth. import { db } from '../prisma/db'; // Select with predicate and limit. -const plan = db.sql.post +const plan = db.sql.public.post .select('id', 'title', 'userId', 'createdAt') .where((f, fns) => fns.eq(f.userId, userId)) .limit(limit) @@ -295,14 +318,14 @@ The `.where(...)` callback receives `(fields, fns)` — `fields` is the field pr ```typescript // Insert and return selected columns. -const plan = db.sql.user +const plan = db.sql.public.user .insert({ email }) .returning('id', 'email') .build(); const [row] = await db.runtime().execute(plan); // Update with predicate and returning. -const updatePlan = db.sql.user +const updatePlan = db.sql.public.user .update({ email: newEmail }) .where((f, fns) => fns.eq(f.id, userId)) .returning('id', 'email') @@ -310,7 +333,7 @@ const updatePlan = db.sql.user const rows = await db.runtime().execute(updatePlan); // Delete with predicate. -const deletePlan = db.sql.user +const deletePlan = db.sql.public.user .delete() .where((f, fns) => fns.eq(f.id, userId)) .build(); @@ -323,7 +346,7 @@ await db.runtime().execute(deletePlan); ```typescript // Project a computed expression alongside model fields. -const plan = db.sql.cafe +const plan = db.sql.public.cafe .select('id', 'name') .select('meters', (f, fns) => fns.distanceSphere(f.location, point)) .orderBy((f, fns) => fns.distanceSphere(f.location, point), { direction: 'asc' }) @@ -333,8 +356,8 @@ const plan = db.sql.cafe const rows = await db.runtime().execute(plan); // Self-join with an alias. -db.sql.post - .innerJoin(db.sql.post.as('p2'), (f, fns) => fns.ne(f.p1.userId, f.p2.userId)) +db.sql.public.post + .innerJoin(db.sql.public.post.as('p2'), (f, fns) => fns.ne(f.p1.userId, f.p2.userId)) // ... .build(); ``` @@ -345,11 +368,11 @@ The concept: `db.transaction(fn)` opens a transaction and passes a `tx` context ```typescript await db.transaction(async (tx) => { - const user = await tx.orm.User.create({ id, email }); - await tx.orm.Post.create({ userId: user.id, title: 'hello' }); + const user = await tx.orm.public.User.create({ id, email }); + await tx.orm.public.Post.create({ userId: user.id, title: 'hello' }); // SQL-builder plan inside the transaction. - const plan = tx.sql.post.update({ status: 'archived' }) + const plan = tx.sql.public.post.update({ status: 'archived' }) .where((f, fns) => fns.lt(f.createdAt, cutoff)) .build(); await tx.execute(plan); @@ -362,7 +385,7 @@ The callback's return value passes through `db.transaction(...)`. Capture insert ## Namespace-aware accessors -When the contract declares multiple namespaces, both `db.sql` and `db.orm` expose a namespace coordinate alongside the flat bare-name surface: +Models and tables are always addressed by namespace coordinate — the first key under `db.orm` / `db.sql` is the namespace: ```typescript // db.sql..
@@ -375,7 +398,7 @@ const user = await db.orm.public.User.create({ id: 1, email: 'a@x.io' }); const authUser = await db.orm.auth.User.create({ id: 2, token: 'tok' }); ``` -The flat `db.sql.users` / `db.orm.User` form still works when bare names are unique across all namespaces. When the same bare name appears in more than one namespace, use the coordinate form — both the type system and the runtime require it to resolve to the right table. +There is no flat `db.sql.users` / `db.orm.User` form — the runtime's first-level keys are namespace ids only, whatever the contract's namespace count. Models outside any `namespace` block live in the default namespace (`public`), so single-namespace apps address everything under `db.orm.public.*` / `db.sql.public.*`. Cross-namespace relations (e.g. `public.Profile` → `auth.User`) follow the same `.include()` syntax; the ORM resolves the correct schema-qualified join automatically. @@ -385,17 +408,19 @@ Cross-namespace relations (e.g. `public.Profile` → `auth.User`) follow the sam 2. **Using `.all()` when you wanted one row.** `.all()` issues no implicit limit. Use `.first()` or `.first({ pk })`. 3. **Coalescing `count()` with `?? 0` "just in case".** `count()` is `number`, not `number | null` — SQL answers an empty set with `0`. The `?? 0` belongs on `sum` / `avg` / `min` / `max`, and its zero should match the aggregate's own type (`0` for an integer sum, `0n` for `sumBigInt`, `'0'` where the result is a decimal string). 4. **Reaching for `.between(a, b)` on a field proxy.** It doesn't exist. Either chain `.where((m) => m.field.gte(a)).where((m) => m.field.lte(b))` or use `and(m.field.gte(a), m.field.lte(b))` inside one `.where()` clause. -5. **Importing `and` / `or` / `not` from a Postgres façade subpath.** The combinators currently live in `@internal/sql-orm-client` — an internal package. See *What Prisma Next doesn't do yet* in [`queries.md`](./queries.md). -6. **Trying to `db.sql.from(tables.user)`.** That surface does not exist. The builder is table-shaped: `db.sql..select(...)`. There is no `db.schema.tables` either. +5. **Importing `and` / `or` / `not` from `@prisma/orm-postgres/runtime`.** The combinators live on the `/orm-client` subpath: `import { and, or, not } from '@prisma/orm-postgres/orm-client'`. See *What Prisma Next doesn't do yet* in [`queries.md`](./queries.md). +6. **Trying to `db.sql.from(tables.user)`.** That surface does not exist. The builder is table-shaped: `db.sql..
.select(...)`. There is no `db.schema.tables` either. 7. **Trying to `db.execute(plan)` directly.** Plans execute through the runtime: `db.runtime().execute(plan)`. Inside a transaction, use `tx.execute(plan)`. 8. **Setting `capabilities: { lateral: true }` in `prisma.config.ts`.** `defineConfig` does not take `capabilities`. Capabilities are declared by the active adapter and become part of the emitted contract; the Postgres adapter advertises `lateral`, `jsonAgg`, and `returning` out of the box. Enable extension capabilities through `extensions: [...]` in the config (see `references/contract.md`). 9. **Confabulating a TypedSQL or `.stream()` surface.** Neither exists today. Raw SQL does: the client's raw lane, ``db.raw.sql`…` ``. See *What Prisma Next doesn't do yet* in [`queries.md`](./queries.md) for all three. 10. **Mixing the ORM mutation return with `runtime.execute(plan)`.** ORM terminals issue the query themselves and return rows. `runtime.execute` is for SQL-builder plans. -11. **Ordering grouped rows by an aggregate metric.** The grouped collection supports `.orderBy(...)` on group keys plus `.limit(...)` / `.offset(...)`, but it cannot order by an aggregate alias such as `SUM(amount)`. Sorting the materialized aggregate result in JS is fine at small cardinalities; for large grouped result sets, drop to `db.sql.
`. +11. **Ordering grouped rows by an aggregate metric.** The grouped collection supports `.orderBy(...)` on group keys plus `.limit(...)` / `.offset(...)`, but it cannot order by an aggregate alias such as `SUM(amount)`. Sorting the materialized aggregate result in JS is fine at small cardinalities; for large grouped result sets, drop to `db.sql..
`. +12. **Expecting `.delete()` / `.update(...)` to affect every matching row.** They affect the **first** match only and return it (`Row | null`). Use `.deleteAll()` / `.updateAll(...)` for the rows, or `.deleteAndCount()` / `.updateAndCount(...)` for the count. +13. **Writing a JS `Date` into a `DateTime` column, or reading one without a `Temporal` global.** The timestamptz codec is Temporal-based: writes need Temporal instants, and decode throws `RUNTIME.TEMPORAL_UNAVAILABLE` without a global `Temporal`. Load `temporal-polyfill/global` at the entry point or use a string-typed column (e.g. `TimestamptzString`). ## Reference Files -- Example queries under [`examples/prisma-8-demo/src/orm-client/`](examples/prisma-8-demo/src/orm-client/) and [`examples/prisma-8-demo/src/queries/`](examples/prisma-8-demo/src/queries/) — canonical ORM and SQL-builder shapes. +- Example queries under [`examples/prisma-8-demo/src/orm-client/`](../../../examples/prisma-8-demo/src/orm-client/) and [`examples/prisma-8-demo/src/queries/`](../../../examples/prisma-8-demo/src/queries/) — canonical ORM and SQL-builder shapes. - ORM client source under `packages/3-extensions/sql-orm-client/src/`. - SQL builder source under `packages/2-sql/4-lanes/sql-builder/src/`. @@ -408,8 +433,9 @@ Cross-namespace relations (e.g. `public.Profile` → `auth.User`) follow the sam - [ ] Compared and serialised aggregate *results* as what they are — a `bigint` from a suffixed variant needs `0n` literals and `String(value)` rather than bare `JSON.stringify` — leaving the ORM's `having(...)` operands as numbers, and matching each SQL-builder comparison literal to the aggregate's own result codec (`fns.gt(fns.count(), 1)`). - [ ] Expressed ranges as chained `.where(...)` clauses or a single `and(...)` clause — did NOT reach for a non-existent `.between(...)` operator. - [ ] For cursor pagination, used `.orderBy(...).cursor({ field: lastValue }).limit(n).all()` — did NOT hand-write a `.where(p => p.field.lt(cursor))` workaround when the `.cursor()` API serves the same purpose. -- [ ] For ORM combinators, imported `and` / `or` / `not` from the (currently internal) `@internal/sql-orm-client` and noted the façade gap to the user. +- [ ] For ORM combinators, imported `and` / `or` / `not` from `@prisma/orm-postgres/orm-client` (not `/runtime`). +- [ ] Used single-row `.update(...)` / `.delete()` only where one row is meant; used `.updateAll(...)` / `.deleteAll()` / the `*AndCount()` forms for many-row writes. - [ ] Executed SQL-builder plans via `db.runtime().execute(plan)` (or `tx.execute(plan)` inside a transaction). - [ ] Wrapped multi-statement work in `db.transaction(async (tx) => { ... })` where atomicity matters. -- [ ] For top-N grouped aggregates at meaningful scale, dropped to `db.sql.
` rather than JS-side sort + slice over `groupBy(...).aggregate(...)`. +- [ ] For top-N grouped aggregates at meaningful scale, dropped to `db.sql..
` rather than JS-side sort + slice over `groupBy(...).aggregate(...)`. - [ ] Did NOT confabulate TypedSQL, `.stream()`, `db.batch`, `.between(...)`, a `capabilities` field on `defineConfig`, or a `db.sql.from(tables.user)` API — routed to *What Prisma Next doesn't do yet* / `references/feedback.md` instead. Raw SQL is spelled `db.raw.sql`, not `db.sql.raw`. diff --git a/skills/prisma-8/references/queries.md b/skills/prisma-orm-core-concepts/references/queries.md similarity index 70% rename from skills/prisma-8/references/queries.md rename to skills/prisma-orm-core-concepts/references/queries.md index 50b62bed6c80..4eafce996310 100644 --- a/skills/prisma-8/references/queries.md +++ b/skills/prisma-orm-core-concepts/references/queries.md @@ -20,7 +20,7 @@ Once the contract is emitted and the DB is up to date, this skill covers everyth - User wants to add / change a model → `references/contract.md`. - User wants to wire `db.ts` or add middleware → `references/runtime.md`. - User is querying through a Supabase role-bound db (`asUser` / `asAnon` / `asServiceRole`, RLS, `auth.*` admin reads) → `references/supabase.md` for the role-binding surface; everything in this skill then applies to the returned `RoleBoundDb`. -- User wants to debug a query failure (structured error envelope) → `references/debug.md`. +- User wants to debug a query failure (structured error envelope) → `references/failure-modes.md`. ## Pick your target @@ -28,22 +28,22 @@ Prisma Next ships **two query lanes per target** on the same `db` value from `sr | Runtime import in `db.ts` | Load | | --- | --- | -| `@internal/postgres/runtime` | [`queries-postgres.md`](./queries-postgres.md) — `db.orm.` + `db.sql.
` | -| `@internal/mongo/runtime` | [`queries-mongo.md`](./queries-mongo.md) — `db.orm.` + `db.query.from(...)` | -| `@internal/extension-supabase/runtime` | [`queries-postgres.md`](./queries-postgres.md) — a Supabase `RoleBoundDb` is a Postgres surface (`db.orm.` + `db.sql.
`); bind a role first via `references/supabase.md` | +| `@prisma/orm-postgres/runtime` | [`queries-postgres.md`](./queries-postgres.md) — `db.orm..` + `db.sql..
` | +| `@prisma/orm-mongo/runtime` | [`queries-mongo.md`](./queries-mongo.md) — `db.orm.` + `db.query.from(...)` | +| `@prisma/orm-extension-supabase/runtime` | [`queries-postgres.md`](./queries-postgres.md) — a Supabase `RoleBoundDb` is a Postgres surface (`db.orm..` + `db.sql..
`); bind a role first via `references/supabase.md` | Both targets share the contract and connection on one `db` value. Reach for the ORM first; drop to the lower-level lane when the ORM can't express the shape. Lane choice is local — one query function picks one lane, not the whole app. -**Do not mix target examples.** Postgres uses PascalCase model roots (`db.orm.User`) and `db.sql.user`; Mongo uses lowercased plural roots (`db.orm.users`) and `db.query.from('users')`. There is no `db.sql` on Mongo and no `db.query` SQL-builder equivalent on Postgres. +**Do not mix target examples.** Postgres uses namespace-then-PascalCase-model coordinates (`db.orm.public.User`) and `db.sql.public.user`; Mongo uses lowercased plural roots (`db.orm.users`) and `db.query.from('users')`. There is no `db.sql` on Mongo and no `db.query` SQL-builder equivalent on Postgres. ## Namespace-aware accessors -When a contract declares more than one namespace (e.g. `public` and `auth`), models and tables are addressed by namespace coordinate: +On SQL targets, models and tables are always addressed by namespace coordinate — the first key under `db.orm` / `db.sql` is the namespace, never a model or table name: - **ORM**: `db.orm..` — e.g. `db.orm.public.User`, `db.orm.auth.User` - **SQL builder**: `db.sql..
` — e.g. `db.sql.public.users`, `db.sql.auth.users` -The flat `db.orm.User` / `db.sql.users` form still works for single-namespace contracts (or when all table names are unique across namespaces). When the same bare name appears in more than one namespace, you must use the namespace coordinate. +There is no flat `db.orm.User` / `db.sql.users` form — not even for single-namespace contracts. Models outside any `namespace` block land in the default namespace (`public` on Postgres), so a typical single-namespace app addresses everything as `db.orm.public.` / `db.sql.public.
`. See [`queries-postgres.md` § Namespace-aware accessors](./queries-postgres.md#namespace-aware-accessors) for a worked example. @@ -52,7 +52,7 @@ See [`queries-postgres.md` § Namespace-aware accessors](./queries-postgres.md#n Critical to get right early — on **both Postgres and Mongo**, `.all()` returns an **`AsyncIterableResult`**, which is *both* a `PromiseLike` and an `AsyncIterable`. That means three consumption forms all work, and the canonical one is the shortest: ```typescript -const users = await db.orm.User.select('id', 'email').all(); +const users = await db.orm.public.User.select('id', 'email').all(); // ^? Row[] ← the Thenable resolves to a real array. This is the default idiom. ``` @@ -67,12 +67,12 @@ You do **not** need a `collect()` / `toArray()` helper — `await` is enough. In // `Promise.race` combinators all accept the thenable directly — those are // NOT reasons to call `.toArray()`. Whenever you are just going to await it // here, use `await ...all()` and skip `.toArray()`. -const rows: Promise = db.orm.User.select('id', 'email').all().toArray(); +const rows: Promise = db.orm.public.User.select('id', 'email').all().toArray(); // Streaming — process rows one at a time without buffering the whole result. // Use for genuinely large result sets (anything that wouldn't fit comfortably // in memory) or pipelines where you can start work before all rows arrive. -for await (const user of db.orm.User.select('id', 'email').all()) { +for await (const user of db.orm.public.User.select('id', 'email').all()) { process(user); } ``` @@ -80,9 +80,9 @@ for await (const user of db.orm.User.select('id', 'email').all()) { Two single-row shortcuts also exist on the result, in addition to the collection-level `.first()` (which issues `LIMIT 1` on Postgres): ```typescript -const user = await db.orm.User.where({ id }).all().first(); +const user = await db.orm.public.User.where({ id }).all().first(); // ^? Row | null ← buffers, returns the first row or null. Issues no LIMIT. -const required = await db.orm.User.where({ id }).all().firstOrThrow(); +const required = await db.orm.public.User.where({ id }).all().firstOrThrow(); // ^? Row ← buffers; throws `RUNTIME.NO_ROWS` if empty. ``` @@ -92,12 +92,12 @@ For genuine single-row reads, prefer the *collection*-level `.first()` (which ad ```typescript // Bad — second await throws RUNTIME.ITERATOR_CONSUMED. -const result = db.orm.User.select('id', 'email').all(); +const result = db.orm.public.User.select('id', 'email').all(); const a = await result; const b = await result; // Good — buffer once, reuse the array. -const users = await db.orm.User.select('id', 'email').all(); +const users = await db.orm.public.User.select('id', 'email').all(); const a = users; const b = users; ``` @@ -112,9 +112,9 @@ When the user is running a one-off `tsx my-script.ts` (not a long-lived server), // src/scripts/seed.ts import { db } from '../prisma/db'; -// Postgres — PascalCase model root from contract +// Postgres — namespace coordinate + PascalCase model from contract for (const u of users) { - await db.orm.User.create(u); + await db.orm.public.User.create(u); } // Mongo — lowercased plural root from contract (e.g. users, not User) @@ -131,6 +131,7 @@ await db.close(); 1. **Using Postgres examples on a Mongo project (or vice versa).** Check `db.ts` and load the correct target guide ([`queries-postgres.md`](./queries-postgres.md) or [`queries-mongo.md`](./queries-mongo.md)). 2. **Writing a `collect()` / `toArray()` helper to convert `.all()` to an array.** `.all()` returns an `AsyncIterableResult` which *is* a `PromiseLike` — `await collection.all()` directly yields `Row[]`. See *Consuming the result* above. 3. **Consuming an `AsyncIterableResult` twice.** Each result is single-use. The second consumer throws `RUNTIME.ITERATOR_CONSUMED`. Buffer once into a variable and reuse the variable. +4. **Expecting `.delete()` / `.update(data)` to affect every matching row.** They affect the **first** match only (and require a prior `.where(...)`), returning `Row | null`. For every-matching-row semantics use `.deleteAll()` / `.updateAll(data)` (return the affected rows) or `.deleteAndCount()` / `.updateAndCount(data)` (return the count). See the writes section of the target guide. Target-specific pitfalls live in the per-target guides. @@ -138,16 +139,16 @@ Target-specific pitfalls live in the per-target guides. - **N:M `.include()` across a junction table.** The contract IR supports many-to-many relations with a `through` junction table, and `N:M` relations appear as valid relation names on the ORM collection. However, `.include()` on an N:M relation does not emit the two-step junction join — the query plan builder only handles the direct join columns (`localColumn` / `targetColumn`) and ignores the `through` metadata. Attempting it either produces wrong results or an error. Workaround: express the N:M traversal through `db.sql.
` with an explicit join on the junction table. - **N:M nested mutations.** `mutation-executor.ts` explicitly throws `'N:M nested mutations are not supported yet'` for nested creates/links through an N:M relation. -- **`and` / `or` / `not` combinators in the postgres façade.** The combinators currently import from `@internal/sql-orm-client` (an internal package). Workaround today: import them from `@internal/sql-orm-client` directly, the way the example apps do. If you want them on `@internal/postgres/runtime`, file a feature request via `references/feedback.md`. -- **Ordering grouped aggregates by an aggregate alias (Postgres).** `db.orm..groupBy(...)` supports `.orderBy(...)` on group keys plus `.limit(...)` / `.offset(...)`, but the grouped collection cannot order by an aggregate alias such as `SUM(amount)`. A "top-N groups by SUM" query therefore falls back to JS-side sort + slice over the full grouped result, which is fine at small cardinalities and bad at scale. Workarounds: (a) drop to `db.sql.
` and write the `GROUP BY` + `ORDER BY` + `LIMIT` against the aggregated table directly; (b) live with the JS-side sort/slice if the grouped cardinality is bounded. File a feature request via `references/feedback.md` if this is hitting you in production. +- **`and` / `or` / `not` combinators on the `/runtime` subpath.** The combinators are not exported from `@prisma/orm-postgres/runtime`; they live on the façade's `/orm-client` subpath — import them from `@prisma/orm-postgres/orm-client` (same subpath on `@prisma/orm-sqlite`). If you want them surfaced on `/runtime` alongside the factory, file a feature request via `references/feedback.md`. +- **Ordering grouped aggregates by an aggregate alias (Postgres).** `db.orm...groupBy(...)` supports `.orderBy(...)` on group keys plus `.limit(...)` / `.offset(...)`, but the grouped collection cannot order by an aggregate alias such as `SUM(amount)`. A "top-N groups by SUM" query therefore falls back to JS-side sort + slice over the full grouped result, which is fine at small cardinalities and bad at scale. Workarounds: (a) drop to `db.sql.
` and write the `GROUP BY` + `ORDER BY` + `LIMIT` against the aggregated table directly; (b) live with the JS-side sort/slice if the grouped cardinality is bounded. File a feature request via `references/feedback.md` if this is hitting you in production. - **A raw-SQL lane.** This one exists. Write whole-query raw SQL through the client's raw lane: ``db.raw.sql`SELECT ...`.returnsRow({ ... }).build()`` for rows, or `.affectedCount()` for a mutation's row count. Each declared column names the codec that decodes it, so the row stays typed. For an expression fragment inside a builder query, use `fns.raw` in a `.select(...)` callback instead. - **TypedSQL (`.sql` files compiled into typed callables).** Not implemented. Workaround: stick to the SQL builder; for repeated queries, extract a function that returns the built plan and call `db.runtime().execute(plan)` at the call site. If you want a `.sql`-file compile path, file a feature request via `references/feedback.md`. - **`EXPLAIN` / query-plan inspection.** Prisma Next does not expose an `.explain()` method. Workaround: connect a `pg.Pool` you control via the runtime's `pg:` binding (see `references/runtime.md`) and issue `EXPLAIN ANALYZE` through it. If you want a first-class plan-inspection surface, file a feature request via `references/feedback.md`. - **Streaming large result sets.** No `.stream()` cursor today. Workaround: paginate via `.offset(n).limit(m)` for moderate sizes; for very large sets, hold a `pg.Client` from the runtime's `pg:` binding and stream through it directly. If you want a built-in streaming surface, file a feature request via `references/feedback.md`. - **Multi-statement batching (Prisma-7-style `db.$transaction([call1, call2])`).** Prisma Next runs each call sequentially. Workaround: wrap atomically-related work in `db.transaction(async (tx) => { ... })` on Postgres. If you want batch-as-array semantics, file a feature request via `references/feedback.md`. -- **Mongo façade transactions.** `@internal/mongo/runtime` does not expose `db.transaction(...)`. Multi-document atomicity is not yet wrapped in the Prisma Next Mongo façade. Workaround: use the MongoDB driver's session API directly if you control the client binding (`mongoClient:` option). File a feature request via `references/feedback.md` if you need a first-class façade surface. +- **Mongo façade transactions.** `@prisma/orm-mongo/runtime` does not expose `db.transaction(...)`. Multi-document atomicity is not yet wrapped in the Prisma Next Mongo façade. Workaround: use the MongoDB driver's session API directly if you control the client binding (`mongoClient:` option). File a feature request via `references/feedback.md` if you need a first-class façade surface. - **Mongo ORM aggregates.** No `.aggregate(...)` / `.groupBy(...)` on `db.orm.`. Workaround: express aggregations through `db.query.from(...).group(...).build()` and `runtime.execute(plan)`. -- **Mongo filter helpers on the façade.** Rich filters (`.in`, ranges, boolean composition) currently import from `@internal/mongo-query-ast/execution` (`MongoFieldFilter`, etc.) — not yet re-exported on `@internal/mongo/runtime`. Workaround: use object equality `.where({ field: value })` where possible; import from the internal package only when necessary. Tracked alongside façade-completeness gaps in Linear `TML-2526`. +- **Mongo filter helpers on the `/runtime` subpath.** Rich filters (`.in`, ranges, boolean composition) import from `@prisma/orm-mongo/query-ast/execution` (`MongoFieldFilter`, etc.) — not re-exported on `@prisma/orm-mongo/runtime`. Workaround: use object equality `.where({ field: value })` where possible; import from the deeper subpath only when necessary. Tracked alongside façade-completeness gaps in Linear `TML-2526`. - **Automatic N+1 detection.** Prisma Next does not warn when an `.include(...)` is missing. Workaround: be deliberate about `.include(...)` in code review; the `lints` middleware (see `references/runtime.md`) catches the more common authoring slips (missing `WHERE` on a `DELETE` / `UPDATE`, missing `LIMIT` on a `SELECT`). ## Reference Files @@ -160,7 +161,8 @@ This skill is split for selective loading. Target-specific reference paths live ## Checklist - [ ] Confirmed the active target from `db.ts` and loaded the matching guide ([`queries-postgres.md`](./queries-postgres.md) or [`queries-mongo.md`](./queries-mongo.md)). -- [ ] For multi-namespace contracts, used `db.orm..` / `db.sql..
` coordinates when the same bare name exists in more than one namespace. +- [ ] On SQL targets, addressed models and tables by namespace coordinate (`db.orm..` / `db.sql..
`, e.g. `db.orm.public.User`) — never a flat `db.orm.` / `db.sql.
` form. +- [ ] Used `.deleteAll()` / `.updateAll(...)` / the `*AndCount()` forms for many-row writes — `.delete()` / `.update(...)` affect only the first match. - [ ] Chose the right lane (ORM by default; lower-level builder for shapes the ORM doesn't express). - [ ] Used `.first()` / `.first({ pk })` (Postgres) or `.where({ ... }).first()` (Mongo) for single-row reads — not `.all()`. - [ ] Consumed `.all()` with plain `await` (not a `collect()` / `toArray()` helper). Used `for await` only when streaming is actually wanted, and never iterated the same result twice. diff --git a/skills/prisma-8/references/quickstart.md b/skills/prisma-orm-core-concepts/references/quickstart.md similarity index 87% rename from skills/prisma-8/references/quickstart.md rename to skills/prisma-orm-core-concepts/references/quickstart.md index abc357224801..f30326b6ef75 100644 --- a/skills/prisma-8/references/quickstart.md +++ b/skills/prisma-orm-core-concepts/references/quickstart.md @@ -23,16 +23,16 @@ This skill does **not** cover migrating from another ORM (Drizzle, Prisma 6/7, S ## When Not to Use - User already has a PN project and wants to add a model → `references/contract.md`. -- User wants to migrate FROM a specific ORM → install `@internal/migrate-from--skill` (separate). +- User wants to migrate FROM a specific ORM → a separately-installable migrate-from-\ skill covers that, when one exists for the source. - User wants to wire `db.ts` in a project that already has a contract → `references/runtime.md`. - User wants to integrate Prisma Next with a build tool (Vite plugin, Next.js, …) → `references/build.md`. ## Key Concepts - **Contract**: the data model. Authored as `contract.prisma` (PSL, the canonical surface) or `contract.ts` (TypeScript builder). The framework reads it and emits two artefacts: `contract.json` (runtime IR) and `contract.d.ts` (types). -- **Target**: the backing store. Today: `postgres` or `mongodb`. Picked at `init` time; baked into the `@internal/` façade the scaffold imports from. +- **Target**: the backing store. Today: `postgres` or `mongodb`. Picked at `init` time; baked into the `@prisma/orm-` façade the scaffold imports from. - **Authoring mode**: how you write the contract. `psl` (Prisma Schema Language, default) or `typescript` (programmatic builder, optionally paired with the Vite plugin for auto-emit during `vite dev` — see `references/build.md`). -- **Façade packages.** The scaffold installs exactly one façade per target — `@internal/postgres` (or `@internal/mongo`). User code imports from façade subpaths (`@internal/postgres/config`, `@internal/postgres/runtime`, `@internal/postgres/contract-builder`). The façade bakes in the family / target / adapter / driver wiring; never reach past it. See `references/contract.md` for the full list. +- **Façade packages.** The scaffold installs exactly one façade per target — `@prisma/orm-postgres` (or `@prisma/orm-mongo`). User code imports from façade subpaths (`@prisma/orm-postgres/config`, `@prisma/orm-postgres/runtime`, `@prisma/orm-postgres/contract-builder`). The façade bakes in the family / target / adapter / driver wiring; never reach past it. See `references/contract.md` for the full list. - **`db.ts`**: the runtime entry point. Lives next to the contract source at `src/prisma/db.ts`. Imports the contract artefacts and exports a `db` value the rest of the app uses. - **Marker**: a `pn_meta_marker` row in your database that records the contract hash. Lets PN detect drift between contract and live DB. Created by `db init` (greenfield / first-touch orientation) or `db sign` (brownfield). @@ -84,19 +84,21 @@ import 'dotenv/config'; import { db } from './prisma/db'; // Write a row against the starter model. Adapt the field names to whatever -// model your contract source actually declares — read it first. -await db.orm.User.create({ email: 'alice@example.com' }); +// model your contract source actually declares — read it first. The first +// key under db.orm is the namespace (`public` unless the model sits in a +// `namespace` block). +await db.orm.public.User.create({ email: 'alice@example.com' }); // Read it back. -const users = await db.orm.User.select('id', 'email').all(); +const users = await db.orm.public.User.select('id', 'email').all(); console.log(users); ``` If that prints `[{ id: 1, email: 'alice@example.com' }]`, the project is wired end-to-end and the user has crossed from *"I have a project"* to *"I'm building."* -`db.orm.` is the default ORM lane — model-shaped, fully typed against the contract, lazily connects to the database on first use (it picks up `DATABASE_URL` from `.env` via the runtime's `dotenv/config`-loaded environment). The deeper `references/queries.md` reference covers the rest of the supported surface (filters, joins, transactions, the SQL builder) when the user is ready — and names the gaps (raw SQL and TypedSQL are not currently available). +`db.orm..` is the default ORM lane — model-shaped, fully typed against the contract, lazily connects to the database on first use (it picks up `DATABASE_URL` from `.env` via the runtime's `dotenv/config`-loaded environment). The deeper `references/queries.md` reference covers the rest of the supported surface (filters, joins, transactions, the SQL builder, the `db.raw.sql` raw lane) when the user is ready — and names the gaps (TypedSQL is not currently available). -> **Mongo target:** the snippet above is SQL-target shape. On `@internal/mongo`, `db.orm` is keyed by the collection's storage name (`@@map(...)`, or the lowercased model name if no `@@map`), so the same arc reads `await db.orm.users.create(...)` / `await db.orm.users.select('id', 'email').all()` — not `db.orm.User`. Full rule and rewrite recipe in `references/queries.md` § *MongoDB ORM addressing*. +> **Mongo target:** the snippet above is SQL-target shape. On `@prisma/orm-mongo`, `db.orm` is keyed by the collection's storage name (`@@map(...)`, or the lowercased model name if no `@@map`), so the same arc reads `await db.orm.users.create(...)` / `await db.orm.users.select('id', 'email').all()` — not `db.orm.public.User`. Full rule and rewrite recipe in `references/queries.md` § *MongoDB ORM addressing*. **Prerequisites for the arc to work.** All three paths leave these in place by the time you reach the arc: @@ -130,7 +132,7 @@ Before saying anything specific to the user, read: - The contract source the config declares (canonically `src/prisma/contract.prisma` or `src/prisma/contract.ts`; a project that pre-dates [TML-2532](https://linear.app/prisma-company/issue/TML-2532) may have it at `prisma/contract.{prisma,ts}` instead — check the `contract` field of the config) — what starter models, if any, exist. - `src/prisma/db.ts` (next to the contract) — the runtime entry point. - `.env` / `.env.example` — is `DATABASE_URL` set, or only the example? -- Optionally `pnpm prisma-cli db verify` — does the live DB match the contract? +- Optionally `pnpm prisma db verify` — does the live DB match the contract? Then **say the contract path back to the user, with its role attached**. Something like: *"Your contract is at `src/prisma/contract.prisma`, and it currently declares a `User` model. The contract describes your app — every query type, migration, and runtime type the framework gives you flows from this file. Let's get your app connected to a database next."* The exact wording is up to the agent; what matters is that the user leaves the first response knowing *where the contract is* and *that it is the source of truth*. @@ -139,9 +141,9 @@ Then **say the contract path back to the user, with its role attached**. Somethi The motivation is *"so your app can actually run against your database"*, not *"so the prerequisite checklist passes"*. The mechanics depend on what's already in place from Step 1: - **Everything already wired.** Go straight to writing and reading a row (see *Your first arc — connect, write, read* above). Adapt the snippet to whatever model the contract declares. -- **`DATABASE_URL` not set.** Have the user set it in `.env` (not in `prisma.config.ts` — see Pitfall 5). Then `pnpm prisma-cli db init` to apply the current contract to that database and write the marker row. Now the app can connect. -- **Database is connectable but not yet aware of the contract** (marker row missing; `db verify` reports drift). Run `pnpm prisma-cli db init`. (`db update` is the alternative for quick dev cycles — it's looser, doesn't write a migration history, and is what users reach for when they want to iterate on the schema fast. Mention it if the user asks how to make schema changes flow to the DB; don't pre-explain it.) -- **Contract is empty** (bootstrap left the source blank). Add **one** model with **two** fields (e.g. `User { id, email }`), `pnpm prisma-cli contract emit`, then `pnpm prisma-cli db init`. Minimal — get the round-trip working, *then* extend. +- **`DATABASE_URL` not set.** Have the user set it in `.env` (not in `prisma.config.ts` — see Pitfall 5). Then `pnpm prisma db init` to apply the current contract to that database and write the marker row. Now the app can connect. +- **Database is connectable but not yet aware of the contract** (marker row missing; `db verify` reports drift). Run `pnpm prisma db init`. (`db update` is the alternative for quick dev cycles — it's looser, doesn't write a migration history, and is what users reach for when they want to iterate on the schema fast. Mention it if the user asks how to make schema changes flow to the DB; don't pre-explain it.) +- **Contract is empty** (bootstrap left the source blank). Add **one** model with **two** fields (e.g. `User { id, email }`), `pnpm prisma contract emit`, then `pnpm prisma db init`. Minimal — get the round-trip working, *then* extend. The user encounters `db init` (and optionally `db update`, `contract emit`) here because they're the commands their current move *requires*. They learn what those commands are by using them. @@ -192,7 +194,6 @@ The flags `init` accepts (run `prisma orm init --help` for the source of truth): - `--probe-db` — connect to `DATABASE_URL` once and check the server version against the target's minimum. - `--strict-probe` — fail init if the probe fails (no-op without `--probe-db`). - `--skip-install` — skip dependency install + initial contract emit. -- `--skip-skills` — skip Prisma Next skills installation (air-gapped / restricted environments). The skill cluster is always installed at the project level — never globally — so its version stays locked to the project's Prisma Next version. `init` writes (when it runs cleanly): @@ -202,8 +203,8 @@ The flags `init` accepts (run `prisma orm init --help` for the source of truth): - `prisma-next.md` — a human quick-reference. - `.env.example` (and `.env` if `--write-env`). - Updates `package.json` (deps + scripts) and `tsconfig.json` (required compiler options). -- Installs deps and runs `prisma-cli contract emit` once (the project-local bin `@prisma/cli` installs). -- Registers Prisma Next skills with the local agent runtime. +- Installs deps and runs `prisma contract emit` once (via the project-local `prisma` bin that `@prisma/cli` installs). +- Cleans up retired Prisma skill directories under the agent skill roots (`.claude/skills`, `.cursor/skills`, `.agents/skills`, `.windsurf/skills`); the current skills ship inside the installed `@prisma/orm-*` packages. **If you took `init`'s default and ended up with a top-level `prisma/` directory** (TML-2532), the cleanup is one move + one config edit: @@ -211,7 +212,7 @@ The flags `init` accepts (run `prisma orm init --help` for the source of truth): mkdir -p src && mv prisma src/prisma # Then update prisma.config.ts so `contract` reads # 'src/prisma/contract.prisma' (or .ts) instead of 'prisma/contract.prisma'. -pnpm prisma-cli contract emit # re-emits contract.json + contract.d.ts under src/prisma/ +pnpm prisma contract emit # re-emits contract.json + contract.d.ts under src/prisma/ ``` Do this before running `db init` — once the marker row is written, restructuring is harder. @@ -219,7 +220,7 @@ Do this before running `db init` — once the marker row is written, restructuri After init succeeds, the path converges on *Your first arc — connect, write, read* above. `init` has already seeded a starter contract with `User` and `Post` models (with a relation between them) and run `contract emit` once; the only remaining prerequisites are setting `DATABASE_URL` and initialising the database. Two commands: 1. Set `DATABASE_URL` in `.env` (copy from `.env.example`). -2. Initialise the database: `pnpm prisma-cli db init`. Creates tables, indexes, constraints, and writes the marker row — using the starter contract `init` generated. +2. Initialise the database: `pnpm prisma db init`. Creates tables, indexes, constraints, and writes the marker row — using the starter contract `init` generated. Then run the snippet from *Your first arc* above against the `User` model. When the user is ready to extend the contract — add more models, change fields, add relations — chain to `references/contract.md`. For more queries, chain to `references/queries.md`. @@ -240,7 +241,7 @@ pnpm dlx @prisma/cli@next orm init --yes --target postgres --authoring psl \ Then, with `DATABASE_URL` set in `.env`: ```bash -pnpm prisma-cli contract infer --db "$DATABASE_URL" --output src/prisma/contract.prisma +pnpm prisma contract infer --db "$DATABASE_URL" --output src/prisma/contract.prisma ``` (Note: the flag is `--output`, not `--out`. Run `prisma contract infer --help` for the full surface.) @@ -255,9 +256,9 @@ The agent should pause here and read the inferred PSL. Symptoms a re-author pass Then re-emit and sign: ```bash -pnpm prisma-cli contract emit -pnpm prisma-cli db sign -pnpm prisma-cli db verify # clean immediately after a pull; reports drift if the DB changes later +pnpm prisma contract emit +pnpm prisma db sign +pnpm prisma db verify # clean immediately after a pull; reports drift if the DB changes later ``` Then run the snippet from *Your first arc — connect, write, read* above, using one of your existing tables in place of the starter model. The arc is the same; only the path that got you there differs. @@ -270,13 +271,13 @@ A reference table — not a script to recite at the user. Commands surface in th |---|---|---| | Apply the current contract to the DB the first time | `prisma db init` | this skill | | Re-emit `contract.json` + `contract.d.ts` after editing the contract source | `prisma contract emit` | `references/contract.md` | -| Quick dev-only schema sync (no migration history kept) | `prisma db update` | `references/migrations.md` | -| Plan a migration from a contract diff | `prisma migration plan --name ` | `references/migrations.md` | -| Apply pending migrations | `prisma db migrate` | `references/migrations.md` | -| Inspect the live database | `prisma db schema` | `references/debug.md` | -| Confirm the DB matches the contract (drift check) | `prisma db verify` | `references/debug.md` | +| Quick dev-only schema sync (no migration history kept) | `prisma db update` | `prisma-orm-migrations/references/migrations.md` | +| Plan a migration from a contract diff | `prisma migration plan --name ` | `prisma-orm-migrations/references/migrations.md` | +| Apply pending migrations | `prisma db migrate` | `prisma-orm-migrations/references/migrations.md` | +| Inspect the live database | `prisma db schema` | `references/failure-modes.md` | +| Confirm the DB matches the contract (drift check) | `prisma db verify` | `references/failure-modes.md` | | Bring an existing DB into a PN contract | `prisma contract infer --db "$DATABASE_URL"` | this skill (brownfield) | -| Decode a structured error envelope | (read the `code` / `why` / `fix` fields) | `references/debug.md` | +| Decode a structured error envelope | (read the `code` / `why` / `fix` fields) | `references/failure-modes.md` | | Report a bug or request a feature | (file via the feedback skill) | `references/feedback.md` | ## Decision — PSL vs TypeScript authoring @@ -298,8 +299,8 @@ Switch authoring later by re-running `prisma orm init` in the same directory. Th ## What Prisma Next doesn't do yet -- **Migration from another ORM.** Prisma Next doesn't migrate your schema *from* Drizzle / Prisma 6/7 / Sequelize / TypeORM / Kysely / Knex / a raw driver. Workaround: install the matching `@internal/migrate-from--skill` if one exists for your source, or treat the source as a brownfield database and `contract infer` from it. If you need a guided migration flow built-in, file a feature request via the `references/feedback.md` skill. -- **`prisma db push`-style production sync.** `db update` is the quick development path; for production, use migrations (`migration plan` + `db migrate`). PN deliberately does not offer a "push-to-prod-without-a-migration" surface — see `references/migrations.md`. +- **Migration from another ORM.** Prisma Next doesn't migrate your schema *from* Drizzle / Prisma 6/7 / Sequelize / TypeORM / Kysely / Knex / a raw driver. Workaround: install the matching migrate-from-\ skill if one exists for your source, or treat the source as a brownfield database and `contract infer` from it. If you need a guided migration flow built-in, file a feature request via the `references/feedback.md` skill. +- **`prisma db push`-style production sync.** `db update` is the quick development path; for production, use migrations (`migration plan` + `db migrate`). PN deliberately does not offer a "push-to-prod-without-a-migration" surface — see `prisma-orm-migrations/references/migrations.md`. - **Studio / GUI database browser.** Use `prisma db schema` for a CLI tree-style summary of the live DB. If you need an interactive UI, file a feature request via the `references/feedback.md` skill. ## Reference Files diff --git a/skills/prisma-8/references/runtime.md b/skills/prisma-orm-core-concepts/references/runtime.md similarity index 74% rename from skills/prisma-8/references/runtime.md rename to skills/prisma-orm-core-concepts/references/runtime.md index 6db0e8e9e697..8d34a1ee7a9b 100644 --- a/skills/prisma-8/references/runtime.md +++ b/skills/prisma-orm-core-concepts/references/runtime.md @@ -21,16 +21,16 @@ This skill covers the **runtime entry point** — `db.ts` — and how to compose - User is on Supabase — the `supabase()` role-first factory, `asUser(jwt)` / `asAnon()` / `asServiceRole()`, JWT config, RLS → `references/supabase.md`. - User wants to edit the contract → `references/contract.md`. - User wants to wire Prisma Next into a build tool (Vite plugin, Next.js, …) → `references/build.md`. -- User wants to debug a connection / runtime error → `references/debug.md`. +- User wants to debug a connection / runtime error → `references/failure-modes.md`. - User wants to file a bug or feature request → `references/feedback.md`. ## Key Concepts -- **`db.ts` is the runtime entry point.** Imports the runtime factory from the `@internal/` façade (`@internal/postgres/runtime`, `@internal/sqlite/runtime`, or `@internal/mongo/runtime`), the contract artefacts (`contract.json` + the `Contract` type from `contract.d.ts`), and any middleware. Exports a `db` value the rest of your app imports. -- **The façade's runtime factory is the only surface user-authored `db.ts` imports from.** Each factory is a *default* export. For Postgres: `import postgres from '@internal/postgres/runtime'`; SQLite: `import sqlite from '@internal/sqlite/runtime'`; Mongo: `import mongo from '@internal/mongo/runtime'`. The factory signature is `(options)` — a single type parameter (the `Contract` type from `contract.d.ts`), and one options object. +- **`db.ts` is the runtime entry point.** Imports the runtime factory from the `@prisma/orm-` façade (`@prisma/orm-postgres/runtime`, `@prisma/orm-sqlite/runtime`, or `@prisma/orm-mongo/runtime`), the contract artefacts (`contract.json` + the `Contract` type from `contract.d.ts`), and any middleware. Exports a `db` value the rest of your app imports. +- **The façade's runtime factory is the only surface user-authored `db.ts` imports from.** Each factory is a *default* export. For Postgres: `import postgres from '@prisma/orm-postgres/runtime'`; SQLite: `import sqlite from '@prisma/orm-sqlite/runtime'`; Mongo: `import mongo from '@prisma/orm-mongo/runtime'`. The factory signature is `(options)` — a single type parameter (the `Contract` type from `contract.d.ts`), and one options object. - **Lazy connect.** The factory does not connect to the database synchronously. Static query surfaces (`db.sql`, `db.orm`) are available immediately; the driver / pool is instantiated on the first call that needs a runtime (or when you explicitly call `await db.connect({ url })`). This is why `db.ts` can be imported in modules that load before the env is ready. - **Middleware composes in order.** The first middleware in the `middleware: [...]` array runs *outermost* — it sees the operation first on the way in and last on the way out. Telemetry first means budget / lint failures show up inside telemetry spans. -- **`prisma.config.ts` vs `.env`.** The config (`defineConfig({ contract, db, extensions, migrations })`) is for static project shape: contract path, installed extensions, migrations directory, default connection string. `.env` is for per-environment values (`DATABASE_URL`, secrets). The config reads `.env` automatically via `dotenv/config`. Hardcoding `DATABASE_URL` in the config file leaks credentials and bypasses per-env overrides. +- **`prisma.config.ts` vs `.env`.** The config — `definePrismaConfig({ orm: ormConfig({ contract, db, extensions, migrations }) })`, composing `definePrismaConfig` from `@prisma/cli-engine` with the façade's `defineConfig` (imported as `ormConfig`) — is for static project shape: contract path, installed extensions, migrations directory, default connection string. `.env` is for per-environment values (`DATABASE_URL`, secrets). The config reads `.env` automatically via `dotenv/config`. Hardcoding `DATABASE_URL` in the config file leaks credentials and bypasses per-env overrides. - **Build-system / dev-server integration is a separate skill.** `vite dev` auto-emit lives in `references/build.md`. The runtime side (this skill) reads `contract.json` / `contract.d.ts` regardless of how they got onto disk, so the two skills compose cleanly. ## Workflow — Basic `db.ts` @@ -41,7 +41,7 @@ The concept: `db.ts` is the seam between the emitted contract artefacts (target- ```typescript // src/prisma/db.ts -import postgres from '@internal/postgres/runtime'; +import postgres from '@prisma/orm-postgres/runtime'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; @@ -59,7 +59,7 @@ Three things to know: - **`with { type: 'json' }` is required.** Node's ESM JSON-import-attribute spec. Without it, the import errors. - **`url` is optional at construct time.** If `DATABASE_URL` is not set when `db.ts` loads, the factory still returns a client; you can call `await db.connect({ url })` later. The factory throws lazily — only when a runtime is actually needed. -The Mongo façade has the same construction shape — `import mongo from '@internal/mongo/runtime'` — and the same `db.connect(...)` / `db.close()` lifecycle methods. **The Mongo façade does not expose `db.transaction(...)`.** See *What Prisma Next doesn't do yet* for the workaround. **The ORM surface differs in one place: keys.** On Mongo, `db.orm` is keyed by the collection's storage name (from `@@map(...)`, or the lowercased model name if no `@@map` is set), not by the PSL model name — so `model User { … @@map("users") }` is reached at `db.orm.users`, not `db.orm.User`. The SQL builder lane (`db.sql.
`) doesn't exist on Mongo at all (`db.sql` is `undefined`). See `references/queries.md` § *MongoDB ORM addressing* for the full rule and a rewrite recipe for SQL-target examples. +The Mongo façade has the same construction shape — `import mongo from '@prisma/orm-mongo/runtime'` — and the same `db.connect(...)` / `db.close()` lifecycle methods. **The Mongo façade does not expose `db.transaction(...)`.** See *What Prisma Next doesn't do yet* for the workaround. **The ORM surface differs in one place: keys.** On Mongo, `db.orm` is keyed by the collection's storage name (from `@@map(...)`, or the lowercased model name if no `@@map` is set), not by the PSL model name — so `model User { … @@map("users") }` is reached at `db.orm.users`, not `db.orm.public.User`. The SQL builder lane (`db.sql..
`) doesn't exist on Mongo at all (`db.sql` is `undefined`). See `references/queries.md` § *MongoDB ORM addressing* for the full rule and a rewrite recipe for SQL-target examples. ## Workflow — Running as a script (teardown) @@ -71,8 +71,8 @@ The concept: short scripts that connect, query, then expect the process to exit // src/scripts/hello.ts import { db } from '../prisma/db'; -const created = await db.orm.User.create({ email: 'alice@example.com', name: 'Alice' }); -const read = await db.orm.User.first(); +const created = await db.orm.public.User.create({ email: 'alice@example.com', name: 'Alice' }); +const read = await db.orm.public.User.first(); console.log({ created, read }); await db.close(); @@ -82,13 +82,13 @@ await db.close(); ```typescript // src/scripts/hello.ts — top-level await in a script module -import postgres from '@internal/postgres/runtime'; +import postgres from '@prisma/orm-postgres/runtime'; import type { Contract } from '../prisma/contract.d'; import contractJson from '../prisma/contract.json' with { type: 'json' }; await using db = postgres({ contractJson, url: process.env.DATABASE_URL! }); -const user = await db.orm.User.first(); +const user = await db.orm.public.User.first(); console.log(user); // db.close() runs automatically when the script module exits. ``` @@ -101,7 +101,7 @@ This is the most important rule in this section. `await using db = postgres(...) // DO NOT do this — closes the pool after every request. app.get('/users', async (req, res) => { await using db = postgres({ contractJson, url: process.env.DATABASE_URL! }); - const users = await db.orm.User.all(); + const users = await db.orm.public.User.all(); res.json(users); }); ``` @@ -116,7 +116,7 @@ export const db = postgres({ contractJson, url: process.env.DATABASE_U import { db } from '../prisma/db'; app.get('/users', async (req, res) => { - const users = await db.orm.User.all(); + const users = await db.orm.public.User.all(); res.json(users); }); ``` @@ -132,40 +132,40 @@ Servers (HTTP handlers, workers in a request loop) **do not call `db.close()`** **`db.end()` does not exist.** The universal `node-postgres` name is `pool.end()` on a `pg.Pool`; the Prisma Next runtime client is not a `pg.Pool`. The right call is `await db.close()`. -## Workflow — Telemetry middleware +## Workflow — Observability middleware (custom) -The concept: telemetry middleware sees every operation and emits a structured event for each (start, success, error). Pair the events with your observability stack's collector. +The concept: Prisma Next ships no first-party telemetry middleware (see *What Prisma Next doesn't do yet*). A middleware is a plain `SqlMiddleware`-shaped object — a `name` plus one or more hooks (`beforeCompile`, `beforeQuery`, `interceptQuery`, `onRow`, `afterQuery`) — so a small custom one covers logging and event forwarding: ```typescript -import postgres from '@internal/postgres/runtime'; -import { createTelemetryMiddleware } from '@internal/middleware-telemetry'; +import type { SqlMiddleware } from '@prisma/orm-postgres/family-runtime'; +import postgres from '@prisma/orm-postgres/runtime'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; +const queryLog: SqlMiddleware = { + name: 'query-log', + async afterQuery(plan, result) { + // forward to your collector, log, etc. + console.log(plan.sql, { rowCount: result.rowCount, latencyMs: result.latencyMs }); + }, +}; + export const db = postgres({ contractJson, url: process.env['DATABASE_URL'], - middleware: [ - createTelemetryMiddleware({ - onEvent: (event) => { - // forward to your collector, log, etc. - }, - }), - ], + middleware: [queryLog], }); ``` -`createTelemetryMiddleware` is shipped as a separate user-installable package (`@internal/middleware-telemetry`), not as a `/middleware` subpath of the postgres façade. Install it directly. Run `pnpm ls @internal/middleware-telemetry` to confirm it's on the lockfile. - ## Workflow — Lints and budgets middleware The concept: lints catch authoring mistakes that survive type-check (e.g. `DELETE` without a `WHERE`, `SELECT` without a `LIMIT` on a large table); budgets enforce row-count and latency ceilings at runtime. Both surface findings through the structured-error envelope so an agent can branch on the code. -These ship in the underlying SQL runtime package (`@internal/sql-runtime`) and are *not* yet re-exported from the postgres façade — see *What Prisma Next doesn't do yet*. The example apps under `examples/prisma-8-demo/src/prisma/db.ts` show the canonical import. +`lints` and `budgets` are importable from the façade's `/family-runtime` subpath. The example apps under `examples/prisma-8-demo/src/prisma/db.ts` show the canonical composition. ```typescript -import postgres from '@internal/postgres/runtime'; -import { budgets, lints } from '@internal/sql-runtime'; +import { budgets, lints } from '@prisma/orm-postgres/family-runtime'; +import postgres from '@prisma/orm-postgres/runtime'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; @@ -199,13 +199,13 @@ For the full option surface, read the source: `packages/2-sql/5-runtime/src/midd ```typescript middleware: [ - createTelemetryMiddleware({ onEvent }), // outermost — sees all sub-failures as inner errors + queryLog, // outermost — sees all sub-failures as inner errors lints({ severities: { noLimit: 'error' } }), - budgets({ maxLatencyMs: 5_000 }), // innermost — runs closest to the driver + budgets({ maxLatencyMs: 5_000 }), // innermost — runs closest to the driver ], ``` -Order matters: outermost wraps. Telemetry first means budget / lint failures are captured as spans (the agent can correlate the lint code with the operation in the same trace). +Order matters: outermost wraps. Logging/telemetry first means budget / lint failures are observed by it (the agent can correlate the lint code with the operation in the same trace). ## Workflow — Configure the connection @@ -243,9 +243,9 @@ export const db = postgres({ contractJson, url: process.env['DATABASE_URL'], middleware: isProd - ? [createTelemetryMiddleware({ onEvent })] + ? [queryLog] : [ - createTelemetryMiddleware({ onEvent }), + queryLog, lints({ severities: { noLimit: 'error', deleteWithoutWhere: 'error' } }), ], }); @@ -269,13 +269,13 @@ The callback returns whatever you return from it — the transaction wrapper pas ## Workflow — Switch between Postgres, SQLite, and Mongo -The concept: the façade selection is baked into `db.ts` (`@internal/postgres` or `@internal/mongo`) and `prisma.config.ts` (which `defineConfig` you import from). To switch a project's target, re-run `prisma orm init` in the same directory and pick the other target — the init flow detects the existing scaffold and prompts to reinit (non-interactive runs grant the consent with `--confirm `). PN re-scaffolds `prisma.config.ts` and `db.ts` for the new façade. The contract source needs to be re-authored for the new target's idioms (Mongo expresses nested documents; Postgres expresses relations). +The concept: the façade selection is baked into `db.ts` (`@prisma/orm-postgres` or `@prisma/orm-mongo`) and `prisma.config.ts` (which `defineConfig` you import from). To switch a project's target, re-run `prisma orm init` in the same directory and pick the other target — the init flow detects the existing scaffold and prompts to reinit (non-interactive runs grant the consent with `--confirm `). PN re-scaffolds `prisma.config.ts` and `db.ts` for the new façade. The contract source needs to be re-authored for the new target's idioms (Mongo expresses nested documents; Postgres expresses relations). After the switch (Mongo): ```typescript // src/prisma/db.ts (Mongo) -import mongo from '@internal/mongo/runtime'; +import mongo from '@prisma/orm-mongo/runtime'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; @@ -286,7 +286,7 @@ SQLite: ```typescript // src/prisma/db.ts (SQLite) -import sqlite from '@internal/sqlite/runtime'; +import sqlite from '@prisma/orm-sqlite/runtime'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; @@ -301,7 +301,7 @@ The `db.sql` / `db.orm` surfaces stay the same in name; the operators each surfa If you want contract artefacts to re-emit automatically while the dev server is running (instead of running `prisma contract emit` by hand each time the contract source changes), reach for the build-tool plugin from `references/build.md`: -- **Vite**: install `@internal/vite-plugin-contract-emit` and register `prismaVitePlugin('prisma.config.ts')` in `vite.config.ts`. +- **Vite**: install `@prisma/orm-postgres/vite-plugin-contract-emit` and register `prismaVitePlugin('prisma.config.ts')` in `vite.config.ts`. - **Next.js, Webpack, esbuild, Rollup, Turbopack**: no first-party plugin yet — the workaround is a `prebuild` script that runs `prisma contract emit`. See `references/build.md` for the walkthrough. The runtime side (this skill) is the same regardless: `db.ts` reads `contract.json` + `contract.d.ts` from disk. The build-system plugin's job is to keep those files current during development. @@ -312,17 +312,18 @@ The runtime side (this skill) is the same regardless: `db.ts` reads `contract.js 2. **Omitting the `` type parameter** in `postgres(...)`. Without it, static surfaces collapse to a generic shape and you lose autocomplete for models. There is no second type parameter — the older two-param signature (`postgres`) is gone. 3. **Forgetting `with { type: 'json' }` on the contract import.** Required by Node's ESM JSON-import-attribute spec. 4. **Middleware order matters.** Outermost wraps. Put telemetry first if you want it to capture inner-middleware errors. -5. **Importing middleware from a non-existent façade subpath.** `@internal/postgres/middleware` does *not* exist. Telemetry comes from `@internal/middleware-telemetry`; lints / budgets come from `@internal/sql-runtime` today (see *What Prisma Next doesn't do yet*). +5. **Importing middleware from a non-existent façade subpath.** `@prisma/orm-postgres/middleware` does *not* exist. `lints` / `budgets` (and the `SqlMiddleware` type) come from `@prisma/orm-postgres/family-runtime`; there is no first-party telemetry middleware (see *What Prisma Next doesn't do yet*). 6. **Confabulating lint / budget option names.** Lints take `severities` (with the five keys above), not `requireWhere` / `maxRowsWithoutLimit`. Budgets use `maxLatencyMs` (not `maxDurationMs`) plus `maxRows` / `defaultTableRows` / `tableRows`. When in doubt, read the source. 7. **Switching targets without re-emitting.** The contract artefacts are target-shaped; emit after the target change. 8. **Script hangs after queries finish on Postgres.** The `pg.Pool` keeps Node's event loop alive. Solution: `await db.close()` before the script returns, or `await using db = postgres(...)` at the top of a script module. Do not put `await using db = postgres(...)` inside a request handler — it's block-scoped and would close the pool after every request. The right server pattern is a module-level singleton in `db.ts` that lives for the process lifetime. ## What Prisma Next doesn't do yet -- **`@internal/postgres/middleware` subpath.** The postgres façade re-exports the runtime factory (`./runtime`), config (`./config`), contract-builder (`./contract-builder`), control (`./control`), family (`./family`), target (`./target`), and serverless (`./serverless`) — but not middleware. Today's workaround: import `lints` and `budgets` from `@internal/sql-runtime`, and `createTelemetryMiddleware` from `@internal/middleware-telemetry`. File additional gaps you hit via `references/feedback.md`. +- **`@prisma/orm-postgres/middleware` subpath.** The postgres façade exposes the runtime factory (`./runtime`), config (`./config`), contract-builder (`./contract-builder`), control (`./control`), and serverless (`./serverless`) — but no `./middleware`. Import `lints`, `budgets`, and the `SqlMiddleware` type from `./family-runtime` instead. File additional gaps you hit via `references/feedback.md`. +- **First-party telemetry middleware.** No published telemetry middleware package ships with Prisma Next. Workaround: write a small custom `SqlMiddleware` (see *Workflow — Observability middleware*) that forwards events to your collector. If you want a first-party telemetry middleware, file a feature request via `references/feedback.md`. - **Multi-database routing / read replicas.** Prisma Next doesn't ship a built-in primary/replica router or shard-aware client. Workaround: configure separate `db.ts` instances per data store and call the right one in your application code. If you need first-class multi-database routing, file a feature request via the `references/feedback.md` skill. - **Connection pooling as a first-class config field.** `poolOptions.connectionTimeoutMillis` and `poolOptions.idleTimeoutMillis` are wired through, but the rest of `pg.Pool`'s tuning surface (max connections, `allowExitOnIdle`, ssl options, …) is not exposed by name. Workaround: construct the `pg.Pool` yourself and pass it via `pg:`. If you need more pool fields surfaced on the façade, file a feature request via the `references/feedback.md` skill. -- **Query logger middleware as a built-in.** Prisma Next doesn't ship a "log every query" middleware. Workaround: write a small custom middleware that wraps each operation and logs; or use `createTelemetryMiddleware` and log inside the `onEvent` callback. If you need a built-in query log, file a feature request via the `references/feedback.md` skill. +- **Query logger middleware as a built-in.** Prisma Next doesn't ship a "log every query" middleware. Workaround: write a small custom `SqlMiddleware` that logs from its hooks (see *Workflow — Observability middleware*). If you need a built-in query log, file a feature request via the `references/feedback.md` skill. ## Reference Files @@ -330,7 +331,7 @@ This skill is intentionally body-only; `prisma orm init --help`, the `defineConf ## Checklist -- [ ] `db.ts` imports the runtime factory from `@internal//runtime` (`postgres`, `sqlite`, or `mongo`) and the `Contract` type from `./contract.d`. +- [ ] `db.ts` imports the runtime factory from `@prisma/orm-/runtime` (`postgres`, `sqlite`, or `mongo`) and the `Contract` type from `./contract.d`. - [ ] `with { type: 'json' }` on the contract JSON import. - [ ] `` is the single type parameter on `postgres(...)` (no second parameter). - [ ] `DATABASE_URL` lives in `.env`, not in `prisma.config.ts`. @@ -338,7 +339,7 @@ This skill is intentionally body-only; `prisma orm init --help`, the `defineConf - [ ] `lints` / `budgets` use the verified option keys (`severities`, `maxLatencyMs`, `maxRows`, `tableRows`). - [ ] Per-env divergence (if any) gated by `NODE_ENV` or similar. - [ ] Did NOT hardcode credentials in any committed file. -- [ ] Did NOT confabulate a `@internal/postgres/middleware` subpath, a `@internal/postgres-extension-audit` package, or a second type parameter on `postgres<...>`. +- [ ] Did NOT confabulate a `@prisma/orm-postgres/middleware` subpath, a first-party telemetry middleware package, or a second type parameter on `postgres<...>`. - [ ] Did NOT claim `db.transaction(...)` exists on the Mongo façade — only Postgres and SQLite expose it. - [ ] Did NOT confabulate read-replica / multi-DB / extra pool config — pointed at *What Prisma Next doesn't do yet* and routed to `references/feedback.md`. - [ ] For build-system / dev-server prompts (Vite plugin, Next.js plugin, …) routed to `references/build.md`. diff --git a/skills/prisma-8/references/supabase.md b/skills/prisma-orm-core-concepts/references/supabase.md similarity index 86% rename from skills/prisma-8/references/supabase.md rename to skills/prisma-orm-core-concepts/references/supabase.md index bfdadef78870..d958e075bc6f 100644 --- a/skills/prisma-8/references/supabase.md +++ b/skills/prisma-orm-core-concepts/references/supabase.md @@ -19,11 +19,11 @@ This skill covers using Prisma Next against a **Supabase** project end-to-end: c - General contract editing (models, fields, relations) → `references/contract.md`. - Non-Supabase `db.ts` wiring, middleware, teardown → `references/runtime.md`. - General query shapes (filtering, includes, aggregates) → `references/queries.md` — everything there applies to a role-bound `db` too. -- Migration planning / applying → `references/migrations.md`. +- Migration planning / applying → `prisma-orm-migrations/references/migrations.md`. ## Key Concepts -- **The pack is an `external` contract space.** `@internal/extension-supabase/pack` ships a complete, introspection-generated contract of everything Supabase owns — the `auth` and `storage` schemas, their native enum types, and the platform roles (`anon`, `authenticated`, `service_role`) — all with control policy `external`. Composed via `extensions`, it means: the migration planner **emits no DDL** for those objects (Supabase manages them), and `db verify` **confirms they exist** in the live database. Your own tables stay `managed` as usual. +- **The pack is an `external` contract space.** `@prisma/orm-extension-supabase/pack` ships a complete, introspection-generated contract of everything Supabase owns — the `auth` and `storage` schemas, their native enum types, and the platform roles (`anon`, `authenticated`, `service_role`) — all with control policy `external`. Composed via `extensions`, it means: the migration planner **emits no DDL** for those objects (Supabase manages them), and `db verify` **confirms they exist** in the live database. Your own tables stay `managed` as usual. - **Roles come from the pack; you never declare them.** RLS `roles = [authenticated]` identifiers resolve against the composed contract. Pointing the runtime at a non-Supabase Postgres fails verify with a `not-found` issue naming the missing role — the common "wrong database" misconfiguration surfaces before queries run. - **The runtime is role-first.** `supabase()` returns a `SupabaseDb` with **no top-level query surface** — there is no `db.sql` / `db.orm` until you bind a role. `await db.asUser(jwt)` / `db.asAnon()` / `db.asServiceRole()` each return a `RoleBoundDb` exposing `.sql`, `.orm`, `.raw`, `.execute(plan)`, and `.transaction(fn)`. This is deliberate: in a Supabase app there is no meaningful "no role" execution context, and defaulting to the connection's login role is a silent-RLS-bypass footgun. - **Role binding is below middleware and cannot leak.** Each role-bound query runs on a connection that had `set_config('role', …)` and `set_config('request.jwt.claims', …)` applied beneath the user-middleware chain, with `RESET ALL` on release. Postgres-side `auth.uid()` / `auth.jwt()` read those session vars — RLS enforcement is Postgres's job; the runtime's job is binding the context. @@ -33,32 +33,20 @@ This skill covers using Prisma Next against a **Supabase** project end-to-end: c ## Workflow — Wire the pack into the config -The concept: the pack registers the Supabase contract space so your contract can reference it and the planner/verifier know what Supabase owns. The extension has no `/control` subpath yet, so it can't go through the target façade's `defineConfig({ extensions: [...] })` — it wires into the low-level config's `extensions` (see *What Prisma Next doesn't do yet*). The low-level imports below are a **deliberate exception** to the façade-only import rule, forced by that gap; the block mirrors `examples/supabase/prisma.config.ts` verbatim — copy it rather than composing your own: +The concept: the pack registers the Supabase contract space so your contract can reference it and the planner/verifier know what Supabase owns. The extension publishes no `/control` subpath — its `/pack` descriptor goes straight into the façade config's `extensions: [...]`. The block mirrors `examples/supabase/prisma.config.ts` verbatim — copy it rather than composing your own: ```typescript // prisma.config.ts -import postgresAdapter from '@internal/adapter-postgres/control'; -import { defineConfig } from '@internal/cli/config-types'; -import postgresDriver from '@internal/driver-postgres/control'; -import supabasePack from '@internal/extension-supabase/pack'; -import sql from '@internal/family-sql/control'; -import { prismaContract } from '@internal/sql-contract-psl/provider'; -import postgres from '@internal/target-postgres/control'; -import postgresPackRef from '@internal/target-postgres/pack'; -import { postgresCreateNamespace } from '@internal/target-postgres/types'; - -export default defineConfig({ - family: sql, - target: postgres, - adapter: postgresAdapter, - driver: postgresDriver, - extensions: [supabasePack], - contract: prismaContract('./src/contract.prisma', { - output: 'src/contract.json', - target: postgresPackRef, - createNamespace: postgresCreateNamespace, +import { definePrismaConfig } from '@prisma/cli-engine'; +import supabasePack from '@prisma/orm-extension-supabase/pack'; +import { defineConfig as ormConfig } from '@prisma/orm-postgres/config'; + +export default definePrismaConfig({ + orm: ormConfig({ + contract: './src/contract.prisma', + extensions: [supabasePack], + migrations: { dir: 'migrations' }, }), - migrations: { dir: 'migrations' }, }); ``` @@ -109,9 +97,9 @@ The pieces: - **Per-operation policy blocks**: `policy_select`, `policy_insert`, `policy_update`, `policy_delete`, `policy_all`. Body is `key = value`: `target` (a model in this namespace), `roles` (resolve against the composed contract — the pack supplies `anon` / `authenticated` / `service_role`), `using`, and (for write operations) `withCheck`. Multiple permissive policies per `(target, operation)` are valid — Postgres ORs them. A block may also carry `@@map("physical name")` to adopt an existing live policy under its exact name (no wire-name hash; drift detection then byte-compares the body against Postgres's reprint, so keep the text as captured — hand-authoring it warns). - **`@@rls` is required on policy targets.** A `policy_*` block whose target model lacks `@@rls` fails emit with `PSL_EXTENSION_TARGET_MODEL_MISSING_ATTRIBUTE`. A model with `@@rls` and *no* policies is also meaningful: RLS enabled, deny-all. - **Predicates are verbatim SQL strings.** Quote camelCase column names inside them (`\"userId\"`), and cast where needed — `auth.uid()` returns `uuid`. Renames in your contract do not rewrite predicate bodies. -- **TS-builder parity exists.** `@internal/postgres/contract-builder` exports `policySelect` / `policyInsert` / `policyUpdate` / `policyDelete` / `policyAll`, `rlsEnabled(Model)`, and `role('anon')` — mirroring the PSL lowering key-for-key (identical emitted wire names). PSL is the canonical path shown here. +- **TS-builder parity exists.** `@prisma/orm-postgres/contract-builder` exports `policySelect` / `policyInsert` / `policyUpdate` / `policyDelete` / `policyAll`, `rlsEnabled(Model)`, and `role('anon')` — mirroring the PSL lowering key-for-key (identical emitted wire names). PSL is the canonical path shown here. -Emit + migrate as usual (`prisma contract emit`, then `references/migrations.md`). The plan creates your table, its FK, `ENABLE ROW LEVEL SECURITY`, and the `CREATE POLICY` statements — and **no DDL for `auth.*`**. +Emit + migrate as usual (`prisma contract emit`, then `prisma-orm-migrations/references/migrations.md`). The plan creates your table, its FK, `ENABLE ROW LEVEL SECURITY`, and the `CREATE POLICY` statements — and **no DDL for `auth.*`**. ## Workflow — `db.ts` with the `supabase()` factory @@ -119,7 +107,7 @@ The concept: instead of the stock `postgres()` factory, a Supabase app builds it ```typescript // src/prisma/db.ts -import { supabase } from '@internal/extension-supabase/runtime'; +import { supabase } from '@prisma/orm-extension-supabase/runtime'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; @@ -219,7 +207,7 @@ The concept: the runtime needs a **direct, session-capable** Postgres connection ## What Prisma Next doesn't do yet -- **No `/control` subpath on the extension** — it can't register through the target façade's `defineConfig({ extensions: [...] })`; wiring goes through the low-level config's `extensions` as shown above. File interest via `references/feedback.md`. +- **No `/control` subpath on the extension** — registration passes the `/pack` descriptor into the façade config's `extensions: [...]` as shown above, unlike other extensions whose config-side descriptor comes from `/control`. File interest via `references/feedback.md`. - **`GRANT` authoring.** Table privileges are not contract elements; the one grant a Supabase app needs (the `service_role` `auth.*` pair for admin reads) is run once by hand (SQL editor / `psql`). If you want grants managed by the contract, file via `references/feedback.md`. - **Transactions spanning the app root and the `.supabase` admin root.** The two roots are separate contract-bound runtimes sharing one pool; a cross-root transaction is not supported. - **Triggers / functions as contract elements.** The classic "create a profile row on signup" `auth.users` trigger is authored as raw SQL against your database, not in the contract. `auth.uid()` etc. appear only inside opaque policy predicate strings. @@ -233,7 +221,7 @@ The concept: the runtime needs a **direct, session-capable** Postgres connection ## Checklist -- [ ] `extensions: [supabasePack]` in the low-level `defineConfig` (no `/control` subpath exists). +- [ ] `extensions: [supabasePack]` (the `/pack` descriptor) inside the façade `ormConfig({...})` section of `definePrismaConfig` (no `/control` subpath exists). - [ ] Cross-space FK typed `supabase:auth.AuthUser` with explicit `fields` / `references` (+ `onDelete` if wanted). - [ ] Every policy target model carries `@@rls`; predicates quote camelCase columns and cast for `auth.uid()`. - [ ] `db.ts` uses `await supabase({ contractJson, url, jwksUrl | jwtSecret })` — exactly one JWT key source; `jwksUrl` for current projects, `jwtSecret` only for legacy HS256. diff --git a/skills/prisma-8/references/upgrade-app.md b/skills/prisma-orm-core-concepts/references/upgrade-app.md similarity index 73% rename from skills/prisma-8/references/upgrade-app.md rename to skills/prisma-orm-core-concepts/references/upgrade-app.md index 62969ee21f8d..72f054684e33 100644 --- a/skills/prisma-8/references/upgrade-app.md +++ b/skills/prisma-orm-core-concepts/references/upgrade-app.md @@ -1,18 +1,12 @@ # Upgrade Prisma 8 (user app) -This reference upgrades a project that **consumes** Prisma 8 via the public package API (`@internal/postgres`, `@internal/mongo`, the contract files in `prisma/`, etc.). If the project is itself a Prisma 8 *extension*, use [`upgrade-extension.md`](upgrade-extension.md) instead — or both, if the repo contains both an app and an extension package. +This reference upgrades a project that **consumes** Prisma 8 via the public package API (`@prisma/orm-postgres`, `@prisma/orm-mongo`, the contract files in `prisma/`, etc.). If the project is itself a Prisma 8 *extension*, use [`upgrade-extension.md`](upgrade-extension.md) instead — or both, if the repo contains both an app and an extension package. The per-transition instructions this reference reads live under [`../upgrading/app/upgrades/`](../upgrading/app/upgrades/). -## Step 0 — Upgrade to the newest instructions, then re-read - -The upgrade instructions ship inside the installed Prisma packages, so the copy on disk describes the version currently installed — not the version being upgraded *to*. Bug fixes to *old* per-transition instructions ship with each release as part of the cumulative set, so the newest copy is the one to run. - -Do the version bump first (step 1 of the per-step flow below), re-sync the skills from the newly installed packages (`prisma skills sync`), and re-read this reference and the per-transition instructions before applying any code translation. If the agent runtime supports an in-session refresh, perform it after the sync; otherwise finish the session's reasoning against the re-read files. - ## Pre-flight — extension compatibility -Before changing any code, refuse to upgrade past any installed extension's pinned Prisma Next version. Extensions in Prisma Next pin every `@internal/*` dependency to a single exact version (no carets, no ranges); that pin is the highest version the extension has been validated against. Upgrading the user app past that pin would silently desynchronise the extension's type identity from the app's. +This runs before anything else, including the Step 0 version bump below: it only reads `prisma.config.ts` and installed `package.json` files, so it needs no bump. Refuse to upgrade past any installed extension's pinned Prisma Next version. Extensions in Prisma Next pin every `@internal/*` dependency to a single exact version (no carets, no ranges); that pin is the highest version the extension has been validated against. Upgrading the user app past that pin would silently desynchronise the extension's type identity from the app's. Steps: @@ -27,19 +21,25 @@ Do not auto-downgrade the target; do not skip the lagging extension; do not bump If `prisma.config.ts` is absent or names no extensions, skip the pre-flight. +## Step 0 — Upgrade to the newest instructions, then re-read + +The upgrade instructions ship inside the installed Prisma packages, so the copy on disk describes the version currently installed — not the version being upgraded *to*. Bug fixes to *old* per-transition instructions ship with each release as part of the cumulative set, so the newest copy is the one to run. + +Once the pre-flight has established the target is reachable, do the version bump (step 1 of the per-step flow below), re-sync the skills from the newly installed packages (`prisma skills sync`), and re-read this reference and the per-transition instructions before applying any code translation. If the agent runtime supports an in-session refresh, perform it after the sync; otherwise finish the session's reasoning against the re-read files. + ## Role detection This flow applies when the project **consumes** Prisma Next: -- `package.json` declares one or more `@internal/*` packages under `dependencies` / `devDependencies`, and +- `package.json` declares one or more `@prisma/orm-*` packages under `dependencies` / `devDependencies`, and - the package is *not* itself an extension (no `@internal/contract` (or other SPI) under `dependencies`/`peerDependencies`; name does not match `^@.*/extension-`; not referenced from a sibling app's `prisma.config.ts`). If the project also matches the extension-author role, run **this** flow first and then [`upgrade-extension.md`](upgrade-extension.md) in the same session. If detection is ambiguous, ask the user. ## Version detection -- **From-version.** Read the currently-installed Prisma Next version from `pnpm-lock.yaml` (or `package-lock.json` / `yarn.lock`) by inspecting the resolved version of any `@internal/*` package. If the lockfile shows multiple `@internal/*` packages at different minors (already broken), the **lowest** minor is the from-version. -- **To-version.** Either the version the user specified, or whatever `npm view @internal/postgres dist-tags.latest` reports. Do not assume that is a stable version: while Prisma 8 is a release candidate, `latest` tracks the newest release, `8.0.0-rc.N` included. If the user wants a stable version specifically, they must name it. +- **From-version.** Read the currently-installed Prisma Next version from `pnpm-lock.yaml` (or `package-lock.json` / `yarn.lock`) by inspecting the resolved version of any `@prisma/orm-*` package. If the lockfile shows multiple `@prisma/orm-*` packages at different minors (already broken), the **lowest** minor is the from-version. +- **To-version.** Either the version the user specified, or whatever `npm view @prisma/orm-postgres dist-tags.latest` reports. Do not assume that is a stable version: while Prisma 8 is a release candidate, `latest` tracks the newest release, `8.0.0-rc.N` included. If the user wants a stable version specifically, they must name it. Report both back to the user before continuing. @@ -61,7 +61,7 @@ The chain order does not depend on which extensions are installed; the pre-fligh For each `(from, to)` step in the chain: -1. **Bump `@internal/*` deps.** Rewrite every `@internal/*` entry in the project's `package.json` to the exact `` version (no caret, no tilde). All entries advance to the same version. Cover `dependencies` and `devDependencies`. The skill itself ships inside the Prisma packages, so bumping them is what updates it; there is no separate skill package to bump. +1. **Bump `@prisma/orm-*` deps.** Rewrite every `@prisma/orm-*` entry in the project's `package.json` to the exact `` version (no caret, no tilde). All entries advance to the same version. Cover `dependencies` and `devDependencies`. The skill itself ships inside the Prisma packages, so bumping them is what updates it; there is no separate skill package to bump. 2. **Install.** Run `pnpm install` (or the project's lockfile-managing command). The project's code is now broken against the new types — the upgrade instructions for `` exist to fix it. @@ -83,7 +83,7 @@ For each `(from, to)` step in the chain: 6. **Commit.** One commit per step containing the `package.json` bump, lockfile churn, and any source rewrites: ```text - chore: upgrade @internal/* to + chore: upgrade @prisma/orm-* to ``` (Or the project's own commit-message convention.) Never squash steps. The user may squash on merge; the in-flight history must be per-step so a failed step is bisectable. diff --git a/skills/prisma-8/references/upgrade-extension.md b/skills/prisma-orm-core-concepts/references/upgrade-extension.md similarity index 96% rename from skills/prisma-8/references/upgrade-extension.md rename to skills/prisma-orm-core-concepts/references/upgrade-extension.md index 874c41a3a158..768d3b8154c0 100644 --- a/skills/prisma-8/references/upgrade-extension.md +++ b/skills/prisma-orm-core-concepts/references/upgrade-extension.md @@ -2,7 +2,7 @@ This reference upgrades a project that **is** a Prisma 8 extension — a package that consumes the framework SPI (`@internal/contract`, `@internal/framework-components`, `@internal/migration-tools`, etc.) and exposes contract / middleware / codec / migration surfaces that downstream apps install via `prisma.config.ts`. -If the project you are upgrading is a consumer **app** (it imports `@internal/postgres` or `@internal/mongo` from its application code), use [`upgrade-app.md`](upgrade-app.md) instead — or both, if the repo contains both a consumer app and an extension package, in which case run the app flow first then this one in the same session. +If the project you are upgrading is a consumer **app** (it imports `@prisma/orm-postgres` or `@prisma/orm-mongo` from its application code), use [`upgrade-app.md`](upgrade-app.md) instead — or both, if the repo contains both a consumer app and an extension package, in which case run the app flow first then this one in the same session. The per-transition instructions this reference reads live under [`../upgrading/extension/upgrades/`](../upgrading/extension/upgrades/). diff --git a/skills/prisma-8/upgrading/app/upgrades/0.10-to-0.11/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.10-to-0.11/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/0.10-to-0.11/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.10-to-0.11/instructions.md diff --git a/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/instructions.md similarity index 94% rename from skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/instructions.md index 04a1cc889364..fcb69d1e0ba7 100644 --- a/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/instructions.md @@ -227,7 +227,7 @@ Starting at the 0.12 release, the migration manifest schema is closed (`'+': 're Run the colocated codemod from your project root: ```bash -pnpm exec tsx ./strip-migration-labels-hints.ts +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts ``` It walks every `migration.json` that has a sibling `ops.json` (a complete on-disk migration package), removes the `labels` and `hints` keys, and recomputes `migrationHash` over the slimmed metadata plus the operations. The edit is format-preserving — only the two key lines are removed and the hash value is swapped in place, so the rest of each manifest (key order, indentation, inline-vs-expanded arrays) is left untouched and the diff stays minimal. The codemod is idempotent: re-running it over already-migrated manifests makes no further changes. @@ -237,7 +237,7 @@ It walks every `migration.json` that has a sibling `ops.json` (a complete on-dis Run the codemod in dry-run mode to confirm no manifest still carries the removed keys or a stale hash: ```bash -pnpm exec tsx ./strip-migration-labels-hints.ts --check +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts --check ``` `--check` lists every manifest that still needs fixing and exits non-zero if any remain, so wire it into a pre-commit hook or CI step to keep stale manifests out of the tree. A fully migrated tree reports `0 needing fix` and exits `0`. @@ -260,7 +260,7 @@ Two authoring constraints apply before emit succeeds: Run the colocated script from your project root: ```bash -pnpm exec tsx ./re-emit-closed-mongo-contracts.ts +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts ``` It finds every directory with a `prisma.config.ts` and a committed Mongo `contract.json`, then runs `pnpm emit` (or `prisma-next contract emit` when no emit script exists) in each. The regenerated `contract.json` / `contract.d.ts` pick up closed validators and an updated `storageHash`. @@ -268,7 +268,7 @@ It finds every directory with a `prisma.config.ts` and a committed Mongo `contra Use `--check` to list contracts that still need re-emitting without writing files: ```bash -pnpm exec tsx ./re-emit-closed-mongo-contracts.ts --check +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts --check ``` ### Apply the validator migration @@ -305,13 +305,13 @@ Explicit opt-in to the sentinel remains available: `namespace unbound { … }` i Run the colocated script from your project root: ```bash -pnpm exec tsx ./re-emit-postgres-public-default.ts +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/re-emit-postgres-public-default.ts ``` It finds every committed `contract.json` whose storage tree still carries `"kind": "postgres-unbound-schema"`, then runs `pnpm emit` (or `prisma-next contract emit`) in the matching contract space. Use `--check` to list spaces that still need re-emitting without writing files: ```bash -pnpm exec tsx ./re-emit-postgres-public-default.ts --check +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/re-emit-postgres-public-default.ts --check ``` ### After re-emit @@ -331,13 +331,13 @@ Starting at the 0.12 release, the application plane is symmetric with storage: m Run the colocated script from your project root: ```bash -pnpm exec tsx ./re-emit-domain-namespaced-contracts.ts +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/re-emit-domain-namespaced-contracts.ts ``` It finds contract spaces whose on-disk artefacts still use the flat domain shape (JSON missing `domain.namespaces`, or `contract.d.ts` still referencing `Contract['models']`), then re-emits each space. Use `--check` for a dry-run: ```bash -pnpm exec tsx ./re-emit-domain-namespaced-contracts.ts --check +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/re-emit-domain-namespaced-contracts.ts --check ``` If you already re-emitted for `public-default-namespace` on 0.12, a single emit pass covers both transitions — run whichever entry's detection matches your tree. @@ -348,7 +348,7 @@ After re-emitting, run `pnpm typecheck && pnpm test`. The regenerated `contract. ## `runtime-qualified-sql-default-namespace` -Starting at the 0.12 release, runtime SQL on Postgres qualifies table identifiers with the storage namespace the flat DSL/ORM surface resolved ([ADR 223](../../../../docs/architecture%20docs/adrs/ADR%20223%20-%20Target-owned%20default%20namespace.md)). Un-namespaced Postgres models continue to resolve through the `public` default; explicit `namespace unbound { … }` in PSL still maps to `__unbound__`. +Starting at the 0.12 release, runtime SQL on Postgres qualifies table identifiers with the storage namespace the flat DSL/ORM surface resolved ([ADR 223](../../../../../../docs/architecture%20docs/adrs/ADR%20223%20-%20Target-owned%20default%20namespace.md)). Un-namespaced Postgres models continue to resolve through the `public` default; explicit `namespace unbound { … }` in PSL still maps to `__unbound__`. ### Application code diff --git a/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts similarity index 99% rename from skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts index 08d1c9d5a34a..e85c2208fffb 100644 --- a/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts +++ b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts @@ -171,7 +171,7 @@ for (const dir of configDirs) { if (mongoDirs.length === 0) { console.error(`No Mongo contract directories found under ${projectRoot}.`); - process.exit(1); + process.exit(dryRun ? 0 : 1); } let needsFix = 0; diff --git a/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-domain-namespaced-contracts.ts b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/re-emit-domain-namespaced-contracts.ts similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-domain-namespaced-contracts.ts rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/re-emit-domain-namespaced-contracts.ts diff --git a/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-postgres-public-default.ts b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/re-emit-postgres-public-default.ts similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-postgres-public-default.ts rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/re-emit-postgres-public-default.ts diff --git a/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts similarity index 93% rename from skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts index e501317d8eb6..cbcb2d9b7ae9 100644 --- a/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +++ b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts @@ -302,6 +302,26 @@ async function processFile(path: string): Promise { if (out === raw) { return { path, status: 'already-clean' }; } + + // Fail closed before writing: the text surgery assumes one-key-per-line + // formatting, so reparse the stripped text and refuse to write if it is no + // longer valid JSON (e.g. a stranded trailing comma) or if either obsolete + // key survived the strip (e.g. a minified manifest defeats the + // newline-anchored key removal). + let reparsed: unknown; + try { + reparsed = JSON.parse(out); + } catch (error) { + throw new Error( + `${path}: stripped manifest is no longer valid JSON — refusing to write (${error instanceof Error ? error.message : String(error)})`, + ); + } + if (isJsonObject(reparsed) && ('labels' in reparsed || 'hints' in reparsed)) { + throw new Error( + `${path}: \`labels\`/\`hints\` survived the strip (minified manifest?) — refusing to write; slim this manifest by hand and re-run`, + ); + } + if (!dryRun) await writeFile(path, out, 'utf-8'); return { path, status: dryRun ? 'needs-fix' : 'fixed' }; } diff --git a/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.12-to-0.13/instructions.md similarity index 98% rename from skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.12-to-0.13/instructions.md index 392a5dc71c92..4e9c2c9a297f 100644 --- a/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.12-to-0.13/instructions.md @@ -205,7 +205,7 @@ Single-table inheritance variants — `@@base(...)` models **without** their own Run the colocated script from your project root: ```bash -pnpm exec tsx ./re-emit-mti-variant-link-columns.ts +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.12-to-0.13/re-emit-mti-variant-link-columns.ts ``` It walks the project for `prisma.config.ts` directories, resolves each space's committed `contract.json`, and re-emits any contract whose MTI variant table still lacks its link column (an MTI variant model whose storage table has no `primaryKey`). It prefers a package's `emit` script when present, otherwise runs `prisma-next contract emit --config `. @@ -213,7 +213,7 @@ It walks the project for `prisma.config.ts` directories, resolves each space's c Use `--check` for a dry-run that lists the contract-spaces still needing re-emit and exits non-zero if any remain: ```bash -pnpm exec tsx ./re-emit-mti-variant-link-columns.ts --check +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.12-to-0.13/re-emit-mti-variant-link-columns.ts --check ``` The regenerated `contract.json` gains the variant's link columns (the base PK's column set), their primary key, and the cascading foreign key under `storage.namespaces..tables.`, and the contract's `storageHash` changes. `contract.d.ts` picks up the new columns on the variant's row type. diff --git a/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/re-emit-mti-variant-link-columns.ts b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.12-to-0.13/re-emit-mti-variant-link-columns.ts similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/re-emit-mti-variant-link-columns.ts rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.12-to-0.13/re-emit-mti-variant-link-columns.ts diff --git a/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.13-to-0.14/instructions.md similarity index 99% rename from skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.13-to-0.14/instructions.md index 076bef34a160..53022ea43d32 100644 --- a/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.13-to-0.14/instructions.md @@ -332,7 +332,7 @@ override get operations() { The colocated script applies this transformation automatically. Run it from your project root: ```bash -pnpm exec tsx .claude/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts ``` ## `postgres-contract-serializer` diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts similarity index 91% rename from skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts index dfd1bb56cd68..e4ce8ec4458d 100644 --- a/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +++ b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts @@ -69,7 +69,10 @@ function readToken(src: string, offset: number): { value: string; end: number } if (src[i] === "'" || src[i] === '"' || src[i] === '`') { const q = src[i]; let end = i + 1; - while (end < src.length && src[end] !== q) end++; + while (end < src.length && src[end] !== q) { + if (src[end] === '\\') end++; + end++; + } return { value: src.slice(i, end + 1), end: end + 1 }; } let depth = 0; @@ -94,7 +97,7 @@ type Rewrite = { const rewrites: Rewrite[] = [ // dropColumn(schema, table, column) { - pattern: /\bdropColumn\(/g, + pattern: /(? 0) { - if (out[end] === '(') depth++; - else if (out[end] === ')') depth--; + const ch = out[end]; + if (ch === "'" || ch === '"' || ch === '`') { + end++; + while (end < out.length && out[end] !== ch) { + if (out[end] === '\\') end++; + end++; + } + } else if (ch === '(') depth++; + else if (ch === ')') depth--; end++; } result += out.slice(last, match.index) + replacement; diff --git a/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/uuid-preset-rename.ts b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.13-to-0.14/uuid-preset-rename.ts similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/uuid-preset-rename.ts rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.13-to-0.14/uuid-preset-rename.ts diff --git a/skills/prisma-8/upgrading/app/upgrades/0.14-to-0.15/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.14-to-0.15/instructions.md similarity index 99% rename from skills/prisma-8/upgrading/app/upgrades/0.14-to-0.15/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.14-to-0.15/instructions.md index b25f16b1bee2..eb4e8fb0645d 100644 --- a/skills/prisma-8/upgrading/app/upgrades/0.14-to-0.15/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.14-to-0.15/instructions.md @@ -4,7 +4,7 @@ to: "0.15" changes: - id: sql-orm-includes-decode-codec-values summary: | - SQL ORM includes now decode every scalar child field through its contract-bound codec, matching top-level query results. Update application code and tests that relied on included fields retaining the database's JSON representation: for example, Postgres `bytea` include fields now return `Uint8Array` instead of `\\x`-prefixed hex text, and timestamp include fields return `Date` instead of timestamp strings. Custom codec-backed include fields likewise return the value produced by the codec's `decodeJson` method. + SQL ORM includes now decode every scalar child field through its contract-bound codec, matching top-level query results. Update application code and tests that relied on included fields retaining the database's JSON representation: for example, Postgres `bytea` include fields now return `Uint8Array` instead of `\x`-prefixed hex text, and timestamp include fields return `Date` instead of timestamp strings. Custom codec-backed include fields likewise return the value produced by the codec's `decodeJson` method. - id: db-verify-json-single-issue-list summary: | `prisma-next db verify --json` (and `db verify --schema-only --json`) now report a single diff --git a/skills/prisma-8/upgrading/app/upgrades/0.15-to-0.16/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.15-to-0.16/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/0.15-to-0.16/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.15-to-0.16/instructions.md diff --git a/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.16-to-0.17/instructions.md similarity index 99% rename from skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.16-to-0.17/instructions.md index 8451ab6a4ad5..977928d25665 100644 --- a/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.16-to-0.17/instructions.md @@ -600,7 +600,7 @@ Two distinct effects on your checked-in artefacts: Run the colocated codemod from your project root, **before** `scripts/migrate-migrations-layout.mjs` (the snapshot-layout entries below) — the 0.17 layout migrator accepts only bare-hex trees: ```bash -pnpm exec tsx ./strip-sha256-hash-prefixes.ts +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts ``` For every on-disk migration package (a `migration.json` with a sibling `ops.json`) it strips the prefix from the manifest's `from` / `to`, from hash literals inside `ops.json`, in pre-store sibling contract snapshots (`*-contract.json`, `*.d.ts`, `migration.ts`), and in content-addressed store entries (`migrations/snapshots//contract.json` + `contract.d.ts` — the directory name is the hash's hex and does not change), recomputes `migrationHash` over the bare-hex content, and rewrites `refs/*.json` — repointing refs that held old migration hashes at the recomputed ones, and mapping the empty-tree sentinel `sha256:empty` to `empty`. The edit is format-preserving (only hash literals and the recomputed hash value change) and idempotent: re-running over an already-bare tree makes no further changes. @@ -608,7 +608,7 @@ For every on-disk migration package (a `migration.json` with a sibling `ops.json Use `--check` for a dry run that lists files still needing the fix and exits non-zero if any remain: ```bash -pnpm exec tsx ./strip-sha256-hash-prefixes.ts --check +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts --check ``` ### Re-emit live contract artefacts diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts similarity index 97% rename from skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts index 199b26375f23..4dc843a2db75 100644 --- a/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +++ b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts @@ -271,12 +271,15 @@ async function processPackage(manifestPath: string): Promise { const out: Result[] = []; + // Stage both transformed files and run every validation before writing + // either, so a manifest failure cannot leave ops.json updated while + // migration.json stays stale. + // Ops: strip prefixes (e.g. `meta.storageHash` stamps inside operation // payloads), then parse the stripped text — the recomputed hash covers the // bare-hex operations exactly as they will sit on disk. const strippedOpsRaw = stripHashPrefixes(opsRaw); const ops: unknown = JSON.parse(strippedOpsRaw); - out.push(await emit(opsPath, opsRaw, strippedOpsRaw)); // Manifest: strip prefixes from `from` / `to` (and the sentinel), then // recompute `migrationHash` over the bare-hex envelope + bare-hex ops. @@ -297,13 +300,10 @@ async function processPackage(manifestPath: string): Promise { migrationHashMap.set(oldStoredHash, newHash); migrationHashMap.set(strippedOldHash, newHash); - out.push( - await emit( - manifestPath, - raw, - replaceMigrationHash(strippedManifestRaw, strippedOldHash, newHash), - ), - ); + const finalManifestRaw = replaceMigrationHash(strippedManifestRaw, strippedOldHash, newHash); + + out.push(await emit(opsPath, opsRaw, strippedOpsRaw)); + out.push(await emit(manifestPath, raw, finalManifestRaw)); // Siblings: contract snapshots (`*-contract.json`), branded-literal type // files (`*.d.ts`), and the executable `migration.ts` all carry contract diff --git a/skills/prisma-8/upgrading/app/upgrades/0.17-to-8.0.0-rc.1/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.17-to-8.0.0-rc.1/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/0.17-to-8.0.0-rc.1/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.17-to-8.0.0-rc.1/instructions.md diff --git a/skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.7-to-0.8/instructions.md diff --git a/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.8-to-0.9/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.8-to-0.9/instructions.md diff --git a/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/strip-inline-contracts.ts b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.8-to-0.9/strip-inline-contracts.ts similarity index 93% rename from skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/strip-inline-contracts.ts rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.8-to-0.9/strip-inline-contracts.ts index 37911f4d34a3..f7c080c19618 100644 --- a/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/strip-inline-contracts.ts +++ b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.8-to-0.9/strip-inline-contracts.ts @@ -47,7 +47,14 @@ async function findManifests(root: string): Promise { const out: string[] = []; async function walk(dir: string): Promise { - const entries = await readdir(dir, { withFileTypes: true }); + let entries: Awaited>; + try { + entries = await readdir(dir, { withFileTypes: true }); + } catch { + // Unreadable directory — skip silently. The consumer's project root may + // legitimately contain restricted directories. + return; + } for (const entry of entries) { if (entry.isDirectory()) { if (SKIP_DIRS.has(entry.name)) continue; @@ -70,8 +77,7 @@ async function findManifests(root: string): Promise { function looksLikeMigrationManifest(value: unknown): value is Record { if (typeof value !== 'object' || value === null) return false; - const obj = value as Record; - return 'from' in obj && 'to' in obj && 'migrationHash' in obj; + return 'from' in value && 'to' in value && 'migrationHash' in value; } /** @@ -155,6 +161,7 @@ function removeTopLevelKey(text: string, key: string): string { if (text[removeEnd] === ',') { removeEnd += 1; + if (text[removeEnd] === '\r') removeEnd += 1; if (text[removeEnd] === '\n') removeEnd += 1; let lineStart = removeStart; while (lineStart > 0 && text[lineStart - 1] !== '\n') lineStart -= 1; @@ -164,9 +171,11 @@ function removeTopLevelKey(text: string, key: string): string { while (back > 0 && /[ \t]/.test(text[back] ?? '')) back -= 1; if (text[back] === '\n') { let prev = back - 1; + if (text[prev] === '\r') prev -= 1; // CRLF: the comma sits before \r\n while (prev > 0 && /[ \t]/.test(text[prev] ?? '')) prev -= 1; if (text[prev] === ',') { removeStart = prev; + if (text[removeEnd] === '\r') removeEnd += 1; if (text[removeEnd] === '\n') removeEnd += 1; } } diff --git a/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.9-to-0.10/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.9-to-0.10/instructions.md diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts similarity index 95% rename from skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts index c0c35d949374..bd9676cb08ff 100644 --- a/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +++ b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts @@ -61,13 +61,14 @@ * This is the safe default for unknown codec IDs (including any * future extension-contributed codecs). * - * The transformation re-serialises each affected file via - * `JSON.stringify(value, null, 2) + '\n'` — the same formatting the - * CLI uses when authoring snapshots originally, so the diff outside - * `storage.types` is zero on files the CLI generated. Hand-edited - * contract snapshots may experience cosmetic whitespace shifts; this - * is acceptable because on-disk contract snapshots are CLI-authored - * artefacts, not user-edited source. + * The transformation re-serialises each affected file via the local + * `formatJson` helper — 2-space-indented JSON that additionally keeps + * short arrays of primitives inline (see the `formatJson` doc block), + * matching the formatting the CLI uses when authoring snapshots + * originally, so the diff outside `storage.types` is zero on files the + * CLI generated. Hand-edited contract snapshots may experience cosmetic + * whitespace shifts; this is acceptable because on-disk contract + * snapshots are CLI-authored artefacts, not user-edited source. * * The codemod is idempotent: running it on already-stamped snapshots * is a no-op (every entry passes the `kind`-already-present check). diff --git a/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md similarity index 99% rename from skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md index 5f18074ec10c..5802f6352e1d 100644 --- a/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md @@ -390,6 +390,8 @@ SQLite states the same policy in its own terms — `count`, integer `sum`, and ` // ↑ the replacer can go ``` + The replacer can only go when nothing in the serialized result is a `bigint`. A result that includes `countBigInt()` or `sumBigInt()` values (item 3 below) still needs it. + 3. **Change the method, not the value, where you need exactness.** A decimal-string average was doing real work in a money or reporting path; `avgDecimal(field)` returns exactly what `avg(field)` used to, and `countBigInt()` exactly what `count()` used to. `sumBigInt(field)` matches the old `sum(field)` everywhere but one column class. On PostgreSQL, a `BigInt` or `BigIntNumber` column's `sum` used to be a decimal `string`, because the database totals a 64-bit column as `numeric`; `sumBigInt` reads that same total as a `bigint`. So a money path summing a `BigInt` column gets a `bigint` where it had a string — exact either way, but a different type. Convert at the consumption site (`String(total)`) if a decimal library or a string comparison is downstream. Over every other integer column, and on SQLite, `sumBigInt` is the old `sum` unchanged. diff --git a/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md diff --git a/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md diff --git a/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md diff --git a/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.5-to-8.0.0-rc.6/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.5-to-8.0.0-rc.6/instructions.md similarity index 98% rename from skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.5-to-8.0.0-rc.6/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.5-to-8.0.0-rc.6/instructions.md index 9313f9c77958..98dbdf2839d8 100644 --- a/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.5-to-8.0.0-rc.6/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.5-to-8.0.0-rc.6/instructions.md @@ -83,7 +83,8 @@ changes: summary: | A literal `@default(...)` on a Temporal-backed temporal column cannot be emitted today. The default value is encoded through the column's codec while the contract is being - emitted, inside the CLI's own process, and stock Node ships no `Temporal`. So + emitted, inside the CLI's own process, and on a runtime without a global `Temporal` + implementation the encoding fails. So ```prisma occurredAt Timestamptz @default("2024-01-01T00:00:00Z") diff --git a/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.6-to-8.0.0-rc.7/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.6-to-8.0.0-rc.7/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.6-to-8.0.0-rc.7/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.6-to-8.0.0-rc.7/instructions.md diff --git a/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.7-to-8.0.0-rc.8/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.7-to-8.0.0-rc.8/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.7-to-8.0.0-rc.8/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.7-to-8.0.0-rc.8/instructions.md diff --git a/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.8-to-8.0.0-rc.9/instructions.md b/skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.8-to-8.0.0-rc.9/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.8-to-8.0.0-rc.9/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/app/upgrades/8.0.0-rc.8-to-8.0.0-rc.9/instructions.md diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.10-to-0.11/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.10-to-0.11/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/0.10-to-0.11/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.10-to-0.11/instructions.md diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/instructions.md similarity index 97% rename from skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/instructions.md index 8866e317c612..ed8cb0692d46 100644 --- a/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/instructions.md @@ -551,7 +551,7 @@ Starting at the 0.12 release, the migration manifest schema is closed (`'+': 're Run the colocated codemod from your extension's package root: ```bash -pnpm exec tsx ./strip-migration-labels-hints.ts +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts ``` It walks every `migration.json` that has a sibling `ops.json` (a complete on-disk migration package), removes the `labels` and `hints` keys, and recomputes `migrationHash` over the slimmed metadata plus the operations. The edit is format-preserving — only the two key lines are removed and the hash value is swapped in place, so the rest of each manifest (key order, indentation, inline-vs-expanded arrays) is left untouched and the diff stays minimal. The codemod is idempotent: re-running it over already-migrated manifests makes no further changes. @@ -561,7 +561,7 @@ It walks every `migration.json` that has a sibling `ops.json` (a complete on-dis Run the codemod in dry-run mode to confirm no committed manifest still carries the removed keys or a stale hash: ```bash -pnpm exec tsx ./strip-migration-labels-hints.ts --check +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts --check ``` `--check` lists every manifest that still needs fixing and exits non-zero if any remain, so wire it into your extension's CI alongside `prisma-8-check-pins`. A fully migrated tree reports `0 needing fix` and exits `0`. @@ -585,7 +585,7 @@ Starting at the 0.12 release, Postgres extension packs whose contract-space decl Run the colocated script from your extension package root (or monorepo root if it hosts multiple extension packs): ```bash -pnpm exec tsx ./regenerate-extension-public-baseline.ts +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/regenerate-extension-public-baseline.ts ``` For each extension root whose `src/contract.json` still carries `"kind": "postgres-unbound-schema"`, the script runs `pnpm build:contract-space`, copies `src/contract.{json,d.ts}` into each baseline migration directory as `end-contract.{json,d.ts}`, patches the baseline `migration.ts` `to` hash, self-emits the migration (`pnpm exec tsx migrations/.../migration.ts`), and updates `migrations/refs/head.json`. @@ -593,7 +593,7 @@ For each extension root whose `src/contract.json` still carries `"kind": "postgr Use `--check` to list packs that still need regeneration: ```bash -pnpm exec tsx ./regenerate-extension-public-baseline.ts --check +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/regenerate-extension-public-baseline.ts --check ``` Path B baselines (hand-authored install migrations with no planner scaffold) follow the same loop documented in your extension README: edit `describe().to`, then self-emit. @@ -615,13 +615,13 @@ Starting at the 0.12 release, two SPI changes affect extension authors: Run the colocated codemod from your extension root: ```bash -pnpm exec tsx ./migrate-contract-testing-imports.ts +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/migrate-contract-testing-imports.ts ``` It rewrites every `@internal/contract/testing` import to `@repo/test-utils`. Use `--check` for a dry-run: ```bash -pnpm exec tsx ./migrate-contract-testing-imports.ts --check +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/migrate-contract-testing-imports.ts --check ``` Exports are unchanged — only the package path moves: @@ -681,7 +681,7 @@ Run `pnpm typecheck && pnpm test` on your extension package. The import codemod ## `default-namespace-domain-access-retire-projection-helpers` -Starting at the 0.12 release (runtime qualification, [ADR 223](../../../../docs/architecture%20docs/adrs/ADR%20223%20-%20Target-owned%20default%20namespace.md)), the foundation `contract` package retires the transitional projection helpers introduced during the symmetric domain-plane migration. Extension code that still calls them will fail to compile after the bump. +Starting at the 0.12 release (runtime qualification, [ADR 223](../../../../../../docs/architecture%20docs/adrs/ADR%20223%20-%20Target-owned%20default%20namespace.md)), the foundation `contract` package retires the transitional projection helpers introduced during the symmetric domain-plane migration. Extension code that still calls them will fail to compile after the bump. The default namespace a bare name resolves through is **inferred** from the contract (sole namespace, else insertion order) — there are no `…ForSqlTarget` / `…ForMongo` helpers to import. A target's default namespace is declared on its descriptor (`defaultNamespaceId`) and consumed only by authoring; runtime code resolves target-agnostically. diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/migrate-contract-testing-imports.ts b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/migrate-contract-testing-imports.ts similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/migrate-contract-testing-imports.ts rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/migrate-contract-testing-imports.ts diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/regenerate-extension-public-baseline.ts b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/regenerate-extension-public-baseline.ts similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/regenerate-extension-public-baseline.ts rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/regenerate-extension-public-baseline.ts diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts similarity index 93% rename from skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts index e501317d8eb6..cbcb2d9b7ae9 100644 --- a/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +++ b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts @@ -302,6 +302,26 @@ async function processFile(path: string): Promise { if (out === raw) { return { path, status: 'already-clean' }; } + + // Fail closed before writing: the text surgery assumes one-key-per-line + // formatting, so reparse the stripped text and refuse to write if it is no + // longer valid JSON (e.g. a stranded trailing comma) or if either obsolete + // key survived the strip (e.g. a minified manifest defeats the + // newline-anchored key removal). + let reparsed: unknown; + try { + reparsed = JSON.parse(out); + } catch (error) { + throw new Error( + `${path}: stripped manifest is no longer valid JSON — refusing to write (${error instanceof Error ? error.message : String(error)})`, + ); + } + if (isJsonObject(reparsed) && ('labels' in reparsed || 'hints' in reparsed)) { + throw new Error( + `${path}: \`labels\`/\`hints\` survived the strip (minified manifest?) — refusing to write; slim this manifest by hand and re-run`, + ); + } + if (!dryRun) await writeFile(path, out, 'utf-8'); return { path, status: dryRun ? 'needs-fix' : 'fixed' }; } diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.12-to-0.13/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.12-to-0.13/instructions.md similarity index 97% rename from skills/prisma-8/upgrading/extension/upgrades/0.12-to-0.13/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.12-to-0.13/instructions.md index a148fc148723..6bd554539f6b 100644 --- a/skills/prisma-8/upgrading/extension/upgrades/0.12-to-0.13/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.12-to-0.13/instructions.md @@ -171,7 +171,7 @@ Because the `storageHash` changes, re-generate the migration baselines so ### Validation -After re-emitting and re-pinning, run `pnpm typecheck && pnpm test --filter `, +After re-emitting and re-pinning, run `pnpm --filter typecheck && pnpm --filter test`, then confirm `prisma-next migration check` passes. The `contract.json` diff should show `"typeParams": {}` removed from every `storage.types` entry. @@ -242,8 +242,8 @@ re-emit `ops.json` + `migration.json`, then update `migrations/refs/head.json`. ### Validation -After re-emitting and re-pinning, run `pnpm typecheck && pnpm test --filter -`, then confirm `prisma-next migration check` passes. +After re-emitting and re-pinning, run `pnpm --filter typecheck && +pnpm --filter test`, then confirm `prisma-next migration check` passes. ## Declarative PSL-block SPI (additive) diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.13-to-0.14/instructions.md similarity index 99% rename from skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.13-to-0.14/instructions.md index 2bfa011761e0..79d75e903eff 100644 --- a/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.13-to-0.14/instructions.md @@ -320,7 +320,7 @@ override get operations() { The colocated script applies this transformation automatically. Run it from your extension root: ```bash -pnpm exec tsx .claude/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts ``` ## `create-runtime-removed` diff --git a/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts similarity index 91% rename from skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts index dfd1bb56cd68..e4ce8ec4458d 100644 --- a/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +++ b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts @@ -69,7 +69,10 @@ function readToken(src: string, offset: number): { value: string; end: number } if (src[i] === "'" || src[i] === '"' || src[i] === '`') { const q = src[i]; let end = i + 1; - while (end < src.length && src[end] !== q) end++; + while (end < src.length && src[end] !== q) { + if (src[end] === '\\') end++; + end++; + } return { value: src.slice(i, end + 1), end: end + 1 }; } let depth = 0; @@ -94,7 +97,7 @@ type Rewrite = { const rewrites: Rewrite[] = [ // dropColumn(schema, table, column) { - pattern: /\bdropColumn\(/g, + pattern: /(? 0) { - if (out[end] === '(') depth++; - else if (out[end] === ')') depth--; + const ch = out[end]; + if (ch === "'" || ch === '"' || ch === '`') { + end++; + while (end < out.length && out[end] !== ch) { + if (out[end] === '\\') end++; + end++; + } + } else if (ch === '(') depth++; + else if (ch === ')') depth--; end++; } result += out.slice(last, match.index) + replacement; diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/uuid-preset-rename.ts b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.13-to-0.14/uuid-preset-rename.ts similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/uuid-preset-rename.ts rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.13-to-0.14/uuid-preset-rename.ts diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.14-to-0.15/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.14-to-0.15/instructions.md similarity index 99% rename from skills/prisma-8/upgrading/extension/upgrades/0.14-to-0.15/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.14-to-0.15/instructions.md index 5eb430845a8f..c90d86d45b97 100644 --- a/skills/prisma-8/upgrading/extension/upgrades/0.14-to-0.15/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.14-to-0.15/instructions.md @@ -60,7 +60,7 @@ changes: database inside JSON values. SQL include decoding calls `decodeJson`; ordinary column decoding continues to call `decode`. Update custom SQL codecs whose database JSON representation differs from their normal driver wire representation, then re-emit committed contracts and defaults. - Built-in representation changes are: `pg/bytea@1` base64 -> `\\x`-prefixed hex, + Built-in representation changes are: `pg/bytea@1` base64 -> `\x`-prefixed hex, `pg/numeric@1` string -> JSON number, `pg/timestamp@1` UTC `Z` suffix -> no timezone suffix, `pg/timestamptz@1` UTC `Z` suffix -> `+00:00`, `sqlite/bigint@1` string -> JSON number, `pg/vector@1` JSON array -> Postgres vector text, and `pg/geometry@1` GeoJSON object -> HEXEWKB diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.15-to-0.16/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.15-to-0.16/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/0.15-to-0.16/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.15-to-0.16/instructions.md diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.16-to-0.17/instructions.md similarity index 98% rename from skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.16-to-0.17/instructions.md index 33efa2fcf651..00b067e502ad 100644 --- a/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.16-to-0.17/instructions.md @@ -574,7 +574,7 @@ Two distinct effects on your pack's checked-in artefacts: Run the colocated codemod from your extension's repository root, **before** `scripts/migrate-migrations-layout.mjs` (the snapshot-layout entries above) — the 0.17 layout migrator accepts only bare-hex trees: ```bash -pnpm exec tsx ./strip-sha256-hash-prefixes.ts +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts ``` For every on-disk migration package (a `migration.json` with a sibling `ops.json`) it strips the prefix from the manifest's `from` / `to`, from hash literals inside `ops.json`, in pre-store sibling contract snapshots (`*-contract.json`, `*.d.ts`, `migration.ts`), and in content-addressed store entries (`migrations/snapshots//contract.json` + `contract.d.ts` — the directory name is the hash's hex and does not change), recomputes `migrationHash` over the bare-hex content, and rewrites `refs/*.json` — repointing refs that held old migration hashes at the recomputed ones, and mapping the empty-tree sentinel `sha256:empty` to `empty`. The edit is format-preserving (only hash literals and the recomputed hash value change) and idempotent: re-running over an already-bare tree makes no further changes. @@ -582,7 +582,7 @@ For every on-disk migration package (a `migration.json` with a sibling `ops.json Use `--check` for a dry run that lists files still needing the fix and exits non-zero if any remain: ```bash -pnpm exec tsx ./strip-sha256-hash-prefixes.ts --check +pnpm exec tsx .claude/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts --check ``` ### Re-emit committed contract artefacts @@ -605,6 +605,10 @@ prisma-next db sign After the codemod and re-emit, run `pnpm typecheck && pnpm test` in your extension repo, and exercise any flow that loads your migrations — the loader recomputes and verifies each manifest's `migrationHash` on read, so a stale or still-prefixed manifest fails immediately. `git grep -n "sha256:"` over your repository should return no hits in committed artefacts. +## Import-path note for the entries below + +The entries from here on spell import paths with the workspace names (`@internal/sql-relational-core/ast`, `@internal/sql-schema-ir/naming`, `@internal/target-postgres/...`, …). Those spellings apply as written to packs that live inside the Prisma Next workspace. A pack built against the published packages cannot install `@internal/*`; it resolves the same modules through the `@prisma/*` subpath entrypoints — see the `build-against-published-packages-not-workspace-names` entry above for the one-hop mapping. + ## `adopt-sql-json-projection-ast-foundations` Relational JSON container AST construction now requires an explicit value-projection variant. Import `NativeJsonValueProjection` from `@internal/sql-relational-core/ast` and wrap every expression that 0.16 code passed directly to `JsonObjectExpr.entry(key, expression)` or `JsonArrayAggExpr.of(expression, ...)`: use `JsonObjectExpr.entry(key, new NativeJsonValueProjection(expression))` and `JsonArrayAggExpr.of(new NativeJsonValueProjection(expression), ...)`. `NativeJsonValueProjection` preserves the pre-0.17 target-native JSON conversion. Use `CodecJsonValueProjection` only when the extension deliberately supplies a `CodecRef` for codec-owned JSON conversion, and use `JsonDocumentProjection` only when the wrapped expression already produces a JSON document. diff --git a/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts similarity index 97% rename from skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts index 199b26375f23..4dc843a2db75 100644 --- a/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +++ b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts @@ -271,12 +271,15 @@ async function processPackage(manifestPath: string): Promise { const out: Result[] = []; + // Stage both transformed files and run every validation before writing + // either, so a manifest failure cannot leave ops.json updated while + // migration.json stays stale. + // Ops: strip prefixes (e.g. `meta.storageHash` stamps inside operation // payloads), then parse the stripped text — the recomputed hash covers the // bare-hex operations exactly as they will sit on disk. const strippedOpsRaw = stripHashPrefixes(opsRaw); const ops: unknown = JSON.parse(strippedOpsRaw); - out.push(await emit(opsPath, opsRaw, strippedOpsRaw)); // Manifest: strip prefixes from `from` / `to` (and the sentinel), then // recompute `migrationHash` over the bare-hex envelope + bare-hex ops. @@ -297,13 +300,10 @@ async function processPackage(manifestPath: string): Promise { migrationHashMap.set(oldStoredHash, newHash); migrationHashMap.set(strippedOldHash, newHash); - out.push( - await emit( - manifestPath, - raw, - replaceMigrationHash(strippedManifestRaw, strippedOldHash, newHash), - ), - ); + const finalManifestRaw = replaceMigrationHash(strippedManifestRaw, strippedOldHash, newHash); + + out.push(await emit(opsPath, opsRaw, strippedOpsRaw)); + out.push(await emit(manifestPath, raw, finalManifestRaw)); // Siblings: contract snapshots (`*-contract.json`), branded-literal type // files (`*.d.ts`), and the executable `migration.ts` all carry contract diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.17-to-8.0.0-rc.1/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.17-to-8.0.0-rc.1/instructions.md similarity index 99% rename from skills/prisma-8/upgrading/extension/upgrades/0.17-to-8.0.0-rc.1/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.17-to-8.0.0-rc.1/instructions.md index 8540c0202894..b8281ef442c2 100644 --- a/skills/prisma-8/upgrading/extension/upgrades/0.17-to-8.0.0-rc.1/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.17-to-8.0.0-rc.1/instructions.md @@ -173,7 +173,7 @@ export interface SqlQueryable { query(sql: string, params?: readonly unknown[]): Promise>; } -// After (0.18) +// After (8.0.0-rc.1) export interface SqlQueryable { query(request: SqlExecuteRequest): AsyncIterable; execute(request: SqlExecuteRequest): Promise; // { affectedRows } diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.7-to-0.8/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.7-to-0.8/instructions.md diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.8-to-0.9/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.8-to-0.9/instructions.md diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/strip-inline-contracts.ts b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.8-to-0.9/strip-inline-contracts.ts similarity index 93% rename from skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/strip-inline-contracts.ts rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.8-to-0.9/strip-inline-contracts.ts index 37911f4d34a3..f7c080c19618 100644 --- a/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/strip-inline-contracts.ts +++ b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.8-to-0.9/strip-inline-contracts.ts @@ -47,7 +47,14 @@ async function findManifests(root: string): Promise { const out: string[] = []; async function walk(dir: string): Promise { - const entries = await readdir(dir, { withFileTypes: true }); + let entries: Awaited>; + try { + entries = await readdir(dir, { withFileTypes: true }); + } catch { + // Unreadable directory — skip silently. The consumer's project root may + // legitimately contain restricted directories. + return; + } for (const entry of entries) { if (entry.isDirectory()) { if (SKIP_DIRS.has(entry.name)) continue; @@ -70,8 +77,7 @@ async function findManifests(root: string): Promise { function looksLikeMigrationManifest(value: unknown): value is Record { if (typeof value !== 'object' || value === null) return false; - const obj = value as Record; - return 'from' in obj && 'to' in obj && 'migrationHash' in obj; + return 'from' in value && 'to' in value && 'migrationHash' in value; } /** @@ -155,6 +161,7 @@ function removeTopLevelKey(text: string, key: string): string { if (text[removeEnd] === ',') { removeEnd += 1; + if (text[removeEnd] === '\r') removeEnd += 1; if (text[removeEnd] === '\n') removeEnd += 1; let lineStart = removeStart; while (lineStart > 0 && text[lineStart - 1] !== '\n') lineStart -= 1; @@ -164,9 +171,11 @@ function removeTopLevelKey(text: string, key: string): string { while (back > 0 && /[ \t]/.test(text[back] ?? '')) back -= 1; if (text[back] === '\n') { let prev = back - 1; + if (text[prev] === '\r') prev -= 1; // CRLF: the comma sits before \r\n while (prev > 0 && /[ \t]/.test(text[prev] ?? '')) prev -= 1; if (text[prev] === ',') { removeStart = prev; + if (text[removeEnd] === '\r') removeEnd += 1; if (text[removeEnd] === '\n') removeEnd += 1; } } diff --git a/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.9-to-0.10/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.9-to-0.10/instructions.md diff --git a/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts similarity index 95% rename from skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts index c0c35d949374..bd9676cb08ff 100644 --- a/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +++ b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts @@ -61,13 +61,14 @@ * This is the safe default for unknown codec IDs (including any * future extension-contributed codecs). * - * The transformation re-serialises each affected file via - * `JSON.stringify(value, null, 2) + '\n'` — the same formatting the - * CLI uses when authoring snapshots originally, so the diff outside - * `storage.types` is zero on files the CLI generated. Hand-edited - * contract snapshots may experience cosmetic whitespace shifts; this - * is acceptable because on-disk contract snapshots are CLI-authored - * artefacts, not user-edited source. + * The transformation re-serialises each affected file via the local + * `formatJson` helper — 2-space-indented JSON that additionally keeps + * short arrays of primitives inline (see the `formatJson` doc block), + * matching the formatting the CLI uses when authoring snapshots + * originally, so the diff outside `storage.types` is zero on files the + * CLI generated. Hand-edited contract snapshots may experience cosmetic + * whitespace shifts; this is acceptable because on-disk contract + * snapshots are CLI-authored artefacts, not user-edited source. * * The codemod is idempotent: running it on already-stamped snapshots * is a no-op (every entry passes the `kind`-already-present check). diff --git a/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md similarity index 99% rename from skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md index 55d5473954c3..6a5ac7b9e379 100644 --- a/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +++ b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md @@ -443,7 +443,7 @@ await readings.select('id').include('samples', (samples) => reduceToTotal(sample Where an argument was previously widened past the types — `aggregate.sum('counter' as never)` compiled because `AggregateFieldNames` was already `never` for such a contract — the cast moves from the argument to the builder, and the field name goes back to being a plain string. -The better fix, wherever the pack can emit, is to emit: run `prisma-next contract emit` and type the client from the emitted `Contract`. That restores full typing — arities, admitted field names, and per-codec result types — instead of erasing it. +The better fix, wherever the pack can emit, is to emit: run `prisma contract emit` and type the client from the emitted `Contract`. That restores full typing — arities, admitted field names, and per-codec result types — instead of erasing it. ## `count-over-a-field-counts-that-field` diff --git a/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md diff --git a/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md diff --git a/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md diff --git a/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.5-to-8.0.0-rc.6/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.5-to-8.0.0-rc.6/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.5-to-8.0.0-rc.6/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.5-to-8.0.0-rc.6/instructions.md diff --git a/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.6-to-8.0.0-rc.7/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.6-to-8.0.0-rc.7/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.6-to-8.0.0-rc.7/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.6-to-8.0.0-rc.7/instructions.md diff --git a/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.7-to-8.0.0-rc.8/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.7-to-8.0.0-rc.8/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.7-to-8.0.0-rc.8/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.7-to-8.0.0-rc.8/instructions.md diff --git a/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.8-to-8.0.0-rc.9/instructions.md b/skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.8-to-8.0.0-rc.9/instructions.md similarity index 100% rename from skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.8-to-8.0.0-rc.9/instructions.md rename to skills/prisma-orm-core-concepts/upgrading/extension/upgrades/8.0.0-rc.8-to-8.0.0-rc.9/instructions.md diff --git a/skills/prisma-orm-migrations/SKILL.md b/skills/prisma-orm-migrations/SKILL.md new file mode 100644 index 000000000000..f7a43116ec73 --- /dev/null +++ b/skills/prisma-orm-migrations/SKILL.md @@ -0,0 +1,64 @@ +--- +name: prisma-orm-migrations +description: >- + Use when creating, planning, reviewing, or applying database migrations in a + Prisma 8 (Prisma Next) project — "apply my contract change", "plan a + migration", "what runs on deploy", data backfills, editing migration.ts, + migration refs, drift recovery — or when the user mentions db update, + migration plan, db migrate, migration ref, dataTransform, placeholder, + migration graph, baseline, `from: (baseline)`, diamond convergence / + concurrent migrations, migration status, or a MIGRATION.* / PN-MIG-* / + PN-RUN-300x code. Does not apply to Prisma ORM 7 or earlier + (schema.prisma + @prisma/client projects). +metadata: + library: '@prisma/orm-postgres' + library_version: '8.0.0-rc.8' +--- + +# Prisma Next (Prisma 8) — Migrations + +> **Edit your data contract. Prisma handles the rest.** + +Prisma 8 moves fast, and your training data about it is very likely outdated. This skill ships inside the installed Prisma packages, so it describes the exact version this project has — treat it and its reference files as the source of truth, over anything you remember about Prisma. If `metadata.library_version` in this file's frontmatter does not match the project's installed Prisma packages, run `prisma skills sync` and re-read. Additional documentation for the wider Prisma platform lives at . + +This skill routes every migration task to the right reference file — open the reference before writing code; do not answer from this file alone. The references teach concepts, structures, and workflows, not the full CLI surface: for flag-level detail on any individual command, run it with `--help`. + +## The canonical model (one paragraph) + +Migrations are planned from a **contract diff**, not written by hand. You edit the data contract; `migration plan` diffs it against a resolved origin and writes a migration package (`migration.json`, `ops.json`, and a framework-rendered `migration.ts`); you review it, fill any data-transform `placeholder(...)` holes in `migration.ts`, self-emit, and apply with `db migrate`. The on-disk packages form a **graph** — nodes are contract hashes, edges are migrations — and a live database's position in it is recorded by its **marker**, offline by **refs**. + +## One cross-cutting fact — plan origin + +`migration plan` does **not** chain from the newest migration on disk. Its origin is `--from`, else the `db` ref, else an empty database — so a project with no ref keeps planning from scratch. Over existing migrations the CLI refuses that (`MIGRATION.PLAN_ORIGIN_UNKNOWN`) instead of writing a full-create package; choose the exit that matches your intent rather than reflexively passing `--from @empty`. [`references/migration-model.md`](references/migration-model.md) § *The trap* explains which to choose. + +## Routing table + +Open the reference whose triggers match the task. If more than one matches, open each — they are written to compose. + +| Task | Reference | Triggers | +| --- | --- | --- | +| Author migrations | [`references/migrations.md`](references/migrations.md) | `db update` vs `migration plan`, `db migrate`, `migration new`, `migration show`, `db update --dry-run`, `db verify`, `db sign`, data migration, `dataTransform`, placeholder sentinels in framework-rendered `migration.ts`, `MIGRATION.HASH_MISMATCH`, PN-MIG-2001 unfilled placeholder, schema drift | +| Migration graph, refs, plan origin | [`references/migration-model.md`](references/migration-model.md) | migration graph, refs, `migration ref set` / `list` / `delete`, the `db` ref, `--advance-ref`, `migration plan --from`, `from: (baseline)` in plan output, greenfield / from-scratch plan, baseline, first migration before deploy (Composer / CD-managed databases), chaining migrations, retrofitting migrations onto an existing database, `MIGRATION.HASH_NOT_IN_GRAPH`, `MIGRATION.PATH_UNREACHABLE` at plan/chain time | +| Review migrations on deploy | [`references/migration-review.md`](references/migration-review.md) | "what migrations are going to run", "what runs on deploy / merge", merge conflict, diamond convergence, concurrent migrations, migration status, ref management for CI, staging / production environment refs, `MIGRATION.DIVERGED`, `MIGRATION.NO_MARKER`, `MIGRATION.MARKER_NOT_IN_HISTORY`, `db migrate status`, `db migrate diff`, `db migrate resolve` | + +## Routing rules + +If the task clearly matches a row, open that reference directly without asking. + +For a vague prompt, ask **one** disambiguating question: + +- *"Is this about authoring a migration, or about reviewing what's going to run on deploy?"* → [`references/migrations.md`](references/migrations.md) vs [`references/migration-review.md`](references/migration-review.md). +- If it's about where a plan starts, refs, or an unexpected from-scratch plan → [`references/migration-model.md`](references/migration-model.md). + +If you still can't tell which reference applies, ask the user what they want to do. Do not guess. + +## Related skills + +The Prisma Next skills install as a set; everything outside migrations — editing the data contract, queries, runtime wiring, build integration, upgrades, error-envelope diagnosis, orientation questions, filing feedback — is owned by the sibling `prisma-orm-core-concepts` skill. + +## Checklist + +- [ ] If the task matches a routing-table row, open that reference before writing code. +- [ ] If the prompt is vague, ask one disambiguating question. +- [ ] Do not attempt to answer from this file alone — the references carry the verified tool surface. +- [ ] If the task is contract editing, queries, or feedback, route to `prisma-orm-core-concepts` instead. diff --git a/skills/prisma-8/references/migration-model.md b/skills/prisma-orm-migrations/references/migration-model.md similarity index 98% rename from skills/prisma-8/references/migration-model.md rename to skills/prisma-orm-migrations/references/migration-model.md index 208aeafe44c0..6c89eed586a2 100644 --- a/skills/prisma-8/references/migration-model.md +++ b/skills/prisma-orm-migrations/references/migration-model.md @@ -17,7 +17,7 @@ This reference teaches the model behind migration planning: what the migration g - Filling placeholders, applying migrations, hash mismatches, drift recovery → `references/migrations.md`. - What runs on deploy, environment refs in CI, concurrent-migration conflicts → `references/migration-review.md`. -- First-time adoption of an existing database (`contract infer` + `db sign` mechanics) → `references/quickstart.md` § *Brownfield-DB*. +- First-time adoption of an existing database (`contract infer` + `db sign` mechanics) → `prisma-orm-core-concepts/references/quickstart.md` § *Brownfield-DB*. ## Key Concepts @@ -125,7 +125,7 @@ If you skip the chaining, the next default plan resolves to greenfield: the trap ## Workflow — adopt a pre-existing database -The concept: a database that predates Prisma Next enters the system by describing it, not migrating it — `contract infer` derives the contract from the live schema, and after review + `contract emit`, `db sign` records the marker. Full recipe: `references/quickstart.md` § *Brownfield-DB*. +The concept: a database that predates Prisma Next enters the system by describing it, not migrating it — `contract infer` derives the contract from the live schema, and after review + `contract emit`, `db sign` records the marker. Full recipe: `prisma-orm-core-concepts/references/quickstart.md` § *Brownfield-DB*. ```bash pnpm prisma contract infer --db "$DATABASE_URL" --output src/prisma/contract.prisma @@ -161,7 +161,7 @@ The concept: the database exists and its marker is accurate (hash **M**) — it ## What Prisma Next doesn't do yet -- **No plan-time ref advancement.** `migration plan` cannot advance a ref for you; keeping the chain current is manual (`migration ref set` after each plan, or `--from` every time). If you want a plan-time advancement flag, file a feature request via the `references/feedback.md` skill. +- **No plan-time ref advancement.** `migration plan` cannot advance a ref for you; keeping the chain current is manual (`migration ref set` after each plan, or `--from` every time). If you want a plan-time advancement flag, file a feature request via the `prisma-orm-core-concepts/references/feedback.md` skill. ## Checklist diff --git a/skills/prisma-8/references/migration-review.md b/skills/prisma-orm-migrations/references/migration-review.md similarity index 96% rename from skills/prisma-8/references/migration-review.md rename to skills/prisma-orm-migrations/references/migration-review.md index 60378e2fce47..e7e5943b3027 100644 --- a/skills/prisma-8/references/migration-review.md +++ b/skills/prisma-orm-migrations/references/migration-review.md @@ -18,8 +18,8 @@ The skill teaches *the system's mental model* — what a ref is, what a marker i ## When Not to Use - User wants to *author* a migration → `references/migrations.md`. -- User wants to fix a hash-mismatch / drift in a single env → `references/migrations.md` (re-plan path) or `references/debug.md` (envelope-driven). -- User wants to edit the contract → `references/contract.md`. +- User wants to fix a hash-mismatch / drift in a single env → `references/migrations.md` (re-plan path) or `prisma-orm-core-concepts/references/failure-modes.md` (envelope-driven). +- User wants to edit the contract → `prisma-orm-core-concepts/references/contract.md`. ## Key Concepts — the navigation model @@ -78,7 +78,7 @@ Both flags are also available on `migration list` and `migration graph`. `migrat ### Plan- and apply-time diagnostics -These codes surface on `migration plan`, `migration ref set`, and `db migrate` — not on `migration status`. See [Migration System § Recovery affordances](../../docs/architecture%20docs/subsystems/7.%20Migration%20System.md#recovery-affordances) and [ADR 218](../../docs/architecture%20docs/adrs/ADR%20218%20-%20Refs%20with%20paired%20contract%20snapshots%20and%20universal%20graph-node%20invariant.md). +These codes surface on `migration plan`, `migration ref set`, and `db migrate` — not on `migration status`. See [Migration System § Recovery affordances](../../../docs/architecture%20docs/subsystems/7.%20Migration%20System.md#recovery-affordances) and [ADR 218](../../../docs/architecture%20docs/adrs/ADR%20218%20-%20Refs%20with%20paired%20contract%20snapshots%20and%20universal%20graph-node%20invariant.md). | Code | When | Meaning | Next move | |---|---|---|---| @@ -206,8 +206,8 @@ For a human-readable ordered preview of the migration path before applying, use ## What Prisma Next doesn't do yet -- **Per-environment migration ordering beyond the default chain.** If you need staging to skip a migration that production requires (or vice versa), the supported path is to author the per-env divergence as separate migrations and gate them in your deploy script. If you want first-class per-env routing, file a feature request via the `references/feedback.md` skill. -- **A built-in side-by-side "branch diff" view.** There is a full-graph render (`migration graph`) that shows branches, but no `git diff`-style comparison between two branches' migration sets. Workaround: run `migration status` on each branch and `diff` the output. If you want a built-in branch-comparison view, file a feature request via the `references/feedback.md` skill. +- **Per-environment migration ordering beyond the default chain.** If you need staging to skip a migration that production requires (or vice versa), the supported path is to author the per-env divergence as separate migrations and gate them in your deploy script. If you want first-class per-env routing, file a feature request via the `prisma-orm-core-concepts/references/feedback.md` skill. +- **A built-in side-by-side "branch diff" view.** There is a full-graph render (`migration graph`) that shows branches, but no `git diff`-style comparison between two branches' migration sets. Workaround: run `migration status` on each branch and `diff` the output. If you want a built-in branch-comparison view, file a feature request via the `prisma-orm-core-concepts/references/feedback.md` skill. ## Reference Files diff --git a/skills/prisma-8/references/migrations.md b/skills/prisma-orm-migrations/references/migrations.md similarity index 74% rename from skills/prisma-8/references/migrations.md rename to skills/prisma-orm-migrations/references/migrations.md index d21a1cb69a37..efa54c997beb 100644 --- a/skills/prisma-8/references/migrations.md +++ b/skills/prisma-orm-migrations/references/migrations.md @@ -5,7 +5,7 @@ The three-step user model: -1. **You edit your data contract.** (`references/contract.md`) +1. **You edit your data contract.** (`prisma-orm-core-concepts/references/contract.md`) 2. **Prisma Next plans the migration for you.** ← this skill 3. **If a data transform is needed, you edit `migration.ts` and self-emit.** ← this skill @@ -25,8 +25,8 @@ Once the contract changes, you choose how the change reaches the database. This - User wants to know what migrations *will run on deploy* / on merge, or to manage refs and invariants → `references/migration-review.md`. - User is deciding where a plan should chain from, saw `from: (baseline)` unexpectedly, is setting up migrations for a deploy-first (Composer / CD-managed) project, or is retrofitting migrations onto an existing database → `references/migration-model.md`. -- User wants to edit the contract → `references/contract.md`. -- User wants a deeper read of a single structured error envelope → `references/debug.md`. +- User wants to edit the contract → `prisma-orm-core-concepts/references/contract.md`. +- User wants a deeper read of a single structured error envelope → `prisma-orm-core-concepts/references/failure-modes.md`. ## Key Concepts @@ -37,11 +37,11 @@ Once the contract changes, you choose how the change reaches the database. This - `migration.json` — manifest (metadata + `migrationHash`). - `ops.json` — canonical operation list. Content-addressed; `migrationHash` is computed over this. - `migration.ts` — TypeScript authoring source, **framework-rendered** by `migration plan` (or `migration new`). You edit specific holes in it (see *Fill a placeholder* below) and re-emit `ops.json` / `migration.json` by running it. -- **Contract snapshots.** `migration.ts` imports its bookend contracts from the shared, content-addressed store at `migrations/snapshots//contract.json` + `contract.d.ts` (`` is the contract's 64-hex storage hash) — not from files inside the migration package. +- **Contract snapshots.** `migration.ts` imports its bookend contracts from the shared, content-addressed store at `migrations/snapshots//contract.json` + `contract.d.ts` (`` is the contract's 64-hex storage hash) — not from files inside the migration package. Each bookend is imported twice — the JSON value plus its `Contract` type (`import type { Contract as Start } from '../../snapshots//contract'`) — and bound on the class via the `startContractJson` / `endContractJson` overrides. - **Self-emit.** Running `node migrations/app//migration.ts` regenerates `ops.json` and `migration.json` from the (possibly edited) TS source. This is the only supported way to update an existing migration package after edits. -- **`migration.ts` shape.** Framework-rendered. A class extending `Migration` (from `@internal/family-mongo/migration` on Mongo, or re-exported via `@internal/postgres/migration` on Postgres — see the framing block below), with an `operations` getter that returns an array of factory-call values. The file ends with `MigrationCLI.run(import.meta.url, M)` so executing it self-emits. -- **`placeholder(slot)`.** A sentinel the planner emits into the rendered `migration.ts` (from `@internal/errors/migration` on Mongo, or the `@internal/postgres/migration` import on Postgres) wherever a data transform is needed. Calling `placeholder(...)` at emit time throws `PN-MIG-2001` *Unfilled migration placeholder*. The user replaces the `() => placeholder(...)` arrow with a real query-plan closure (Postgres) or fills `dataTransform({ check, run })` sources (Mongo — see *Fill a placeholder*), then self-emits. -- **`this.dataTransform(endContract, name, { check, run })`.** The data-transform factory. `check` is a rowset query whose presence-of-any-row signals "work remains"; `run` is one or more mutation queries that perform the backfill. Both are lazy closures returning query-plans built against `endContract`. The runner wraps `check` as `EXISTS(...)` for precheck and `NOT EXISTS(...)` for postcheck, so the same closure asserts both "there is work" and "the work is done". +- **`migration.ts` shape.** Framework-rendered. A class extending `Migration` (imported from `@prisma/orm-postgres/migration` on Postgres, `@prisma/orm-mongo/target/migration` on Mongo — see the framing block below) that overrides `startContractJson` / `endContractJson` with the bookend snapshots and declares an `operations` getter. On Postgres each operation is a protected **method call on `this`** taking one options object (`this.addColumn({ schema, table, column: col('name', 'text') })`); on Mongo operations are free-factory calls. The file ends with `MigrationCLI.run(import.meta.url, M)` so executing it self-emits. +- **`placeholder(slot)`.** A sentinel the planner emits into the rendered `migration.ts` (imported from the rendered file's own migration import line — `@prisma/orm-postgres/migration` on Postgres, `@prisma/orm-mongo/target/migration` on Mongo) wherever a data transform is needed. Calling `placeholder(...)` at emit time throws `PN-MIG-2001` *Unfilled migration placeholder*. The user replaces the `() => placeholder(...)` arrow with a real query-plan closure (Postgres) or fills `dataTransform({ check, run })` sources (Mongo — see *Fill a placeholder*), then self-emits. +- **`this.dataTransform(endContract, name, { check, run })`.** The Postgres data-transform operation (an instance method, like the DDL ops). `check` is a rowset query whose presence-of-any-row signals "work remains"; `run` is one or more mutation queries that perform the backfill. Both are lazy closures returning query-plans built against `endContract`. The runner wraps `check` as `EXISTS(...)` for precheck and `NOT EXISTS(...)` for postcheck, so the same closure asserts both "there is work" and "the work is done". - **`pendingPlaceholders`.** A boolean field on the JSON result of `migration plan`. `true` means the package was written but contains unfilled placeholders — `db migrate` will throw `PN-MIG-2001` until you edit `migration.ts` and self-emit. - **`migrationHash`.** Content-addressed identity of a migration package. `MIGRATION.HASH_MISMATCH` fires when the stored hash in `migration.json` disagrees with the hash recomputed from the on-disk files (almost always: someone edited `migration.ts` without self-emitting). - **Marker.** Records "this database is at contract hash X for space Y". **Postgres:** a row in `prisma_contract.marker`. **Mongo:** a document in the `_prisma_migrations` collection (keyed by space). Each successful migration advances the marker once schema verification passes for that space. `db sign` writes the marker from the current contract hash, but only after a schema-verification pass succeeds (it will not sign a database whose live schema disagrees with the contract). @@ -52,15 +52,15 @@ Once the contract changes, you choose how the change reaches the database. This Files under `migrations///migration.ts` (for your own app, `` is always `app/`) are **rendered for you** by the framework — `prisma migration plan` writes a populated package whenever the contract changes, and `prisma migration new` writes an empty scaffold when you want to author operations directly. You do not write these files from scratch. You edit specific holes the framework leaves behind — chiefly replacing `placeholder("")` sentinels (Postgres) or filling `dataTransform({ check, run })` pipeline slots (Mongo) — then self-emit. -**Postgres** rendered imports point at `@internal/postgres/migration` (or `@internal/sqlite/migration` for SQLite projects). +**Postgres** rendered files carry one package import — `import { col, Migration, MigrationCLI } from '@prisma/orm-postgres/migration'` (`@prisma/orm-sqlite/migration` for SQLite projects), plus `placeholder` on that same line when the plan needs data transforms. The operations themselves are **methods on `this`**, so adding an op never changes the import; only helpers (`col`, `placeholder`, …) live on the import line. -**Mongo** rendered imports use `@internal/family-mongo/migration` for the `Migration` base class and `@internal/target-mongo/migration` for operation factories (`createIndex`, `dataTransform`, …). `MigrationCLI` comes from `@internal/cli/migration-cli`. +**Mongo** rendered files import `Migration`, `MigrationCLI`, and the operation factories (`createIndex`, `dataTransform`, `setValidation`, …) from the single `@prisma/orm-mongo/target/migration` line, plus query-plan shapes from `@prisma/orm-mongo/query-ast/execution` when a data transform is present. Treat the rendered import lines as framework-managed on both targets: -- Leave them where they are. Don't rewrite them to a different `@internal/<…>` path; the framework's renderer is the authoritative shape and any change you make by hand will be reverted (and may trip `MIGRATION.HASH_MISMATCH`) the next time the package is re-rendered or self-emitted. -- If you need an additional factory symbol, **add it to the existing rendered import line** (Postgres: `@internal/postgres/migration`; Mongo: `@internal/target-mongo/migration`) rather than introducing a second import from a different `@internal/...` subpath. -- The "user code imports only from `@internal/`" convention applies to *your* own modules (queries, runtime setup, contract authoring). The framework-rendered `migration.ts` scaffold is the framework's surface, not yours; the rule is suspended for that one file. +- Leave them where they are. Don't rewrite them to a different path; the framework's renderer is the authoritative shape and any change you make by hand will be reverted (and may trip `MIGRATION.HASH_MISMATCH`) the next time the package is re-rendered or self-emitted. +- If you need an additional helper or factory symbol, **add it to the existing rendered import line** (Postgres: `@prisma/orm-postgres/migration`; Mongo: `@prisma/orm-mongo/target/migration`) rather than introducing a second import from a different subpath. Postgres ops need no import at all — they are `this.(...)` calls. +- The "user code imports only the façade's user-facing subpaths" convention applies to *your* own modules (queries, runtime setup, contract authoring). The framework-rendered `migration.ts` scaffold is the framework's surface, not yours; leave its imports as rendered. ## Diagnostic codes you route on @@ -139,7 +139,7 @@ If the `db` ref's pointer is itself missing and the hash isn't a graph node eith `db` is a **default ref name**, not a reserved one. The framework overwrites it on the next dev cycle; you may `migration ref set db ` explicitly and accept that a subsequent `db update` replaces it when run against the default URL. -Canonical detail: [Migration System § Contract resolution through the snapshot store](../../docs/architecture%20docs/subsystems/7.%20Migration%20System.md#contract-resolution-through-the-snapshot-store), [§ `migration plan`](../../docs/architecture%20docs/subsystems/7.%20Migration%20System.md#migration-plan), [§ Recovery affordances](../../docs/architecture%20docs/subsystems/7.%20Migration%20System.md#recovery-affordances), [ADR 218 — Refs with paired contract snapshots and universal graph-node invariant](../../docs/architecture%20docs/adrs/ADR%20218%20-%20Refs%20with%20paired%20contract%20snapshots%20and%20universal%20graph-node%20invariant.md) (TML-2629, its paired-snapshot part superseded — see the ADR's Status note), and [ADR 240 — Contract snapshots live in a content-addressed store](../../docs/architecture%20docs/adrs/ADR%20240%20-%20Contract%20snapshots%20live%20in%20a%20content-addressed%20store.md). +Canonical detail: [Migration System § Contract resolution through the snapshot store](../../../docs/architecture%20docs/subsystems/7.%20Migration%20System.md#contract-resolution-through-the-snapshot-store), [§ `migration plan`](../../../docs/architecture%20docs/subsystems/7.%20Migration%20System.md#migration-plan), [§ Recovery affordances](../../../docs/architecture%20docs/subsystems/7.%20Migration%20System.md#recovery-affordances), [ADR 218 — Refs with paired contract snapshots and universal graph-node invariant](../../../docs/architecture%20docs/adrs/ADR%20218%20-%20Refs%20with%20paired%20contract%20snapshots%20and%20universal%20graph-node%20invariant.md) (TML-2629, its paired-snapshot part superseded — see the ADR's Status note), and [ADR 240 — Contract snapshots live in a content-addressed store](../../../docs/architecture%20docs/adrs/ADR%20240%20-%20Contract%20snapshots%20live%20in%20a%20content-addressed%20store.md). ## Workflow — `db update` (quick path) @@ -222,18 +222,19 @@ The scaffold the planner emits looks like: ```typescript // migrations/app/20260515T1200_add_user_name/migration.ts +import { col, Migration, MigrationCLI, placeholder } from '@prisma/orm-postgres/migration'; +import type { Contract as End } from '../../snapshots/93f07d1b…c9e1e5a2/contract'; import endContract from '../../snapshots/93f07d1b…c9e1e5a2/contract.json' with { type: 'json' }; -import { Migration, MigrationCLI, addColumn, placeholder } from '@internal/postgres/migration'; +import type { Contract as Start } from '../../snapshots/789dd79a…d94360a4/contract'; +import startContract from '../../snapshots/789dd79a…d94360a4/contract.json' with { type: 'json' }; + +export default class M extends Migration { + override readonly startContractJson = startContract; + override readonly endContractJson = endContract; -export default class M extends Migration { override get operations() { return [ - addColumn('public', 'user', { - name: 'name', - typeSql: 'text', - defaultSql: '', - nullable: true, - }), + this.addColumn({ schema: 'public', table: 'user', column: col('name', 'text') }), this.dataTransform(endContract, 'backfill user.name', { check: () => placeholder('backfill user.name:check'), run: () => placeholder('backfill user.name:run'), @@ -247,27 +248,28 @@ MigrationCLI.run(import.meta.url, M); Replace both `placeholder(...)` calls with query-plan closures built from `endContract`. The `check` closure must return a **rowset query whose presence of any row signals "work remains"** — conventionally `
.select('id').where().limit(1)`. Scalar/aggregate shapes (`count(*)`, `bool_and(...)`) silently break the contract: the runner wraps `check` twice (`EXISTS(...)` for precheck, `NOT EXISTS(...)` for postcheck), and a query that always returns one row makes `EXISTS` always true and `NOT EXISTS` always false. -Build the query builder against `endContract` so the storage hashes line up — using a different contract reference raises `PN-MIG-2005`. The filled-in shape (the rendered scaffold above with `placeholder(...)` calls replaced; if you need an extra factory like `setNotNull`, add it to the *existing* `@internal/postgres/migration` import line rather than authoring a second import). See `references/queries.md` for the surrounding `db` setup: +Build the query builder against `endContract` so the storage hashes line up — using a different contract reference raises `PN-MIG-2005`. The filled-in shape is the rendered scaffold above with the `placeholder(...)` calls replaced; extra operations like `setNotNull` are further `this.(...)` method calls, needing no import change. See `prisma-orm-core-concepts/references/queries.md` for the surrounding `db` setup: ```typescript +import { col, Migration, MigrationCLI } from '@prisma/orm-postgres/migration'; +import type { Contract as End } from '../../snapshots/93f07d1b…c9e1e5a2/contract'; import endContract from '../../snapshots/93f07d1b…c9e1e5a2/contract.json' with { type: 'json' }; -import { Migration, MigrationCLI, addColumn, setNotNull } from '@internal/postgres/migration'; +import type { Contract as Start } from '../../snapshots/789dd79a…d94360a4/contract'; +import startContract from '../../snapshots/789dd79a…d94360a4/contract.json' with { type: 'json' }; import { db } from './db'; // sql({ context: createExecutionContext({ contract: endContract, ... }) }) -export default class M extends Migration { +export default class M extends Migration { + override readonly startContractJson = startContract; + override readonly endContractJson = endContract; + override get operations() { return [ - addColumn('public', 'user', { - name: 'name', - typeSql: 'text', - defaultSql: '', - nullable: true, - }), + this.addColumn({ schema: 'public', table: 'user', column: col('name', 'text') }), this.dataTransform(endContract, 'backfill user.name', { check: () => db.users.select('id').where((f, fns) => fns.eq(f.name, null)).limit(1), run: () => db.users.update({ name: '' }).where((f, fns) => fns.eq(f.name, null)), }), - setNotNull('public', 'user', 'name'), + this.setNotNull({ schema: 'public', table: 'user', column: 'name' }), ]; } } @@ -285,48 +287,61 @@ Self-emit regenerates `ops.json` and recomputes `migrationHash` in `migration.js ### Mongo -Mongo `dataTransform` operations take `{ check, run }` objects whose `source` / `run` return Mongo query-plan shapes (often `RawAggregateCommand` / `RawUpdateManyCommand` from `@internal/mongo-query-ast/execution`). The planner may leave `placeholder(...)` inside those sources until you fill them. Every rendered `migration.ts` includes `describe()` bookends (`from` / `to` contract hashes) — the Postgres examples above omit them for brevity. Import factories from `@internal/target-mongo/migration`: +Mongo `dataTransform` stays a free factory: `dataTransform('name', { check: { source: () => plan }, run: () => plan })`, where each plan is a `MongoQueryPlan` built from the shapes in `@prisma/orm-mongo/query-ast/execution` (typed `AggregateCommand` pipelines with stage/expr classes, or raw commands like `RawUpdateManyCommand`). The planner may leave `placeholder(...)` inside those sources until you fill them. The class shape is the same `Migration` + bookend overrides as Postgres; the model below follows `examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.ts`: ```typescript -import { MigrationCLI } from '@internal/cli/migration-cli'; -import { Migration } from '@internal/family-mongo/migration'; -import { createIndex, dataTransform } from '@internal/target-mongo/migration'; -import { RawAggregateCommand, RawUpdateManyCommand } from '@internal/mongo-query-ast/execution'; - -class M extends Migration { - override describe() { - return { from: '', to: '', labels: ['normalize-names'] }; - } +import { + AggregateCommand, + MongoExistsExpr, + MongoLimitStage, + MongoMatchStage, + type MongoQueryPlan, + RawUpdateManyCommand, +} from '@prisma/orm-mongo/query-ast/execution'; +import { dataTransform, Migration, MigrationCLI } from '@prisma/orm-mongo/target/migration'; +import type { Contract as Start } from '../../snapshots//contract'; +import startContract from '../../snapshots//contract.json' with { type: 'json' }; +import type { Contract as End } from '../../snapshots//contract'; +import endContract from '../../snapshots//contract.json' with { type: 'json' }; + +function productsWithoutStatus(storageHash: string): MongoQueryPlan { + return { + collection: 'products', + command: new AggregateCommand('products', [ + new MongoMatchStage(new MongoExistsExpr('status', false)), + new MongoLimitStage(1), + ]), + meta: { target: 'mongo', storageHash, lane: 'mongo-pipeline' }, + }; +} + +function backfillRun(storageHash: string): MongoQueryPlan { + return { + collection: 'products', + command: new RawUpdateManyCommand( + 'products', + { status: { $exists: false } }, + { $set: { status: 'active' } }, + ), + meta: { target: 'mongo', storageHash, lane: 'mongo-raw' }, + }; +} + +export default class M extends Migration { + override readonly startContractJson = startContract; + override readonly endContractJson = endContract; override get operations() { + const storageHash = this.endContract.storage.storageHash; return [ - createIndex('users', [{ field: 'name', direction: 1 }]), - dataTransform('lowercase-user-name', { - check: { - source: () => ({ - collection: 'users', - command: new RawAggregateCommand('users', [ - { $match: { name: { $regex: '[A-Z]' } } }, - { $limit: 1 }, - ]), - meta: { target: 'mongo', storageHash: '…', lane: 'mongo-pipeline', paramDescriptors: [] }, - }), - }, - run: () => ({ - collection: 'users', - command: new RawUpdateManyCommand( - 'users', - { name: { $exists: true } }, - [{ $set: { name: { $toLower: '$name' } } }], - ), - meta: { target: 'mongo', storageHash: '…', lane: 'mongo-raw', paramDescriptors: [] }, - }), + dataTransform('backfill-product-status', { + check: { source: () => productsWithoutStatus(storageHash) }, + run: () => backfillRun(storageHash), }), ]; } } -export default M; MigrationCLI.run(import.meta.url, M); ``` @@ -340,20 +355,20 @@ The concept: the same `Migration` class shape lets you author operations directl pnpm prisma migration new --name ``` -Add factory names to the framework-rendered import line for your target (Postgres: `@internal/postgres/migration`; Mongo: `@internal/target-mongo/migration`). Browse with `--help` and the import list the renderer emitted. +On Postgres the operations are **protected methods on `this`** — each takes a single options object, and calling one needs no import; helpers like `col(...)` come from the rendered `@prisma/orm-postgres/migration` import line. On Mongo the operations are free factories — add their names to the rendered `@prisma/orm-mongo/target/migration` import line. -**Postgres** factories (representative set): +**Postgres** operation methods (each `this.({ ... })` with one options object): -- Tables: `createTable`, `dropTable`. -- Columns: `addColumn`, `dropColumn`, `alterColumnType`, `setNotNull`, `dropNotNull`, `setDefault`, `dropDefault`. -- Constraints: `addPrimaryKey`, `addForeignKey`, `addUnique`, `dropConstraint`. -- Indexes: `createIndex`, `dropIndex`. -- Enums: `createEnumType`, `addEnumValues`, `renameType`, `dropEnumType`. -- Dependencies: `createSchema`, `createExtension`, `installExtension`. -- Raw escape hatch: `rawSql({ id, label, operationClass, target, precheck, execute, postcheck, ... })`. -- Data transforms: `this.dataTransform(endContract, name, { check, run })` (instance method, not a free factory). +- Tables / schemas: `createTable`, `dropTable`, `createSchema`. +- Columns: `addColumn` (takes `column: col('name', 'text', { notNull?, default? })`), `dropColumn`, `alterColumnType`, `setNotNull`, `dropNotNull`, `setDefault`, `dropDefault`. +- Constraints: `addPrimaryKey`, `addForeignKey`, `addUnique`, `addCheckConstraint`, `renameCheckConstraint`, `dropCheckConstraint`, `dropConstraint`. +- Indexes: `createIndex` (columns or expression form), `renameIndex`, `dropIndex`. +- Native enums: `createNativeEnumType`, `dropNativeEnumType`, `addNativeEnumValue`. +- Extensions / RLS: `installExtension`, `enableRowLevelSecurity`, `disableRowLevelSecurity`, `createRlsPolicy`, `dropRlsPolicy`, `renameRlsPolicy`. +- Data transforms: `this.dataTransform(endContract, name, { check, run })`. +- Raw escape hatch: `rawSql(op)` — a free export of `@prisma/orm-postgres/migration`; pass a fully-materialized op object (`{ id, label, operationClass, target, precheck, execute, postcheck }`) for work the structured methods don't cover. -**Mongo** factories (from `@internal/target-mongo/migration`): +**Mongo** factories (from `@prisma/orm-mongo/target/migration`): - Collections: `createCollection`, `dropCollection`, `validatedCollection`, `setValidation`. - Indexes: `createIndex`, `dropIndex`. @@ -408,7 +423,7 @@ pnpm prisma db sign --db $DATABASE_URL The concept: drift means `db verify` reports the live DB schema doesn't match what the marker says it should be. Two valid moves, picked by which side is correct: - **The contract is right; the DB is wrong** → run a migration. Either `db update` (quick path, dev DB only) or `migration plan` + `db migrate` (everywhere else). -- **The DB is right; the contract or marker is wrong** → edit the contract to match the DB (see `references/contract.md`), emit, then `db sign` to refresh the marker. +- **The DB is right; the contract or marker is wrong** → edit the contract to match the DB (see `prisma-orm-core-concepts/references/contract.md`), emit, then `db sign` to refresh the marker. The diagnostic that reveals which side is right: @@ -478,16 +493,16 @@ In non-interactive contexts (CI, `--no-interactive`, `--json`), the destructive- 5. **Routine `db verify` after a successful `db update` or `db migrate`.** Redundant on the happy path — reserve `db verify` for drift diagnosis (manual edits, restore, failed `db migrate`). 6. **Aggregate `check` closure in Postgres `this.dataTransform`.** Returning `count(*)` or `bool_and(...)` breaks the precheck/postcheck contract — both sides resolve to constants. Use a rowset shape: `select('id').where().limit(1)`. 7. **Two contract references in one migration.** Building a query plan against a different contract than the one passed to `this.dataTransform(endContract, ...)` raises `PN-MIG-2005`. Always import `endContract` once at module scope and use the same reference. -8. **Renaming and expecting the planner to detect it (Postgres).** Prisma Next has no in-contract rename hint today; the planner emits a destructive drop+add. Hand-edit `migration.ts` to rewrite the destructive op as a `rawSql({ ... })` that issues `ALTER TABLE ... RENAME COLUMN ...` (or use the two-migration keep / backfill / drop pattern), then self-emit. See `references/contract.md` § *Edit a field — rename*. +8. **Renaming and expecting the planner to detect it (Postgres).** Prisma Next has no in-contract rename hint today; the planner emits a destructive drop+add. Hand-edit `migration.ts` to rewrite the destructive op as a raw op — `rawSql(...)` over a full op object whose `execute` steps issue `ALTER TABLE ... RENAME COLUMN ...` (or use the two-migration keep / backfill / drop pattern) — then self-emit. See `prisma-orm-core-concepts/references/contract.md` § *Edit a field — rename*. 9. **Planning with no `db` ref and no `--from` in a project that already has migrations.** The origin falls through to the empty database, which would make the plan a full-create migration; `migration plan` refuses with `MIGRATION.PLAN_ORIGIN_UNKNOWN` rather than writing it. Pick the exit that matches your intent — the error lists them, and `references/migration-model.md` § *The trap* explains which to choose. -10. **Hand-authoring `migration.ts` from a blank file, or rewriting the rendered import line.** Migration files are framework-rendered — let `prisma migration plan` (or `migration new`) render the package, then edit only the holes the framework leaves for you. On Postgres leave the rendered `@internal/postgres/migration` (or `@internal/sqlite/migration`) import path alone; on Mongo use `@internal/family-mongo/migration` + `@internal/target-mongo/migration` as rendered. Add symbols to the existing factory import line rather than introducing new import paths. +10. **Hand-authoring `migration.ts` from a blank file, or rewriting the rendered import line.** Migration files are framework-rendered — let `prisma migration plan` (or `migration new`) render the package, then edit only the holes the framework leaves for you. On Postgres leave the rendered `@prisma/orm-postgres/migration` (or `@prisma/orm-sqlite/migration`) import path alone — ops are `this.(...)` methods and need no import; on Mongo keep the rendered `@prisma/orm-mongo/target/migration` line and add factory names to it rather than introducing new import paths. ## What Prisma Next doesn't do yet -- **Runtime-apply migrations.** Prisma Next doesn't apply pending migrations from your app's startup code (the "Drizzle pattern" for serverless / edge). Workaround: run `prisma db migrate` from your deploy pipeline before the app starts. If you need runtime-apply built-in, file a feature request via the `references/feedback.md` skill. -- **Seeds-as-first-class.** Prisma Next doesn't ship a `prisma db seed` equivalent. Workaround: write a TypeScript script that imports your `db` instance and runs your setup queries; invoke it from `package.json`'s scripts. If you need first-class seeding, file a feature request via the `references/feedback.md` skill. -- **Migration squashing.** Prisma Next doesn't squash older migrations into a baseline. They accumulate; for very large histories, manual baseline-and-truncate is the path. If you need built-in squashing, file a feature request via the `references/feedback.md` skill. -- **In-contract rename hints.** The planner cannot detect that a field rename is a rename rather than a drop+add. Workaround: hand-edit `migration.ts` to issue a `RENAME COLUMN` via `rawSql(...)`, or use a keep / backfill / drop pattern across two migrations. If you need a contract-level rename hint, file a feature request via the `references/feedback.md` skill. +- **Runtime-apply migrations.** Prisma Next doesn't apply pending migrations from your app's startup code (the "Drizzle pattern" for serverless / edge). Workaround: run `prisma db migrate` from your deploy pipeline before the app starts. If you need runtime-apply built-in, file a feature request via the `prisma-orm-core-concepts/references/feedback.md` skill. +- **Seeds-as-first-class.** Prisma Next doesn't ship a `prisma db seed` equivalent. Workaround: write a TypeScript script that imports your `db` instance and runs your setup queries; invoke it from `package.json`'s scripts. If you need first-class seeding, file a feature request via the `prisma-orm-core-concepts/references/feedback.md` skill. +- **Migration squashing.** Prisma Next doesn't squash older migrations into a baseline. They accumulate; for very large histories, manual baseline-and-truncate is the path. If you need built-in squashing, file a feature request via the `prisma-orm-core-concepts/references/feedback.md` skill. +- **In-contract rename hints.** The planner cannot detect that a field rename is a rename rather than a drop+add. Workaround: hand-edit `migration.ts` to issue a `RENAME COLUMN` via `rawSql(...)`, or use a keep / backfill / drop pattern across two migrations. If you need a contract-level rename hint, file a feature request via the `prisma-orm-core-concepts/references/feedback.md` skill. ## Graph and history commands @@ -500,11 +515,11 @@ For the full graph topology: `pnpm prisma migration graph` (also supports `--leg ## `@@control` and DDL scope -Objects whose `@@control` policy excludes them from Prisma Next's managed surface are omitted from planned DDL. The four policies are: `managed` (Prisma plans and applies DDL), `tolerated` (object may exist, no DDL emitted), `external` (object is expected to exist, no DDL), `observed` (Prisma reads but never writes). Declare `@@control(managed|tolerated|external|observed)` in your schema; see `references/contract.md` and [`packages/2-sql/2-authoring/contract-psl/README.md`](../../packages/2-sql/2-authoring/contract-psl/README.md) for authoring syntax. +Objects whose `@@control` policy excludes them from Prisma Next's managed surface are omitted from planned DDL. The four policies are: `managed` (Prisma plans and applies DDL), `tolerated` (object may exist, no DDL emitted), `external` (object is expected to exist, no DDL), `observed` (Prisma reads but never writes). Declare `@@control(managed|tolerated|external|observed)` in your schema; see `prisma-orm-core-concepts/references/contract.md` and [`packages/2-sql/2-authoring/contract-psl/README.md`](../../../packages/2-sql/2-authoring/contract-psl/README.md) for authoring syntax. ## Telemetry -The CLI collects anonymous usage data by default. To opt out, set `PRISMA_NEXT_DISABLE_TELEMETRY=1` or `DO_NOT_TRACK=1` in your environment. See [`docs/Telemetry.md`](../../docs/Telemetry.md) for the full opt-out reference. +The CLI collects anonymous usage data by default. To opt out, set `PRISMA_NEXT_DISABLE_TELEMETRY=1` or `DO_NOT_TRACK=1` in your environment. See [`docs/Telemetry.md`](../../../docs/Telemetry.md) for the full opt-out reference. ## Checklist diff --git a/test/integration/test/cli.init-skill-distribution.integration.test.ts b/test/integration/test/cli.init-skill-distribution.integration.test.ts index 21a01a0f42bd..aa1c2bdc6abe 100644 --- a/test/integration/test/cli.init-skill-distribution.integration.test.ts +++ b/test/integration/test/cli.init-skill-distribution.integration.test.ts @@ -104,16 +104,20 @@ describe('init skill distribution (offline integration, real CLI)', () => { testDirs.add(testDir); expect(manifestOf(testDir).scripts?.['postinstall']).toBeUndefined(); - expect(gitignoreOf(testDir)).not.toContain('skills/prisma-8/'); + expect(gitignoreOf(testDir)).not.toContain('skills/prisma-'); }); - it('removes skill directories the router replaced', { timeout: 60_000 }, async () => { + it('removes retired skill directories', { timeout: 60_000 }, async () => { const testDir = createIntegrationTestDir(); testDirs.add(testDir); writeFileSync(join(testDir, 'pnpm-lock.yaml'), '', 'utf8'); - const retired = join(testDir, '.agents', 'skills', 'prisma-next-upgrade'); - mkdirSync(retired, { recursive: true }); - writeFileSync(join(retired, 'SKILL.md'), '---\nname: prisma-next-upgrade\n---\n', 'utf8'); + const retiredNames = ['prisma-next-upgrade', 'prisma-8']; + const retiredDirs = retiredNames.map((name) => { + const dir = join(testDir, '.agents', 'skills', name); + mkdirSync(dir, { recursive: true }); + writeFileSync(join(dir, 'SKILL.md'), `---\nname: ${name}\n---\n`, 'utf8'); + return dir; + }); const { fakeBinDir, logPath } = createFakeManagerHarness(testDir); const { exitCode, stderr } = runEngineInit(testDir, { @@ -122,7 +126,9 @@ describe('init skill distribution (offline integration, real CLI)', () => { }); expect(exitCode, stderr).toBe(0); - expect(existsSync(retired)).toBe(false); + for (const dir of retiredDirs) { + expect(existsSync(dir)).toBe(false); + } }); });