From 98fcc3c4172b8bdd189f9b8b02280f5af6e56380 Mon Sep 17 00:00:00 2001 From: willbot Date: Wed, 26 Aug 2026 13:05:38 +0200 Subject: [PATCH 1/3] refactor: rename Prisma Next to Prisma ORM across the repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upstream product shipped as Prisma ORM (v8) and the source repo moved to prisma/orm, so the pre-release naming is retired everywhere: - Prose: "Prisma Next" -> "Prisma ORM" in README, guides, design docs, ADRs, working notes; repo links point at prisma/orm. - Public API: the ORM-managed database is now the default name — the `./prisma-next` entrypoint becomes `./orm` exporting `postgres()` (formerly `pnPostgres`) and `dataContract()` (formerly `pnContract`); the unmanaged factory in the main entry becomes `rawPostgres()` with `rawPostgresContract`. - Node kinds follow the factories: `prisma-next` -> `postgres`, plain `postgres` -> `raw-postgres` (neither persists across deploys). - Alchemy resource type `PrismaNext.Migration` -> `PrismaOrm.Migration`, with an on-read state rewrite (legacy-resources.ts) so existing stacks adopt the new type without churn. - Files/examples: `pn-*`/`prisma-next*` sources renamed to `orm-*`; `prisma-next.config.ts` -> `prisma.config.ts` (upstream convention); `examples/pn-widgets` -> `examples/orm-demo`. - The `// use prisma-next` PSL pragma, emitted contract headers, and `@prisma-next/*` npm names in historical incident notes are upstream-owned or factual and stay as-is. Signed-off-by: willbot Signed-off-by: Will Madden --- .drive/deferred.md | 11 +- .drive/next-session-brief.md | 2 +- .../alchemy-provider-adoption/plan.md | 2 +- .../alchemy-provider-adoption/spec.md | 2 +- .drive/projects/auth-module/design-notes.md | 14 +- .drive/projects/auth-module/plan.md | 16 +- .../slices/adr-0041-migration/spec.md | 8 +- .../slices/auth-module-core/plan.md | 10 +- .drive/projects/auth-module/spec.md | 104 ++++----- .drive/projects/email-module/spec.md | 4 +- .../exports-entrypoint/investigation.md | 16 +- .../projects/exports-entrypoint/learnings.md | 8 +- .../slices/exports-entrypoint/d4a-brief.md | 10 +- .../slices/exports-entrypoint/d5-brief.md | 6 +- .../slices/exports-entrypoint/d8-brief.md | 2 +- .../slices/exports-entrypoint/spec.md | 24 +-- .../slices/thin-exports/spec.md | 6 +- .../forcing-function-apps/design-notes.md | 2 +- .drive/projects/forcing-function-apps/plan.md | 4 +- .../slices/datahub-port/spec.md | 12 +- .../streams-binding-design.md | 6 +- .gitattributes | 4 +- .github/CODEOWNERS | 2 +- .../actions/deploy-verify-destroy/action.yml | 2 +- .github/workflows/dco.yml | 2 +- .github/workflows/e2e-deploy.yml | 22 +- .gitignore | 6 +- CLAUDE.md | 27 ++- README.md | 6 +- agent-os/product/mission.md | 2 +- agent-os/product/naming-proposal.md | 10 +- agent-os/product/naming.md | 4 +- agent-os/product/prisma-data-rename.md | 6 +- agent-os/product/tech-stack.md | 12 +- architecture.config.json | 14 +- biome.jsonc | 6 +- dependency-cruiser.config.mjs | 4 +- docs/design/00-purpose/README.md | 2 +- docs/design/00-purpose/goals.md | 2 +- .../01-principles/guiding-principles.md | 2 +- .../03-domain-model/authoring-surface.md | 12 +- .../03-domain-model/core-and-targets.md | 6 +- docs/design/03-domain-model/domain-map.md | 2 +- docs/design/03-domain-model/glossary.md | 12 +- docs/design/03-domain-model/layering.md | 4 +- docs/design/10-domains/core-model.md | 42 ++-- docs/design/10-domains/local-dev.md | 8 +- ...y-derives-everything-from-the-root-node.md | 4 +- .../ADR-0006-every-node-is-named.md | 2 +- ...012-the-state-store-speaks-sql-directly.md | 14 +- ...sioned-by-modules-deps-are-declarations.md | 10 +- ...esolve-to-bindings-clients-are-app-side.md | 18 +- ...trol-plane-loads-through-the-app-config.md | 4 +- ...-data-deps-carry-a-prisma-orm-contract.md} | 56 ++--- ...-app-is-one-project-a-stage-is-a-branch.md | 2 +- ...ckages-compose-and-compose-prisma-cloud.md | 2 +- ...d-layers-enforced-by-dependency-cruiser.md | 12 +- ...-public-entrypoints-live-in-src-exports.md | 6 +- ...ding-carries-the-url-and-a-lazy-client.md} | 26 +-- ...deploy-pipeline-against-local-providers.md | 2 +- ...046-the-orm-facade-is-a-peer-dependency.md | 22 +- ...come-from-the-upstream-alchemy-provider.md | 2 +- docs/design/90-decisions/README.md | 10 +- docs/guides/building-an-app.md | 30 +-- docs/guides/deploying.md | 2 +- docs/guides/getting-started.md | 6 +- examples/auth/module.ts | 6 +- .../{prisma-next.config.ts => orm.config.ts} | 8 +- examples/auth/src/contract.ts | 8 +- examples/auth/tests/pg-harness.ts | 4 +- examples/auth/tsconfig.json | 2 +- .../{pn-widgets => orm-demo}/contract.d.ts | 0 .../{pn-widgets => orm-demo}/contract.json | 0 .../{pn-widgets => orm-demo}/contract.prisma | 0 .../app/20260711T1921_init/migration.json | 0 .../app/20260711T1921_init/migration.ts | 0 .../app/20260711T1921_init/ops.json | 0 .../contract.d.ts | 0 .../contract.json | 0 examples/{pn-widgets => orm-demo}/module.ts | 10 +- .../orm.config.ts} | 10 +- .../{pn-widgets => orm-demo}/package.json | 6 +- .../prisma-composer.config.ts | 0 .../scripts/e2e-verify.ts | 10 +- .../{pn-widgets => orm-demo}/src/contract.ts | 10 +- .../{pn-widgets => orm-demo}/src/server.ts | 4 +- .../{pn-widgets => orm-demo}/src/service.ts | 10 +- .../{pn-widgets => orm-demo}/tsconfig.json | 0 examples/{pn-widgets => orm-demo}/turbo.json | 0 examples/store/DEMO.md | 6 +- examples/store/README.md | 2 +- .../orm.config.ts} | 4 +- examples/store/modules/catalog/src/data.ts | 14 +- examples/store/modules/catalog/src/module.ts | 8 +- examples/store/modules/catalog/src/server.ts | 2 +- examples/store/modules/catalog/src/service.ts | 6 +- .../orm.config.ts} | 4 +- examples/store/modules/orders/src/data.ts | 10 +- examples/store/modules/orders/src/module.ts | 8 +- examples/store/modules/orders/src/server.ts | 2 +- examples/store/modules/orders/src/service.ts | 4 +- .../modules/auth/src/module.ts | 4 +- .../modules/auth/src/server.ts | 2 +- .../modules/auth/src/service.ts | 6 +- gotchas.md | 16 +- open-chat-port-friction.md | 82 +++---- .../foundation/src/internal-error.ts | 2 +- .../0-foundation/tsdown-config/base.ts | 6 +- .../fixtures/probe-core-authoring.ts | 2 +- .../1-core/core/src/__tests__/helpers.ts | 2 +- .../3-tooling/cli/src/load-config.ts | 2 +- .../0-lowering/local-target/src/postgres.ts | 2 +- .../state/__tests__/legacy-resources.test.ts | 16 ++ .../lowering/src/state/legacy-resources.ts | 22 ++ .../1-extensions/target/package.json | 2 +- .../target/src/__tests__/control-env.test.ts | 2 +- .../src/__tests__/control-lowering.test.ts | 71 +++--- .../target/src/__tests__/extension.test.ts | 58 ++--- ...prisma-next.config.ts => prisma.config.ts} | 0 .../fixtures/packed-contract/pack.ts | 2 +- .../packed-contract/source/contract.ts | 2 +- ...prisma-next.config.ts => prisma.config.ts} | 2 +- ...s.config.ts => prisma.spaceless.config.ts} | 2 +- .../src/__tests__/fixtures/probe-authoring.ts | 6 +- .../__tests__/fixtures/side-effect-service.ts | 8 +- .../widget-contract/source/contract.ts | 2 +- ...prisma-next.config.ts => prisma.config.ts} | 0 .../target/src/__tests__/invariants.test.ts | 33 ++- .../{pn-config.test.ts => orm-config.test.ts} | 26 +-- ...> orm-extension-packs.integration.test.ts} | 14 +- ...est.ts => orm-migrate.integration.test.ts} | 203 +++++++++--------- ...test.ts => orm-migration-resource.test.ts} | 46 ++-- ...est-d.ts => orm-postgres-shapes.test-d.ts} | 58 ++--- ...st.ts => orm-postgres.integration.test.ts} | 18 +- ...isma-next.test.ts => orm-postgres.test.ts} | 153 +++++++------ ...get-ref.test.ts => orm-target-ref.test.ts} | 12 +- .../src/__tests__/pack-preflight.test.ts | 34 +-- .../target/src/__tests__/postgres-harness.ts | 4 +- .../src/__tests__/postgres-shapes.test-d.ts | 34 --- .../__tests__/raw-postgres-shapes.test-d.ts | 34 +++ .../__tests__/widget-migrations-fixture.ts | 2 +- .../1-extensions/target/src/bucket.ts | 2 +- .../target/src/control/extension.ts | 10 +- .../{prisma-next.ts => orm-postgres.ts} | 26 +-- .../{postgres.ts => raw-postgres.ts} | 4 +- .../1-extensions/target/src/exports/index.ts | 4 +- .../1-extensions/target/src/exports/orm.ts | 2 + .../target/src/exports/prisma-next.ts | 2 - .../target/src/local-target/descriptor.ts | 4 +- .../target/src/local-target/emulators.ts | 6 +- .../src/{pn-config.ts => orm-config.ts} | 16 +- ...{prisma-next-migrate.ts => orm-migrate.ts} | 52 ++--- ...-resource.ts => orm-migration-resource.ts} | 52 ++--- .../src/{prisma-next.ts => orm-postgres.ts} | 107 +++++---- .../1-extensions/target/src/pg-connection.ts | 2 +- .../target/src/pg-warm-resource.ts | 6 +- .../1-extensions/target/src/preflight.ts | 22 +- .../src/{postgres.ts => raw-postgres.ts} | 34 +-- .../target/src/required-pack-head.ts | 26 ++- .../1-extensions/target/src/s3-credentials.ts | 2 +- .../1-extensions/target/tsdown.config.ts | 2 +- .../2-shared-modules/auth/README.md | 8 +- .../2-shared-modules/auth/package.json | 2 +- .../auth/src/__tests__/contract.test-d.ts | 18 +- .../auth/src/__tests__/contract.test.ts | 2 +- .../auth/src/__tests__/module.test.ts | 10 +- .../auth/src/__tests__/postgres-harness.ts | 4 +- .../2-shared-modules/auth/src/contract.ts | 8 +- .../src/execution/empty-app-contract.json | 4 +- .../auth/src/execution/local-schema.ts | 6 +- .../auth/src/pack/constants.ts | 2 +- .../2-shared-modules/auth/src/pack/index.ts | 2 +- .../2-shared-modules/auth/tsdown.config.ts | 2 +- .../email/src/__tests__/module.test-d.ts | 4 +- .../email/src/__tests__/module.test.ts | 2 +- .../email/src/email-module.ts | 4 +- .../email/src/email-service.ts | 4 +- .../storage/src/__tests__/module.test-d.ts | 4 +- .../storage/src/__tests__/module.test.ts | 2 +- .../storage/src/__tests__/pg-harness.ts | 2 +- .../2-shared-modules/storage/src/contract.ts | 2 +- .../storage/src/storage-module.ts | 4 +- .../storage/src/storage-service.ts | 4 +- .../2-shared-modules/streams/README.md | 2 +- .../streams/src/__tests__/pg-harness.ts | 2 +- .../2-shared-modules/streams/src/contract.ts | 4 +- .../composer-prisma-cloud/package.json | 4 +- .../composer-prisma-cloud/src/exports/orm.ts | 1 + .../src/exports/prisma-next.ts | 1 - .../composer-prisma-cloud/tsdown.config.ts | 2 +- pnpm-lock.yaml | 2 +- scripts/ci-cleanup-utils.test.ts | 20 +- scripts/lint-casts.mjs | 2 +- scripts/lint-framework-vocabulary.mjs | 2 +- scripts/lint-orm-pins.mjs | 4 +- scripts/setup-worktree-refs.sh | 4 +- skills/prisma-composer/SKILL.md | 24 +-- .../fixtures/local-dev/built/web-server.mjs | 4 +- .../test/fixtures/local-dev/module.ts | 4 +- .../test/fixtures/local-dev/web-service.ts | 4 +- .../local-dev-criteria-4-5.integration.ts | 2 +- .../test/local-dev-store.integration.ts | 4 +- tsconfig.depcruise.json | 8 +- 203 files changed, 1260 insertions(+), 1191 deletions(-) mode change 120000 => 100644 CLAUDE.md rename docs/design/90-decisions/{ADR-0022-data-deps-carry-a-prisma-next-contract.md => ADR-0022-data-deps-carry-a-prisma-orm-contract.md} (83%) rename docs/design/90-decisions/{ADR-0040-the-pn-binding-carries-the-url-and-a-lazy-client.md => ADR-0040-the-orm-binding-carries-the-url-and-a-lazy-client.md} (82%) rename examples/auth/{prisma-next.config.ts => orm.config.ts} (70%) rename examples/{pn-widgets => orm-demo}/contract.d.ts (100%) rename examples/{pn-widgets => orm-demo}/contract.json (100%) rename examples/{pn-widgets => orm-demo}/contract.prisma (100%) rename examples/{pn-widgets => orm-demo}/migrations/app/20260711T1921_init/migration.json (100%) rename examples/{pn-widgets => orm-demo}/migrations/app/20260711T1921_init/migration.ts (100%) rename examples/{pn-widgets => orm-demo}/migrations/app/20260711T1921_init/ops.json (100%) rename examples/{pn-widgets => orm-demo}/migrations/snapshots/2c0c3445737906d85590514e3cec242919c12f8112346c2cebe74e681d65f0f0/contract.d.ts (100%) rename examples/{pn-widgets => orm-demo}/migrations/snapshots/2c0c3445737906d85590514e3cec242919c12f8112346c2cebe74e681d65f0f0/contract.json (100%) rename examples/{pn-widgets => orm-demo}/module.ts (65%) rename examples/{pn-widgets/prisma-next.config.ts => orm-demo/orm.config.ts} (53%) rename examples/{pn-widgets => orm-demo}/package.json (75%) rename examples/{pn-widgets => orm-demo}/prisma-composer.config.ts (100%) rename examples/{pn-widgets => orm-demo}/scripts/e2e-verify.ts (89%) rename examples/{pn-widgets => orm-demo}/src/contract.ts (52%) rename examples/{pn-widgets => orm-demo}/src/server.ts (93%) rename examples/{pn-widgets => orm-demo}/src/service.ts (66%) rename examples/{pn-widgets => orm-demo}/tsconfig.json (100%) rename examples/{pn-widgets => orm-demo}/turbo.json (100%) rename examples/store/modules/{orders/prisma-next.config.ts => catalog/orm.config.ts} (85%) rename examples/store/modules/{catalog/prisma-next.config.ts => orders/orm.config.ts} (85%) rename packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/gadget-contract/source/{prisma-next.config.ts => prisma.config.ts} (100%) rename packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/{prisma-next.config.ts => prisma.config.ts} (96%) rename packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/{prisma-next.spaceless.config.ts => prisma.spaceless.config.ts} (87%) rename packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/widget-contract/source/{prisma-next.config.ts => prisma.config.ts} (100%) rename packages/1-prisma-cloud/1-extensions/target/src/__tests__/{pn-config.test.ts => orm-config.test.ts} (82%) rename packages/1-prisma-cloud/1-extensions/target/src/__tests__/{pn-extension-packs.integration.test.ts => orm-extension-packs.integration.test.ts} (95%) rename packages/1-prisma-cloud/1-extensions/target/src/__tests__/{prisma-next-migrate.integration.test.ts => orm-migrate.integration.test.ts} (59%) rename packages/1-prisma-cloud/1-extensions/target/src/__tests__/{pn-migration-resource.test.ts => orm-migration-resource.test.ts} (83%) rename packages/1-prisma-cloud/1-extensions/target/src/__tests__/{prisma-next-shapes.test-d.ts => orm-postgres-shapes.test-d.ts} (63%) rename packages/1-prisma-cloud/1-extensions/target/src/__tests__/{prisma-next.integration.test.ts => orm-postgres.integration.test.ts} (86%) rename packages/1-prisma-cloud/1-extensions/target/src/__tests__/{prisma-next.test.ts => orm-postgres.test.ts} (64%) rename packages/1-prisma-cloud/1-extensions/target/src/__tests__/{pn-target-ref.test.ts => orm-target-ref.test.ts} (93%) delete mode 100644 packages/1-prisma-cloud/1-extensions/target/src/__tests__/postgres-shapes.test-d.ts create mode 100644 packages/1-prisma-cloud/1-extensions/target/src/__tests__/raw-postgres-shapes.test-d.ts rename packages/1-prisma-cloud/1-extensions/target/src/descriptors/{prisma-next.ts => orm-postgres.ts} (72%) rename packages/1-prisma-cloud/1-extensions/target/src/descriptors/{postgres.ts => raw-postgres.ts} (86%) create mode 100644 packages/1-prisma-cloud/1-extensions/target/src/exports/orm.ts delete mode 100644 packages/1-prisma-cloud/1-extensions/target/src/exports/prisma-next.ts rename packages/1-prisma-cloud/1-extensions/target/src/{pn-config.ts => orm-config.ts} (89%) rename packages/1-prisma-cloud/1-extensions/target/src/{prisma-next-migrate.ts => orm-migrate.ts} (89%) rename packages/1-prisma-cloud/1-extensions/target/src/{pn-migration-resource.ts => orm-migration-resource.ts} (70%) rename packages/1-prisma-cloud/1-extensions/target/src/{prisma-next.ts => orm-postgres.ts} (66%) rename packages/1-prisma-cloud/1-extensions/target/src/{postgres.ts => raw-postgres.ts} (55%) create mode 100644 packages/9-public/composer-prisma-cloud/src/exports/orm.ts delete mode 100644 packages/9-public/composer-prisma-cloud/src/exports/prisma-next.ts diff --git a/.drive/deferred.md b/.drive/deferred.md index c24861f2f..0c2df0419 100644 --- a/.drive/deferred.md +++ b/.drive/deferred.md @@ -146,20 +146,21 @@ registry). What we deliberately didn't do: Design the mock-bindings slice against that distinction rather than relying on it accidentally. -# Prisma Next data contract — deferred at project close (2026-07-12) +# Prisma ORM data contract — deferred at project close (2026-07-12) - **Multi-contract / contract-space extension** — full design preserved in ADR-0022 §Alternatives ("Deferred, not rejected"). Prerequisite spike before - pickup: confirm PN handles multiple peer app-authored spaces in one database - end-to-end (PN ADR 212's monorepo-aggregator case). + pickup: confirm Prisma ORM handles multiple peer app-authored spaces in one database + end-to-end (Prisma ORM ADR 212's monorepo-aggregator case). - **Dev-time story** — resolved by ADR-0041: the "lowering unchanged" half of the parked sketch survives, but substitution happens at the Alchemy provider boundary (local providers for the same resource types), not by serving a local copy of the Management API — see `docs/design/90-decisions/ADR-0041-local-dev-runs-the-deploy-pipeline-against-local-providers.md` and `docs/design/10-domains/local-dev.md`. -- **Factory name** — `pnPostgres` is a placeholder; Prisma Next → Prisma Data - rename incoming at GA. Rename the factory when the product name lands. +- **Factory name** — resolved 2026-08-26: the product name landed as Prisma ORM + (v8), and the factory was renamed `pnPostgres` → `postgres` (entrypoint + `./prisma-next` → `./orm`, kind `'prisma-next'` → `'postgres'`). # Standalone effect-resolution slice — deferred (2026-08-03, TML-3158) diff --git a/.drive/next-session-brief.md b/.drive/next-session-brief.md index 6b25a2190..16f6169f9 100644 --- a/.drive/next-session-brief.md +++ b/.drive/next-session-brief.md @@ -61,7 +61,7 @@ its `spec.md` and `plan.md`; tracker issues TML-2998…3005. ## Environment footguns - **Nested checkouts are NOT part of this repo**: `datahub/`, and any of - `ignite/`, `pdp-control-plane/`, `prisma-next/` if present. Never edit, + `ignite/`, `pdp-control-plane/`, `prisma/orm/` if present. Never edit, stage, or scan them (gitignored + biome-excluded, as are `.agents/`, `.claude/`). - **Deploy credentials**: untracked `.env` at the main checkout root diff --git a/.drive/projects/alchemy-provider-adoption/plan.md b/.drive/projects/alchemy-provider-adoption/plan.md index 29ce547f0..97b2cee4e 100644 --- a/.drive/projects/alchemy-provider-adoption/plan.md +++ b/.drive/projects/alchemy-provider-adoption/plan.md @@ -10,7 +10,7 @@ Three slices: two stacked (postgres family, then compute family) and one paralle ### Slice 1 — Postgres family adoption (TML-3154) -Bump alchemy to the first released beta containing the Prisma provider; wire upstream live providers + `PrismaEnvironment` auth; rename our collection tag; swap `Project`/`Database`/`Connection` to upstream classes; rewire postgres/prisma-next descriptors; create-then-PATCH branch attach; `directConnectionString`; state-row migration (mechanics decided here: aliases vs SQL); rebind postgres emulator provider. +Bump alchemy to the first released beta containing the Prisma provider; wire upstream live providers + `PrismaEnvironment` auth; rename our collection tag; swap `Project`/`Database`/`Connection` to upstream classes; rewire postgres/prisma/orm descriptors; create-then-PATCH branch attach; `directConnectionString`; state-row migration (mechanics decided here: aliases vs SQL); rebind postgres emulator provider. - **Builds on:** nothing (first slice). - **Hands to:** slice 2 — alchemy bumped, upstream live-provider wiring + auth layer in place, collection tag renamed, state-migration mechanism proven on the postgres rows. diff --git a/.drive/projects/alchemy-provider-adoption/spec.md b/.drive/projects/alchemy-provider-adoption/spec.md index cada9c6de..501014d5a 100644 --- a/.drive/projects/alchemy-provider-adoption/spec.md +++ b/.drive/projects/alchemy-provider-adoption/spec.md @@ -11,7 +11,7 @@ Four workstreams: 1. **Adopt** — replace Composer's six overlapping resources (`Project`, `Database`, `Connection`, `ComputeService`, `Deployment`, `EnvironmentVariable`, ~670 lines in `packages/1-prisma-cloud/0-lowering/lowering/src/`) with upstream's resource classes and live providers. Requires the alchemy bump beta.59 → beta.66+ (the provider ships inside the `alchemy` package; beta.59 has no `Prisma/` directory). 2. **Port** — rewire Composer to upstream's shapes: descriptor call sites to upstream prop/attribute names; state rows migrated off the five colliding type-ids; our provider collection tag renamed (upstream also uses `'Prisma'`, and Effect context merge silently drops one of two same-key collections); auth via `Layer.succeed(PrismaEnvironment, …)` instead of `fromProfile()` (which prompts on TTY / hard-fails non-interactive); `directConnectionString` bound explicitly (upstream's `databaseUrl` resolves pooled-first); platform-seeded `DATABASE_URL` kept out of the resource graph (verified `isManagedBySystem: true`); branch attachment via create-then-PATCH (verified in PDP: create+attach are separate transactions, no idempotency key). 3. **Contribute upstream** — object storage resources (~161 lines; upstream deferred exactly the routes we call) and the generic core of the Postgres state store (~450 lines; only alchemy state backend with distributed locking, which upstream's own `Compute` docstring asks users to find). `PgWarm` offered to upstream; drop ours if they solve cold-start in `Database`/`Connection`. -4. **Keep local** — the dev emulators (~3,200 lines + s3-protocol) and the five Composer-concept resources (`ServiceKey`, `GeneratedParam`, `S3Credentials`, `PnMigration`, state-store policy layer). Emulators plug in behind `LowerOptions.providers` exactly as today, now paired with upstream's live providers. +4. **Keep local** — the dev emulators (~3,200 lines + s3-protocol) and the five Composer-concept resources (`ServiceKey`, `GeneratedParam`, `S3Credentials`, `OrmMigration`, state-store policy layer). Emulators plug in behind `LowerOptions.providers` exactly as today, now paired with upstream's live providers. Aman has agreed to the direction (call, 2026-08-03). Composition landed without waiting: the local wiring rebuilds the live provider layer from upstream's exported resource classes. The `liveProviderLayer()` export ask (or an equivalent override on `providers()`) remains as a temporary-dependency cleanup — once upstream exports it, the local rebuild is deleted. diff --git a/.drive/projects/auth-module/design-notes.md b/.drive/projects/auth-module/design-notes.md index 270c667e6..182e619b7 100644 --- a/.drive/projects/auth-module/design-notes.md +++ b/.drive/projects/auth-module/design-notes.md @@ -11,24 +11,24 @@ The central design problem: the module owns Better Auth's Postgres tables, consumers need real FKs to `auth.user.id`, and the framework must be able to assert the schema is present. -- **Bare `postgres()` dep + Better Auth's own migrator at boot** — rejected. +- **Bare `rawPostgres()` dep + Better Auth's own migrator at boot** — rejected. Nothing the framework can see asserts the schema; boot-time DDL from a third-party migration engine is exactly the nondeterministic mutation the principles forbid. Also loses the FK story entirely. -- **A dedicated pnPostgres contract per mode (exact-match v1, "slice +- **A dedicated postgres contract per mode (exact-match v1, "slice satisfaction" milestone 2)** — collapsed. The two-milestone split assumed - Prisma Next lacked multi-contract-per-database. It doesn't: contract + Prisma ORM lacked multi-contract-per-database. It doesn't: contract spaces are fully shipped — extension packs ship `contractSpace = { contractJson, headRef, migrations }`, the CLI seed phase materialises pack-shipped migrations, `planAllSpaces`/`executePerSpace` plan and apply per space, and `prisma_contract.marker` keys one row per space (each space signs the DB with its own hash). The Supabase pack ships zero migrations only because GoTrue owns its tables; the machinery is generic. -- **Winner: the auth package ships a Prisma Next extension pack** +- **Winner: the auth package ships a Prisma ORM extension pack** (`id: 'auth'`) carrying the Better Auth schema contract and authored migration packages. The consumer lists it in `extensionPacks`; one deploy migration step brings every space to head; `auth:User` FKs come from - prisma-next ADR 226 unchanged. Dedicated-DB standalone use is the same + prisma/orm ADR 226 unchanged. Dedicated-DB standalone use is the same mechanism with an empty app space, not a separate mode. ## Where the "does this DB carry the auth schema?" check runs @@ -41,9 +41,9 @@ assert the schema is present. the worst failure shape (Will: "boot time is already too late"). - **Winner: deploy-time preflight.** The db dependency carries a pack requirement (`packId` + head hash from the installed package); the deploy - lowering checks the wired resource's PN config lists the pack at that + lowering checks the wired resource's Prisma ORM config lists the pack at that hash before the migration step runs, and the same deploy then migrates - the auth space and signs the marker. `pnContract().satisfies` stays + the auth space and signs the marker. `dataContract().satisfies` stays wireability-only for pack requirements. ## Instance secret diff --git a/.drive/projects/auth-module/plan.md b/.drive/projects/auth-module/plan.md index 771de76aa..0b9827a37 100644 --- a/.drive/projects/auth-module/plan.md +++ b/.drive/projects/auth-module/plan.md @@ -19,7 +19,7 @@ Linear: project "Composer auth module" (Terminal) — S1 [TML-3076], S2 **Outcome:** `@internal/auth` exists and deploys. Email+password signup, login, logout, sessions, JWTs with JWKS verification, `session` + `admin` ports, proxy helper, local testing export. `examples/auth` (dedicated DB via -an empty-app-space PN project) deploys to real Prisma Cloud and its smoke +an empty-app-space Prisma ORM project) deploys to real Prisma Cloud and its smoke script passes. **Contents (spec sections):** Package layout · Contracts · Pack (contract, @@ -124,35 +124,35 @@ Two operator decisions: ## Open items (recorded during S2, 2026-07-23) -- **Upstream PN change: contract must sign extension HEAD HASHES, not just +- **Upstream Prisma ORM change: contract must sign extension HEAD HASHES, not just versions (decided 2026-07-24, Will).** The correct pack-compatibility check reads the wired database's emitted app contract — its signed `extensionPacks` section — and asks "is my required head hash present?" Today that section carries only `{ kind, id, familyId, targetId, version }` (`ExtensionPackRef` → `PackRefBase`, no hash field), so the check isn't expressible and compose's current preflight compares a descriptor against - itself (inert). Fix, at `~/Projects/prisma/prisma-next`: contract emit + itself (inert). Fix, at `~/Projects/prisma/orm`: contract emit records each extension's `contractSpace.headRef.hash` in the `ExtensionPackRef` written into the composed contract. THEN compose's preflight becomes a trivial hash-presence check against the wired resource's contract. For PR #163 now: delete the inert head-comparison - branch of `runPackPreflight` (keep the "pack not listed" branch); PN's own + branch of `runPackPreflight` (keep the "pack not listed" branch); Prisma ORM's own migrate/verify still catches real stale-head drift until the proper check lands. This supersedes the earlier "compare against on-disk refs/head.json" idea. -- **Latent (NOT yet live): a future Prisma Next flips the config field - `extensionPacks` → `extensions`.** `pn-config.ts` reads +- **Latent (NOT yet live): a future Prisma ORM flips the config field + `extensionPacks` → `extensions`.** `orm-config.ts` reads `config.extensionPacks`. The ADR-0042 migration is on **published `@prisma-next/config@0.16.0`, which STILL validates `extensionPacks`** (its `config-validation` rejects `extensions`), so the field compose reads is populated and the pack path works — verified on-branch, and the pack tests pass. The rename lives only in the **unpublished** local - `./prisma-next` checkout (also labelled 0.16.0 but ahead of npm). When + `./orm` checkout (also labelled 0.16.0 but ahead of npm). When that rename ships, `config.extensionPacks` becomes `undefined → []`, silently emptying the preflight pack lookup and the - `packHeadRefHashes` diff key. Fix before adopting that PN: read + `packHeadRefHashes` diff key. Fix before adopting that Prisma ORM: read `config.extensions ?? config.extensionPacks`. (Corrects the earlier note that pegged the break to 0.16 — it's a later published version.) diff --git a/.drive/projects/auth-module/slices/adr-0041-migration/spec.md b/.drive/projects/auth-module/slices/adr-0041-migration/spec.md index 7b162887d..392facdb2 100644 --- a/.drive/projects/auth-module/slices/adr-0041-migration/spec.md +++ b/.drive/projects/auth-module/slices/adr-0041-migration/spec.md @@ -216,8 +216,8 @@ tree-hash verification; the orchestrator pushes, the implementer never does). serialize yields the resource + env row per generated leaf. 11. **Carried S1/S2 items that must survive the re-application** (verify each against the map, none regress): `composeServiceFetch` subpath + - prefix guard (`33e813cd`, `24207103`); PN vocabulary renames - (`d4302327`); PN dbInit bootstrap (`51983e40`); transient-ID sweep + prefix guard (`33e813cd`, `24207103`); Prisma ORM vocabulary renames + (`d4302327`); Prisma ORM dbInit bootstrap (`51983e40`); transient-ID sweep (`5f1b1c3b`); Hono examples (`07fe5104`); S2 email flows + bundling fixes (`b03b6e97..9f2fedb2` as re-stacked). The inert head-comparison branch of `runPackPreflight` is DELETED in this migration (keep the @@ -266,7 +266,7 @@ tree-hash verification; the orchestrator pushes, the implementer never does). - Any change to `envParam`/`envSecret` behavior. - ADR-0030 service keys migrating onto `generatedParam()` plumbing (they stay on their own channel; unification is a possible later slice). -- The upstream PN contract-signed-extension-hashes change (separate brief, +- The upstream Prisma ORM contract-signed-extension-hashes change (separate brief, separate repo). ## 9. Validation (the implementer runs; all must pass) @@ -274,7 +274,7 @@ tree-hash verification; the orchestrator pushes, the implementer never does). Root `pnpm typecheck`; target package tests; service-rpc tests; auth package full suite; email package suite (untouched-green); `examples/auth` tests; `pnpm lint`, `pnpm lint:deps`, `pnpm lint:casts`; fresh auth build — deployed -entrypoint bundle contains no PN control client, testing bundle +entrypoint bundle contains no Prisma ORM control client, testing bundle self-contained; built public surface still does not export `composeServiceFetch`; repo-wide grep proves zero occurrences of `mintedSecret|MintedSecret|minted-secret|isMintedSecret|_MINTED` outside diff --git a/.drive/projects/auth-module/slices/auth-module-core/plan.md b/.drive/projects/auth-module/slices/auth-module-core/plan.md index 4c2c7d3ff..a63213fa2 100644 --- a/.drive/projects/auth-module/slices/auth-module-core/plan.md +++ b/.drive/projects/auth-module/slices/auth-module-core/plan.md @@ -20,11 +20,11 @@ importable and tested. **Outcome:** a pn database can carry an extension-pack requirement: wireable, preflighted at deploy, migrated multi-space. **Focus:** spec § Target changes items 2–4: `pnPackRequirement` + -`satisfies` branch (`prisma-next.ts`); preflight with the three pinned -error messages (`preflight.ts` + hook into `descriptors/prisma-next.ts`); -`resolvePnProject` (`pn-config.ts`) + `extensionPacks` threading + +`satisfies` branch (`orm-postgres.ts`); preflight with the three pinned +error messages (`preflight.ts` + hook into `descriptors/orm-postgres.ts`); +`resolvePnProject` (`orm-config.ts`) + `extensionPacks` threading + noop-suppression + migration-resource key folding pack hashes -(`prisma-next-migrate.ts`, `pn-migration-resource.ts`). Unit tests for +(`orm-migrate.ts`, `orm-migration-resource.ts`). Unit tests for each; integration test for multi-space migrate against local PG using a synthetic pack fixture. **Builds on:** —. **Hands to:** target APIs the pack (D3) and module (D4) @@ -73,7 +73,7 @@ tests. **Outcome:** `examples/auth` deploys to real Prisma Cloud; smoke passes; packaging/lint surfaces clean. -**Focus:** `examples/auth` per spec (empty-app-space PN project + +**Focus:** `examples/auth` per spec (empty-app-space Prisma ORM project + authPack; api service with proxy + verifier + session dep; ops service with admin dep; `scripts/smoke.ts`; local integration tests); public package re-exports; README (S1 sections); exports-entrypoints exception diff --git a/.drive/projects/auth-module/spec.md b/.drive/projects/auth-module/spec.md index 05b5e5a42..5090b92db 100644 --- a/.drive/projects/auth-module/spec.md +++ b/.drive/projects/auth-module/spec.md @@ -11,11 +11,11 @@ Signup, login, sessions, and JWT verification as a composed module wrapping **Better Auth** (TypeScript library, in-process, owns Postgres tables — not a remote IdP). Dedicated-service shape: one Compute service, three consumer surfaces (`api` public HTTP, `session` rpc, `admin` rpc), plus a stateless -`jwtVerifier()` binding. The Better Auth schema ships as a **Prisma Next +`jwtVerifier()` binding. The Better Auth schema ships as a **Prisma ORM extension pack** (`auth:` contract space) carrying the contract and authored migrations: the consumer's one deploy migration step creates and evolves the auth tables beside their own, the database's marker is signed per space, and -consumer tables FK `auth:User` via prisma-next ADR 226. Email (verification, +consumer tables FK `auth:User` via prisma/orm ADR 226. Email (verification, reset, magic links) rides the `email` module (PR #146) through consumer-declared templates. The instance secret is platform-minted. Browsers reach Better Auth through a proxy route on the consumer app's origin. @@ -33,15 +33,15 @@ reach Better Auth through a proxy route on the consumer app's origin. (the bucket is a platform primitive). D8's semantics (mint-once, stable, unexposed, no rotation) are unchanged — only the mechanism moves into the platform's existing secret representation. -2. **Local testing bootstrap uses PN migrations.** `startLocalAuthServer` - runs the real PN `dbInit` path (control client, as - `prisma-next-migrate.ts` does deploy-side) against the caller's local +2. **Local testing bootstrap uses Prisma ORM migrations.** `startLocalAuthServer` + runs the real Prisma ORM `dbInit` path (control client, as + `orm-migrate.ts` does deploy-side) against the caller's local database instead of applying a rendered `schema.sql`. DELETE `scripts/generate-schema.ts`, `src/pack/schema.sql`, `src/pack/schema-sql.ts`. The schema-conformance test keeps its real - assertion (migrate a scratch DB via PN, run Better Auth's schema + assertion (migrate a scratch DB via Prisma ORM, run Better Auth's schema generation, assert zero pending changes) minus the schema.sql-drift - half. Escape hatch: if bundling the PN control client into the testing + half. Escape hatch: if bundling the Prisma ORM control client into the testing export genuinely fails, STOP and report — do not silently revert to raw SQL. 3. **Fetch composition is a framework concern.** `@internal/service-rpc` @@ -51,15 +51,15 @@ reach Better Auth through a proxy route on the consumer app's origin. `serve.ts` uses the (now exported) `isRpcContract` type guard instead of the inline `kind !== 'rpc'` literal. First-class multi-port routing stays wired-egress territory; this is only the composition seam. -4. **Prisma Next vocabulary.** Renames (types, functions, fields, and +4. **Prisma ORM vocabulary.** Renames (types, functions, fields, and prose): `PnProject`/`resolvePnProject` → the loaded thing is a config — - `ResolvedPrismaNextConfig`/`resolvePrismaNextConfig`; - `packHead`/`packHeads` → `packHeadRefHash`/`packHeadRefHashes` (PN + `ResolvedOrmConfig`/`resolveOrmConfig`; + `packHead`/`packHeads` → `packHeadRefHash`/`packHeadRefHashes` (Prisma ORM terms: a pack's contract-space *head ref* and its *storage hash*); `PnPackRequirement`/`pnPackRequirement`/`packRequirementOf` → `RequiredPackHead`/`requiredPackHead`/`requiredPackHeadOf` (the concept is compose's own — the name now says what it is instead of - masquerading as PN glossary). + masquerading as Prisma ORM glossary). 5. **No transient project identifiers in shipped code.** Comments referencing Drive slice/decision IDs (`S1`, `slice S2`, `D5`, `D12`, …) are forbidden in `packages/**` and `examples/**` — replace each @@ -78,9 +78,9 @@ reach Better Auth through a proxy route on the consumer app's origin. |---|---|---| | D1 | Dedicated Compute service wrapping Better Auth; module owns its tables behind the boundary | ADR-0016; Better Auth is a library — our service hosts it | | D2 | The database is a boundary dependency (`db` slot), never self-provisioned by the module | Streams precedent (`store: s3()`); the root decides dedicated vs shared; sharing is a wiring choice, not a module-shape change (ADR-0013 one-provision-N-wirings) | -| D3 | Better Auth's schema ships as a Prisma Next **extension pack** (`id: 'auth'`, contract + authored migration packages); Better Auth's own migrator never runs anywhere | Framework-owned deterministic migration (ADR-0005/0022); per-space marker signing and pack-shipped migrations are shipped PN machinery; `auth:User` FK falls out of PN ADR 226 | -| D4 | Shared-DB-with-FK is the golden path from v1; dedicated DB is the same mechanism with an empty app space | No PN work needed; the FK story is the headline capability | -| D5 | The db dep's schema assertion runs at **deploy time**: a preflight checks the wired resource's PN config lists the `auth` pack at the installed package's head hash, before the migration step | Boot-time is too late (service down after a green deploy); wiring-time reads a stale proxy artifact. `pnContract().satisfies` stays wireability-only for pack requirements | +| D3 | Better Auth's schema ships as a Prisma ORM **extension pack** (`id: 'auth'`, contract + authored migration packages); Better Auth's own migrator never runs anywhere | Framework-owned deterministic migration (ADR-0005/0022); per-space marker signing and pack-shipped migrations are shipped Prisma ORM machinery; `auth:User` FK falls out of Prisma ORM ADR 226 | +| D4 | Shared-DB-with-FK is the golden path from v1; dedicated DB is the same mechanism with an empty app space | No Prisma ORM work needed; the FK story is the headline capability | +| D5 | The db dep's schema assertion runs at **deploy time**: a preflight checks the wired resource's Prisma ORM config lists the `auth` pack at the installed package's head hash, before the migration step | Boot-time is too late (service down after a green deploy); wiring-time reads a stale proxy artifact. `dataContract().satisfies` stays wireability-only for pack requirements | | D6 | Sessions: stateless JWT by default (15-min TTL, EdDSA via JWKS); instant logout is an explicit per-call opt-in via the `session` rpc port | "No DB access" is the JWT binding's whole value; revocation is a per-route decision | | D7 | Social OAuth is post-v1; the factory reserves an options-driven mechanism (per-provider secret slots + params) but v1 ships none | Zero-click-ops golden path; ADR-0029 has no optional secrets, so slots must be option-conditional | | D8 | The Better Auth instance secret is a **minted resource** (`authSecret`), mirroring `s3Credentials`: minted once deploy-side, stable across deploys, wired as a dependency binding; not exposed to consumers in v1; rotation unsupported in v1 (documented) | Zero-click-ops; the target already owns this exact pattern; rotating would invalidate sessions + AES-encrypted jwks rows | @@ -135,7 +135,7 @@ packages/1-prisma-cloud/2-shared-modules/auth/ │ ├── contract.json # emitted (committed) │ ├── contract.d.ts # emitted (committed) │ ├── schema.sql # generated flat DDL (committed; used by testing export) - │ ├── migrations/ # authored PN migration packages (committed; v1: 0001_init) + │ ├── migrations/ # authored Prisma ORM migration packages (committed; v1: 0001_init) │ └── index.ts # authPack descriptor (SqlControlExtensionDescriptor<'postgres'>) ├── execution/ │ ├── auth-entrypoint.ts # boot program @@ -152,7 +152,7 @@ packages/1-prisma-cloud/2-shared-modules/auth/ Planes (`architecture.config.json`, non-overlapping globs, email's pattern): `src/*.ts` → shared; `src/pack/**` → shared (descriptor + JSON only — the -pack's `index.ts` must not import `@internal/lowering`/`effect`/PN control); +pack's `index.ts` must not import `@internal/lowering`/`effect`/Prisma ORM control); `src/execution/**` → execution; `src/exports/index.ts`, `src/exports/pack.ts`, `src/exports/embedded.ts`, `src/exports/auth-service.ts` → shared (the service shim is re-exported from the authoring barrel — email's exact @@ -333,18 +333,18 @@ Semantics: ### Db dependency — `authDb()` ```ts -/** The auth service's claim on a PN-typed database that carries the auth pack. */ -export function authDb(): DependencyEnd<{ url: string }, PnPostgresContract>; +/** The auth service's claim on a Prisma-ORM-typed database that carries the auth pack. */ +export function authDb(): DependencyEnd<{ url: string }, PostgresContract>; ``` -Built with core `dependency()`: `type: 'prisma-next'`, connection params +Built with core `dependency()`: `type: 'postgres'`, connection params `{ url: string() }`, hydrate identity (`({url}) => ({url})`) — Better Auth -builds its own pool; no PN client. Its `required` contract is +builds its own pool; no Prisma ORM client. Its `required` contract is `pnPackRequirement({ packId: 'auth', headHash: AUTH_PACK_HEAD_HASH })` (§ Target changes), where `AUTH_PACK_HEAD_HASH` is imported from `src/pack/index.ts` (the emitted contract's `storage.storageHash`). -## The Prisma Next extension pack (`src/pack/`) +## The Prisma ORM extension pack (`src/pack/`) - `contract.prisma`: PSL under `namespace auth` containing the Better Auth tables at the pinned version with plugins jwt+admin+magicLink+bearer: @@ -353,17 +353,17 @@ Target changes), where `AUTH_PACK_HEAD_HASH` is imported from Better Auth's default (camelCase) column names, transcribed to PSL. Tables live in Postgres schema `auth` (PSL namespace → PG schema, as `resolveDdlSchemaForNamespaceStorage` maps it). -- Emitted `contract.json`/`contract.d.ts`: produced by PN's contract emit +- Emitted `contract.json`/`contract.d.ts`: produced by Prisma ORM's contract emit toolchain (same procedure as `@prisma-next/extension-supabase`'s `src/contract/`), committed. Control policy: **managed** (the default) — unlike Supabase's `external`, OUR migrations create these tables. -- `migrations/0001_init`: one authored PN migration package creating the five - tables + indexes, authored with PN's migration tooling against the emitted +- `migrations/0001_init`: one authored Prisma ORM migration package creating the five + tables + indexes, authored with Prisma ORM's migration tooling against the emitted contract; committed. `headRef = { hash: contract.storage.storageHash, invariants: [] }`. - `schema.sql`: flat DDL equivalent of applying the pack's migration graph to an empty database, generated by a package script (`pnpm generate:schema` — - runs PN `dbInit` in plan mode against the pack space and renders SQL), + runs Prisma ORM `dbInit` in plan mode against the pack space and renders SQL), committed. Consumed ONLY by the testing export (D16). - `index.ts`: exports `authPack` (`SqlControlExtensionDescriptor<'postgres'>`, `id: 'auth'`, `familyId: 'sql'`, `targetId: 'postgres'`, `version` from @@ -378,7 +378,7 @@ Target changes), where `AUTH_PACK_HEAD_HASH` is imported from Consumer usage (shared DB, the golden path): ```ts -// prisma-next.config.ts +// prisma.config.ts import authPack from '@prisma/composer-prisma-cloud/auth/pack'; export default defineConfig({ ..., extensionPacks: [authPack] }); ``` @@ -394,8 +394,8 @@ namespace public { } ``` -Cross-space relations are non-navigable in the generated client (PN ADR 226); -the value is the real FK constraint. Dedicated-DB standalone: a PN project +Cross-space relations are non-navigable in the generated client (Prisma ORM ADR 226); +the value is the real FK constraint. Dedicated-DB standalone: a Prisma ORM project whose app space is empty and whose config lists only `authPack` (the smoke example, § Examples). @@ -412,45 +412,45 @@ Four additions, no new machinery classes: `btoa(String.fromCharCode(...randomBytes(32)))` on first create; reconcile keeps existing output (stable across deploys). Registered in the same descriptor tables `s3Credentials` is. -2. **`pnPackRequirement`** (in `prisma-next.ts`): +2. **`pnPackRequirement`** (in `orm-postgres.ts`): ```ts export interface PnPackRequirement { readonly packId: string; readonly headHash: string; } - export function pnPackRequirement(req: PnPackRequirement): PnPostgresContract; + export function pnPackRequirement(req: PnPackRequirement): PostgresContract; ``` - Returns a `'prisma-next'`-kind contract whose `__cmp` is - `{ packRequirement: req }`. `pnContract()`'s `satisfies` gains one branch + Returns a `'postgres'`-kind contract whose `__cmp` is + `{ packRequirement: req }`. `dataContract()`'s `satisfies` gains one branch BEFORE hash comparison: if `required.__cmp` carries `packRequirement` (checked defensively like `storageHashOf`), return `true` — wireability only; enforcement is the deploy preflight (D5). 3. **Pack preflight** (new function in `preflight.ts`, invoked from the deploy lowering beside the existing pn migration-step construction in - `descriptors/prisma-next.ts`): for every dependency edge whose consumer + `descriptors/orm-postgres.ts`): for every dependency edge whose consumer `required.__cmp.packRequirement` is set and whose provider is a - `PnPostgresResourceNode` — load the resource's PN config (the same c12 + `PostgresResourceNode` — load the resource's Prisma ORM config (the same c12 load `resolveMigrationsDir` does), read `config.extensionPacks`. Fail the deploy with: - - missing: `prisma-next database "" does not list extension - pack "" in its prisma-next.config.ts extensionPacks — service + - missing: `postgres database "" does not list extension + pack "" in its prisma.config.ts extensionPacks — service "" requires it. Add the pack and run migration plan.` - hash mismatch: `extension pack "" in "" is at head , but the installed package requires . Re-run migration plan so the pack's shipped migrations are materialised, then redeploy.` - A `packRequirement` edge wired to a non-`pnPostgres` provider fails: + A `packRequirement` edge wired to a non-`postgres` provider fails: `service "" requires extension pack "", which only a - pnPostgres resource can carry.` -4. **Multi-space migrate passthrough** (`prisma-next-migrate.ts` + - `pn-config.ts`): `loadConfig` already returns the config; surface + postgres resource can carry.` +4. **Multi-space migrate passthrough** (`orm-migrate.ts` + + `orm-config.ts`): `loadConfig` already returns the config; surface `config.extensionPacks ?? []` alongside `resolveMigrationsDir` (new `resolvePnProject(configPath) → { migrationsDir, extensionPacks }`). Thread `extensionPacks` into `createPostgresControlClient({ connection, extensionPacks })` — client-creation options only; the client threads them into `dbInit`/`migrate` internally (amended 2026-07-22, D2: the - per-call options do not accept them, the creation options do). Decision change in `applyPnMigration`: when + per-call options do not accept them, the creation options do). Decision change in `applyOrmMigration`: when `extensionPacks.length > 0` and `decideMigrationAction` returns `noop` - for the app space, still call `client.migrate` (PN's per-space path + for the app space, still call `client.migrate` (Prisma ORM's per-space path resolution no-ops each up-to-date space); `noop` is returned to the - lowering only when packs are absent. The PnMigration resource key + lowering only when packs are absent. The OrmMigration resource key (hash+invariants) must additionally fold in each pack's `contractSpace.headRef.hash` (sorted by pack id) so a pack upgrade produces a distinct deploy step. @@ -525,7 +525,7 @@ Pinned option values: `options: '-c search_path=auth'`, plus the target's connection hardening values (`connectionTimeoutMillis: 20_000`, `idleTimeoutMillis: 5_000`, pool `error` listener logging — copied semantics from - `prisma-next.ts`'s `resilientPool`, reimplemented locally; the module may + `orm-postgres.ts`'s `resilientPool`, reimplemented locally; the module may not import target internals). - `emailAndPassword: { enabled: true, requireEmailVerification: , sendResetPassword: , revokeSessionsOnPasswordReset: true }`. @@ -668,7 +668,7 @@ export function createEmbeddedAuth(inputs: EmbeddedAuthInputs): ReturnType, config: './prisma-next.config.ts' })` with +Root `module.ts`: `postgres({ name: 'database', contract: , config: './prisma.config.ts' })` with `extensionPacks: [authPack]`; `provision(auth(), { deps: { db }, params: { baseUrl: envParam('AUTH_BASE_URL') } })`; an `api` compute service with `deps: { authApi: authApi(), verifier: jwtVerifier(), session: @@ -719,7 +719,7 @@ against `startLocalAuthServer`. ### `examples/storefront-auth` rework (S3) — the real consumer, shared DB -Replace the toy `modules/auth` with this module. One shared `pnPostgres` +Replace the toy `modules/auth` with this module. One shared `postgres` database: app space = storefront's contract (gains `Profile.userId → auth:User @relation(..., onDelete: Cascade)`), `extensionPacks: [authPack]`. Storefront service proxies `/api/auth/*`, @@ -757,7 +757,7 @@ pinned values (snapshot of `buildAuthOptions` output shape). Integration signup/login/JWT verify with real `jose`; bearer + cookie flows; magic-link e2e via capture; S2: against the email module's local server reading the outbox; embedded-vs-service parity (same flows through `createEmbeddedAuth`). -Schema conformance: migrate a scratch DB with the pack (PN control client), +Schema conformance: migrate a scratch DB with the pack (Prisma ORM control client), run Better Auth's schema generation against it, assert zero pending changes; also assert `schema.sql` equals regenerated output. Target unit tests: `pnPackRequirement` satisfies branch, preflight failure messages, @@ -794,7 +794,7 @@ per-consumer contract slices on shared DBs · exposing the instance secret · 1. **Consume `@prisma-next/extension-better-auth` instead of authoring our own pack?** Discovered at planning time (2026-07-22): Linear project "BetterAuth Extension" (Terminal, lead Serhii Tatarintsev, in progress - since 2026-07-13) is building exactly the § Pack artifact in prisma-next + since 2026-07-13) is building exactly the § Pack artifact in prisma/orm itself — `spaceId: 'better-auth'`, managed control, BetterAuth core models (`user`, `session`, `account`, `verification`), plus a BetterAuth **database adapter** over contract-typed `sql-orm-client` collections. @@ -829,8 +829,8 @@ improvised. codec, testing-export shape, exports discipline. - Prior art: `packages/1-prisma-cloud/2-shared-modules/{storage,streams,cron}`; target patterns: `s3-credentials*.ts`, `streams-keys.ts`, - `prisma-next{,-migrate}.ts`, `http.ts`. -- Prisma Next (worktree `./prisma-next`): pack descriptor + `orm-postgres.ts`, `orm-migrate.ts`, `http.ts`. +- Prisma ORM (worktree `./orm`): pack descriptor `packages/3-extensions/supabase/src/pack/index.ts`; per-space runner `packages/3-targets/3-targets/postgres/src/core/migrations/runner.ts`; seed phase `packages/1-framework/3-tooling/cli/src/utils/contract-space-seed-phase.ts`; diff --git a/.drive/projects/email-module/spec.md b/.drive/projects/email-module/spec.md index 4f9645b8f..256d74ce7 100644 --- a/.drive/projects/email-module/spec.md +++ b/.drive/projects/email-module/spec.md @@ -289,7 +289,7 @@ const deliveryModeSchema = type("'resend'|'smtp'|'none'"); export function emailService(opts?: { deliveryUrl?: string }): /* ServiceNode */ { return compute({ name: 'email', - deps: { db: postgres() }, + deps: { db: rawPostgres() }, params: { deliveryMode: param(deliveryModeSchema), deliveryUrl: string({ default: opts?.deliveryUrl ?? 'https://api.resend.com' }), @@ -318,7 +318,7 @@ export function email(opts?: { name?: string; deliveryUrl?: string }): ModuleNod expose: { send: emailSendContract, outbox: emailOutboxContract }, }, ({ params, secrets, provision }) => { - const db = provision(postgres({ name: 'db' }), { id: 'db' }); + const db = provision(rawPostgres({ name: 'db' }), { id: 'db' }); const service = provision(emailService({ deliveryUrl: opts?.deliveryUrl }), { id: 'service', deps: { db }, diff --git a/.drive/projects/exports-entrypoint/investigation.md b/.drive/projects/exports-entrypoint/investigation.md index f28e0f336..8b14da466 100644 --- a/.drive/projects/exports-entrypoint/investigation.md +++ b/.drive/projects/exports-entrypoint/investigation.md @@ -1,9 +1,9 @@ # Plan: separate package source from exports via `src/exports/` entrypoints Branch: `claude/prisma-exports-entrypoint-996287` (based on origin/main). -Reference: prisma-next (cloned read-only into the session scratchpad). +Reference: prisma/orm (cloned read-only into the session scratchpad). -## The pattern in prisma-next +## The pattern in prisma/orm Sources studied: `.agents/rules/multi-plane-packages.mdc`, `multi-plane-entrypoints.mdc`, `no-barrel-files.mdc`, `cli-package-exports.mdc`, @@ -36,10 +36,10 @@ Sources studied: `.agents/rules/multi-plane-packages.mdc`, internals (e.g. `core/src` has `index.ts`, `testing.ts`, `deploy.ts`, `app-config.ts` next to `graph.ts`, `hydrate.ts`, `toposort.ts`, …). Nothing marks which files are public. -- `@internal/tsdown-config` already mirrors prisma-next's base (`exports: true`) +- `@internal/tsdown-config` already mirrors prisma/orm's base (`exports: true`) but 7 of 15 packages opt out with `exports: false` and hand-maintain their maps. Its doc comment claims tsdown 0.15.x; the workspace actually has - 0.22.4 — the same version prisma-next uses, so `enabled: 'local-only'` + + 0.22.4 — the same version prisma/orm uses, so `enabled: 'local-only'` + `customExports` port directly. - `architecture.config.json` has 61 entries, mostly per-file globs (`core/src/deploy.ts` → control, `core/src/graph.ts` → shared, …) because @@ -57,7 +57,7 @@ docs-only PRs). ### Phase 0 — tooling: `@internal/tsdown-config` -- Port prisma-next's `customExports` (strip `exports/` prefix, `./` → `.`, +- Port prisma/orm's `customExports` (strip `exports/` prefix, `./` → `.`, single-entry collapse fix) into `baseConfig`. - Switch `exports: true` → `exports: { enabled: 'local-only', customExports, exclude: [/bin\./] }` (excludes the CLI bin from importable subpaths). @@ -80,7 +80,7 @@ no `src/core/` shuffle needed. | `@internal/assemble` | `index.ts` | | `@internal/cli` | `index.ts` (`bin.ts` stays internal; excluded from exports) | | `@internal/lowering` | `index.ts`, `compute.ts`, `postgres.ts`, `state.ts` (replacing the `compute/index.ts`-style subdir entries with thin exports files) | -| `@internal/prisma-cloud` (target) | `index.ts`, `control.ts`, `prisma-next.ts`, `testing.ts`, `pg-connection.ts` → `exports/connection.ts` | +| `@internal/prisma-cloud` (target) | `index.ts`, `control.ts`, `orm-postgres.ts`, `testing.ts`, `pg-connection.ts` → `exports/connection.ts` | | `@internal/cron` | `index.ts`, `scheduler-service.ts`, `scheduler-entrypoint.ts` | | `@internal/storage` / `@internal/streams` | `index.ts`, `*-service.ts`, `*-entrypoint.ts`, `testing.ts` | | `@prisma/composer` | all 11 subpath files (`index`, `config`, `deploy`, `testing`, `casts`, `assertions`, `rpc`, `node`, `node-control`, `nextjs`, `nextjs-control`) — these are already thin re-exports, pure moves | @@ -91,7 +91,7 @@ hand-maintain `{types, default}` conditional exports "for the public packages' dts bundling", and `@prisma/composer` needs `exports: false` anyway (its bin entry plus `noExternal` bundling). Try auto-generation first; keep a manual map **only** where the generated one demonstrably breaks consumer type resolution, -and document the exception in the rule (prisma-next's `cli-package-exports.mdc` +and document the exception in the rule (prisma/orm's `cli-package-exports.mdc` treats manual maps as exception-only, same stance). Test fallout: within-package tests that import a moved file @@ -135,7 +135,7 @@ fails on a known-bad import (glob order: specific before `src/**`). ### Risks -- tsdown 0.22.4 `customExports` shape: prisma-next runs the identical version +- tsdown 0.22.4 `customExports` shape: prisma/orm runs the identical version with this exact hook, so this is a copy, not an experiment. - Generated string-form exports (`"./x": "./dist/x.mjs"`) rely on TS finding the sibling `dist/x.d.mts`; the packages already built this way (`core`, diff --git a/.drive/projects/exports-entrypoint/learnings.md b/.drive/projects/exports-entrypoint/learnings.md index 1127dd801..0e1b9bb14 100644 --- a/.drive/projects/exports-entrypoint/learnings.md +++ b/.drive/projects/exports-entrypoint/learnings.md @@ -84,11 +84,11 @@ exception needed. This de-risks D5 (the public packages). **This entry recorded an accepted characteristic that has since been rejected. Kept for the history of how it happened.** -Originally: target's `pg-connection.ts` and `prisma-next.ts` were IMPLEMENTATION +Originally: target's `pg-connection.ts` and `orm-postgres.ts` were IMPLEMENTATION homes (not thin re-exports), and three internal modules imported them from -`src/exports/` (`pg-warm-resource.ts`, `prisma-next-migrate.ts` → -`./exports/pg-connection.ts`; `descriptors/prisma-next.ts` → -`../exports/prisma-next.ts`). That inverts prisma-next's shape, was green +`src/exports/` (`pg-warm-resource.ts`, `orm-migrate.ts` → +`./exports/pg-connection.ts`; `descriptors/orm-postgres.ts` → +`../exports/orm.ts`). That inverts prisma/orm's shape, was green (lint:deps clean — compatible planes), and was ACCEPTED as a consequence of the slice's deliberate relocation-not-refactor choice, with the purer form noted as an optional future refactor. diff --git a/.drive/projects/exports-entrypoint/slices/exports-entrypoint/d4a-brief.md b/.drive/projects/exports-entrypoint/slices/exports-entrypoint/d4a-brief.md index f208f7a79..69a385812 100644 --- a/.drive/projects/exports-entrypoint/slices/exports-entrypoint/d4a-brief.md +++ b/.drive/projects/exports-entrypoint/slices/exports-entrypoint/d4a-brief.md @@ -43,19 +43,19 @@ Import rewrites (grep to confirm exact lines — do not trust this list as exhau Current public entrypoints (hand-maintained `{types,default}` map, `exports:false`): - `.` ← `src/index.ts` - `./control` ← `src/control.ts` -- `./prisma-next` ← `src/prisma-next.ts` +- `./orm` ← `src/orm-postgres.ts` - `./testing` ← `src/testing.ts` - `./connection` ← `src/pg-connection.ts` -Move all five into `src/exports/` (keep filenames — including `pg-connection.ts`). Internals stay at root: `descriptors/`, `compute.ts`, `http.ts`, `param.ts`, `pg-warm-resource.ts`, `pn-config.ts`, `pn-migration-resource.ts`, `postgres.ts`, `preflight.ts`, `prisma-next-migrate.ts`, `s3-*.ts`, `secret.ts`, `serializer.ts`, `service-keys.ts`. +Move all five into `src/exports/` (keep filenames — including `pg-connection.ts`). Internals stay at root: `descriptors/`, `compute.ts`, `http.ts`, `param.ts`, `pg-warm-resource.ts`, `orm-config.ts`, `orm-migration-resource.ts`, `postgres.ts`, `preflight.ts`, `orm-migrate.ts`, `s3-*.ts`, `secret.ts`, `serializer.ts`, `service-keys.ts`. - Fix the 5 entrypoints' relative imports of internal modules `./X.ts` → `../X.ts` (grep authoritative — these entrypoints import many internals). Sibling entrypoint refs (if any among the 5) stay `./`. - Grep the package + `test/`/`examples/`/siblings for references to the 5 moved files (imports AND hardcoded paths) and repoint. - tsdown entry (object, keep the `./connection` subpath via object key `connection` mapping the `pg-connection.ts` file): - `{ index: 'src/exports/index.ts', control: 'src/exports/control.ts', 'prisma-next': 'src/exports/prisma-next.ts', testing: 'src/exports/testing.ts', connection: 'src/exports/pg-connection.ts' }` + `{ index: 'src/exports/index.ts', control: 'src/exports/control.ts', 'postgres': 'src/exports/orm.ts', testing: 'src/exports/testing.ts', connection: 'src/exports/pg-connection.ts' }` - **GENERATION PROBE:** DROP `exports: false` and let the map generate. Then build the WHOLE workspace (`pnpm build`) — this builds `@prisma/composer-prisma-cloud`, which bundles target's dts. If the generated string-form exports break the public package's dts resolution / build, **revert target to its hand-maintained `{types,default}` map** (keep `exports:false`, repoint the dist values if any changed — they shouldn't, object keys preserve output names) and record WHY in the commit message + report. Do not force generation if it breaks dts. -- architecture.config.json: repoint target's 5 entrypoint globs 1:1 to `src/exports/…` — `control.ts` → control; `index.ts`, `prisma-next.ts`, `testing.ts`, `pg-connection.ts` → shared. Leave the internal-file globs (`descriptors/**`, `pn-migration-resource`, `pg-warm-resource`, `prisma-next-migrate`, `serializer`, `service-keys`, `compute`, `postgres`, `pn-config`, `http`) untouched. -- **Key parity:** keys `.`, `./control`, `./prisma-next`, `./testing`, `./connection`, `./package.json` unchanged. If generated: value form may change (string vs `{types,default}`) — acceptable if the workspace + dts build stays green. If reverted to manual: byte-identical to today. +- architecture.config.json: repoint target's 5 entrypoint globs 1:1 to `src/exports/…` — `control.ts` → control; `index.ts`, `orm-postgres.ts`, `testing.ts`, `pg-connection.ts` → shared. Leave the internal-file globs (`descriptors/**`, `orm-migration-resource`, `pg-warm-resource`, `orm-migrate`, `serializer`, `service-keys`, `compute`, `postgres`, `orm-config`, `http`) untouched. +- **Key parity:** keys `.`, `./control`, `./orm`, `./testing`, `./connection`, `./package.json` unchanged. If generated: value form may change (string vs `{types,default}`) — acceptable if the workspace + dts build stays green. If reverted to manual: byte-identical to today. ## Scope IN: lowering, target. OUT: cron/storage/streams (D4b), public packages (D5), D6 collapse, any file rename beyond the barrel-index flattening (which is a move, `index.ts`→`.ts`). diff --git a/.drive/projects/exports-entrypoint/slices/exports-entrypoint/d5-brief.md b/.drive/projects/exports-entrypoint/slices/exports-entrypoint/d5-brief.md index 118fe1cd7..11d204dbe 100644 --- a/.drive/projects/exports-entrypoint/slices/exports-entrypoint/d5-brief.md +++ b/.drive/projects/exports-entrypoint/slices/exports-entrypoint/d5-brief.md @@ -30,10 +30,10 @@ of `src/` + dynamic-import string literals for references to moved files. - Verify: exports map, `dist/` file list, and `bin` field byte-identical to HEAD. ## Package 2 — `@prisma/composer-prisma-cloud` (`packages/9-public/composer-prisma-cloud`) -- `git mv` these 9 thin re-export entrypoints into `src/exports/`: `index.ts`, `control.ts`, `prisma-next.ts`, `testing.ts`, `cron.ts`, `storage.ts`, `storage-testing.ts`, `streams.ts`, `streams-testing.ts`. +- `git mv` these 9 thin re-export entrypoints into `src/exports/`: `index.ts`, `control.ts`, `orm-postgres.ts`, `testing.ts`, `cron.ts`, `storage.ts`, `storage-testing.ts`, `streams.ts`, `streams-testing.ts`. - All import `@internal/...` — no relative fixes. Grep for path references + repoint if any. - tsdown: keep ALL NINE passes, the `externalizeFramework` resolve plugin, the `FRAMEWORK` map, all `outDir`/`external`/`dts:false` options, and `exports:false`. Update ONLY the entries that reference `src/…` files — repoint to `src/exports/…`: - - Pass 1: `{ index: 'src/exports/index.ts', control: 'src/exports/control.ts', 'prisma-next': 'src/exports/prisma-next.ts', testing: 'src/exports/testing.ts' }` + - Pass 1: `{ index: 'src/exports/index.ts', control: 'src/exports/control.ts', 'postgres': 'src/exports/orm.ts', testing: 'src/exports/testing.ts' }` - Pass 2 (→dist/cron): `{ index: 'src/exports/cron.ts' }` - Pass 4 (→dist/storage): `{ index: 'src/exports/storage.ts' }` - Pass 6 (→dist/storage): `{ testing: 'src/exports/storage-testing.ts' }` @@ -41,7 +41,7 @@ of `src/` + dynamic-import string literals for references to moved files. - Pass 9 (→dist/streams): `{ testing: 'src/exports/streams-testing.ts' }` - Passes 3, 5, 8 read `@internal/*/dist/*.mjs` — LEAVE UNCHANGED. - Do NOT touch the hand-maintained `exports` map (nested keys `./cron/scheduler-entrypoint`, `./storage/storage-entrypoint`, `./storage/testing`, `./streams/…`). -- architecture.config.json: repoint the 5 existing per-file globs 1:1 (`control`→control; `index`, `prisma-next`, `testing`, `cron`→shared). The `storage`/`storage-testing`/`streams`/`streams-testing` source files have NO globs today — leave them unmapped (a D6/follow-up concern, same as the storage/streams packages). +- architecture.config.json: repoint the 5 existing per-file globs 1:1 (`control`→control; `index`, `prisma/orm`, `testing`, `cron`→shared). The `storage`/`storage-testing`/`streams`/`streams-testing` source files have NO globs today — leave them unmapped (a D6/follow-up concern, same as the storage/streams packages). - Verify: exports map + `dist/` file list (nested structure) byte-identical to HEAD. ## Scope diff --git a/.drive/projects/exports-entrypoint/slices/exports-entrypoint/d8-brief.md b/.drive/projects/exports-entrypoint/slices/exports-entrypoint/d8-brief.md index 0e59628a5..318dda867 100644 --- a/.drive/projects/exports-entrypoint/slices/exports-entrypoint/d8-brief.md +++ b/.drive/projects/exports-entrypoint/slices/exports-entrypoint/d8-brief.md @@ -71,7 +71,7 @@ cross-ref; main changed it for the bearer key), `composer/tsdown.config.ts`, `streams/tsdown.config.ts`, `cli/package.json`, `cli/tsdown.config.ts`, plus tests: `core/src/__tests__/lowering.test.ts`, `node/src/__tests__/{assemble,node}.test.ts`, `cli/src/__tests__/run.test.ts`, `target/src/__tests__/{control-lowering,extension,invariants}.test.ts`, -`streams/src/__tests__/entrypoint.integration.test.ts`, `target/src/descriptors/prisma-next.ts`. +`streams/src/__tests__/entrypoint.integration.test.ts`, `target/src/descriptors/orm-postgres.ts`. Remember the two grep gaps: internal non-test source importing an entrypoint, and DYNAMIC `await import('…')` string literals. diff --git a/.drive/projects/exports-entrypoint/slices/exports-entrypoint/spec.md b/.drive/projects/exports-entrypoint/slices/exports-entrypoint/spec.md index ffd21cf0c..ebf874a71 100644 --- a/.drive/projects/exports-entrypoint/slices/exports-entrypoint/spec.md +++ b/.drive/projects/exports-entrypoint/slices/exports-entrypoint/spec.md @@ -6,7 +6,7 @@ live in [exports-entrypoint-plan.md](./exports-entrypoint-plan.md). ## At a glance -Adopt prisma-next's exports/entrypoint pattern across the Composer workspace: +Adopt prisma/orm's exports/entrypoint pattern across the Composer workspace: every package's public subpaths become files under `src/exports/`, the shared tsdown base generates each `package.json#exports` map from those entries, and `architecture.config.json` keys plane assignments to entrypoint files instead @@ -18,12 +18,12 @@ of listing every source file. Published subpath keys do not change. subpath. Internal implementation stays where it is at the `src/` root — the presence of `src/exports/` is the separation; no `src/core/` shuffle. - **`tsdown.config.ts` entries list only `src/exports/*.ts`.** -- **`@internal/tsdown-config` generates the exports map.** Port prisma-next's +- **`@internal/tsdown-config` generates the exports map.** Port prisma/orm's `customExports` hook (strip the `exports/` prefix, `./` → `.`, single-entry collapse fix) and switch `exports: true` to `{ enabled: 'local-only', customExports, exclude: [/bin\./] }`. Dist stays flat (`dist/control.mjs`); generated maps are committed. Workspace tsdown is - 0.22.4 — the same version prisma-next runs this hook on. + 0.22.4 — the same version prisma/orm runs this hook on. - **Plane globs key off entrypoints.** `architecture.config.json` maps each `src/exports/.ts` to its plane plus one `src/**` fallback per package; the current 61 mostly-per-file globs collapse. Existing plane assignments @@ -65,7 +65,7 @@ checking the invariant (subpath key sets unchanged) rather than every hunk. **Deliberately out:** - Any change to published subpath keys or their runtime behavior. -- Restructuring internals into `src/core/` (prisma-next does this for +- Restructuring internals into `src/core/` (prisma/orm does this for multi-plane packages; not needed here). - Website, examples, and test/** packages (no published surface). - Renaming the `@internal/*` packages or moving directories. @@ -76,7 +76,7 @@ checking the invariant (subpath key sets unchanged) rather than every hunk. | --- | --- | | Public packages' conditional exports | `@prisma/composer` and `@prisma/composer-prisma-cloud` hand-maintain `{types, default}` maps "for dts bundling", and composer's bin must stay non-importable. Try generation first; keep a manual map only where consumer type resolution demonstrably breaks, and record the exception in the rule. | | `customExports` on pre-move packages | The hook strips an `exports/` prefix that pre-move entries don't have — a no-op — so the tooling change can land first without breaking anything. | -| Single-entry packages (`rpc`, `assemble`) | tsdown collapses one entry to `.`; prisma-next's hook derives the subpath from the output filename. `index.ts` must still map to `.` — covered by the ported hook, verify in D1. | +| Single-entry packages (`rpc`, `assemble`) | tsdown collapses one entry to `.`; prisma/orm's hook derives the subpath from the output filename. `index.ts` must still map to `.` — covered by the ported hook, verify in D1. | | Nested subpaths (`./cron/scheduler-entrypoint`) | Entry keys with slashes (or nested dirs under `exports/`) produce nested subpath keys; composer-prisma-cloud needs this. | | Stale comment in tsdown-config | The base config's comment claims tsdown 0.15.x; actual is 0.22.4. Fix in D1 so nobody "fixes" the config back. | | Glob precedence in depcruise | Specific `src/exports/*.ts` globs must win over the `src/**` fallback; verify a known-bad import still fails `pnpm lint:deps`. | @@ -97,11 +97,11 @@ evidence, not up front. ## References -- prisma-next: `.agents/rules/multi-plane-packages.mdc`, +- prisma/orm: `.agents/rules/multi-plane-packages.mdc`, `no-barrel-files.mdc`, `cli-package-exports.mdc`, `packages/0-config/tsdown/base.ts` (the `customExports` hook to port), `docs/architecture docs/Package-Layering.md` § Package Exports Pattern. - Clone at `/prisma-next`. + Clone at `/prisma/orm`. - Composer investigation: [exports-entrypoint-plan.md](./exports-entrypoint-plan.md). - ADR-0028 (numbered domains/layers, depcruise), `.agents/rules/no-bundling.mdc`, `test-import-patterns.mdc`. @@ -139,7 +139,7 @@ green. pre-move). - **Builds on:** origin/main. - **Hands to:** a base config every later dispatch's rebuild flows through. -- **Focus:** copy prisma-next's hook faithfully (including the single-entry +- **Focus:** copy prisma/orm's hook faithfully (including the single-entry collapse fix); don't redesign it. - **Completed when:** hook in place; `pnpm build` green; `git status` shows no manifest churn; packages still using `exports: false` untouched. @@ -205,7 +205,7 @@ would clobber the others' export map), so those keep their hand-maintained map. `dist/` flattens (`dist/compute/index.mjs` → `dist/compute.mjs`, a value change only). architecture.config.json: `lowering/src/**` glob covers it — no change. -- **`target`**: move the 5 entrypoints (`index`, `control`, `prisma-next`, +- **`target`**: move the 5 entrypoints (`index`, `control`, `prisma/orm`, `testing`, `pg-connection`) into `src/exports/`; internals (`descriptors/`, `pn-*`, `pg-warm-resource`, `serializer`, `service-keys`, `compute`, `postgres`, `http`, `s3-*`, `secret`, `param`, `preflight`) stay at root. @@ -216,7 +216,7 @@ would clobber the others' export map), so those keep their hand-maintained map. resolution breaks, **revert target to the hand-maintained `{types,default}` map and document why** (exception, per the rule). Repoint target's 5 entrypoint globs in architecture.config.json 1:1 (control → control; index, - prisma-next, testing, pg-connection → shared). + prisma/orm, testing, pg-connection → shared). - **Completed when:** both build, key sets identical, tests green, lint:deps green, and the target generated-vs-manual finding is recorded. @@ -263,13 +263,13 @@ byte-identical. the `bin` FIELD (`{"prisma-composer": "./dist/bin.mjs"}`) untouched. Repoint the 11 per-file arch-config globs 1:1. - **composer-prisma-cloud** (`packages/9-public/composer-prisma-cloud`): move - the 9 thin re-export entrypoints (`index`, `control`, `prisma-next`, `testing`, + the 9 thin re-export entrypoints (`index`, `control`, `prisma/orm`, `testing`, `cron`, `storage`, `storage-testing`, `streams`, `streams-testing`) into `src/exports/`. Update ONLY the passes whose entries are `src/…` paths (passes 1,2,4,6,7,9); the re-emit passes (3,5,8) read `@internal/*/dist/*.mjs` — unchanged. Keep the resolve plugin, nested `outDir`s, `exports:false`, and the hand-maintained map (with nested keys) intact. Repoint the 5 existing - per-file arch-config globs 1:1 (`control`→control; `index`, `prisma-next`, + per-file arch-config globs 1:1 (`control`→control; `index`, `prisma/orm`, `testing`, `cron`→shared); the `storage`/`streams`/`*-testing` entries are unmapped today (leave them — a D6/follow-up concern like storage/streams). - **Builds on:** D4b (all `@internal` packages converted). diff --git a/.drive/projects/exports-entrypoint/slices/thin-exports/spec.md b/.drive/projects/exports-entrypoint/slices/thin-exports/spec.md index 538c277e5..6d36e66ef 100644 --- a/.drive/projects/exports-entrypoint/slices/thin-exports/spec.md +++ b/.drive/projects/exports-entrypoint/slices/thin-exports/spec.md @@ -26,9 +26,9 @@ src/ ``` Non-overlapping by construction. Uses the repo's own plane vocabulary -(ADR-0017/0028) rather than prisma-next's `core/` naming. +(ADR-0017/0028) rather than prisma/orm's `core/` naming. -**Note:** this is stricter than prisma-next, whose own +**Note:** this is stricter than prisma/orm, whose own `adapter-postgres/src/exports/column-types.ts` is 185 lines of implementation. Deliberate — the rule is better than the reference. @@ -52,7 +52,7 @@ Already compliant, do not touch: both published packages (all 21 files), | cli | `render-deployment.ts` (55) | `src/render-deployment.ts` (root; cli is all-control via `src/**`) | | lowering | `index.ts` (20) | `src/providers.ts` (root; lowering is all-control via `src/**`) | | target | `control.ts` (130) | `src/control/.ts` | -| target | `pg-connection.ts` (64), `prisma-next.ts` (101), `testing.ts` (21) | `src/.ts` (root, shared) | +| target | `pg-connection.ts` (64), `orm-postgres.ts` (101), `testing.ts` (21) | `src/.ts` (root, shared) | | cron | `scheduler-service.ts` (1), `scheduler-entrypoint.ts` (4) | `src/execution/.ts` | | storage | `storage-service.ts` (12), `storage-entrypoint.ts` (5) | `src/execution/.ts` | | streams | `streams-service.ts` (11), `streams-entrypoint.ts` (37) | `src/execution/.ts` | diff --git a/.drive/projects/forcing-function-apps/design-notes.md b/.drive/projects/forcing-function-apps/design-notes.md index 657bdf7c7..bc585f44a 100644 --- a/.drive/projects/forcing-function-apps/design-notes.md +++ b/.drive/projects/forcing-function-apps/design-notes.md @@ -177,7 +177,7 @@ minted values (rpc accepted keys, streams API key) become target-owned reserved params — declared, schema-validated, carried by the normal serialize/stash pipeline — deleting `restashAddressFree` and the raw env scrapes it fed; (B) the streams contract names its streams (optional per-stream event schema, -untyped retained as the `postgres()` parity), `durableStreams(contract)` +untyped retained as the `rawPostgres()` parity), `durableStreams(contract)` hydrates to per-stream handles that own ensure-create and the proven-safe 404 heal, so no stream lifecycle code remains in userspace. diff --git a/.drive/projects/forcing-function-apps/plan.md b/.drive/projects/forcing-function-apps/plan.md index cec2472c6..262c9f21e 100644 --- a/.drive/projects/forcing-function-apps/plan.md +++ b/.drive/projects/forcing-function-apps/plan.md @@ -97,8 +97,8 @@ current deployment, and cut the team's real instance over. Closes M1. ### Follow-ups (evidence from the S2+S3 port) -- **pnPostgres conversion of `@workspace/db`** (ADR-0022): datahub's db layer - is prisma-next with a contract — exactly what `pnPostgres` types. Converting +- **postgres conversion of `@workspace/db`** (ADR-0022): datahub's db layer + is prisma/orm with a contract — exactly what `postgres` types. Converting would also eliminate the phantom-dependency fragility the port had to pin around (`@prisma-next/*` + `pg` reached only via hoisting; adding the cloud target package broke the hoist and silently degraded `db.orm` to `any`). diff --git a/.drive/projects/forcing-function-apps/slices/datahub-port/spec.md b/.drive/projects/forcing-function-apps/slices/datahub-port/spec.md index 1ac55d4a0..4259aa8b1 100644 --- a/.drive/projects/forcing-function-apps/slices/datahub-port/spec.md +++ b/.drive/projects/forcing-function-apps/slices/datahub-port/spec.md @@ -29,7 +29,7 @@ based on #45's branch). `PORT`, `TICK_INTERVAL_MS`. (* = secret.) - **`apps/web`** — Next.js 16 dashboard, reads postgres through `@workspace/db` in `lib/queries.ts`. -- **`packages/db`** — prisma-next client (`@prisma-next/postgres` + contract +- **`packages/db`** — prisma/orm client (`@prisma-next/postgres` + contract files) over a hand-built `pg` Pool (custom idle-error listener for Compute sleep/resume), **module-global**, reads `process.env.DATABASE_URL` at import. - Current deploy: `prisma.compute.ts` (`@prisma/compute-sdk`), env from @@ -50,7 +50,7 @@ based on #45's branch). ```ts // system.ts (datahub root) export default system('datahub', {}, ({ provision }) => { - const db = provision('database', postgres({ name: 'database' })); + const db = provision('database', rawPostgres({ name: 'database' })); provision('cron', cron('cron', { schedule, router: ingestService }), { db }); provision('web', webService, { db }); return {}; @@ -78,7 +78,7 @@ based on #45's branch). **before** any module that imports `@workspace/db` loads (ingest: entry sets it, then dynamic-imports the app; web: `instrumentation.ts` or equivalent — implementer grounds which runs first in Next 16 standalone). - Converting `@workspace/db` to `pnPostgres` (ADR-0022) is the deep port — + Converting `@workspace/db` to `postgres` (ADR-0022) is the deep port — record it as follow-up evidence, do not do it in this slice. 6. **Framework packages via pkg.pr.new previews of [#45](https://github.com/prisma/composer/pull/45)** (the owner/repo-scoped @@ -91,7 +91,7 @@ based on #45's branch). - Root: `system.ts`, the app config (prismaCloud + nodeBuild + nextjsBuild + state, mirroring storefront-auth's), root `package.json` deploy/destroy scripts, framework deps. -- `apps/ingest`: `service.ts` (compute: deps `{ db: postgres() }`, params per +- `apps/ingest`: `service.ts` (compute: deps `{ db: rawPostgres() }`, params per env.ts with zod schemas + secret facets, expose `{ trigger }`, node build adapter); entry refactor (config from `service.config()`, DATABASE_URL bridge, serveSchedule mounted, in-process `setInterval` scheduler and @@ -119,13 +119,13 @@ based on #45's branch). `build` green; no `@prisma/compute-sdk` deploy config remains. - [x] The in-process tick scheduler is gone; the only clock is `cron.scheduler`. -- [x] Follow-up evidence recorded in the project plan (pnPostgres conversion, +- [x] Follow-up evidence recorded in the project plan (postgres conversion, module-global-client pattern, deploy-values mechanism). ## Non-goals - **Live deploy / cutover (S4)** — needs team secrets + workspace creds. -- **pnPostgres conversion of `@workspace/db`** — recorded follow-up. +- **postgres conversion of `@workspace/db`** — recorded follow-up. - **ClickHouse/PostHog/Stripe as framework resources** — they are external SaaS; params suffice. - **open-chat / M2.** diff --git a/.drive/projects/forcing-function-apps/streams-binding-design.md b/.drive/projects/forcing-function-apps/streams-binding-design.md index f3c5c31e2..af1a23681 100644 --- a/.drive/projects/forcing-function-apps/streams-binding-design.md +++ b/.drive/projects/forcing-function-apps/streams-binding-design.md @@ -135,7 +135,7 @@ prevent. A streams contract **names the streams it transports**, each with an **optional event definition**. Untyped streams are retained deliberately — -the parity is `postgres()`, which binds a real resource without a schema +the parity is `rawPostgres()`, which binds a real resource without a schema contract — but "untyped" only drops the event type, never the lifecycle: no variant of the API requires the app to name streams in call sites, create them, or heal them. @@ -172,7 +172,7 @@ const delivery = await events.jobs.tail({ offset: nextOffset }); - `durableStreams(contract)` hydrates to **one handle per declared stream**, keyed by name. The handle owns the name; no `STREAM` constant. - Bare `durableStreams()` (no contract) is retained for dynamic stream names - (e.g. per-tenant streams, and the raw parity with `postgres()`): it + (e.g. per-tenant streams, and the raw parity with `rawPostgres()`): it hydrates to a client whose surface is `stream(name)` returning an untyped handle. Same lifecycle ownership; the name is data, not app-side protocol handling. @@ -279,7 +279,7 @@ review of the as-built branch: - **The provider contract is the postgres pattern** (`7b07aa0`): the module's port is `Contract<'streams', StreamDefs>` with an honest empty - def map as its unread placeholder — the same encoding `postgresContract` + def map as its unread placeholder — the same encoding `rawPostgresContract` uses. The consumer's required type is equally wide (kind is the whole wiring requirement); literal handle typing comes from the generic parameter. The `never`-typed `__cmp` and its cast are deleted. diff --git a/.gitattributes b/.gitattributes index 84ebb35b3..65a705155 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,6 @@ -# Emitted Prisma Next artifacts (contract emit / migration plan outputs) — +# Emitted Prisma ORM artifacts (contract emit / migration plan outputs) — # mark as generated so GitHub collapses them in diffs and they don't inflate -# PR stats. Mirrors prisma-next's own .gitattributes. +# PR stats. Mirrors prisma/orm's own .gitattributes. **/contract.d.ts linguist-generated **/contract.json linguist-generated **/end-contract.d.ts linguist-generated diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fe9b9f7b1..64bb7d3ad 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,7 +6,7 @@ # # NOTE: verify this team slug is correct for prisma/app before # enabling code-owner-required branch protection — it is carried over -# from prisma-next and may need to change. +# from prisma/orm and may need to change. # # This is intentionally a flat ownership rule: until the maintainer # group is large enough to warrant subsystem-level routing, a single diff --git a/.github/actions/deploy-verify-destroy/action.yml b/.github/actions/deploy-verify-destroy/action.yml index d0dccb815..7068a1f9c 100644 --- a/.github/actions/deploy-verify-destroy/action.yml +++ b/.github/actions/deploy-verify-destroy/action.yml @@ -24,7 +24,7 @@ inputs: sweep-prefixes: description: Space-separated project-name prefixes for ci-cleanup.ts required: false - default: storefront-auth pn-widgets hello + default: storefront-auth orm-demo hello runs: using: composite diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml index 12efb1330..08e2ad187 100644 --- a/.github/workflows/dco.yml +++ b/.github/workflows/dco.yml @@ -1,6 +1,6 @@ name: DCO -# No probot DCO app here (unlike prisma-next) — this workflow enforces +# No probot DCO app here (unlike prisma/orm) — this workflow enforces # sign-off itself by checking every commit in the range for a # "Signed-off-by:" trailer. diff --git a/.github/workflows/e2e-deploy.yml b/.github/workflows/e2e-deploy.yml index 2ce2fb0b5..0a35b8697 100644 --- a/.github/workflows/e2e-deploy.yml +++ b/.github/workflows/e2e-deploy.yml @@ -1,6 +1,6 @@ name: E2E deploy -# Deploys examples/storefront-auth and examples/pn-widgets to real Prisma +# Deploys examples/storefront-auth and examples/orm-demo to real Prisma # Cloud, verifies each round trip, then destroys them. Requires repo secret # PRISMA_SERVICE_TOKEN and repo variable PRISMA_WORKSPACE_ID. @@ -47,8 +47,8 @@ jobs: stack-name: storefront-auth-ci-${{ github.run_id }} verify-command: bash scripts/e2e-verify.sh - deploy-pn-widgets: - name: Deploy, verify, destroy (pn-widgets) + deploy-orm-demo: + name: Deploy, verify, destroy (orm-demo) runs-on: ubuntu-latest # Not a data dependency — runs after storefront so the two deploys never # hold projects/databases concurrently (workspace DB-quota headroom). @@ -65,17 +65,17 @@ jobs: persist-credentials: false - uses: ./.github/actions/deploy-verify-destroy with: - working-directory: examples/pn-widgets - build-filter: '@prisma/example-pn-widgets...' - stack-name: pn-widgets-ci-${{ github.run_id }} + working-directory: examples/orm-demo + build-filter: '@prisma/example-orm-demo...' + stack-name: orm-demo-ci-${{ github.run_id }} verify-command: bun scripts/e2e-verify.ts - destroy-label: 'pn-widgets ' - sweep-prefixes: storefront-auth pn-widgets hello canary bucket + destroy-label: 'orm-demo ' + sweep-prefixes: storefront-auth orm-demo hello canary bucket deploy-bucket: name: Deploy, verify, destroy (bucket) runs-on: ubuntu-latest - needs: deploy-pn-widgets + needs: deploy-orm-demo timeout-minutes: 3 # Secrets aren't available on forked-PR runs; skip rather than fail loudly. if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository @@ -93,7 +93,7 @@ jobs: stack-name: bucket-ci-${{ github.run_id }} verify-command: bun scripts/e2e-verify.ts destroy-label: 'bucket ' - sweep-prefixes: storefront-auth pn-widgets hello canary bucket + sweep-prefixes: storefront-auth orm-demo hello canary bucket cold-connect-canary: name: Cold-connect canary (FT-5226) @@ -162,7 +162,7 @@ jobs: stack-name: streams-canary-ci-${{ github.run_id }} verify-command: bun "${{ github.workspace }}/scripts/cold-start-canary.ts" destroy-label: 'streams-canary ' - sweep-prefixes: storefront-auth pn-widgets hello canary streams-canary bucket resolver + sweep-prefixes: storefront-auth orm-demo hello canary streams-canary bucket resolver logical-id-resolution: name: Logical-id resolution diff --git a/.gitignore b/.gitignore index 68d08322d..3597bc893 100644 --- a/.gitignore +++ b/.gitignore @@ -26,14 +26,14 @@ packages/e2e-tests/.tmp-output .prisma-composer/ .makerkit/ -# Local clone of prisma-next used as a scaffolding reference; not part of this repo. -/prisma-next/ +# Local clone of prisma/orm used as a scaffolding reference; not part of this repo. +/orm/ # External reference checkouts, populated per-worktree by # scripts/setup-worktree-refs.sh (never commit). ignite/ pdp-control-plane/ -prisma-next/ +orm/ datahub/ open-chat/ diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 120000 index 47dc3e3d8..000000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -AGENTS.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..91f2dbe22 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,26 @@ +# Agent guidance — Prisma Composer + +**Before doing any design or implementation work in this repo, read the +guiding principles: [`docs/design/01-principles/`](docs/design/01-principles/).** +They are binding, not advisory. Proposals and code that contradict a recorded +principle are wrong by definition — the principle wins until an ADR supersedes +it. In particular: **we don't bundle the app's code, and we don't guess** — the +framework never bundles/transforms your code, and assembles the deploy artifact +only by documented, deterministic steps (no filename/depth guessing, no tree +laundering; symlinks are preserved as links only when their resolved target +stays inside the bundle, and never dereferenced) +([ADR-0005](docs/design/90-decisions/ADR-0005-users-build-the-framework-assembles.md), +amended by +[ADR-0047](docs/design/90-decisions/ADR-0047-compute-assembly-preserves-safe-runtime-topology.md)). + +For design work, also check: + +- [`docs/design/00-purpose/`](docs/design/00-purpose/) — what this framework is for. +- [`docs/design/90-decisions/README.md`](docs/design/90-decisions/README.md) — + the ADR index. Settled decisions are not relitigated; ground proposals in + what is already decided. + +Operational rules (naming, casts, build isolation, test idioms) live in +[`.agents/rules/`](.agents/rules/) and load automatically in harnesses that +support `.mdc` rules; if yours doesn't, read that directory's README and the +rules relevant to the files you touch. diff --git a/README.md b/README.md index 3c9f43233..ccd16f497 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ flowchart TB Each box is a **Module**: a boundary that owns some code and data and is reachable only through typed ports. catalog and orders each own their own -Postgres — a [Prisma Next](https://github.com/prisma/prisma-next)-typed one, +Postgres — a [Prisma ORM](https://github.com/prisma/orm)-typed one, with migrations applied at deploy — and the root never sees them; the only edges are the exposed, contract-typed RPC ports. Because nothing reaches inside a boundary, every dependency in the app is an explicit, @@ -157,7 +157,7 @@ have. | Guide | Covers | | --- | --- | | [Getting started](docs/guides/getting-started.md) | Your first app end to end; porting an existing Node or Next.js app | -| [Building an app](docs/guides/building-an-app.md) | Contracts, databases (plain + Prisma Next-typed with migrations), reusable Modules, cron/storage/streams, config, secrets | +| [Building an app](docs/guides/building-an-app.md) | Contracts, databases (plain + Prisma-ORM-typed with migrations), reusable Modules, cron/storage/streams, config, secrets | | [Testing](docs/guides/testing.md) | Unit tests with `mockService`, integration tests with `bootstrapService` | | [Deploying and operating](docs/guides/deploying.md) | Stages, destroy, CI, how apps behave in production | @@ -167,7 +167,7 @@ Complete, deployable apps under [`examples/`](examples/): | Example | Demonstrates | | --- | --- | -| [pn-widgets](examples/pn-widgets/) | The minimal app: one service + one Prisma Next-typed Postgres | +| [orm-demo](examples/orm-demo/) | The minimal app: one service + one Prisma-ORM-typed Postgres | | [storefront-auth](examples/storefront-auth/) | Next.js frontend + API service, a reusable Module owning its database, secrets | | [store](examples/store/) | Four modules, typed databases with migrations, the shared cron module | | [cron](examples/cron/) | Scheduled jobs: `defineSchedule` + `serveSchedule` + the cron module | diff --git a/agent-os/product/mission.md b/agent-os/product/mission.md index 31052d896..c27c35cf1 100644 --- a/agent-os/product/mission.md +++ b/agent-os/product/mission.md @@ -55,7 +55,7 @@ without changing app code, enabling fast iteration and easy isolation. ### Collaboration Features - **Composable Components:** Compose pre-packaged units with explicit “ports” (dependencies) and link them together safely. -- **Prisma Next integration:** Seamless integration with Prisma Next so common workflows work out of the box. +- **Prisma ORM integration:** Seamless integration with Prisma ORM so common workflows work out of the box. ### Advanced Features - **Platform interface contract:** Artifact structure + metadata map (JSON) for Prisma Compute/Foundry to provision and run diff --git a/agent-os/product/naming-proposal.md b/agent-os/product/naming-proposal.md index 78f067f8d..7bba9a67c 100644 --- a/agent-os/product/naming-proposal.md +++ b/agent-os/product/naming-proposal.md @@ -5,7 +5,7 @@ > the strategic reasoning (one product identity; why now, with agents). The decision > differs in three ways: the framework is **Prisma App** — not "Prisma Composer"; the > building block is a **System** — not "Hex"; and the registry (proposed here as -> "Hexicon") is **deferred**, its name reopened. **Prisma Next → Prisma Data** remains +> "Hexicon") is **deferred**, its name reopened. **Prisma ORM → Prisma Data** remains > proposed. Where this doc says "Prisma Composer" read "Prisma App", and where it says > "Hex" read "System". @@ -45,8 +45,8 @@ And the platform around them: - **MakerKit → Prisma Composer.** "MakerKit" sounds like a standalone starter kit and doesn't sit in the family. What it actually does is *compose* the other pieces into an app — so the name should just say that. Prisma Composer belongs. -- **Prisma Next → Prisma Data.** This one's a bigger call, so it has [its own - write-up](prisma-data-rename.md). Short version: Prisma Next was going to become +- **Prisma ORM → Prisma Data.** This one's a bigger call, so it has [its own + write-up](prisma-data-rename.md). Short version: Prisma ORM was going to become "Prisma 8", but it isn't really the next ORM — it's a different product with a different mental model, and a version number would promise a smooth upgrade we can't honor (and cap us at "still an ORM"). "Data" names what people actually care about, @@ -56,7 +56,7 @@ And the platform around them: ## One product, many components -Right now each product has its own identity — MakerKit, Prisma Next, Prisma Postgres, +Right now each product has its own identity — MakerKit, Prisma ORM, Prisma Postgres, Prisma Compute (and in the past, Accelerate, Pulse, Optimize). They read like separate products that happen to share a logo. @@ -127,7 +127,7 @@ before it ever ships. ## So, the pitch One identity — the **Prisma App** — with components named for what they do in -context. Rename **MakerKit → Prisma Composer** and **Prisma Next → Prisma Data**. +context. Rename **MakerKit → Prisma Composer** and **Prisma ORM → Prisma Data**. Build **Hexicon** as the Hex registry (hosting stays on npm). And **Prisma Dev** to run it all locally. diff --git a/agent-os/product/naming.md b/agent-os/product/naming.md index 016303130..bec59ec21 100644 --- a/agent-os/product/naming.md +++ b/agent-os/product/naming.md @@ -14,7 +14,7 @@ and **Module** (← System ← Hex), recorded in [ADR-0025](../../docs/design/90-decisions/ADR-0025-name-the-unit-of-composition-module.md). **"Prisma App" names the artifact** — the thing you build and deploy — never the tool. -**Prisma Data** (← Prisma Next) is still proposed. The **registry** name is deferred +**Prisma Data** (← Prisma ORM) is still proposed. The **registry** name is deferred until the registry itself is built; **Prism** is on its shortlist. ## The Prisma product family @@ -26,7 +26,7 @@ building an app is actually *for*: |---|---|---| | Prisma Postgres | persist | my data has a home | | Prisma Compute | execute | my code runs | -| Prisma Data *(← Prisma Next)* | data | I model, access, and manage my data | +| Prisma Data *(← Prisma ORM)* | data | I model, access, and manage my data | | Prisma Composer *(← Prisma App ← MakerKit)* | compose | my app comes together from parts | | Durable Streams | stream | my events flow and survive | | Connection | connect | my services reach each other | diff --git a/agent-os/product/prisma-data-rename.md b/agent-os/product/prisma-data-rename.md index ca060da71..32fdc617e 100644 --- a/agent-os/product/prisma-data-rename.md +++ b/agent-os/product/prisma-data-rename.md @@ -1,8 +1,8 @@ -# Renaming Prisma Next → Prisma Data +# Renaming Prisma ORM → Prisma Data ## The decision -At GA, **Prisma Next becomes Prisma Data** — not "Prisma 8", which was the original +At GA, **Prisma ORM becomes Prisma Data** — not "Prisma 8", which was the original plan. Prisma ORM keeps its name and continues to serve everyone already on it; Prisma Data is the product we point new work at. @@ -19,7 +19,7 @@ here's your upgrade path." Prisma Data can't honor that promise: is more than an ORM, and the name should fit what it's becoming, not what it evolved from. -Forcing Prisma Next into a Prisma 8 shape is also a large, painful compatibility +Forcing Prisma ORM into a Prisma 8 shape is also a large, painful compatibility project — one we take on only to ship a name that misrepresents the product. ## Why "Data" diff --git a/agent-os/product/tech-stack.md b/agent-os/product/tech-stack.md index 51e5fe84c..055d62074 100644 --- a/agent-os/product/tech-stack.md +++ b/agent-os/product/tech-stack.md @@ -8,7 +8,7 @@ platform primitives it targets on the Prisma Platform. - **Runtime (target):** Bun (Prisma Compute runs JS on Bun instances) - **Runtime (dev/tooling):** Node.js `>=22.18` (the version that turns TypeScript type stripping on by default, which is how the CLI imports the user's `.ts` entry) + Bun where useful - **Distribution:** npm packages (Prisma Composer as a library + companion packages) -- **Package Manager:** pnpm (match Prisma Next; pnpm workspaces) +- **Package Manager:** pnpm (match Prisma ORM; pnpm workspaces) - **Monorepo tooling:** Turborepo (task runner + caching) - **Module system:** ESM (`"type": "module"`) @@ -18,7 +18,7 @@ platform primitives it targets on the Prisma Platform. - **File Storage:** Prisma File Storage (working title; not yet created) - **Streaming:** Durable Streams (working title; not yet created) -## Build & Tooling (match Prisma Next) +## Build & Tooling (match Prisma ORM) - **Build system:** Turbo pipelines (`turbo run build`, `turbo watch build`) - **Package builds:** tsdown (base config) and tsup (where needed) - **Typechecking:** `tsc --noEmit` with TS project references @@ -35,16 +35,16 @@ platform primitives it targets on the Prisma Platform. ## Integrations - **ORM / Data Access:** Prisma ORM -- **Framework integration:** Prisma Next (must integrate seamlessly) +- **Framework integration:** Prisma ORM (must integrate seamlessly) ## Local Dev & Testing - **Local runtime:** Local implementations/emulators of platform services (storage/streams/compute) swapped via DI - **Test isolation:** Easy per-test/per-suite environment isolation by swapping implementations and/or provisioning isolated resources -- **Test Framework:** Vitest (match Prisma Next) -- **Frontend/tooling (if needed):** Vite (match Prisma Next toolchain usage) +- **Test Framework:** Vitest (match Prisma ORM) +- **Frontend/tooling (if needed):** Vite (match Prisma ORM toolchain usage) ## Quality -- **Linting/Formatting:** Biome (match Prisma Next) +- **Linting/Formatting:** Biome (match Prisma ORM) - **ESLint:** Used selectively (e.g., custom lint rules packaged as an ESLint plugin), otherwise prefer Biome - **Type Safety:** TypeScript strict mode (recommended; align with shared tsconfig) diff --git a/architecture.config.json b/architecture.config.json index 5ff5b6edf..cf1d11c60 100644 --- a/architecture.config.json +++ b/architecture.config.json @@ -157,7 +157,7 @@ "plane": "shared" }, { - "glob": "packages/1-prisma-cloud/1-extensions/target/src/prisma-next.ts", + "glob": "packages/1-prisma-cloud/1-extensions/target/src/orm-postgres.ts", "domain": "prisma-cloud", "layer": "extensions", "plane": "shared" @@ -229,13 +229,13 @@ "plane": "shared" }, { - "glob": "packages/1-prisma-cloud/1-extensions/target/src/prisma-next-migrate.ts", + "glob": "packages/1-prisma-cloud/1-extensions/target/src/orm-migrate.ts", "domain": "prisma-cloud", "layer": "extensions", "plane": "control" }, { - "glob": "packages/1-prisma-cloud/1-extensions/target/src/pn-migration-resource.ts", + "glob": "packages/1-prisma-cloud/1-extensions/target/src/orm-migration-resource.ts", "domain": "prisma-cloud", "layer": "extensions", "plane": "control" @@ -265,7 +265,7 @@ "plane": "shared" }, { - "glob": "packages/1-prisma-cloud/1-extensions/target/src/postgres.ts", + "glob": "packages/1-prisma-cloud/1-extensions/target/src/raw-postgres.ts", "domain": "prisma-cloud", "layer": "extensions", "plane": "shared" @@ -277,13 +277,13 @@ "plane": "shared" }, { - "glob": "packages/1-prisma-cloud/1-extensions/target/src/exports/prisma-next.ts", + "glob": "packages/1-prisma-cloud/1-extensions/target/src/exports/orm.ts", "domain": "prisma-cloud", "layer": "extensions", "plane": "shared" }, { - "glob": "packages/1-prisma-cloud/1-extensions/target/src/pn-config.ts", + "glob": "packages/1-prisma-cloud/1-extensions/target/src/orm-config.ts", "domain": "prisma-cloud", "layer": "extensions", "plane": "shared" @@ -799,7 +799,7 @@ "plane": "shared" }, { - "glob": "packages/9-public/composer-prisma-cloud/src/exports/prisma-next.ts", + "glob": "packages/9-public/composer-prisma-cloud/src/exports/orm.ts", "domain": "public", "layer": "public", "plane": "shared" diff --git a/biome.jsonc b/biome.jsonc index 6f436cbc7..a2160d392 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -12,7 +12,7 @@ "!**/dist-*", "!**/coverage", "!**/node_modules", - "!prisma-next", + "!orm", "!datahub", "!docs", "!.agents", @@ -23,11 +23,11 @@ "!**/fixtures/**/emitted", "!**/examples/**/migrations", // The auth pack's emitted/authored artifacts: byte-owned by - // `prisma-next contract emit` / `migration plan` — reformatting them + // `prisma contract emit` / `migration plan` — reformatting them // would dirty the tree on every regeneration. "!packages/1-prisma-cloud/2-shared-modules/auth/src/pack/contract.json", "!packages/1-prisma-cloud/2-shared-modules/auth/src/pack/migrations", - "!examples/pn-widgets/contract.json", + "!examples/orm-demo/contract.json", "!**/pnpm-lock.yaml" ] }, diff --git a/dependency-cruiser.config.mjs b/dependency-cruiser.config.mjs index 54e072673..28ba93ee9 100644 --- a/dependency-cruiser.config.mjs +++ b/dependency-cruiser.config.mjs @@ -5,7 +5,7 @@ * * Derives module groups from architecture.config.json and encodes the * same-layer/downward-only semantics for domains and layers, plus the - * control/execution plane split (ADR-0017). Copied from Prisma Next's + * control/execution plane split (ADR-0017). Copied from Prisma ORM's * implementation (its Package-Layering doc and ADR 140), with two additions: * 9-public is a sink (no internal package imports it), and examples plus the * integration tests may import only the 9-public packages. @@ -228,6 +228,8 @@ export default { 'vitest\\..*config', 'tsdown\\.config', 'next\\.config', + 'prisma\\.config', + 'prisma\\.config', '\\.d\\.ts$', '\\.d\\.mts$', 'dist', diff --git a/docs/design/00-purpose/README.md b/docs/design/00-purpose/README.md index 2a0cf4c08..e0935f915 100644 --- a/docs/design/00-purpose/README.md +++ b/docs/design/00-purpose/README.md @@ -17,7 +17,7 @@ between Modules, and the topology they produce. It borrows everything underneath rather than reinventing it: - **Alchemy** — the resource model and the provisioning engine. -- **Prisma Next** — data contracts, the interface to data resources. +- **Prisma ORM** — data contracts, the interface to data resources. - **Prisma Cloud** — hosting, as one deployment target (shipped as an extension pack). ## Read next diff --git a/docs/design/00-purpose/goals.md b/docs/design/00-purpose/goals.md index 543d6f413..e7211f27d 100644 --- a/docs/design/00-purpose/goals.md +++ b/docs/design/00-purpose/goals.md @@ -29,7 +29,7 @@ from the purpose, not the other way around. independently. - **Bundling or compiling your code.** Bundlers (tsdown, Bun.build, Alchemy) do that; the framework references the bundles. -- **Generating data contracts.** Prisma Next produces those; the framework +- **Generating data contracts.** Prisma ORM produces those; the framework references them. - **UI / client-framework bindings.** React hooks and Convex-style embedding are a deployment-target pack concern, not the core. diff --git a/docs/design/01-principles/guiding-principles.md b/docs/design/01-principles/guiding-principles.md index 659379f07..cc816d897 100644 --- a/docs/design/01-principles/guiding-principles.md +++ b/docs/design/01-principles/guiding-principles.md @@ -13,7 +13,7 @@ general; let composition produce the richness. ## Don't reinvent the wheel Where an established solution exists in the ecosystem — Alchemy for provisioning, -Prisma Next for data contracts, Prisma Cloud for hosting — use it rather than +Prisma ORM for data contracts, Prisma Cloud for hosting — use it rather than building our own. The framework builds only the composition layer that's genuinely missing. diff --git a/docs/design/03-domain-model/authoring-surface.md b/docs/design/03-domain-model/authoring-surface.md index a3af68091..ebfa1cf52 100644 --- a/docs/design/03-domain-model/authoring-surface.md +++ b/docs/design/03-domain-model/authoring-surface.md @@ -12,12 +12,12 @@ Grounding example — a service with a Postgres dependency: ```ts import { compute, postgres } from "@prisma/composer-prisma-cloud" -export default compute({ db: postgres() }, ({ db }) => +export default compute({ db: rawPostgres() }, ({ db }) => Bun.serve({ port, fetch: async () => Response.json(await db`select 1 as ok`) }) ) ``` -`postgres()` is a **Resource** the service depends on; `compute(deps, handler)` is a +`rawPostgres()` is a **Resource** the service depends on; `compute(deps, handler)` is a **Service**. The framework provisions the Postgres and the compute unit, injects a typed `db` client into the handler, and serves — with no `process.env` in the service code. The vocabulary (`compute`, `postgres`, `http`, …) belongs to the target; the wiring @@ -34,7 +34,7 @@ consumes and produces them: ```ts // a service with an Input (db) and an Output (its API) export default compute( - { db: postgres() }, + { db: rawPostgres() }, ({ db }) => ({ api: Auth.serve(buildApp(db)) }) // returned ⇒ Output ) ``` @@ -58,7 +58,7 @@ provider happened to publish. export const Auth = http(AuthInterface) // neutral connection type export default compute( - { db: postgres(), auth: Auth }, // Auth as dependency ⇒ Input ⇒ typed client + { db: rawPostgres(), auth: Auth }, // Auth as dependency ⇒ Input ⇒ typed client (deps) => ({ web: next(deps) }) // 'web' Output served by a Next.js adapter ) ``` @@ -81,7 +81,7 @@ import store, { Auth } from "./storefront-service" export default module("storefront", { auth: Auth, web: http(StoreInterface) }, // module-level ports ({ auth, provision }) => { - const db = provision(postgres()) // the Module owns resources + const db = provision(rawPostgres()) // the Module owns resources const svc = provision(store, { db, auth }) // forward module Input → service Input return { web: svc.web } // forward service Output → module Output } @@ -97,7 +97,7 @@ complete. ## The returned data is the manifest -A constructor like `compute(...)` or `postgres()` runs no logic — it returns a plain +A constructor like `compute(...)` or `rawPostgres()` runs no logic — it returns a plain data object (see [core and targets](core-and-targets.md)). That object *is* the manifest: there is no separate framework manifest file (the platform's `compute.manifest.json` stays — it only names the boot entrypoint). The same value is diff --git a/docs/design/03-domain-model/core-and-targets.md b/docs/design/03-domain-model/core-and-targets.md index 30b1e0733..4d759be45 100644 --- a/docs/design/03-domain-model/core-and-targets.md +++ b/docs/design/03-domain-model/core-and-targets.md @@ -16,14 +16,14 @@ Grounding example — a service with a Postgres dependency, deployed to Prisma C ```ts import { compute, postgres } from "@prisma/composer-prisma-cloud" -export default compute({ db: postgres() }, ({ db }) => Bun.serve(/* uses db */)) +export default compute({ db: rawPostgres() }, ({ db }) => Bun.serve(/* uses db */)) ``` - **`@prisma/composer`** knows three kinds — **Service**, **Resource**, **Connection** — as a graph, plus the machinery to Load, validate, lower, and run that graph. It imports no deployment target. It never learns what "Postgres" or "Compute" is. - **`@prisma/composer-prisma-cloud`** (a target pack) provides the concrete vocabulary: - `compute()` — a Service; `postgres()` — a Resource; and connection types like + `compute()` — a Service; `rawPostgres()` — a Resource; and connection types like `http()`. Each is an ergonomic constructor that returns a **plain data object** carrying the metadata that routes it to an Alchemy Stack/Provider. @@ -32,7 +32,7 @@ underneath. ## Constructors return data, not behaviour -`postgres()` does not *do* anything — it returns a description: +`rawPostgres()` does not *do* anything — it returns a description: ```ts // inside @prisma/composer-prisma-cloud — illustrative shape diff --git a/docs/design/03-domain-model/domain-map.md b/docs/design/03-domain-model/domain-map.md index 4661c78bc..eabb112bd 100644 --- a/docs/design/03-domain-model/domain-map.md +++ b/docs/design/03-domain-model/domain-map.md @@ -18,7 +18,7 @@ flowchart TB Input["Input — requires"] Output["Output — provides"] Conn["Connection"] - DC["Data Contract — Prisma Next"] + DC["Data Contract — Prisma ORM"] Topology -->|contains| Module Topology -->|contains| Resource diff --git a/docs/design/03-domain-model/glossary.md b/docs/design/03-domain-model/glossary.md index 5cbaacbdf..07a9390d8 100644 --- a/docs/design/03-domain-model/glossary.md +++ b/docs/design/03-domain-model/glossary.md @@ -67,7 +67,7 @@ provisioned third-party account is a Resource too — a Stripe product, a Tigris bucket, a Prisma-brokered Mailchimp account. The test is whether something manages its lifecycle, not whether it's first-party. -- **First-class**: Prisma **Postgres** (data, via Prisma Next contracts) — +- **First-class**: Prisma **Postgres** (data, via Prisma ORM contracts) — the framework-native treatment. - **BYO**: any Alchemy resource (object storage, cache, queue, provisioned third-party) exposed through a capability Layer. The Module depends on the @@ -193,7 +193,7 @@ that external thing as a node, provision it (a Resource) or wrap it in a Service ### Data Contract -A **Prisma Next** contract — a deterministic, hashable description of the schema +A **Prisma ORM** contract — a deterministic, hashable description of the schema slice a Module may access (identified by its `storageHash`). A Module's Data Input declares the contract it requires; this is also the per-Module least-privilege scope. @@ -209,7 +209,7 @@ shared, the enclosing **implicit root Module** that wires it to its consumers. T owner owns the schema and the migration; each consumer connects via a Data Input declaring the contract slice it needs. The owner's schema must satisfy the **aggregate** — the union of every consumer's contract — and consumer slices must -not overlap (a Prisma Next concept). The cloud can verify the live DB satisfies the +not overlap (a Prisma ORM concept). The cloud can verify the live DB satisfies the aggregate via the marker/ledger. ## Planes & process @@ -224,7 +224,7 @@ The deploy report calls a thing on the hosting plane a **Deployment entity** ### Lowering The compilation from one plane to the next: authoring topology → provisioning -resource graph → hosting primitives. Analogous to Prisma Next lowering a contract +resource graph → hosting primitives. Analogous to Prisma ORM lowering a contract to a plan. ### Control plane / Execution plane @@ -325,7 +325,7 @@ conform (dependency inversion). ### provision The Module-scoped operator that turns a dependency descriptor into an **owned -Resource** (`provision(postgres())`) or instantiates and wires an owned node +Resource** (`provision(rawPostgres())`) or instantiates and wires an owned node (`provision(svc, { db })`). Ownership and provisioning are a Module concern; a Service only *requires*. Forwarding is just passing a Module's Inputs down and returning owned nodes' Outputs up. @@ -472,7 +472,7 @@ is where the framework's own binding layer gets built. application you build and deploy. Use **Topology** for the wired graph and **Module** for a unit; **App** for the composed whole. - **Descriptor** → an internal/substrate term; avoid in the authoring vocabulary - (and note Prisma Next uses "Descriptor" for its own components). + (and note Prisma ORM uses "Descriptor" for its own components). - **Durable Stream as "the backbone"** → streams are *one of two* transports (alongside request/response), not the universal substrate. See the streaming reconciliation note in the decisions log. diff --git a/docs/design/03-domain-model/layering.md b/docs/design/03-domain-model/layering.md index b24acc0be..53507abdf 100644 --- a/docs/design/03-domain-model/layering.md +++ b/docs/design/03-domain-model/layering.md @@ -5,7 +5,7 @@ in the framework's plane; the framework **lowers** that into the Alchemy/Effect **provisioning plane**, which provisions it to run as Prisma Cloud hosting primitives. -This is the same shape as Prisma Next: an authored data contract lowers to an IR +This is the same shape as Prisma ORM: an authored data contract lowers to an IR / plan, which executes against a database. Here, an authored topology lowers to a resource graph, which deploys to the cloud. @@ -94,7 +94,7 @@ Layer); the wire is valid iff the provided capability satisfies the required one Alchemy type-checks it. - **First-class** (Postgres): framework-native treatment — Postgres data uses - Prisma Next data contracts (hashed, verifiable). (Compute is a Service's target, + Prisma ORM data contracts (hashed, verifiable). (Compute is a Service's target, not a Resource; a stream is a connection style.) - **BYO** (object storage, cache, queues, third-party): *any* Alchemy resource, exposed through a capability Layer. The Module depends on the capability, not diff --git a/docs/design/10-domains/core-model.md b/docs/design/10-domains/core-model.md index b4d711d80..7b1fb3b1f 100644 --- a/docs/design/10-domains/core-model.md +++ b/docs/design/10-domains/core-model.md @@ -66,7 +66,7 @@ is small and pure and still sits on `.`; the config *types* (`defineConfig`, | `@prisma/composer` | node factories (`service`, `resource`, `dependency`, `module`), `Load`, `configOf`, `hydrate`, `BuildAdapter` type, model types (incl. `Config`) | nothing | | `@prisma/composer/config` | `defineConfig`, `PrismaAppConfig`, `ExtensionDescriptor`, `NodeDescriptor`, `PreflightInput`, `TeardownInput` — the types `prisma-composer.config.ts` is checked against (ADR-0017) | nothing (types + one identity function) | | `@prisma/composer/deploy` | `lower()`, `lowering()`, the SPI types (`ServiceLowering`, `Lowering`, `ApplicationDescriptor`, `ProvisionerDescriptor`, `LowerContext`, `Outputs`, `LoweredResult`, `DeployedEntity`), `Bundle`/`AssembleInput` (the assembler's contract, defined once here) | `alchemy`, `effect` | -| `@prisma/composer-prisma-cloud` | `compute()` (declares a service; carries `run`/`load`), `postgres()` (`{ name }` identity or `{ client }` dependency, by argument shape) + `postgresContract`, `http()` | `@prisma/composer` only | +| `@prisma/composer-prisma-cloud` | `compute()` (declares a service; carries `run`/`load`), `rawPostgres()` (`{ name }` identity or `{ client }` dependency, by argument shape) + `rawPostgresContract`, `http()` | `@prisma/composer` only | | `@prisma/composer/arktype` | `secretString()` — the arktype spelling of a `SecretString` input leaf (ADR-0042). Opt-in: no other entry imports it, so a Zod app never loads arktype | `arktype` | | `@prisma/composer/service-rpc` | the RPC Contract kind — `contract()`, `rpc()`, `serve()`, the typed client binding (see [`connection-contracts.md`](connection-contracts.md)) | `@prisma/composer` + a Standard Schema validator | | `@prisma/composer-prisma-cloud/cron` | cron as a driver (see [ADR-0020](../90-decisions/ADR-0020-scheduled-work-is-a-driver-not-a-resource.md)) — `defineSchedule`, `serveSchedule`, `cronScheduler`, `cron()`, `triggerContract` | `@prisma/composer` + `app-node` + `app-rpc` | @@ -276,7 +276,7 @@ interface BuildAdapter { // ——— Nodes ——— // A Resource's identity: the ONE place a piece of infrastructure exists. A module -// provisions it (`h.provision("db", postgres({ name: "db" }))`) and wires the +// provisions it (`h.provision("db", rawPostgres({ name: "db" }))`) and wires the // returned ref into each consumer's dependency slot — a resource is never // created because a service mentioned it. `provides` is the Contract it offers // (its single port); the routing `type` is DERIVED as `provides.kind`, so a @@ -869,31 +869,31 @@ import { resource, dependency, service, configOf, hydrate, string, number, type Contract, type Deps, type DependencyEnd, type Loaded, type ResourceNode, type RunnableServiceNode } from "@prisma/composer" -export interface PostgresConfig { readonly url: string } +export interface RawPostgresConfig { readonly url: string } // The contract a Postgres provides AND its consumers require. satisfies() // compares KIND, not identity — a pack module can be duplicated across a // workspace (same rationale as the Symbol.for node brand), and every // duplicate's contract must still satisfy. -export const postgresContract: Contract<"postgres", PostgresConfig> = Object.freeze({ +export const rawPostgresContract: Contract<"postgres", RawPostgresConfig> = Object.freeze({ kind: "postgres", __cmp: { url: "" }, satisfies: (required) => required.kind === "postgres", }) // ONE postgres factory, two shapes. { name }: the identity a module provisions — -// the ONE place the database exists, providing postgresContract. postgres() +// the ONE place the database exists, providing rawPostgresContract. rawPostgres() // (no args): the consumer's dependency requiring it. No client factory — the -// dependency's BINDING is the typed config PostgresConfig itself (hydrate is +// dependency's BINDING is the typed config RawPostgresConfig itself (hydrate is // the identity on its values); the app builds its own client from { url } in // app code (ADR-0015). -export function postgres(opts: { name: string }): ResourceNode -export function postgres(): DependencyEnd +export function postgres(opts: { name: string }): ResourceNode +export function rawPostgres(): DependencyEnd export function postgres(opts?: { name: string }): unknown { - if (opts?.name !== undefined) return resource({ name: opts.name, extension: "@prisma/composer-prisma-cloud", provides: postgresContract }) + if (opts?.name !== undefined) return resource({ name: opts.name, extension: "@prisma/composer-prisma-cloud", provides: rawPostgresContract }) return dependency({ type: "postgres", connection: { params: { url: string() }, hydrate: (v) => v }, - required: postgresContract, + required: rawPostgresContract, }) } @@ -1165,14 +1165,14 @@ only; the app writes and bundles its own entry: ```ts // src/service.ts — the authored service: name + deps + build + where it lives. -// No handler, no driver. `db` is a DEPENDENCY (a slot): `postgres()` requires -// postgresContract and never provisions anything — the composing module owns the -// database and wires its ref in. Its binding is `PostgresConfig` ({ url }); the +// No handler, no driver. `db` is a DEPENDENCY (a slot): `rawPostgres()` requires +// rawPostgresContract and never provisions anything — the composing module owns the +// database and wires its ref in. Its binding is `RawPostgresConfig` ({ url }); the // app builds its own client in server.ts (ADR-0015). import { compute, postgres } from "@prisma/composer-prisma-cloud" import node from "@prisma/composer/node" -const db = postgres() +const db = rawPostgres() export default compute({ name: "hello", // ADR-0006: every node named @@ -1183,14 +1183,14 @@ export default compute({ }) // src/module.ts — the app root: the module OWNS the database. It provisions the -// identity `postgres({ name })` and wires its ref into the service's slot (the +// identity `rawPostgres({ name })` and wires its ref into the service's slot (the // contract matches); its name names the app (ADR-0006). import { module } from "@prisma/composer" import { postgres } from "@prisma/composer-prisma-cloud" import service from "./service.ts" export default module("hello", (h) => { - const db = h.provision("db", postgres({ name: "db" })) + const db = h.provision("db", rawPostgres({ name: "db" })) h.provision("hello", service, { db }) }) @@ -1200,7 +1200,7 @@ export default module("hello", (h) => { import { SQL } from "bun" // the APP's choice of client import service from "./service" -const { db, port } = service.load() // db: PostgresConfig ({ url }); port: number +const { db, port } = service.load() // db: RawPostgresConfig ({ url }); port: number const sql = new SQL({ url: db.url }) // module-scoped: one pool per process Bun.serve({ port, hostname: "0.0.0.0", fetch: async () => Response.json(await sql`select 1 as ok`) }) @@ -1215,8 +1215,8 @@ Bun.serve({ port, hostname: "0.0.0.0", // Alchemy — no bundle map, no hand-written stack file. ``` -`service.load()` is typed end to end by the chain `postgres()` → -`PostgresConfig` → `compute({ deps: { db } })` captures `{ db: PostgresConfig }` → +`service.load()` is typed end to end by the chain `rawPostgres()` → +`RawPostgresConfig` → `compute({ deps: { db } })` captures `{ db: RawPostgresConfig }` → `load()` returns it, and the app types its own `sql` from `db.url`. The app never annotates a dependency type. Note where Bun appears: only in `server.ts` (the `new SQL` client and `Bun.serve`, the app's own entry) — the app's choice, since @@ -1232,7 +1232,7 @@ model promises. The config round-trip is proven separately at the pack level ### Two services, connected — the module (a framework-hosted consumer) The storefront-auth shape: `auth` is a self-served Hono service shaped like the -one above — its `db` is a `postgres()` dependency whose binding is the config +one above — its `db` is a `rawPostgres()` dependency whose binding is the config its own server builds a client from, while the composing module below owns and provisions the database; `storefront` is a **framework-hosted** Next.js service whose page pulls the `auth` @@ -1269,7 +1269,7 @@ import { postgres } from "@prisma/composer-prisma-cloud" import authService from "./modules/auth/src/service" import storefrontService from "./modules/storefront/src/service" export default module("storefront-auth", (h) => { - const db = h.provision("db", postgres({ name: "db" })) + const db = h.provision("db", rawPostgres({ name: "db" })) const authRef = h.provision("auth", authService, { db }) // db→auth dependency edge h.provision("storefront", storefrontService, { auth: authRef }) // auth→storefront dependency edge }) diff --git a/docs/design/10-domains/local-dev.md b/docs/design/10-domains/local-dev.md index 0115f0d18..4e9f3648d 100644 --- a/docs/design/10-domains/local-dev.md +++ b/docs/design/10-domains/local-dev.md @@ -176,7 +176,7 @@ semantics): | `ServiceKey` | **unchanged** — mints locally, persists in state | | `S3Credentials` | **unchanged** — mints locally, persists in state | | `PgWarm` | **unchanged** — real `select 1` against the local URL | -| `PnMigration` | **unchanged** — real migrations against the local URL | +| `OrmMigration` | **unchanged** — real migrations against the local URL | Because module-backed kinds (storage, streams, email) lower to compute services plus databases, they run their **real service code** locally against local @@ -185,7 +185,7 @@ SQLite test server remains a testing utility, not part of the dev loop. ### Postgres -The emulator is the ORM CLI's local Postgres (`prisma dev`), **one named, detached instance per `Database` resource** — instance names are derived from the app and database ids, so instances are isolated, discoverable (`prisma dev ls`), and survive across dev sessions for warm starts. Migrations are not special-cased: `PnMigration` runs exactly as it does in a deploy, against the local URL — replay-only (ADR-0022 as revised), so it applies committed migrations and never synthesizes schema. Dev-loop schema iteration therefore happens through the ORM's own `prisma db update`, run directly against the emulator database: `db update` moves the database and its marker to the current contract, and the pipeline's migration step no-ops because the marker matches the target. A dev run against a database that was neither updated nor covered by a planned migration hits the same structured refusal a deploy would, naming both exits (`prisma db update` to iterate, `contract emit` + `migration plan` to author the path). `PgWarm` is near-instant locally and is kept (not stubbed) so the provider set stays uniform. +The emulator is the ORM CLI's local Postgres (`prisma dev`), **one named, detached instance per `Database` resource** — instance names are derived from the app and database ids, so instances are isolated, discoverable (`prisma dev ls`), and survive across dev sessions for warm starts. Migrations are not special-cased: `OrmMigration` runs exactly as it does in a deploy, against the local URL — replay-only (ADR-0022 as revised), so it applies committed migrations and never synthesizes schema. Dev-loop schema iteration therefore happens through the ORM's own `prisma db update`, run directly against the emulator database: `db update` moves the database and its marker to the current contract, and the pipeline's migration step no-ops because the marker matches the target. A dev run against a database that was neither updated nor covered by a planned migration hits the same structured refusal a deploy would, naming both exits (`prisma db update` to iterate, `contract emit` + `migration plan` to author the path). `PgWarm` is near-instant locally and is kept (not stubbed) so the provider set stays uniform. ### Buckets: a disk-backed S3 emulator @@ -324,8 +324,8 @@ replaces. no `watch` entries reports `[dev]
has no watchable inputs` at startup and needs its rebuilds triggered manually. - **App-owned migrations are not run by `dev`** (by design — ADR-0022, spec - § 4's `PnMigration` line is for framework-run migrations only). An app that - runs its own migrations (e.g. via `prisma-next db init`, like the + § 4's `OrmMigration` line is for framework-run migrations only). An app that + runs its own migrations (e.g. via `prisma db init`, like the open-chat port) needs that as a manual step against the local Postgres URL on a fresh dev instance; `dev` does not know to run it automatically. diff --git a/docs/design/90-decisions/ADR-0003-deploy-derives-everything-from-the-root-node.md b/docs/design/90-decisions/ADR-0003-deploy-derives-everything-from-the-root-node.md index 2609fd72e..3bed5f5ac 100644 --- a/docs/design/90-decisions/ADR-0003-deploy-derives-everything-from-the-root-node.md +++ b/docs/design/90-decisions/ADR-0003-deploy-derives-everything-from-the-root-node.md @@ -20,7 +20,7 @@ wiring it in. import { compute, postgres } from "@prisma/composer-prisma-cloud"; import node from "@prisma/composer/node"; -const db = postgres(); // a dependency slot: the binding is typed config (ADR-0015) +const db = rawPostgres(); // a dependency slot: the binding is typed config (ADR-0015) export default compute({ name: "hello", @@ -34,7 +34,7 @@ import { postgres } from "@prisma/composer-prisma-cloud"; import service from "./service.ts"; export default module("hello", ({ provision }) => { - const db = provision(postgres({ name: "db" })); + const db = provision(rawPostgres({ name: "db" })); provision(service, { deps: { db } }); }); ``` diff --git a/docs/design/90-decisions/ADR-0006-every-node-is-named.md b/docs/design/90-decisions/ADR-0006-every-node-is-named.md index c238ba3a5..87c8e8a0e 100644 --- a/docs/design/90-decisions/ADR-0006-every-node-is-named.md +++ b/docs/design/90-decisions/ADR-0006-every-node-is-named.md @@ -45,7 +45,7 @@ standing deployment in the same workspace. One nuance at the core layer: a dependency end constructed by an authoring surface with no room for a name argument (e.g. `rpc(contract)`, which takes only the contract) defaults its name to the connection's type. Pack factories -that take an options object (`http({ name })`, `postgres({ name, … })`) +that take an options object (`http({ name })`, `rawPostgres({ name, … })`) require the explicit name. ## Consequences diff --git a/docs/design/90-decisions/ADR-0012-the-state-store-speaks-sql-directly.md b/docs/design/90-decisions/ADR-0012-the-state-store-speaks-sql-directly.md index af4551e46..c3625c1a3 100644 --- a/docs/design/90-decisions/ADR-0012-the-state-store-speaks-sql-directly.md +++ b/docs/design/90-decisions/ADR-0012-the-state-store-speaks-sql-directly.md @@ -1,11 +1,11 @@ -# ADR-0012: The state store speaks SQL directly; Prisma Next adoption is deferred +# ADR-0012: The state store speaks SQL directly; Prisma ORM adoption is deferred -> Closed as obsolete by [ADR-0045](ADR-0045-deploy-state-lives-behind-the-platform-state-api.md), via this record's own pick-up trigger: the platform-side state API landed, the SQL store is gone, and composer speaks the API through Alchemy's stock HTTP client — there is no store data layer left to adopt Prisma Next for. +> Closed as obsolete by [ADR-0045](ADR-0045-deploy-state-lives-behind-the-platform-state-api.md), via this record's own pick-up trigger: the platform-side state API landed, the SQL store is gone, and composer speaks the API through Alchemy's stock HTTP client — there is no store data layer left to adopt Prisma ORM for. ## Decision The hosted state store's data access is hand-written SQL over a plain Postgres -driver (postgres.js) — not Prisma Next. Adopting Prisma Next for the store is +driver (postgres.js) — not Prisma ORM. Adopting Prisma ORM for the store is deferred, with the pick-up triggers recorded below, not rejected. ## Reasoning @@ -26,7 +26,7 @@ already typed at the boundary, because the store implements the engine's `StateService` interface; no caller ever sees a row shape. That is the context in which an ORM's value proposition has to argue. -Feasibility is not the question — Prisma Next fits this shape today. Its +Feasibility is not the question — Prisma ORM fits this shape today. Its control API applies a contract's schema programmatically (`dbUpdate` with apply mode reconciles the database and writes its own marker, refusing destructive plans by default), which matches the store's bootstrap-at-runtime @@ -56,7 +56,7 @@ decision flips when the balance does. **Pick-up triggers.** Revisit adoption when any of these holds: - the store's schema or queries grow past trivial key-value shapes; -- exercising Prisma Next from inside a library (framework dogfooding) becomes +- exercising Prisma ORM from inside a library (framework dogfooding) becomes worth the re-proof cost on its own; - the platform-side state API lands — in which case this store shrinks to a client or disappears, and this record closes as obsolete rather than @@ -67,14 +67,14 @@ decision flips when the balance does. - The store's data layer stays a few hundred lines of reviewable SQL with no framework dependency; its test suite runs against a real Postgres. - The lock's proven driver-specific behavior remains undisturbed. -- The store does not dogfood Prisma Next — a deliberate, recorded trade, not +- The store does not dogfood Prisma ORM — a deliberate, recorded trade, not an oversight. - The schema-creation code carries a pointer to this record so the deferral stays discoverable at the site it governs. ## Alternatives considered -- **Adopt Prisma Next now** — feasible (control API, explicit-DSN runtime, +- **Adopt Prisma ORM now** — feasible (control API, explicit-DSN runtime, committable artifacts) and attractive as dogfooding, but the cost lands in the lock port and the re-proof of live-proven code, against thin gain on trivial CRUD. Deferred on that balance. diff --git a/docs/design/90-decisions/ADR-0013-resources-are-provisioned-by-modules-deps-are-declarations.md b/docs/design/90-decisions/ADR-0013-resources-are-provisioned-by-modules-deps-are-declarations.md index 9befb726e..12781a1c1 100644 --- a/docs/design/90-decisions/ADR-0013-resources-are-provisioned-by-modules-deps-are-declarations.md +++ b/docs/design/90-decisions/ADR-0013-resources-are-provisioned-by-modules-deps-are-declarations.md @@ -20,7 +20,7 @@ import ingestService from "./ingest.ts"; import apiService from "./api.ts"; export default module("datahub", ({ provision }) => { - const db = provision(postgres({ name: "db" })); // provides postgresContract + const db = provision(rawPostgres({ name: "db" })); // provides rawPostgresContract provision(ingestService, { deps: { db } }); // ingest.deps.db requires it provision(apiService, { deps: { db } }); // api.deps.db requires it }); @@ -83,14 +83,14 @@ again at Load — the point where the module body actually runs and the graph gets built — as a runtime backstop. There is no branching on what kind of thing produced the reference anywhere: a service's exposed port cannot fill a postgres-requiring slot, not because Load special-cases kinds, but because no -service port carries a contract whose kind is `"postgres"`. `postgresContract` +service port carries a contract whose kind is `"postgres"`. `rawPostgresContract` (the concrete Postgres contract) makes this precise: its `satisfies` compares `kind`, not object identity, so a duplicated copy of the postgres package elsewhere in a workspace still satisfies the same requirement. -The `postgres()` factory has exactly two shapes, chosen by what you pass it. +The `rawPostgres()` factory has exactly two shapes, chosen by what you pass it. `{ name }` returns the resource identity — the thing a module provisions, -providing `postgresContract`. Called with no argument at all, it returns the +providing `rawPostgresContract`. Called with no argument at all, it returns the dependency — the thing a service declares in its own `deps`, requiring that same contract; its binding is the typed connection config itself, and the app builds its own client from it. The two shapes are mutually exclusive at the @@ -153,7 +153,7 @@ slot is a Load-time error pointing at deploying the composing module instead. them removes a whole parallel vocabulary and every place it forked. - **The `Dependable` dual-form** — a value that is both a provisionable identity and, via a `toDependency()` conversion interface, a slot usable directly in - `deps`, so a single-consumer app could write one `postgres({ name, client })`. + `deps`, so a single-consumer app could write one `rawPostgres({ name, client })`. Rejected: it added a bespoke core primitive (a conversion interface, `service()` input normalization, a `NormalizedDeps` type) and a spread-built dual object — for a convenience no example actually needed once resources are diff --git a/docs/design/90-decisions/ADR-0015-dependencies-resolve-to-bindings-clients-are-app-side.md b/docs/design/90-decisions/ADR-0015-dependencies-resolve-to-bindings-clients-are-app-side.md index 7e1a78e14..b541324b9 100644 --- a/docs/design/90-decisions/ADR-0015-dependencies-resolve-to-bindings-clients-are-app-side.md +++ b/docs/design/90-decisions/ADR-0015-dependencies-resolve-to-bindings-clients-are-app-side.md @@ -12,12 +12,12 @@ Concretely, a service that depends on a database and another service writes this ```ts // service.ts — pure requirement: a contract, no driver, no factory export default compute({ - deps: { db: postgres(), auth: rpc(authContract) }, + deps: { db: rawPostgres(), auth: rpc(authContract) }, build: node({ module: import.meta.url, entry: "../dist/server.js" }), }); // server.ts — the app's own entry constructs and owns its client -const { db, auth } = service.load(); // db: PostgresConfig ({ url }); auth: a typed rpc client +const { db, auth } = service.load(); // db: RawPostgresConfig ({ url }); auth: a typed rpc client export const sql = new SQL({ url: db.url, max: 1, idleTimeout: 10 }); await auth.verify({ token }); // auth came back ready to call ``` @@ -29,10 +29,10 @@ Two cases follow from "what the contract alone can construct": determine it, with no driver to choose. `rpc(contract)` binds to a typed generated client; `http()` binds to a thin fetch wrapper. - A **resource kind** (postgres) resolves to its **typed connection config** — - `PostgresConfig`, i.e. `{ url }`. The app constructs its own client from that + `RawPostgresConfig`, i.e. `{ url }`. The app constructs its own client from that config, with its own driver, in app code. -So `postgres()` (no arguments) is the dependency, and `postgres({ name })` is the +So `rawPostgres()` (no arguments) is the dependency, and `rawPostgres({ name })` is the provisionable identity. A declaration never carries a client factory; the framework constructs only the clients it can choose. @@ -63,14 +63,14 @@ blessing or shipping a driver it cannot choose correctly for every app. So the declaration carries only what is irreducibly shared — the typed config the contract describes — and the driver choice is placed where it honestly lives: in app code, next to the app's other runtime choices. `load()` hands over -`PostgresConfig`; the app writes `new SQL({ url: db.url })`. Because that +`RawPostgresConfig`; the app writes `new SQL({ url: db.url })`. Because that construction never touches the dependency's contract, it cannot change which providers wire — provider-independence is a structural property, not a convention the app must remember to preserve. Handing back config rather than a client keeps every property that matters: -- **DI is typed.** The binding is typed by the contract — `PostgresConfig` for +- **DI is typed.** The binding is typed by the contract — `RawPostgresConfig` for postgres, `Client` for rpc — so the app entry has full type information. - **The entry is wiring-free** in the sense that matters: it never reads the environment, a key name, or the topology. `load()` hands it the binding and it @@ -86,7 +86,7 @@ for the framework to model; it is ordinary app code. ## Consequences -- **A postgres dependency's `load()` value is `PostgresConfig`, not a client.** +- **A postgres dependency's `load()` value is `RawPostgresConfig`, not a client.** App code constructs the client — one `new SQL(...)` (or the app's driver of choice) at module scope. This is the visible obligation for app authors. - **`load()`'s return type is mixed by design:** a derived client for @@ -112,11 +112,11 @@ decision stops short of it. ## Alternatives considered -- **A client factory in the declaration** (`postgres({ client })`). Rejected: it +- **A client factory in the declaration** (`rawPostgres({ client })`). Rejected: it conflates requirement with consumption mechanics, and the driver choice is irreducible — a postgres client's type *is* the choice, so it can only be placed, and app code is its honest home. The same objection rejects **named - adapters in the declaration** (`postgres({ driver: bunSql })`): keeping any + adapters in the declaration** (`rawPostgres({ driver: bunSql })`): keeping any stipulation out of `deps` is precisely what makes provider-independence structural rather than conventional. - **A `clients` map on the service** (`compute({ deps, clients: { db: bunSql } })`). diff --git a/docs/design/90-decisions/ADR-0017-control-plane-loads-through-the-app-config.md b/docs/design/90-decisions/ADR-0017-control-plane-loads-through-the-app-config.md index 6645a0fe3..7683e41fc 100644 --- a/docs/design/90-decisions/ADR-0017-control-plane-loads-through-the-app-config.md +++ b/docs/design/90-decisions/ADR-0017-control-plane-loads-through-the-app-config.md @@ -19,8 +19,8 @@ export default defineConfig({ ``` Deploy tooling loads the config (found by walking up from the deploy entry, -loaded with c12 — the same mechanism Prisma Next uses for -`prisma-next.config.ts`), then looks up each node's control-plane behavior in +loaded with c12 — the same mechanism Prisma ORM uses for +`prisma.config.ts`), then looks up each node's control-plane behavior in the registries the descriptors provide, keyed by **(extension ID, node ID)**: a node's `extension` field (`"@prisma/composer-prisma-cloud"`) and its `type` (`"compute"`). Nodes are pure data; the framework never constructs a module diff --git a/docs/design/90-decisions/ADR-0022-data-deps-carry-a-prisma-next-contract.md b/docs/design/90-decisions/ADR-0022-data-deps-carry-a-prisma-orm-contract.md similarity index 83% rename from docs/design/90-decisions/ADR-0022-data-deps-carry-a-prisma-next-contract.md rename to docs/design/90-decisions/ADR-0022-data-deps-carry-a-prisma-orm-contract.md index be3021611..dcbee8562 100644 --- a/docs/design/90-decisions/ADR-0022-data-deps-carry-a-prisma-next-contract.md +++ b/docs/design/90-decisions/ADR-0022-data-deps-carry-a-prisma-orm-contract.md @@ -1,32 +1,32 @@ -# ADR-0022: Data deps carry a Prisma Next contract; deploys migrate to its ref +# ADR-0022: Data deps carry a Prisma ORM contract; deploys migrate to its ref ## Decision -A second data primitive joins bare `postgres()`: `pnPostgres`, a -Prisma Next-typed Postgres resource and dependency, shipped in +A second data primitive joins bare `postgres()`: `postgres`, a +Prisma-ORM-typed Postgres resource and dependency, shipped in `@prisma/composer-prisma-cloud` behind its own subpath entry -(`@prisma/composer-prisma-cloud/prisma-next`). **Prisma Next** is Prisma's +(`@prisma/composer-prisma-cloud/orm`). **Prisma ORM** is Prisma's schema-and-migration engine; its unit is a **contract** — a deterministic, hashable description of a database schema, emitted as `contract.json` (data) and `contract.d.ts` (types). -The same `pnPostgres` factory serves both ends of a data edge — a resource to +The same `postgres` factory serves both ends of a data edge — a resource to provision, and a dependency that resolves to a typed client: ```ts -// contract.ts — wrap Prisma Next's emitted artifact into the framework's kind. +// contract.ts — wrap Prisma ORM's emitted artifact into the framework's kind. // contract.json is pure data; Contract is its emitted, branded type. Neither -// pulls in Prisma Next's CLI or migration engine. -import { pnContract } from '@prisma/composer-prisma-cloud/prisma-next'; +// pulls in Prisma ORM's CLI or migration engine. +import { dataContract } from '@prisma/composer-prisma-cloud/orm'; import type { Contract } from '../contract.d.ts'; import contractJson from '../contract.json' with { type: 'json' }; -export const widgetContract = pnContract(contractJson); +export const widgetContract = dataContract(contractJson); -// service.ts — the DEPENDENCY end. load() hands back a typed Prisma Next +// service.ts — the DEPENDENCY end. load() hands back a typed Prisma ORM // client, built by the framework from the contract plus the injected URL. export default compute({ name: 'widgets', - deps: { db: pnPostgres(widgetContract) }, + deps: { db: postgres(widgetContract) }, build: node({ module: import.meta.url, entry: '../dist/server.mjs' }), }); @@ -35,10 +35,10 @@ const { db } = service.load(); await db.orm.public.Widget.create({ label }); // module.ts — the RESOURCE end. It takes the contract AND the -// prisma-next.config.ts PATH — a plain string the deploy reads, never imported. -export default module('pn-widgets', ({ provision }) => { +// prisma.config.ts PATH — a plain string the deploy reads, never imported. +export default module('orm-demo', ({ provision }) => { const db = provision( - pnPostgres({ name: 'database', contract: widgetContract, config: './prisma-next.config.ts' }), + postgres({ name: 'database', contract: widgetContract, config: './prisma.config.ts' }), { id: 'database' }, ); provision(widgetsService, { id: 'widgets', deps: { db } }); @@ -48,12 +48,12 @@ export default module('pn-widgets', ({ provision }) => { The two ends of the resource pull in opposite directions, so they enter by different doors. The **contract** is *consumed*: it types and wires the resource and gives the deploy the schema version to migrate to. The -**`prisma-next.config.ts`** is *located*, by path only — deploy-only metadata +**`prisma.config.ts`** is *located*, by path only — deploy-only metadata the migration step reads to find the migrations directory. The app build never -imports it, because importing it would pull Prisma Next's CLI, migration engine, +imports it, because importing it would pull Prisma ORM's CLI, migration engine, and source providers into the user's bundle. One contract per database. -At deploy, the lowering gains a migration step per `pnPostgres` resource. Its +At deploy, the lowering gains a migration step per `postgres` resource. Its target is a **ref** — `{ hash, invariants }` — and the live database carries a **marker** recording its current `{ storageHash, invariants }`. An **invariant** is a named postcondition established by a `data`-class migration step (a @@ -89,7 +89,7 @@ deps, the same role `http()` plays for communication. ## Reasoning -Prisma Next's surface fits this shape with almost no adaptation. Its contracts +Prisma ORM's surface fits this shape with almost no adaptation. Its contracts are committed artifacts (`contract.json` + `contract.d.ts`) whose branded `storageHash` type puts the schema version in the type system; its runtime is generic over the emitted type with no client codegen and accepts an explicit @@ -102,9 +102,9 @@ deploy step needs. rather than violating it.** ADR-0015's principle is that a binding is the most-derived thing the contract alone can construct; it lands on `{ url }` for bare postgres because constructing more would bless a driver. A dependency that -carries a Prisma Next contract *can* construct the typed client from the contract +carries a Prisma ORM contract *can* construct the typed client from the contract alone — contract plus URL is the client's entire input. Because "data contracts -are Prisma Next" is a framework-level decision, Prisma Next is blessed the way +are Prisma ORM" is a framework-level decision, Prisma ORM is blessed the way RPC is: this dependency kind gets a client, while bare postgres keeps `{ url }`. The dependency cost is contained by packaging — the primitive lives behind its own subpath entry, never re-exported from the index — so a service that opts out @@ -115,11 +115,11 @@ only need to *consume* the contract: `contract.json` (the data the framework hands the runtime at *hydrate* — the boot-time step that builds each dependency's client) and `contract.d.ts` (types), both lightweight and importable into the app build with no deploy machinery attached. The deploy migration step needs to -*locate* the config — the `prisma-next.config.ts` from which Prisma Next resolves +*locate* the config — the `prisma.config.ts` from which Prisma ORM resolves the migrations directory — but it needs only the **path**, a string, read at deploy time. Passing the config as a path rather than an import is what keeps -Prisma Next's CLI and migration engine out of the user's bundle while still -giving the deploy lowering what it needs. A single contract is Prisma Next's +Prisma ORM's CLI and migration engine out of the user's bundle while still +giving the deploy lowering what it needs. A single contract is Prisma ORM's mainline single-space model, so the user authors one contract that serves every consuming module. Each consumer sees the full contract type; per-consumer least-privilege slices are the deferred multi-contract extension (see @@ -137,7 +137,7 @@ place that can actually enforce it, and it means a running service can never be crashed (nor meaningfully warned) by a runtime marker check. The framework injects the connection URL at hydrate, so user code never reads the environment. -**The target must be a ref, not a bare hash.** A marker's invariants only ever accumulate — a step's postcondition, once recorded, is never removed. Keying the migration on `storageHash` alone would silently skip a pure data-invariant change: it is an A→A self-edge (the same hash), which a hash-keyed deploy reads as "already there". Making the target a ref — hash equality plus invariant subset, mirroring Prisma Next's own verifier — closes this. (Before the replay-only revision, the ref also ruled `dbInit` out for invariant-bearing targets, since additive-only synthesis never runs the data steps that establish invariants; with synthesis gone, replay covers that case by construction.) +**The target must be a ref, not a bare hash.** A marker's invariants only ever accumulate — a step's postcondition, once recorded, is never removed. Keying the migration on `storageHash` alone would silently skip a pure data-invariant change: it is an A→A self-edge (the same hash), which a hash-keyed deploy reads as "already there". Making the target a ref — hash equality plus invariant subset, mirroring Prisma ORM's own verifier — closes this. (Before the replay-only revision, the ref also ruled `dbInit` out for invariant-bearing targets, since additive-only synthesis never runs the data steps that establish invariants; with synthesis gone, replay covers that case by construction.) ## Consequences @@ -161,11 +161,11 @@ injects the connection URL at hydrate, so user code never reads the environment. - **Binding = `{ url, contractJson }`, client constructed app-side** — keeps ADR-0015 as written and packs driver-free, at the cost of the framework not constructing the least-privilege client it has every input for. Rejected: - Prisma Next is framework-blessed, not an arbitrary driver. -- **Multi-contract resource declarations mapped to Prisma Next contract + Prisma ORM is framework-blessed, not an arbitrary driver. +- **Multi-contract resource declarations mapped to Prisma ORM contract spaces** — the fuller model: the resource declares the set of contracts it hosts (the aggregate, in code), each consumer's slice is a contract space, - disjointness is Prisma Next-verified, and the space id lives on the contract + disjointness is Prisma ORM-verified, and the space id lives on the contract declaration (never derived from topology names, so renames don't read as conflicts). Deferred, not rejected: it depends on multi-peer contract-space support whose edges are unproven, and a single contract needs none of it. @@ -181,7 +181,7 @@ injects the connection URL at hydrate, so user code never reads the environment. ## Related - [ADR-0015](ADR-0015-dependencies-resolve-to-bindings-clients-are-app-side.md) — - amended: Prisma Next joins the protocol-owned kinds whose binding is a client. + amended: Prisma ORM joins the protocol-owned kinds whose binding is a client. - [ADR-0013](ADR-0013-resources-are-provisioned-by-modules-deps-are-declarations.md) — the slot model this plugs into. - [ADR-0012](ADR-0012-the-state-store-speaks-sql-directly.md) — unaffected; the diff --git a/docs/design/90-decisions/ADR-0023-a-prisma-app-is-one-project-a-stage-is-a-branch.md b/docs/design/90-decisions/ADR-0023-a-prisma-app-is-one-project-a-stage-is-a-branch.md index 800d98175..2a1ce0c7a 100644 --- a/docs/design/90-decisions/ADR-0023-a-prisma-app-is-one-project-a-stage-is-a-branch.md +++ b/docs/design/90-decisions/ADR-0023-a-prisma-app-is-one-project-a-stage-is-a-branch.md @@ -12,7 +12,7 @@ Concretely, this app: ```ts export default module('storefront-auth', {}, ({ provision }) => { - const db = provision('database', postgres({ name: 'database' })); + const db = provision('database', rawPostgres({ name: 'database' })); const auth = provision('auth', authService, { db }); provision('storefront', storefrontService, { auth: auth.rpc }); return {}; diff --git a/docs/design/90-decisions/ADR-0027-two-packages-compose-and-compose-prisma-cloud.md b/docs/design/90-decisions/ADR-0027-two-packages-compose-and-compose-prisma-cloud.md index e269dad31..0ab65ac33 100644 --- a/docs/design/90-decisions/ADR-0027-two-packages-compose-and-compose-prisma-cloud.md +++ b/docs/design/90-decisions/ADR-0027-two-packages-compose-and-compose-prisma-cloud.md @@ -12,7 +12,7 @@ names users install are not. The two public packages: target-agnostic authoring surfaces as subpath exports — `@prisma/compose/rpc`, `@prisma/compose/node`, `@prisma/compose/nextjs`. - **`@prisma/compose-prisma-cloud`** — the Prisma Cloud target: `compute()`, - `postgres()`, the Alchemy provider and lowering (absorbing `@prisma/alchemy`), + `rawPostgres()`, the Alchemy provider and lowering (absorbing `@prisma/alchemy`), and the first-party modules realized on Prisma Cloud — cron first — as subpath entrypoints (`@prisma/compose-prisma-cloud/cron`). diff --git a/docs/design/90-decisions/ADR-0028-numbered-domains-and-layers-enforced-by-dependency-cruiser.md b/docs/design/90-decisions/ADR-0028-numbered-domains-and-layers-enforced-by-dependency-cruiser.md index 7f6f7528d..435312459 100644 --- a/docs/design/90-decisions/ADR-0028-numbered-domains-and-layers-enforced-by-dependency-cruiser.md +++ b/docs/design/90-decisions/ADR-0028-numbered-domains-and-layers-enforced-by-dependency-cruiser.md @@ -13,7 +13,7 @@ packages/ 3-tooling/ # assemble, the CLI implementation 1-prisma-cloud/ # the Prisma Cloud target; may import 0-framework 0-lowering/ # the Alchemy provider - 1-extensions/ # compute(), postgres(), the target descriptor + 1-extensions/ # compute(), rawPostgres(), the target descriptor 2-shared-modules/ # first-party modules realized on this target (cron) 9-public/ # the ONLY publishable packages; imports both domains compose/ # → @prisma/composer (bin + curated re-exports + subpaths) @@ -38,7 +38,7 @@ extension descriptors, deploy), **execution** (boot, serve, `load()`), and **shared**. Control and execution never import each other's code; both may import shared. A multi-plane package maps its entrypoints per plane (e.g. `src/core/**` shared, `src/exports/control.ts` control, `src/exports/runtime.ts` execution), -exactly as Prisma Next's multi-plane adapters do. +exactly as Prisma ORM's multi-plane adapters do. **Internal packages use the `@internal/*` scope** (`@internal/core`, `@internal/lowering`, …), all `"private": true`. The scope is publish-proof twice @@ -57,18 +57,18 @@ tree. **Enforcement is dependency-cruiser**, data-driven from `architecture.config.json` mapping directory globs to `{domain, layer, plane}`, run as `pnpm lint:deps` locally, in lint-staged, and in CI — the same shape as -Prisma Next's, copied with plane support from day one. +Prisma ORM's, copied with plane support from day one. ## Reasoning ADR-0027 constrained the published surface to two packages and declared internal seams free — but "free" without structure decays into a tangle, and a -`"private": true` flag is invisible when browsing. Prisma Next already solved +`"private": true` flag is invisible when browsing. Prisma ORM already solved both problems with a mechanism proven in this codebase's sibling: directory placement dictates allowed dependencies, package name dictates how consumers import, and a machine checks the difference. We adopt it rather than invent one. -The one divergence is `9-public`. Prisma Next publishes everything and needs no +The one divergence is `9-public`. Prisma ORM publishes everything and needs no such domain; we publish exactly two names, and putting them in a numbered terminal domain makes ADR-0027's constraint *physical* — "what do users install" is answered by `ls packages/9-public`, and the publish pipeline can enforce @@ -121,6 +121,6 @@ cruiser lands. two-public-package constraint this organizes. - [ADR-0017](ADR-0017-control-plane-loads-through-the-app-config.md) — the control/execution import surfaces the plane rules enforce. -- Prisma Next: `docs/architecture docs/Package-Layering.md` and ADR 140 — the +- Prisma ORM: `docs/architecture docs/Package-Layering.md` and ADR 140 — the pattern adopted here, including `architecture.config.json` and the dependency-cruiser setup. diff --git a/docs/design/90-decisions/ADR-0035-public-entrypoints-live-in-src-exports.md b/docs/design/90-decisions/ADR-0035-public-entrypoints-live-in-src-exports.md index 2fe76ba8c..dcc241f6e 100644 --- a/docs/design/90-decisions/ADR-0035-public-entrypoints-live-in-src-exports.md +++ b/docs/design/90-decisions/ADR-0035-public-entrypoints-live-in-src-exports.md @@ -128,7 +128,7 @@ published API contract), not on a dts limitation. Packages whose root is genuinely mixed-plane (`target`, `storage`) keep one glob per root file instead. -- **This is stricter than the pattern it adapts.** Prisma Next's own +- **This is stricter than the pattern it adapts.** Prisma ORM's own `adapter-postgres/src/exports/column-types.ts` carries about 185 lines of implementation. We took the stricter rule deliberately: an exports file that can hold implementation eventually does, and then internal modules import it, @@ -175,7 +175,7 @@ published API contract), not on a dts limitation. fallback overlaps every specific glob and breaks plane enforcement. Rejected — globs must be non-overlapping. -- **Collect internals under `src/core/**`, as Prisma Next does.** We separate +- **Collect internals under `src/core/**`, as Prisma ORM does.** We separate implementation from surface the same way, but name the directories after the planes the cruiser already enforces (`src/control/**`, `src/execution/**`, root for shared) rather than adding a `core/` level. That keeps one vocabulary @@ -194,6 +194,6 @@ published API contract), not on a dts limitation. `src/exports/runtime.ts`. - [`.agents/rules/exports-entrypoints.mdc`](../../../.agents/rules/exports-entrypoints.mdc) — the day-to-day rule for adding a subpath. -- Prisma Next: `docs/architecture docs/Package-Layering.md` § Package Exports +- Prisma ORM: `docs/architecture docs/Package-Layering.md` § Package Exports Pattern — the pattern this adapts (its `customExports` hook and multi-plane layout). diff --git a/docs/design/90-decisions/ADR-0040-the-pn-binding-carries-the-url-and-a-lazy-client.md b/docs/design/90-decisions/ADR-0040-the-orm-binding-carries-the-url-and-a-lazy-client.md similarity index 82% rename from docs/design/90-decisions/ADR-0040-the-pn-binding-carries-the-url-and-a-lazy-client.md rename to docs/design/90-decisions/ADR-0040-the-orm-binding-carries-the-url-and-a-lazy-client.md index 8fa9220d1..ab1cc50d1 100644 --- a/docs/design/90-decisions/ADR-0040-the-pn-binding-carries-the-url-and-a-lazy-client.md +++ b/docs/design/90-decisions/ADR-0040-the-orm-binding-carries-the-url-and-a-lazy-client.md @@ -1,21 +1,21 @@ -# ADR-0040: The `prisma-next` binding carries the raw URL and a lazy client +# ADR-0040: The `postgres` binding carries the raw URL and a lazy client ## Decision -`pnPostgres(contract)`'s dependency end hydrates to a two-field binding — +`postgres(contract)`'s dependency end hydrates to a two-field binding — the raw connection string beside the typed client — instead of the typed client alone. The client is constructed on first access, not during hydration. Consider a service that builds its own `pg.Pool` because a third-party -library (say, an auth library that is not a Prisma Next consumer) must share +library (say, an auth library that is not a Prisma ORM consumer) must share it. That service still wants its database schema-checked at wiring and migrated at deploy. It now declares the contract-carrying dependency and reads only the URL: ```ts const service = compute({ - deps: { db: pnPostgres(appContract) }, // contract-checked, deploy-migrated + deps: { db: postgres(appContract) }, // contract-checked, deploy-migrated /* … */ }); @@ -32,7 +32,7 @@ const products = await db.client.orm.public.Product.all(); `url` is the wire value the connection already carries. `client` is a memoized accessor over the same client construction as before; `hydrate` itself no longer constructs anything. The exported binding type is -`PnPostgresBinding = { readonly url: string; readonly client: Client }`. +`PostgresBinding = { readonly url: string; readonly client: Client }`. Everything else about the edge is unchanged: the dependency still names the contract, `satisfies` still compares storage hashes, and the deploy still @@ -42,7 +42,7 @@ migrates the database to the contract's ref before dependent services start ## Reasoning **Before this decision, the binding equated "contract-checked database" with -"framework-built client."** A `pnPostgres` dependency hydrated directly to +"framework-built client."** A `postgres` dependency hydrated directly to the typed client, so the only way to consume a contract-carrying database was through that client. An app that owns its client had to fall back to plain `postgres()` — losing framework-run migrations entirely and pushing @@ -51,7 +51,7 @@ the deploy resolves internally but never surfaces. **Those are two separate wants, and the binding can serve both.** Plain `postgres()` already treats the raw URL as a first-class binding value; a -`prisma-next` connection carries the same URL underneath its client. Putting +`postgres` connection carries the same URL underneath its client. Putting `url` in the binding makes the PN binding a strict superset of `postgres()`'s `{ url }` — an app chooses its client without giving up contract checking or deploy-time migration. @@ -63,7 +63,7 @@ which client reads the data afterwards. The compatibility check is untouched; only what the consumer receives is wider. **Eager construction charged consumers for a client they might never use.** -The Prisma Next runtime deserializes and structurally validates +The Prisma ORM runtime deserializes and structurally validates `contractJson` when the client is constructed (its connection pool is lazy; its validation is not), and hydration resolves every dependency in one synchronous pass. Two consequences: a URL-only consumer paid validation it @@ -81,7 +81,7 @@ or invalidity. ## Consequences -1. Binding shape change for existing `pnPostgres(contract)` consumers: +1. Binding shape change for existing `postgres(contract)` consumers: `db.orm.…` becomes `db.client.orm.…`. Mechanical; the full client surface (`sql`, `transaction`, `close`, …) remains reachable under `client`. 2. A contract the installed runtime cannot validate no longer fails at @@ -89,15 +89,15 @@ or invalidity. run against a contract the runtime's validator would reject — the storage-hash check at wiring remains the compatibility check that matters (ADR-0022). -3. `PnMigration` and the deploy lowering are untouched: provisioning - `pnPostgres({ name, contract, config })` still migrates at deploy. An app +3. `OrmMigration` and the deploy lowering are untouched: provisioning + `postgres({ name, contract, config })` still migrates at deploy. An app owning its client gets framework-run migrations with no operator step. ## Alternatives considered -- **Cross-kind `satisfies`** — let a `'prisma-next'` resource satisfy a plain +- **Cross-kind `satisfies`** — let a `'postgres'` resource satisfy a plain `'postgres'` dependency, so an app declares `postgres()` and provisions - `pnPostgres`. Touches core's kind model to express what is really a + `postgres`. Touches core's kind model to express what is really a binding concern, and a naive "no required hash → satisfied" rule would let a PN resource satisfy unrelated kinds. Rejected. - **`{ url, orm }` — expose only the ORM, not the full client** — loses diff --git a/docs/design/90-decisions/ADR-0041-local-dev-runs-the-deploy-pipeline-against-local-providers.md b/docs/design/90-decisions/ADR-0041-local-dev-runs-the-deploy-pipeline-against-local-providers.md index eb0bd5aae..551ef4892 100644 --- a/docs/design/90-decisions/ADR-0041-local-dev-runs-the-deploy-pipeline-against-local-providers.md +++ b/docs/design/90-decisions/ADR-0041-local-dev-runs-the-deploy-pipeline-against-local-providers.md @@ -62,7 +62,7 @@ For the Prisma Cloud extension, the substitution splits its twelve resource types four/eight. Four providers already run entirely locally and are shared verbatim: `ServiceKey` and `S3Credentials` (both mint via Web Crypto and persist in Alchemy state, never touching a platform), `PgWarm` (a real `select 1` -against whatever URL it is handed), and `PnMigration` (real migrations against +against whatever URL it is handed), and `OrmMigration` (real migrations against whatever URL it is handed). The remaining eight get local implementations in three clusters: diff --git a/docs/design/90-decisions/ADR-0046-the-orm-facade-is-a-peer-dependency.md b/docs/design/90-decisions/ADR-0046-the-orm-facade-is-a-peer-dependency.md index aa387ddf1..b619b22b3 100644 --- a/docs/design/90-decisions/ADR-0046-the-orm-facade-is-a-peer-dependency.md +++ b/docs/design/90-decisions/ADR-0046-the-orm-facade-is-a-peer-dependency.md @@ -1,10 +1,10 @@ -# ADR-0046: The published packages take the Prisma Next facade as a peer dependency +# ADR-0046: The published packages take the Prisma ORM facade as a peer dependency ## Decision -`@prisma/composer-prisma-cloud` declares `@prisma/orm-postgres` — the Prisma Next postgres facade — as a **peer dependency** pinned to one exact version, plus a devDependency at the same version so the workspace can build. It is not a regular dependency. +`@prisma/composer-prisma-cloud` declares `@prisma/orm-postgres` — the Prisma ORM postgres facade — as a **peer dependency** pinned to one exact version, plus a devDependency at the same version so the workspace can build. It is not a regular dependency. -The application installs the facade itself. Composer's `/prisma-next` and `/auth/pack` entrypoints import it at runtime from whatever copy the application resolved. +The application installs the facade itself. Composer's `/orm` and `/auth/pack` entrypoints import it at runtime from whatever copy the application resolved. Every `@prisma/orm-*` specifier anywhere in this workspace is a single exact version, and all of them name the same version. `scripts/lint-orm-pins.mjs` enforces both halves. @@ -12,9 +12,9 @@ The packages Composer *drives* rather than *extends* stay regular dependencies: ## Reasoning -Prisma Next publishes one system as several packages (upstream ADR 242): a per-database facade, and the platform shells behind it. Their types and registries are compatible only within one version. Two copies of a shell in one tree means two codec registries, two operation registries, and two class identities — a value produced by one copy is rejected by the other, and `instanceof` stops holding. Nothing detects it; the failure shows up as a type error that names the same type on both sides, or as a runtime rejection of a value that looks correct. +Prisma ORM publishes one system as several packages (upstream ADR 242): a per-database facade, and the platform shells behind it. Their types and registries are compatible only within one version. Two copies of a shell in one tree means two codec registries, two operation registries, and two class identities — a value produced by one copy is rejected by the other, and `instanceof` stops holding. Nothing detects it; the failure shows up as a type error that names the same type on both sides, or as a runtime rejection of a value that looks correct. -Composer registers an extension pack against the application's copy of the target. `examples/auth/prisma-next.config.ts` is the shape: +Composer registers an extension pack against the application's copy of the target. `examples/auth/prisma.config.ts` is the shape: ```ts import authPack from '@prisma/composer-prisma-cloud/auth/pack'; @@ -31,10 +31,10 @@ Every `@prisma/orm-*` spec is pinned exactly for the same reason the peer exists ## Consequences -- An application that uses Composer's Prisma Next surface installs `@prisma/orm-postgres` itself, at the version Composer names. It already had to, to write `prisma-next.config.ts` and run the CLI. -- An application that uses Composer *without* Prisma Next gets an unmet-peer warning it can ignore, where previously it silently installed the ORM it never used. -- Upgrading Prisma Next is a coordinated release: Composer's pin and the application's install move together. That is the cost of one resolved copy, and it is the point. -- Two Composer entrypoints depend on the peer being present — `/prisma-next` (the typed client and the deploy-time migration path) and `/auth/pack`, whose built `dist/auth/pack.mjs` imports `@prisma/orm-postgres/family-contract/canonicalization-hooks` at runtime to verify the pack descriptor against its own contract. Neither is reachable from the main barrel (invariant 7 keeps the ORM out of it), so a service that does not opt in never loads the facade. +- An application that uses Composer's Prisma ORM surface installs `@prisma/orm-postgres` itself, at the version Composer names. It already had to, to write `prisma.config.ts` and run the CLI. +- An application that uses Composer *without* Prisma ORM gets an unmet-peer warning it can ignore, where previously it silently installed the ORM it never used. +- Upgrading Prisma ORM is a coordinated release: Composer's pin and the application's install move together. That is the cost of one resolved copy, and it is the point. +- Two Composer entrypoints depend on the peer being present — `/orm` (the typed client and the deploy-time migration path) and `/auth/pack`, whose built `dist/auth/pack.mjs` imports `@prisma/orm-postgres/family-contract/canonicalization-hooks` at runtime to verify the pack descriptor against its own contract. Neither is reachable from the main barrel (invariant 7 keeps the ORM out of it), so a service that does not opt in never loads the facade. - The bundling boundary is now a name prefix rather than a distinct scope. `@prisma-next/*` used to be visibly not-ours; `@prisma/orm-*` sits in the same npm scope as `@prisma/composer*`, so every `noExternal: [/^@prisma\//]` in a `tsdown.config.ts` had to become `/^@prisma\/(?!orm-)/` to keep the ORM out of deployed service bundles. ## Alternatives considered @@ -43,10 +43,10 @@ Every `@prisma/orm-*` spec is pinned exactly for the same reason the peer exists **Peer on `@prisma/orm-target-postgres`** — the platform shell, matching upstream's wording literally. Rejected: Composer imports facade-only entrypoints, so peering on the target would leave the facade itself unpinned and the split still reachable. -**Re-export the facade from Composer** so the application never names it. Rejected: it makes Composer the distributor of somebody else's package surface, and the application still has to name the facade in `prisma-next.config.ts` and to run its CLI. +**Re-export the facade from Composer** so the application never names it. Rejected: it makes Composer the distributor of somebody else's package surface, and the application still has to name the facade in `prisma.config.ts` and to run its CLI. ## Related -- [ADR-0022](ADR-0022-data-deps-carry-a-prisma-next-contract.md) — data dependencies carry a Prisma Next contract; this ADR replaces its consequence bullet about how the ORM is installed. +- [ADR-0022](ADR-0022-data-deps-carry-a-prisma-orm-contract.md) — data dependencies carry a Prisma ORM contract; this ADR replaces its consequence bullet about how the ORM is installed. - [ADR-0017](ADR-0017-control-plane-loads-through-the-app-config.md) — the control plane loads through the app config. - Upstream ADR 242 (`prisma/prisma`) — the published shells and the one-facade-per-application rule. diff --git a/docs/design/90-decisions/ADR-0048-prisma-cloud-resources-come-from-the-upstream-alchemy-provider.md b/docs/design/90-decisions/ADR-0048-prisma-cloud-resources-come-from-the-upstream-alchemy-provider.md index 52a4e2baa..851596d99 100644 --- a/docs/design/90-decisions/ADR-0048-prisma-cloud-resources-come-from-the-upstream-alchemy-provider.md +++ b/docs/design/90-decisions/ADR-0048-prisma-cloud-resources-come-from-the-upstream-alchemy-provider.md @@ -17,7 +17,7 @@ Layer.mergeAll( Prisma.EnvironmentVariableProvider(), ), // + Composer's own resources: Bucket, BucketKey, ServiceKey, -// GeneratedParam, S3Credentials, PnMigration, PgWarm +// GeneratedParam, S3Credentials, OrmMigration, PgWarm ``` and a lowered compute service is upstream resources wired by Composer's descriptors: diff --git a/docs/design/90-decisions/README.md b/docs/design/90-decisions/README.md index b36e037e8..6fa9594a3 100644 --- a/docs/design/90-decisions/README.md +++ b/docs/design/90-decisions/README.md @@ -33,7 +33,7 @@ _Earlier drafts (ADR-0001, ADR-0002) were retired as the high-level design settl - [ADR-0009](ADR-0009-deploy-state-is-hosted-in-the-workspace.md) — Deploy state is hosted in the workspace, not in local files. *(Superseded by ADR-0034: still hosted, now per-stage in the app's own Project.)* - [ADR-0010](ADR-0010-deploys-hold-a-session-advisory-lock.md) — Deploys hold a session advisory lock per stack and stage. *(Superseded by ADR-0045: the lease is server-side now; the fail-fast contention behavior survives.)* - [ADR-0011](ADR-0011-targets-supply-the-deploy-state-layer.md) — Targets supply the deploy state layer; core owns no default. -- [ADR-0012](ADR-0012-the-state-store-speaks-sql-directly.md) — The state store speaks SQL directly; Prisma Next adoption is deferred. *(Closed as obsolete by ADR-0045, via its own pick-up trigger: the platform state API landed and the SQL store is gone.)* +- [ADR-0012](ADR-0012-the-state-store-speaks-sql-directly.md) — The state store speaks SQL directly; Prisma ORM adoption is deferred. *(Closed as obsolete by ADR-0045, via its own pick-up trigger: the platform state API landed and the SQL store is gone.)* - [ADR-0013](ADR-0013-resources-are-provisioned-by-modules-deps-are-declarations.md) — Resources are provisioned by modules; dependencies are uniform contract-checked slots. - [ADR-0014](ADR-0014-one-authoring-primitive.md) — Establishes one authoring primitive with no separate `app()` (the App is the outermost Module). Its framework, package, and CLI names are superseded by ADR-0026 (**Prisma Composer**) and its unit noun by ADR-0025 (**Module**). - [ADR-0015](ADR-0015-dependencies-resolve-to-bindings-clients-are-app-side.md) — Dependencies resolve to bindings (a client for protocol-owned kinds, typed config for resources); clients are constructed app-side. @@ -43,7 +43,7 @@ _Earlier drafts (ADR-0001, ADR-0002) were retired as the high-level design settl - [ADR-0019](ADR-0019-the-target-owns-config-serialization.md) — The deploy target owns config serialization entirely — logic, encoding, and medium; core builds the typed Config and never encodes or reads storage. Params are target-agnostic. - [ADR-0020](ADR-0020-scheduled-work-is-a-driver-not-a-resource.md) — Scheduled work is a driver, not a resource: a scheduler service depends on the `trigger(jobId)` endpoint it calls, with the schedule as build-time config. - [ADR-0021](ADR-0021-params-are-read-through-config-not-load.md) — A service reads dependencies through `load()` and config params through a sibling `config()`; the two never share a namespace. *(Superseded by ADR-0042: `config()` and the `params` declaration are replaced by one `input` schema read through `service.input()`; `load()` stays separate.)* -- [ADR-0022](ADR-0022-data-deps-carry-a-prisma-next-contract.md) — Data deps carry a Prisma Next contract: typed client binding, one contract per database via its config, deploys migrate along authored edges to the contract hash. Revised 2026-08-25: deploys are replay-only — the first-deploy `dbInit` synthesis path is removed; a fresh database replays the committed baseline from empty, and a missing authored path is a refusal naming the fix (`prisma db update` locally, `contract emit` + `migration plan` to ship). *(Proposed)* +- [ADR-0022](ADR-0022-data-deps-carry-a-prisma-orm-contract.md) — Data deps carry a Prisma ORM contract: typed client binding, one contract per database via its config, deploys migrate along authored edges to the contract hash. Revised 2026-08-25: deploys are replay-only — the first-deploy `dbInit` synthesis path is removed; a fresh database replays the committed baseline from empty, and a missing authored path is a refusal naming the fix (`prisma db update` locally, `contract emit` + `migration plan` to ship). *(Proposed)* - [ADR-0023](ADR-0023-a-prisma-app-is-one-project-a-stage-is-a-branch.md) — A Prisma App is one Prisma Cloud Project; Modules are Apps/Databases inside it; a Stage is a Branch, and deploy state is per `(Project, Branch)`. - [ADR-0024](ADR-0024-a-stage-is-a-deploy-time-environment-resolved-to-project-and-branch.md) — A stage is a deploy-time environment; the CLI resolves the app's Project (by root-module name) and the stage's Branch outside Alchemy before the stack runs. - [ADR-0025](ADR-0025-name-the-unit-of-composition-module.md) — The unit of composition is a **Module**, authored with `module()`; supersedes ADR-0014's unit noun ("System"). Registers: package (npm's word) / extension (config slot) / Module (composition). @@ -61,12 +61,12 @@ _Earlier drafts (ADR-0001, ADR-0002) were retired as the high-level design settl - [ADR-0037](ADR-0037-service-rpc-calls-carry-an-idempotency-key.md) — The generated service RPC client carries an `Idempotency-Key` on every call — one per logical call, reused across a bounded retry — and the provider deduplicates on it: one call per key, replaying completed 2xx/4xx answers (never 5xx) from a bounded in-process store. A keyless request (a hand-rolled or older caller) is served once without deduplication rather than rejected. Retrying is permanent protocol behavior, not a platform workaround, and there is no per-method opt-in — a flag would be an unverifiable claim. Handlers may read the key via an optional third argument for their own durable exactly-once. - [ADR-0038](ADR-0038-containers-are-an-extension-descriptor.md) — Container lifecycle (ensure/locate/remove) is an optional `container` descriptor on `ExtensionDescriptor`, the same pattern as `preflight`/`teardown`; the resolved instance is opaque to core and crosses the CLI parent→alchemy child boundary as one framework-named environment variable per extension, via the extension's own `serialize()`/`deserialize()`. `StateDescriptor` names its owning extension so core can hand it that extension's resolved container. Deletes the `crossDomainExceptions` entry that let the CLI import `@internal/lowering` directly — `0-framework` imports nothing again. - [ADR-0039](ADR-0039-a-compute-services-own-origin-is-a-target-resolved-property.md) — A compute service's own platform-assigned origin is a target-resolved property, read as the service's `origin()` — never a declared param, never operator config, never in `config()`. It rides ADR-0031's reserved provider-param channel as the first *service-derived* entry (`valueForService(provisioned, address)`, written for every compute service, exposing or not), sourced from the provisioned App's `appEndpointDomain` (ADR-0048) — made trustworthy pre-promote by the upstream PRO-200 fix. `envParam(…)` remains correct for operator-known origins (custom domains); narrows ADR-0032's `appOrigin` example accordingly. -- [ADR-0040](ADR-0040-the-pn-binding-carries-the-url-and-a-lazy-client.md) — `pnPostgres(contract)`'s dependency binding is `{ url, client }`: the raw connection string plus the typed client, constructed lazily and memoized on first `client` access — `hydrate` builds nothing. The contract remains the compatibility interface (hash check and deploy-time migration unchanged, ADR-0022); the binding becomes a strict superset of plain `postgres()`'s `{ url }`, so an app that owns its database client still gets framework-run migrations. Contract validation cost and failure move from `load()` (where one bad input poisoned every input, unattributed) to the first `client` access. Cross-kind satisfaction (`'prisma-next'` satisfying `'postgres'`) rejected in its favor. -- [ADR-0041](ADR-0041-local-dev-runs-the-deploy-pipeline-against-local-providers.md) — `prisma-composer dev` runs the **same deploy pipeline** (Load → assemble → lower → Alchemy converge) against local implementations of the same Alchemy resource types, declared on an optional `localTarget` field of `ExtensionDescriptor` (a lazy thunk resolving a `LocalTargetDescriptor`; subpaths `@prisma/composer/local-target` and `@prisma/composer-prisma-cloud/local-target` — "dev" names only the user-facing command/prefix/state dir) (providers, container, preflight, emulators, attach, teardown — **no** `nodes`/`provisions`, so the lowering cannot diverge; no `state` either — dev uses Alchemy's own `localState()` through `LowerOptions.state`). The target runs **emulators per node kind**: Compute and buckets are machine-global, multi-tenant daemons (the Compute emulator owns the service child processes — deployment PUTs, crash supervision, logs; buckets serve the S3 wire over plain files on disk), while Postgres runs one detached ORM `prisma dev` instance per `Database` resource under the ORM CLI's own manager. Providers provision instances by communicating with the emulators during converge, and the dev command is a view through `attach`; `ServiceKey`/`S3Credentials`/`PgWarm`/`PnMigration` are shared verbatim. Credential-free by requirement. Rejects a local Management API (reimplements another team's server-side semantics, drifts silently) and per-kind dev descriptors (an open-set parallel seam). +- [ADR-0040](ADR-0040-the-orm-binding-carries-the-url-and-a-lazy-client.md) — `postgres(contract)`'s dependency binding is `{ url, client }`: the raw connection string plus the typed client, constructed lazily and memoized on first `client` access — `hydrate` builds nothing. The contract remains the compatibility interface (hash check and deploy-time migration unchanged, ADR-0022); the binding becomes a strict superset of plain `rawPostgres()`'s `{ url }`, so an app that owns its database client still gets framework-run migrations. Contract validation cost and failure move from `load()` (where one bad input poisoned every input, unattributed) to the first `client` access. Cross-kind satisfaction (`'postgres'` satisfying `'postgres'`) rejected in its favor. +- [ADR-0041](ADR-0041-local-dev-runs-the-deploy-pipeline-against-local-providers.md) — `prisma-composer dev` runs the **same deploy pipeline** (Load → assemble → lower → Alchemy converge) against local implementations of the same Alchemy resource types, declared on an optional `localTarget` field of `ExtensionDescriptor` (a lazy thunk resolving a `LocalTargetDescriptor`; subpaths `@prisma/composer/local-target` and `@prisma/composer-prisma-cloud/local-target` — "dev" names only the user-facing command/prefix/state dir) (providers, container, preflight, emulators, attach, teardown — **no** `nodes`/`provisions`, so the lowering cannot diverge; no `state` either — dev uses Alchemy's own `localState()` through `LowerOptions.state`). The target runs **emulators per node kind**: Compute and buckets are machine-global, multi-tenant daemons (the Compute emulator owns the service child processes — deployment PUTs, crash supervision, logs; buckets serve the S3 wire over plain files on disk), while Postgres runs one detached ORM `prisma dev` instance per `Database` resource under the ORM CLI's own manager. Providers provision instances by communicating with the emulators during converge, and the dev command is a view through `attach`; `ServiceKey`/`S3Credentials`/`PgWarm`/`OrmMigration` are shared verbatim. Credential-free by requirement. Rejects a local Management API (reimplements another team's server-side semantics, drifts silently) and per-kind dev descriptors (an open-set parallel seam). - [ADR-0042](ADR-0042-service-input-is-one-standard-schema.md) — A compute service declares its entire incoming configuration — config and secrets together — as one Standard Schema (`input`), read back through one typed accessor; `params`/`secrets` and `config()`/`secrets()` are replaced. The framework never introspects the schema (validate-only, per the spec): the operator's binding is the traversable structure (sourcing: literals, `envParam`, `envSecret`), the schema is the black-box judge of legality (invoked at deploy over the resolved binding with secrets as opaque `SecretString` boxes, and again at boot), and secretness is a leaf *type* enforced by validation in both directions. The wire format is one self-describing JSON document row per service with `$secret` pointers to platform variables; an env-bound key whose variable is unset resolves to key-omitted and the schema arbitrates absence — subsuming optional secrets and conditional config (`stripeId` only when `stripeEnabled`) without a framework DSL. - [ADR-0043](ADR-0043-the-control-subpath-is-the-programmatic-deploy-surface.md) — `@prisma/composer/control` is the programmatic deploy surface: typed `deploy`/`destroy`/`dev`/`log` operations (structured inputs/results, no argv/console/exit) implemented in `@internal/cli`'s `src/operations/` and re-exported per ADR-0035; the CLI is a thin renderer over them. The entry's static graph stays import-light — each operation lazily imports its executor, so importing the subpath executes nothing, and a tree that cannot load the deploy stack surfaces as a structured `pipeline` failure — and `PRISMA_COMPOSER_DEPLOYMENT_RESULT_FILE` carries the deploy result across the process boundary: the alchemy child's report hook writes a serializable `DeploymentSummary` to the named file, the operation reads it back best-effort (absent/malformed = undefined summary, never a failure). Distinct from an extension's ADR-0017 `/control` entry. - [ADR-0044](ADR-0044-errors-are-structural-envelopes-with-dotted-namespace-codes.md) — Errors are structural envelopes with dotted `NAMESPACE.SUBCODE` codes (the shared prisma/prisma foundation, duplicated pending extraction): structured at origin with why/fix splits, no catch-all codes, bugs carry no code (exit 1 + report hint), recognition is structural (`CliStructuredError.is()`), operation results ride the shared `Result` `ok` discriminator, expected failures exit 2 — with the alchemy child-status passthrough as the documented exception. - [ADR-0045](ADR-0045-deploy-state-lives-behind-the-platform-state-api.md) — Deploy state lives behind the platform state API (the Management API implements Alchemy's stock `HttpStateApi` wire contract per Branch; composer's state layer is Alchemy's stock HTTP client), and deploys hold a server-side per-`(stack, stage)` lease (TTL 60s, heartbeated, released on exit; contention fails fast naming the holder; state operations without a live lease fail 409). Supersedes ADR-0010 (lock → lease) and the storage half of ADR-0034 (Branch scoping and lifetime stand; the visible per-stage database is gone); closes ADR-0012 as obsolete. No migration: legacy stages are refused until destroyed or deleted. -- [ADR-0046](ADR-0046-the-orm-facade-is-a-peer-dependency.md) — `@prisma/composer-prisma-cloud` takes the Prisma Next postgres facade (`@prisma/orm-postgres`) as a **peer** dependency at one exact version, not a regular dependency: Composer registers an extension pack against the application's copy of the target, and two copies of a shell in one tree means two codec/operation registries and two class identities — a value from one is rejected by the other, silently. As a peer, that combination fails at install instead. Every `@prisma/orm-*` spec in the workspace is one exact version and all name the same one (`scripts/lint-orm-pins.mjs`). `@prisma/orm-toolchain`, which Composer drives rather than extends, stays a regular dependency. Replaces ADR-0022's consequence bullet on how the ORM is installed. +- [ADR-0046](ADR-0046-the-orm-facade-is-a-peer-dependency.md) — `@prisma/composer-prisma-cloud` takes the Prisma ORM postgres facade (`@prisma/orm-postgres`) as a **peer** dependency at one exact version, not a regular dependency: Composer registers an extension pack against the application's copy of the target, and two copies of a shell in one tree means two codec/operation registries and two class identities — a value from one is rejected by the other, silently. As a peer, that combination fails at install instead. Every `@prisma/orm-*` spec in the workspace is one exact version and all name the same one (`scripts/lint-orm-pins.mjs`). `@prisma/orm-toolchain`, which Composer drives rather than extends, stays a regular dependency. Replaces ADR-0022's consequence bullet on how the ORM is installed. - [ADR-0047](ADR-0047-compute-assembly-preserves-safe-runtime-topology.md) — Compute assembly traces runtime files from the author-declared Node entry without rebundling app code, preserves only symlinks whose resolved targets remain inside the staged bundle, and installs narrowly runtime-gated bootstrap compatibility when a framework needs Node semantics. Each local mechanism is removed once the upstream Alchemy Compute provider owns the equivalent guarantee. Supersedes ADR-0005's blanket ban on symlinks and its assumption that directory output is already self-contained. - [ADR-0048](ADR-0048-prisma-cloud-resources-come-from-the-upstream-alchemy-provider.md) — The six Management-API resource families (project, database, connection, app, deployment, environment variable) are the upstream `alchemy/Prisma` provider's classes, registered in Composer's `PrismaComposer` collection; Composer defines resources only where the upstream provider has no support yet (buckets) or no Management API exists behind them. Compute binds the low-level App/Deployment/EnvironmentVariable trio (the `COMPOSER_*_ORIGIN` self-edge and ADR-0005 rule out composite `Compute`); the env→deployment ordering edge rides the deployment's `app` prop, and a deployment is replaced exactly when its artifact or environment fingerprint changes — unchanged services are reused. Legacy rows in the platform state API (retired type-ids and shapes) migrate on read — the older retired SQL state stores stay destroy-only under ADR-0045 — branch-stage databases take generated physical names, and the platform's seeded `DATABASE_URL` is left system-managed. diff --git a/docs/guides/building-an-app.md b/docs/guides/building-an-app.md index bd8d658d1..a18d73ecf 100644 --- a/docs/guides/building-an-app.md +++ b/docs/guides/building-an-app.md @@ -2,7 +2,7 @@ This guide covers everything you reach for once [Getting started](getting-started.md) has shown you the shape: giving a -service a database (plain or Prisma Next-typed), packaging pieces as reusable +service a database (plain or Prisma-ORM-typed), packaging pieces as reusable Modules, the cron/storage/streams modules that ship with the framework, and the service input — configuration and secrets as one schema. @@ -10,13 +10,13 @@ the service input — configuration and secrets as one schema. A Prisma App is a tree of **Modules**. At the leaves are **services** — `compute()`, the units that run your code — and **resources** — stateful -things like `postgres()`. A parent module wires them together; your code +things like `rawPostgres()`. A parent module wires them together; your code never participates in the wiring, it just receives the results: ```ts compute({ name: 'auth', // the service's name in the app graph - deps: { db: postgres() }, // what it needs → read via service.load() + deps: { db: rawPostgres() }, // what it needs → read via service.load() input: authInput, // its incoming config → read via service.input() build: node({ module: import.meta.url, entry: '../dist/server.mjs' }), expose: { rpc: authContract },// what it offers to other services @@ -124,7 +124,7 @@ Two limits worth knowing: There are two ways for a service to get a Postgres, depending on how much you want the framework to do. -### `postgres()` — bring your own client +### `rawPostgres()` — bring your own client The dependency delivers connection config — `{ url }` — and nothing else. You build the client you already know (`pg`, Bun's `SQL`, an ORM) in your server @@ -141,10 +141,10 @@ const sql = new SQL({ url: db.url, max: 1, idleTimeout: 10 }); connections get closed; see [Deploying and operating](deploying.md#production-behavior).) -### `pnPostgres()` — a Prisma Next-typed database +### `postgres()` — a Prisma-ORM-typed database If you want typed queries and managed migrations, make the database a -[Prisma Next](https://github.com/prisma/prisma-next) one. `load()` then +[Prisma ORM](https://github.com/prisma/orm) one. `load()` then returns `{ url, client }`: the raw connection string, plus a client generated from your schema — queries like `db.client.orm.public.Product.where({ id }).first()` are compile-time @@ -152,7 +152,7 @@ checked, no SQL strings, no row mapping. The client is constructed on first access, so a service that brings its own Postgres client reads `db.url` and still gets contract-checked wiring and deploy-time migrations (ADR-0040). -The workflow, once per schema change (Prisma Next commands, via the `prisma` CLI — see the Prisma Next docs for the details): +The workflow, once per schema change (Prisma ORM commands, via the `prisma` CLI — see the Prisma ORM docs for the details): 1. Edit `contract.prisma` — your schema. 2. `prisma contract emit` — regenerates `contract.json` + `contract.d.ts` from it. @@ -166,18 +166,18 @@ referenced by both the resource and every service that queries it: ```ts // src/data.ts -import { pnContract } from '@prisma/composer-prisma-cloud/prisma-next'; +import { dataContract } from '@prisma/composer-prisma-cloud/orm'; import type { Contract } from '../contract.d.ts'; import contractJson from '../contract.json' with { type: 'json' }; -export const catalogData = pnContract(contractJson); +export const catalogData = dataContract(contractJson); ``` -`pnPostgres` is both ends of the edge, told apart by what you pass it. The +`postgres` is both ends of the edge, told apart by what you pass it. The contract alone is the dependency end — the service declaring what it queries: ```ts -deps: { db: pnPostgres(catalogData) } +deps: { db: postgres(catalogData) } ``` An options object is the resource end — the module that owns the database @@ -186,13 +186,13 @@ module file) so the deploy can find `migrations/`: ```ts const db = provision( - pnPostgres({ name: 'database', contract: catalogData, config: './prisma.config.ts' }), + postgres({ name: 'database', contract: catalogData, config: './prisma.config.ts' }), ); ``` Because both ends share the contract value, the deploy refuses to wire a service against a database whose schema doesn't match. -[`examples/pn-widgets`](../../examples/pn-widgets/) is the minimal working +[`examples/orm-demo`](../../examples/orm-demo/) is the minimal working version; [`examples/store/modules/catalog`](../../examples/store/modules/catalog/) is the full pattern inside a reusable Module. @@ -216,7 +216,7 @@ export default module( 'auth', { secrets: { signingKey: secret() }, expose: { rpc: authContract } }, ({ secrets, provision }) => { - const db = provision(postgres({ name: 'database' })); + const db = provision(rawPostgres({ name: 'database' })); const service = provision(authService, { id: 'service', deps: { db }, @@ -376,7 +376,7 @@ and choosing the channel is most of the decision: | The value is… | Declare it as | Provide it | Read it | | --- | --- | --- | --- | -| produced by another node — a database, another service | a dependency: `deps: { db: postgres() }` | wire it at `provision()` | `service.load()` | +| produced by another node — a database, another service | a dependency: `deps: { db: rawPostgres() }` | wire it at `provision()` | `service.load()` | | anything else — a region, a flag, a job list, a credential | one field of the service's `input` schema | bind it at `provision()`: a literal, `envParam()`, or `envSecret()` | `service.input()` | Dependencies are covered above. This section is the second row. diff --git a/docs/guides/deploying.md b/docs/guides/deploying.md index 1cf62282d..5eb7b1815 100644 --- a/docs/guides/deploying.md +++ b/docs/guides/deploying.md @@ -293,7 +293,7 @@ Local dev state is not migrated: if `prisma-composer dev` fails at plan time wit ## Updating a database whose schema an older version synthesized -Older framework versions created a fresh `pnPostgres` database's schema at first deploy by synthesizing it from the contract, with no migration authored. Deploys are now replay-only — they apply only committed migrations — so the first contract change against such a database refuses with `MIGRATION_PATH_NOT_FOUND`: the migration graph has no edge reaching the database's current schema, because none was ever authored. +Older framework versions created a fresh `postgres` database's schema at first deploy by synthesizing it from the contract, with no migration authored. Deploys are now replay-only — they apply only committed migrations — so the first contract change against such a database refuses with `MIGRATION_PATH_NOT_FOUND`: the migration graph has no edge reaching the database's current schema, because none was ever authored. The marker those deploys signed is an accurate signature of the schema, so the fix is to make the authored graph reach it. The refusal names the database's current hash. Set a ref to that hash — write `migrations/app/refs/db.json` (the ref `migration plan` reads its origin from) with `{ "hash": "", "invariants": [] }` — then emit and plan as usual: diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index 08ccec593..9f70bae3d 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -41,7 +41,7 @@ build, a deploy. At the end there's a section on The app is deliberately tiny — a `quotes` API and a public `gateway` that calls it, no database — so you can see the whole shape at once. Adding a -Postgres (including a Prisma Next-typed one) is the first thing to do after, +Postgres (including a Prisma-ORM-typed one) is the first thing to do after, and [Building an app](building-an-app.md#databases) covers it. You'll need: @@ -434,7 +434,7 @@ your built server file, then make three changes to the server itself: `envSecret`. [Building an app § Service input](building-an-app.md#service-input) has the how-to-choose table and both shapes. -3. If it talks to Postgres: declare `deps: { db: postgres() }` and build your +3. If it talks to Postgres: declare `deps: { db: rawPostgres() }` and build your existing client (`pg`, Bun's `SQL`, whatever you use today) from the injected `db.url` instead of a connection-string env var. @@ -478,6 +478,6 @@ the wiring for free. - [Deploying and operating](deploying.md) — stages, destroy, CI, how the app behaves in production. - [`examples/`](../../examples/) — complete apps: start with - [pn-widgets](../../examples/pn-widgets/) (one service + one Prisma + [orm-demo](../../examples/orm-demo/) (one service + one Prisma Next-typed database) or [store](../../examples/store/) (four modules, cron, a Next.js storefront). diff --git a/examples/auth/module.ts b/examples/auth/module.ts index c470eabb3..6035a1a99 100644 --- a/examples/auth/module.ts +++ b/examples/auth/module.ts @@ -2,13 +2,13 @@ import { module } from '@prisma/composer'; import { envParam, envSecret } from '@prisma/composer-prisma-cloud'; import { auth } from '@prisma/composer-prisma-cloud/auth'; import { email } from '@prisma/composer-prisma-cloud/email'; -import { pnPostgres } from '@prisma/composer-prisma-cloud/prisma-next'; +import { postgres } from '@prisma/composer-prisma-cloud/orm'; import apiService from './src/api/service.ts'; import { appContract } from './src/contract.ts'; import opsService from './src/ops/service.ts'; /** - * The auth example: a dedicated Prisma Next database carrying ONLY the auth + * The auth example: a dedicated Prisma ORM database carrying ONLY the auth * extension pack (empty app space), the `auth()` module wired to it, the * `email()` module wired as its `email` boundary dep (verification/reset/ * magic-link delivery — the module-depends-on-module proof), and two @@ -30,7 +30,7 @@ import opsService from './src/ops/service.ts'; */ export default module('auth-example', ({ provision }) => { const db = provision( - pnPostgres({ name: 'database', contract: appContract, config: './prisma-next.config.ts' }), + postgres({ name: 'database', contract: appContract, config: './orm.config.ts' }), { id: 'database' }, ); const mail = provision(email(), { diff --git a/examples/auth/prisma-next.config.ts b/examples/auth/orm.config.ts similarity index 70% rename from examples/auth/prisma-next.config.ts rename to examples/auth/orm.config.ts index 37ce2af0a..df68e7f7d 100644 --- a/examples/auth/prisma-next.config.ts +++ b/examples/auth/orm.config.ts @@ -2,13 +2,13 @@ import { definePrismaConfig } from '@prisma/cli-engine'; import authPack from '@prisma/composer-prisma-cloud/auth/pack'; import { defineConfig } from '@prisma/orm-postgres/config'; -// The Prisma Next config anchors the (empty) app contract and the migrations -// directory, and declares the auth extension pack — `prisma-next migration +// The ORM config anchors the (empty) app contract and the migrations +// directory, and declares the auth extension pack — `prisma migration // plan` materialises the pack's shipped migrations into migrations/auth/, and // the deploy's migration step migrates BOTH spaces. The deploy lowering loads -// this file by path (from the pnPostgres resource's `config`); the app build +// this file by path (from the postgres resource's `config`); the app build // never imports it. -// Regenerate contract.{json,d.ts}: prisma contract emit --config prisma-next.config.ts +// Regenerate contract.{json,d.ts}: prisma contract emit --config orm.config.ts export default definePrismaConfig({ orm: defineConfig({ contract: './contract.prisma', diff --git a/examples/auth/src/contract.ts b/examples/auth/src/contract.ts index 3e18b3c52..39ddcf923 100644 --- a/examples/auth/src/contract.ts +++ b/examples/auth/src/contract.ts @@ -1,12 +1,12 @@ /** - * The (empty) app-space contract wrapped into the framework's `prisma-next` - * kind — the resource end references it (`pnPostgres({ name, contract, + * The (empty) app-space contract wrapped into the framework's `postgres` + * kind — the resource end references it (`postgres({ name, contract, * config })`); no service consumes the app space, so there is no dependency * end: the auth module claims the database through its own `authDb()` pack * requirement. */ -import { pnContract } from '@prisma/composer-prisma-cloud/prisma-next'; +import { dataContract } from '@prisma/composer-prisma-cloud/orm'; import type { Contract } from '../contract.d.ts'; import contractJson from '../contract.json' with { type: 'json' }; -export const appContract = pnContract(contractJson); +export const appContract = dataContract(contractJson); diff --git a/examples/auth/tests/pg-harness.ts b/examples/auth/tests/pg-harness.ts index 22bbe75e2..154b40f15 100644 --- a/examples/auth/tests/pg-harness.ts +++ b/examples/auth/tests/pg-harness.ts @@ -1,5 +1,5 @@ /** - * A throwaway local Postgres for the prisma-next integration test — mirrors + * A throwaway local Postgres for the Prisma ORM integration test — mirrors * `packages/alchemy/src/state/__tests__/harness.ts` (the state store's harness) * so both suites share one Postgres availability contract and one CI service * container. Kept as a local copy rather than a cross-package import: that @@ -126,7 +126,7 @@ export const startTestPostgres = (): TestPostgres | undefined => { if (initdb === undefined || pgCtl === undefined) { if (process.env['CI'] !== undefined) { throw new Error( - 'CI is set but no Postgres is available for the prisma-next integration test: neither ' + + 'CI is set but no Postgres is available for the Prisma ORM integration test: neither ' + 'STATE_TEST_DATABASE_URL nor initdb/pg_ctl (PATH, Homebrew, or Ubuntu ' + '/usr/lib/postgresql/*/bin) were found. The CI test job wires a `services: postgres:` ' + 'container and STATE_TEST_DATABASE_URL (see .github/workflows/ci.yml).', diff --git a/examples/auth/tsconfig.json b/examples/auth/tsconfig.json index 89452a3bc..36daa5323 100644 --- a/examples/auth/tsconfig.json +++ b/examples/auth/tsconfig.json @@ -6,7 +6,7 @@ "include": [ "module.ts", "prisma-composer.config.ts", - "prisma-next.config.ts", + "prisma.config.ts", "src", "tests", "scripts" diff --git a/examples/pn-widgets/contract.d.ts b/examples/orm-demo/contract.d.ts similarity index 100% rename from examples/pn-widgets/contract.d.ts rename to examples/orm-demo/contract.d.ts diff --git a/examples/pn-widgets/contract.json b/examples/orm-demo/contract.json similarity index 100% rename from examples/pn-widgets/contract.json rename to examples/orm-demo/contract.json diff --git a/examples/pn-widgets/contract.prisma b/examples/orm-demo/contract.prisma similarity index 100% rename from examples/pn-widgets/contract.prisma rename to examples/orm-demo/contract.prisma diff --git a/examples/pn-widgets/migrations/app/20260711T1921_init/migration.json b/examples/orm-demo/migrations/app/20260711T1921_init/migration.json similarity index 100% rename from examples/pn-widgets/migrations/app/20260711T1921_init/migration.json rename to examples/orm-demo/migrations/app/20260711T1921_init/migration.json diff --git a/examples/pn-widgets/migrations/app/20260711T1921_init/migration.ts b/examples/orm-demo/migrations/app/20260711T1921_init/migration.ts similarity index 100% rename from examples/pn-widgets/migrations/app/20260711T1921_init/migration.ts rename to examples/orm-demo/migrations/app/20260711T1921_init/migration.ts diff --git a/examples/pn-widgets/migrations/app/20260711T1921_init/ops.json b/examples/orm-demo/migrations/app/20260711T1921_init/ops.json similarity index 100% rename from examples/pn-widgets/migrations/app/20260711T1921_init/ops.json rename to examples/orm-demo/migrations/app/20260711T1921_init/ops.json diff --git a/examples/pn-widgets/migrations/snapshots/2c0c3445737906d85590514e3cec242919c12f8112346c2cebe74e681d65f0f0/contract.d.ts b/examples/orm-demo/migrations/snapshots/2c0c3445737906d85590514e3cec242919c12f8112346c2cebe74e681d65f0f0/contract.d.ts similarity index 100% rename from examples/pn-widgets/migrations/snapshots/2c0c3445737906d85590514e3cec242919c12f8112346c2cebe74e681d65f0f0/contract.d.ts rename to examples/orm-demo/migrations/snapshots/2c0c3445737906d85590514e3cec242919c12f8112346c2cebe74e681d65f0f0/contract.d.ts diff --git a/examples/pn-widgets/migrations/snapshots/2c0c3445737906d85590514e3cec242919c12f8112346c2cebe74e681d65f0f0/contract.json b/examples/orm-demo/migrations/snapshots/2c0c3445737906d85590514e3cec242919c12f8112346c2cebe74e681d65f0f0/contract.json similarity index 100% rename from examples/pn-widgets/migrations/snapshots/2c0c3445737906d85590514e3cec242919c12f8112346c2cebe74e681d65f0f0/contract.json rename to examples/orm-demo/migrations/snapshots/2c0c3445737906d85590514e3cec242919c12f8112346c2cebe74e681d65f0f0/contract.json diff --git a/examples/pn-widgets/module.ts b/examples/orm-demo/module.ts similarity index 65% rename from examples/pn-widgets/module.ts rename to examples/orm-demo/module.ts index 8eaccba57..6b87f4025 100644 --- a/examples/pn-widgets/module.ts +++ b/examples/orm-demo/module.ts @@ -1,22 +1,22 @@ import { module } from '@prisma/composer'; -import { pnPostgres } from '@prisma/composer-prisma-cloud/prisma-next'; +import { postgres } from '@prisma/composer-prisma-cloud/orm'; import { widgetContract } from './src/contract.ts'; import widgetsService from './src/service.ts'; /** - * The pn-widgets app: one Prisma Next-typed Postgres and one compute service + * The orm-demo app: one Prisma-ORM-typed Postgres and one compute service * that round-trips through it. A closed root (empty boundary). * * The database provision id is "database" (not "db"): the prisma-cloud target * passes it through as the Prisma resource name, and the Connection API rejects * names shorter than 3 characters. The resource carries two doors (ADR-0022): * `contract` (consumed — types + wires the resource, gives the deploy its - * target storageHash) and `config` (the prisma-next.config.ts PATH the deploy + * target storageHash) and `config` (the ORM config PATH the deploy * migration step loads to find migrations/ — never imported by the app build). */ -export default module('pn-widgets', ({ provision }) => { +export default module('orm-demo', ({ provision }) => { const db = provision( - pnPostgres({ name: 'database', contract: widgetContract, config: './prisma-next.config.ts' }), + postgres({ name: 'database', contract: widgetContract, config: './orm.config.ts' }), { id: 'database' }, ); provision(widgetsService, { id: 'widgets', deps: { db } }); diff --git a/examples/pn-widgets/prisma-next.config.ts b/examples/orm-demo/orm.config.ts similarity index 53% rename from examples/pn-widgets/prisma-next.config.ts rename to examples/orm-demo/orm.config.ts index 2c04b31c9..2b4da83bd 100644 --- a/examples/pn-widgets/prisma-next.config.ts +++ b/examples/orm-demo/orm.config.ts @@ -1,12 +1,14 @@ import { definePrismaConfig } from '@prisma/cli-engine'; import { defineConfig } from '@prisma/orm-postgres/config'; -// The Prisma Next config anchors the contract source and the migrations +// The ORM config anchors the contract source and the migrations // directory on the filesystem. The deploy lowering loads it (by path, from the -// pnPostgres resource's `config`) to resolve `migrations/` — the app build -// never imports it. `db.connection` is dead weight here: the framework injects +// postgres resource's `config`) to resolve `migrations/` — the app build +// never imports it. Named orm.config.ts, not prisma.config.ts: the standalone +// prisma-composer bin discovers prisma.config.ts as its own config and +// rejects the ORM sections (CLI.CONFIG_UNKNOWN_SECTION). `db.connection` is dead weight here: the framework injects // the URL at hydrate (no-globals), so nothing reads it. -// Regenerate contract.{json,d.ts}: prisma contract emit --config prisma-next.config.ts +// Regenerate contract.{json,d.ts}: prisma contract emit --config orm.config.ts export default definePrismaConfig({ orm: defineConfig({ contract: './contract.prisma', diff --git a/examples/pn-widgets/package.json b/examples/orm-demo/package.json similarity index 75% rename from examples/pn-widgets/package.json rename to examples/orm-demo/package.json index 3e599b607..830b3f4c1 100644 --- a/examples/pn-widgets/package.json +++ b/examples/orm-demo/package.json @@ -1,13 +1,13 @@ { - "name": "@prisma/example-pn-widgets", + "name": "@prisma/example-orm-demo", "version": "0.15.0", "private": true, "type": "module", "scripts": { "build": "rm -rf dist && bun build src/server.ts --target=bun --outfile dist/server.mjs", "typecheck": "tsc --noEmit", - "deploy": "pnpm turbo run build --filter @prisma/example-pn-widgets... && ( set -a; . \"${PRISMA_DEPLOY_ENV:-../../.env}\"; set +a; bun node_modules/.bin/prisma-composer deploy module.ts ${PN_WIDGETS_STACK_NAME:+--name \"$PN_WIDGETS_STACK_NAME\"} )", - "destroy": "( set -a; . \"${PRISMA_DEPLOY_ENV:-../../.env}\"; set +a; bun node_modules/.bin/prisma-composer destroy module.ts --production ${PN_WIDGETS_STACK_NAME:+--name \"$PN_WIDGETS_STACK_NAME\"} )" + "deploy": "pnpm turbo run build --filter @prisma/example-orm-demo... && ( set -a; . \"${PRISMA_DEPLOY_ENV:-../../.env}\"; set +a; bun node_modules/.bin/prisma-composer deploy module.ts ${ORM_DEMO_STACK_NAME:+--name \"$ORM_DEMO_STACK_NAME\"} )", + "destroy": "( set -a; . \"${PRISMA_DEPLOY_ENV:-../../.env}\"; set +a; bun node_modules/.bin/prisma-composer destroy module.ts --production ${ORM_DEMO_STACK_NAME:+--name \"$ORM_DEMO_STACK_NAME\"} )" }, "dependencies": { "@prisma/composer": "workspace:0.15.0", diff --git a/examples/pn-widgets/prisma-composer.config.ts b/examples/orm-demo/prisma-composer.config.ts similarity index 100% rename from examples/pn-widgets/prisma-composer.config.ts rename to examples/orm-demo/prisma-composer.config.ts diff --git a/examples/pn-widgets/scripts/e2e-verify.ts b/examples/orm-demo/scripts/e2e-verify.ts similarity index 89% rename from examples/pn-widgets/scripts/e2e-verify.ts rename to examples/orm-demo/scripts/e2e-verify.ts index 0c10e8ef7..905278b9d 100644 --- a/examples/pn-widgets/scripts/e2e-verify.ts +++ b/examples/orm-demo/scripts/e2e-verify.ts @@ -1,8 +1,8 @@ #!/usr/bin/env bun /** - * Resolves the pn-widgets service's deployed URL via the typed Management API + * Resolves the orm-demo service's deployed URL via the typed Management API * client, then polls it until it returns `{"ok":true,...}` — a live round trip - * through the Prisma Next typed client against the migrated schema. + * through the Prisma ORM typed client against the migrated schema. * Requires PRISMA_SERVICE_TOKEN; STACK_NAME overrides the project name. */ @@ -16,7 +16,7 @@ if (token === undefined || token.length === 0) { console.error('PRISMA_SERVICE_TOKEN is required'); process.exit(1); } -const stack = process.env['STACK_NAME'] ?? 'pn-widgets'; +const stack = process.env['STACK_NAME'] ?? 'orm-demo'; function fail(message: string): never { console.error(message); @@ -60,7 +60,7 @@ if (domain === undefined || domain.length === 0) { // appEndpointDomain may arrive WITH the https:// scheme; tolerate either. const url = /^https?:\/\//.test(domain) ? domain : `https://${domain}/`; -console.log(`pn-widgets URL: ${url}`); +console.log(`orm-demo URL: ${url}`); const deadline = Date.now() + POLL_DEADLINE_MS; let lastBody = ''; @@ -69,7 +69,7 @@ while (Date.now() < deadline) { const response = await fetch(url, { signal: AbortSignal.timeout(30_000) }); lastBody = await response.text(); if (lastBody.includes('"ok":true')) { - console.log('Round trip OK — the typed Prisma Next client inserted + read a Widget:'); + console.log('Round trip OK — the typed Prisma ORM client inserted + read a Widget:'); console.log(lastBody); process.exit(0); } diff --git a/examples/pn-widgets/src/contract.ts b/examples/orm-demo/src/contract.ts similarity index 52% rename from examples/pn-widgets/src/contract.ts rename to examples/orm-demo/src/contract.ts index 736a1a5c6..1afa4097f 100644 --- a/examples/pn-widgets/src/contract.ts +++ b/examples/orm-demo/src/contract.ts @@ -1,14 +1,14 @@ /** - * The Widget data contract wrapped into the framework's `prisma-next` kind — + * The Widget data contract wrapped into the framework's `postgres` kind — * the ONE value both ends reference: the resource end - * (`pnPostgres({ name, contract, config })` in module.ts) and the dependency - * end (`pnPostgres(contract)` in service.ts). `contractJson` is the emitted + * (`postgres({ name, contract, config })` in module.ts) and the dependency + * end (`postgres(contract)` in service.ts). `contractJson` is the emitted * data the runtime hydrates from; `Contract` is the emitted branded type the * service's typed client flows from (PSL-first authoring mode — the type is * passed explicitly since a JSON import's inferred type is plain data). */ -import { pnContract } from '@prisma/composer-prisma-cloud/prisma-next'; +import { dataContract } from '@prisma/composer-prisma-cloud/orm'; import type { Contract } from '../contract.d.ts'; import contractJson from '../contract.json' with { type: 'json' }; -export const widgetContract = pnContract(contractJson); +export const widgetContract = dataContract(contractJson); diff --git a/examples/pn-widgets/src/server.ts b/examples/orm-demo/src/server.ts similarity index 93% rename from examples/pn-widgets/src/server.ts rename to examples/orm-demo/src/server.ts index 3359ef55d..f8b6b122c 100644 --- a/examples/pn-widgets/src/server.ts +++ b/examples/orm-demo/src/server.ts @@ -4,12 +4,12 @@ // // A genuine round trip through the contract's schema: each request inserts a // Widget (its id auto-generated by the contract's uuid default) and reads the -// rows back through the SAME typed Prisma Next client, returning the newest +// rows back through the SAME typed Prisma ORM client, returning the newest // label + the row count. The e2e verify script asserts the label round-trips. import service from './service.ts'; -const { db } = service.load(); // db: { url, client } — the Prisma Next binding (ADR-0040) +const { db } = service.load(); // db: { url, client } — the Prisma ORM binding (ADR-0040) const port = service.port(); // A Prisma Postgres direct connection is dropped when it idles / the service diff --git a/examples/pn-widgets/src/service.ts b/examples/orm-demo/src/service.ts similarity index 66% rename from examples/pn-widgets/src/service.ts rename to examples/orm-demo/src/service.ts index 23ab9dc9a..c795d0354 100644 --- a/examples/pn-widgets/src/service.ts +++ b/examples/orm-demo/src/service.ts @@ -1,19 +1,19 @@ import node from '@prisma/composer/node'; import { compute } from '@prisma/composer-prisma-cloud'; -import { pnPostgres } from '@prisma/composer-prisma-cloud/prisma-next'; +import { postgres } from '@prisma/composer-prisma-cloud/orm'; import { widgetContract } from './contract.ts'; /** - * The pn-widgets compute service. Its `db` dependency is the Prisma - * Next-typed Postgres: `pnPostgres(widgetContract)`'s binding (what - * `load()` returns) carries the raw connection URL and the typed Prisma Next + * The orm-demo compute service. Its `db` dependency is the Prisma + * Next-typed Postgres: `postgres(widgetContract)`'s binding (what + * `load()` returns) carries the raw connection URL and the typed Prisma ORM * client, built lazily from the contract + the injected URL (ADR-0040), so * server.ts queries `db.client.orm.public.Widget` directly, typed by the contract. */ export default compute({ name: 'widgets', deps: { - db: pnPostgres(widgetContract), + db: postgres(widgetContract), }, build: node({ module: import.meta.url, entry: '../dist/server.mjs' }), }); diff --git a/examples/pn-widgets/tsconfig.json b/examples/orm-demo/tsconfig.json similarity index 100% rename from examples/pn-widgets/tsconfig.json rename to examples/orm-demo/tsconfig.json diff --git a/examples/pn-widgets/turbo.json b/examples/orm-demo/turbo.json similarity index 100% rename from examples/pn-widgets/turbo.json rename to examples/orm-demo/turbo.json diff --git a/examples/store/DEMO.md b/examples/store/DEMO.md index c615498a4..20505d422 100644 --- a/examples/store/DEMO.md +++ b/examples/store/DEMO.md @@ -16,14 +16,14 @@ runtime. ## 3. A module — [modules/catalog/src/module.ts](modules/catalog/src/module.ts) -catalog owns its own Postgres — a **Prisma Next-typed** one. Three pieces: +catalog owns its own Postgres — a **Prisma-ORM-typed** one. Three pieces: - [contract.prisma](modules/catalog/contract.prisma): the data schema. Emit turns it into a typed contract; `migration plan` authors [migrations/](modules/catalog/migrations), which the deploy applies before the service ever starts — no `create table if not exists` in app code. - [src/service.ts](modules/catalog/src/service.ts): the service declares - `deps: { db: pnPostgres(catalogData) }` and what it exposes; that's the + `deps: { db: postgres(catalogData) }` and what it exposes; that's the whole declaration. - [src/server.ts](modules/catalog/src/server.ts): `load()` hands it the `{ url, client }` binding — `db.client.orm.public.Product.where({ id }).first()`. No SQL strings, no @@ -78,7 +78,7 @@ contracts the real modules serve. No Postgres, no server, no cloud. ## Likely questions - **"What actually got deployed?"** Each compute service is a Prisma Compute - VM; each module's `postgres()` is a Prisma Postgres database. The deploy + VM; each module's `rawPostgres()` is a Prisma Postgres database. The deploy derives the graph from the code above — same code could target another extension pack. - **"What's in an edge?"** At runtime: an env var (`CATALOG_URL`) the diff --git a/examples/store/README.md b/examples/store/README.md index 53f7bef62..b21a31733 100644 --- a/examples/store/README.md +++ b/examples/store/README.md @@ -48,7 +48,7 @@ typed RPC contracts. The whole composition is [module.ts](module.ts). `db.client.orm.public.Product.where({ id }).first()`, no SQL, no row mapping. Consumers wire only the exposed `rpc` port. - [modules/orders](modules/orders) — a Module with a **boundary input**: it - owns its (also Prisma Next-typed) Postgres but declares `deps: { catalog }`, + owns its (also Prisma-ORM-typed) Postgres but declares `deps: { catalog }`, so whoever provisions it supplies a producer of `catalogContract`. `placeOrder` calls catalog to price the order at placement time. - [modules/storefront](modules/storefront) — a real Next.js app. The page diff --git a/examples/store/modules/orders/prisma-next.config.ts b/examples/store/modules/catalog/orm.config.ts similarity index 85% rename from examples/store/modules/orders/prisma-next.config.ts rename to examples/store/modules/catalog/orm.config.ts index bedc669e0..245b98b75 100644 --- a/examples/store/modules/orders/prisma-next.config.ts +++ b/examples/store/modules/catalog/orm.config.ts @@ -2,10 +2,10 @@ import { definePrismaConfig } from '@prisma/cli-engine'; import { defineConfig } from '@prisma/orm-postgres/config'; // Anchors the contract source and migrations/ on disk. The deploy lowering -// loads it (by path, from the pnPostgres resource's `config`) to find the +// loads it (by path, from the postgres resource's `config`) to find the // migrations — the app build never imports it. `db.connection` is dead // weight: the framework injects the URL at hydrate (no-globals). -// Regenerate contract.{json,d.ts}: prisma contract emit --config prisma-next.config.ts +// Regenerate contract.{json,d.ts}: prisma contract emit --config orm.config.ts export default definePrismaConfig({ orm: defineConfig({ contract: './contract.prisma', diff --git a/examples/store/modules/catalog/src/data.ts b/examples/store/modules/catalog/src/data.ts index af6368318..7745f27bb 100644 --- a/examples/store/modules/catalog/src/data.ts +++ b/examples/store/modules/catalog/src/data.ts @@ -1,12 +1,12 @@ /** - * catalog's Prisma Next data contract wrapped into the framework's - * `prisma-next` kind — the ONE value both ends reference: the resource end - * (`pnPostgres({ name, contract, config })` in module.ts) and the dependency - * end (`pnPostgres(catalogData)` in service.ts). Emitted from contract.prisma - * by `prisma-next contract emit`. + * catalog's Prisma ORM data contract wrapped into the framework's + * `postgres` kind — the ONE value both ends reference: the resource end + * (`postgres({ name, contract, config })` in module.ts) and the dependency + * end (`postgres(catalogData)` in service.ts). Emitted from contract.prisma + * by `prisma contract emit`. */ -import { pnContract } from '@prisma/composer-prisma-cloud/prisma-next'; +import { dataContract } from '@prisma/composer-prisma-cloud/orm'; import type { Contract } from '../contract.d.ts'; import contractJson from '../contract.json' with { type: 'json' }; -export const catalogData = pnContract(contractJson); +export const catalogData = dataContract(contractJson); diff --git a/examples/store/modules/catalog/src/module.ts b/examples/store/modules/catalog/src/module.ts index 293af0e3e..eb3df06e1 100644 --- a/examples/store/modules/catalog/src/module.ts +++ b/examples/store/modules/catalog/src/module.ts @@ -2,7 +2,7 @@ * The catalog Module: a reusable unit that owns its own Postgres — a Prisma * Next-typed one (ADR-0022). The resource carries two doors: `contract` * (types + wires the resource, gives the deploy its target storageHash) and - * `config` (the prisma-next.config.ts PATH the deploy's migration step loads + * `config` (the ORM config PATH the deploy's migration step loads * to find migrations/ — never imported by the app build). A consumer wires * only the exposed rpc contract — it never sees the database. * @@ -12,15 +12,15 @@ */ import { fileURLToPath } from 'node:url'; import { module } from '@prisma/composer'; -import { pnPostgres } from '@prisma/composer-prisma-cloud/prisma-next'; +import { postgres } from '@prisma/composer-prisma-cloud/orm'; import { catalogContract } from './contract.ts'; import { catalogData } from './data.ts'; import catalogService from './service.ts'; -const config = fileURLToPath(new URL('../prisma-next.config.ts', import.meta.url)); +const config = fileURLToPath(new URL('../orm.config.ts', import.meta.url)); export default module('catalog', { expose: { rpc: catalogContract } }, ({ provision }) => { - const db = provision(pnPostgres({ name: 'database', contract: catalogData, config })); + const db = provision(postgres({ name: 'database', contract: catalogData, config })); const service = provision(catalogService, { id: 'service', deps: { db } }); return { rpc: service.rpc }; }); diff --git a/examples/store/modules/catalog/src/server.ts b/examples/store/modules/catalog/src/server.ts index 1c792b8ba..2483cf787 100644 --- a/examples/store/modules/catalog/src/server.ts +++ b/examples/store/modules/catalog/src/server.ts @@ -2,7 +2,7 @@ import { serve } from '@prisma/composer/service-rpc'; import type { Product } from './contract.ts'; import service from './service.ts'; -// load() hydrates `db` into the { url, client } Prisma Next binding (ADR-0040) +// load() hydrates `db` into the { url, client } Prisma ORM binding (ADR-0040) // — no SQL, no row mapping; queries are typed by contract.prisma's emitted contract. const { db } = service.load(); const port = service.port(); diff --git a/examples/store/modules/catalog/src/service.ts b/examples/store/modules/catalog/src/service.ts index f9c109579..005ab4364 100644 --- a/examples/store/modules/catalog/src/service.ts +++ b/examples/store/modules/catalog/src/service.ts @@ -1,16 +1,16 @@ import node from '@prisma/composer/node'; import { compute } from '@prisma/composer-prisma-cloud'; -import { pnPostgres } from '@prisma/composer-prisma-cloud/prisma-next'; +import { postgres } from '@prisma/composer-prisma-cloud/orm'; import { catalogContract } from './contract.ts'; import { catalogData } from './data.ts'; -// The `db` dependency is Prisma Next-typed: `load()` returns the { url, client } +// The `db` dependency is Prisma-ORM-typed: `load()` returns the { url, client } // binding, its typed client built lazily from the contract + the injected URL // (ADR-0040) — server.ts queries `db.client.orm.public.Product` directly. export default compute({ name: 'catalog', deps: { - db: pnPostgres(catalogData), + db: postgres(catalogData), }, build: node({ module: import.meta.url, entry: '../dist/server.mjs' }), expose: { rpc: catalogContract }, diff --git a/examples/store/modules/catalog/prisma-next.config.ts b/examples/store/modules/orders/orm.config.ts similarity index 85% rename from examples/store/modules/catalog/prisma-next.config.ts rename to examples/store/modules/orders/orm.config.ts index bedc669e0..245b98b75 100644 --- a/examples/store/modules/catalog/prisma-next.config.ts +++ b/examples/store/modules/orders/orm.config.ts @@ -2,10 +2,10 @@ import { definePrismaConfig } from '@prisma/cli-engine'; import { defineConfig } from '@prisma/orm-postgres/config'; // Anchors the contract source and migrations/ on disk. The deploy lowering -// loads it (by path, from the pnPostgres resource's `config`) to find the +// loads it (by path, from the postgres resource's `config`) to find the // migrations — the app build never imports it. `db.connection` is dead // weight: the framework injects the URL at hydrate (no-globals). -// Regenerate contract.{json,d.ts}: prisma contract emit --config prisma-next.config.ts +// Regenerate contract.{json,d.ts}: prisma contract emit --config orm.config.ts export default definePrismaConfig({ orm: defineConfig({ contract: './contract.prisma', diff --git a/examples/store/modules/orders/src/data.ts b/examples/store/modules/orders/src/data.ts index c7167f7ab..b30e12153 100644 --- a/examples/store/modules/orders/src/data.ts +++ b/examples/store/modules/orders/src/data.ts @@ -1,11 +1,11 @@ /** - * orders' Prisma Next data contract wrapped into the framework's - * `prisma-next` kind — referenced by both the resource end (module.ts) and + * orders' Prisma ORM data contract wrapped into the framework's + * `postgres` kind — referenced by both the resource end (module.ts) and * the dependency end (service.ts). Emitted from contract.prisma by - * `prisma-next contract emit`. + * `prisma contract emit`. */ -import { pnContract } from '@prisma/composer-prisma-cloud/prisma-next'; +import { dataContract } from '@prisma/composer-prisma-cloud/orm'; import type { Contract } from '../contract.d.ts'; import contractJson from '../contract.json' with { type: 'json' }; -export const ordersData = pnContract(contractJson); +export const ordersData = dataContract(contractJson); diff --git a/examples/store/modules/orders/src/module.ts b/examples/store/modules/orders/src/module.ts index 8087cfcdb..78f1d99e3 100644 --- a/examples/store/modules/orders/src/module.ts +++ b/examples/store/modules/orders/src/module.ts @@ -1,5 +1,5 @@ /** - * The orders Module: owns its own Prisma Next-typed Postgres (ADR-0022), but + * The orders Module: owns its own Prisma-ORM-typed Postgres (ADR-0022), but * NOT the catalog — that comes in through the module's boundary * (`deps.catalog`), wired by whoever provisions this module. The consumer * supplies any producer of `catalogContract`; orders never knows which. @@ -7,19 +7,19 @@ import { fileURLToPath } from 'node:url'; import { module } from '@prisma/composer'; import { rpc } from '@prisma/composer/service-rpc'; -import { pnPostgres } from '@prisma/composer-prisma-cloud/prisma-next'; +import { postgres } from '@prisma/composer-prisma-cloud/orm'; import { catalogContract } from '@store/catalog/contract'; import { ordersContract } from './contract.ts'; import { ordersData } from './data.ts'; import ordersService from './service.ts'; -const config = fileURLToPath(new URL('../prisma-next.config.ts', import.meta.url)); +const config = fileURLToPath(new URL('../orm.config.ts', import.meta.url)); export default module( 'orders', { deps: { catalog: rpc(catalogContract) }, expose: { rpc: ordersContract } }, ({ inputs, provision }) => { - const db = provision(pnPostgres({ name: 'database', contract: ordersData, config })); + const db = provision(postgres({ name: 'database', contract: ordersData, config })); const service = provision(ordersService, { id: 'service', deps: { db, catalog: inputs.catalog }, diff --git a/examples/store/modules/orders/src/server.ts b/examples/store/modules/orders/src/server.ts index a7c4ace8c..34f942ff6 100644 --- a/examples/store/modules/orders/src/server.ts +++ b/examples/store/modules/orders/src/server.ts @@ -1,7 +1,7 @@ import { serve } from '@prisma/composer/service-rpc'; import service from './service.ts'; -// load() hydrates both deps: `db` is the { url, client } Prisma Next binding +// load() hydrates both deps: `db` is the { url, client } Prisma ORM binding // (ADR-0040), `catalog` a typed client of catalogContract — both plain async calls. const { db, catalog } = service.load(); const port = service.port(); diff --git a/examples/store/modules/orders/src/service.ts b/examples/store/modules/orders/src/service.ts index 6bea30faf..5e330b367 100644 --- a/examples/store/modules/orders/src/service.ts +++ b/examples/store/modules/orders/src/service.ts @@ -1,7 +1,7 @@ import node from '@prisma/composer/node'; import { rpc } from '@prisma/composer/service-rpc'; import { compute } from '@prisma/composer-prisma-cloud'; -import { pnPostgres } from '@prisma/composer-prisma-cloud/prisma-next'; +import { postgres } from '@prisma/composer-prisma-cloud/orm'; import { catalogContract } from '@store/catalog/contract'; import { ordersContract } from './contract.ts'; import { ordersData } from './data.ts'; @@ -12,7 +12,7 @@ import { ordersData } from './data.ts'; export default compute({ name: 'orders', deps: { - db: pnPostgres(ordersData), + db: postgres(ordersData), catalog: rpc(catalogContract), }, build: node({ module: import.meta.url, entry: '../dist/server.mjs' }), diff --git a/examples/storefront-auth/modules/auth/src/module.ts b/examples/storefront-auth/modules/auth/src/module.ts index 1b2ae112a..526f7dc50 100644 --- a/examples/storefront-auth/modules/auth/src/module.ts +++ b/examples/storefront-auth/modules/auth/src/module.ts @@ -15,7 +15,7 @@ * same as this enclosing module, so a defaulted id would read as "auth.auth". */ import { module, secret } from '@prisma/composer'; -import { postgres } from '@prisma/composer-prisma-cloud'; +import { rawPostgres } from '@prisma/composer-prisma-cloud'; import { authContract } from './contract.ts'; import authService from './service.ts'; @@ -26,7 +26,7 @@ export default module( // point of the forwarding model. { secrets: { signingKey: secret() }, expose: { rpc: authContract } }, ({ secrets, provision }) => { - const db = provision(postgres({ name: 'database' })); + const db = provision(rawPostgres({ name: 'database' })); const service = provision(authService, { id: 'service', deps: { db }, diff --git a/examples/storefront-auth/modules/auth/src/server.ts b/examples/storefront-auth/modules/auth/src/server.ts index 90a77f625..dfaa5976d 100644 --- a/examples/storefront-auth/modules/auth/src/server.ts +++ b/examples/storefront-auth/modules/auth/src/server.ts @@ -7,7 +7,7 @@ import { serve } from '@prisma/composer/service-rpc'; import { SQL } from 'bun'; import service from './service.ts'; -const { db } = service.load(); // db: PostgresConfig — the app owns its client +const { db } = service.load(); // db: RawPostgresConfig — the app owns its client const { signingKey } = service.input(); // signingKey: SecretString — redacts everywhere but expose() (ADR-0042) const port = service.port(); diff --git a/examples/storefront-auth/modules/auth/src/service.ts b/examples/storefront-auth/modules/auth/src/service.ts index 0060d151b..f9a443128 100644 --- a/examples/storefront-auth/modules/auth/src/service.ts +++ b/examples/storefront-auth/modules/auth/src/service.ts @@ -1,16 +1,16 @@ import { secretString } from '@prisma/composer/arktype'; import node from '@prisma/composer/node'; -import { compute, postgres } from '@prisma/composer-prisma-cloud'; +import { compute, rawPostgres } from '@prisma/composer-prisma-cloud'; import { type } from 'arktype'; import { authContract } from './contract.ts'; -// The `db` dependency is pure requirement: its binding is PostgresConfig +// The `db` dependency is pure requirement: its binding is RawPostgresConfig // (`{ url }`), and the app builds its own SQL client from it in server.ts // (ADR-0015). No driver choice lives in the declaration. export default compute({ name: 'auth', deps: { - db: postgres(), + db: rawPostgres(), }, // The whole incoming configuration as ONE schema (ADR-0042): a single // secret field, typed as the redacting SecretString box. The auth module diff --git a/gotchas.md b/gotchas.md index 91486eebe..28a2263bb 100644 --- a/gotchas.md +++ b/gotchas.md @@ -1,13 +1,13 @@ # Gotchas A running log of surprises, workarounds, and undocumented behaviour hit while -_consuming_ **Prisma Next**, **Prisma Compute**, or **Prisma Postgres** in this +_consuming_ **Prisma ORM**, **Prisma Compute**, or **Prisma Postgres** in this project. Each entry captures friction a real user of these products would also hit. Each entry is also filed as a Triage-state Linear ticket in the matching gotchas project so the team can pick it up: -- Prisma Next → [`pn-gotchas`](https://linear.app/prisma-company/project/pn-gotchas-a6f6f5157a5c/overview) +- Prisma ORM → [`pn-gotchas`](https://linear.app/prisma-company/project/pn-gotchas-a6f6f5157a5c/overview) - Prisma Compute → [`compute-gotchas`](https://linear.app/prisma-company/project/compute-gotchas-dd3ac34b5ad4/overview) - Prisma Postgres → [`ppg-gotchas`](https://linear.app/prisma-company/project/ppg-gotchas-afe77336f696/overview) @@ -309,10 +309,10 @@ process.on("unhandledRejection", (e) => console.error(e)); **Filed upstream:** [FT-5226](https://linear.app/prisma-company/issue/FT-5226/first-connection-to-a-freshly-provisioned-postgres-is-rejected-while) — _"First connection to a freshly-provisioned Postgres is rejected while the upstream is cold — breaks deploy-time migrations"_ **Product:** Prisma Postgres (edge proxy / cold-start) **Version:** node-postgres (`pg` 8.21) via `@prisma-next/driver-postgres`; PPg direct connection; connecting at deploy time -**First hit:** `examples/pn-widgets` — the `pnPostgres` deploy-migrate lowering connects to the DB the instant it is provisioned +**First hit:** `examples/orm-demo` — the `postgres` deploy-migrate lowering connects to the DB the instant it is provisioned **Cost:** ~2 hours — several live E2E iterations plus a throwaway-DB diagnosis, and a red-herring SSL "fix" on the way -**Symptom.** A client connecting to a PPg database **immediately after it is provisioned** fails on the first connection. Through Prisma Next's control client it surfaces as `CliStructuredError: Database connection failed`; the raw node-postgres error is `message: "Failed to connect to upstream database. Please contact Prisma support…"`, `err.code === undefined`, no `err.cause`. The **direct** endpoint fast-rejects (~0.4–0.6s); the **pooled** endpoint slow-times-out (~10s). Intermittent — the same DSN sometimes connects on attempt 1; it reproduces reliably only when connecting within a moment of provisioning. +**Symptom.** A client connecting to a PPg database **immediately after it is provisioned** fails on the first connection. Through Prisma ORM's control client it surfaces as `CliStructuredError: Database connection failed`; the raw node-postgres error is `message: "Failed to connect to upstream database. Please contact Prisma support…"`, `err.code === undefined`, no `err.cause`. The **direct** endpoint fast-rejects (~0.4–0.6s); the **pooled** endpoint slow-times-out (~10s). Intermittent — the same DSN sometimes connects on attempt 1; it reproduces reliably only when connecting within a moment of provisioning. The same cold window has a **second presentation**, and it is the more dangerous one. Instead of rejecting, the proxy **completes** the connection — `pg` resolves `connect()` only after authentication succeeds and the server sends `ReadyForQuery` — and then drops the socket, so the failure lands on the *first query* as `Connection terminated unexpectedly`. Two consequences: a retry wrapped around the connect alone never engages, because the connect succeeded; and `pg` reports the drop by emitting an `'error'` event on the client, which is an uncaught exception in any process not listening for it, so it kills the process rather than returning an error you can catch. Both shapes turned up in the same "Cold-connect canary" job within two days (jobs 92958820634 and 93248605341, 2026-08-07 and 2026-08-09). @@ -337,8 +337,8 @@ await withConnectionRetry(() => client.dbInit(...), { attempts: 12, delayMs: 500 **References.** - Upstream: [FT-5226](https://linear.app/prisma-company/issue/FT-5226/first-connection-to-a-freshly-provisioned-postgres-is-rejected-while) -- Workaround source: [`packages/1-prisma-cloud/1-extensions/target/src/pg-connection.ts`](packages/1-prisma-cloud/1-extensions/target/src/pg-connection.ts) (`withConnectionRetry`), used by [`prisma-next-migrate.ts`](packages/1-prisma-cloud/1-extensions/target/src/prisma-next-migrate.ts) and [`pg-warm-resource.ts`](packages/1-prisma-cloud/1-extensions/target/src/pg-warm-resource.ts). Both wrap connect **and** the operation, which is what makes them proof against the socket-drop presentation — wrapping the connect alone would not be. Wrapping is necessary but not sufficient: every client also needs an `'error'` listener, because an unhandled `'error'` event is raised outside the promise and `withConnectionRetry` cannot catch it. -- The control client used for deploy-time migrations (`createPostgresControlClient` → `PostgresControlDriver`) has the same trap, and it bites on the dependency rather than on our code. `@prisma-next/driver-postgres@0.16.0` builds it around a long-lived `pg.Client` with no `'error'` listener (`dist/control.mjs:32`), so a socket drop there takes the deploy process down instead of failing the migration. Fixed upstream in [`prisma/prisma@0e51f1f4d`](https://github.com/prisma/prisma/commit/0e51f1f4d) (2026-07-22), which adds the listener and makes `close()` tolerate a dropped socket. 0.16.0 was published one day earlier and never got it; the fix ships in `@prisma/orm-target-postgres@8.0.0-rc.1` (`client.on("error", () => {})` at `dist/control-6WFTtLAM.mjs:33`, verified in the published tarball), which Composer adopts in the Prisma 8 upgrade. Nothing to do here once that lands — and no dependency patch was ever warranted. +- Workaround source: [`packages/1-prisma-cloud/1-extensions/target/src/pg-connection.ts`](packages/1-prisma-cloud/1-extensions/target/src/pg-connection.ts) (`withConnectionRetry`), used by [`orm-migrate.ts`](packages/1-prisma-cloud/1-extensions/target/src/orm-migrate.ts) and [`pg-warm-resource.ts`](packages/1-prisma-cloud/1-extensions/target/src/pg-warm-resource.ts). Both wrap connect **and** the operation, which is what makes them proof against the socket-drop presentation — wrapping the connect alone would not be. Wrapping is necessary but not sufficient: every client also needs an `'error'` listener, because an unhandled `'error'` event is raised outside the promise and `withConnectionRetry` cannot catch it. +- The control client used for deploy-time migrations (`createPostgresControlClient` → `PostgresControlDriver`) has the same trap, and it bites on the dependency rather than on our code. `@prisma-next/driver-postgres@0.16.0` builds it around a long-lived `pg.Client` with no `'error'` listener (`dist/control.mjs:32`), so a socket drop there takes the deploy process down instead of failing the migration. Fixed upstream in [`prisma/orm@0e51f1f4d`](https://github.com/prisma/orm/commit/0e51f1f4d) (2026-07-22), which adds the listener and makes `close()` tolerate a dropped socket. 0.16.0 was published one day earlier and never got it; the fix ships in `@prisma/orm-target-postgres@8.0.0-rc.1` (`client.on("error", () => {})` at `dist/control-6WFTtLAM.mjs:33`, verified in the published tarball), which Composer adopts in the Prisma 8 upgrade. Nothing to do here once that lands — and no dependency patch was ever warranted. - Removal guard: the CI canary (`scripts/cold-connect-canary.ts`, "Cold-connect canary" E2E job) fails only when every cold connect in a run of at least 14 succeeds — the rejection is intermittent, so a shorter unanimous streak is treated as luck, not a fix (same reasoning as PRO-217's 14-hold requirement below) — when the platform fixes FT-5226 it goes red, forcing removal of `withConnectionRetry` and itself (an inconclusive run passes with a warning annotation instead of blocking). Exit 1 means that verdict and nothing else, because it is read as an instruction to delete working production code: a canary that cannot provision, a teardown that fails, and a stray async error all exit 0 with the reason logged. The stray-error case is not hypothetical — an unhandled `'error'` event from the canary's own `pg` client (the same defect as the idle-close entry below) made bun exit 1 a second *after* the correct "bug still present" verdict had printed, which reads in the log as a crash during project teardown. The canary also cried wolf once: on 2026-08-09 it returned a bug-gone verdict (14/14, run 31330072181) while runs on main minutes either side still saw the rejection. Cause: it sampled back to back, and a connect that reaches an already-warm upstream tests nothing while still counting as evidence — across 20 runs the successes came back in a median of 122ms against 546ms for rejections. Samples are now spaced 60s apart, matching the cold-start canary. If a bug-gone verdict ever appears again, confirm it against a second run before acting on it - Related: [FT-5219](https://linear.app/prisma-company/issue/FT-5219) (idle-close, runtime), [PRO-212](https://linear.app/prisma-company/issue/PRO-212) (nested endpoint DSNs) @@ -371,7 +371,7 @@ pool.on("error", (err) => console.error("pg pool idle client error", err)); **References.** -- Fix in this repo: `resilientPool` in [`packages/1-prisma-cloud/1-extensions/target/src/prisma-next.ts`](packages/1-prisma-cloud/1-extensions/target/src/prisma-next.ts) (commit `0088520`) +- Fix in this repo: `resilientPool` in [`packages/1-prisma-cloud/1-extensions/target/src/orm-postgres.ts`](packages/1-prisma-cloud/1-extensions/target/src/orm-postgres.ts) (commit `0088520`) - Related: [FT-5219](https://linear.app/prisma-company/issue/FT-5219) (same idle-close, persistent Bun.SQL client → 502 loop), [FT-5226](https://linear.app/prisma-company/issue/FT-5226) (same cold/idle family, deploy-time first connect) --- @@ -583,7 +583,7 @@ Setting `NODE_OPTIONS=--import=tsx` globally around the deploy command does make ## prisma dev fetches its implementation at run time — a broken @prisma/cli-dev publish fails every cold-cache invocation, regardless of the pinned CLI version **Filed upstream:** [TML-3098](https://linear.app/prisma-company/issue/TML-3098/prisma-dev-fails-at-startup-freshly-published-prismacli-dev01623) — _"prisma dev fails at startup — freshly published @prisma/cli-dev@0.16.23 errors with 'Dynamic require of "assert" is not supported'"_ -**Product:** Prisma Next (`prisma dev`) +**Product:** Prisma ORM (`prisma dev`) **Version:** `prisma` CLI 7.9.0 (pinned) + `@prisma/cli-dev@0.16.23` (fetched at run time), observed 2026-07-24 **First hit:** CI's "Warm the prisma dev engine cache" step went deterministically red on a workflow-and-docs-only PR; main had been green 90 minutes earlier diff --git a/open-chat-port-friction.md b/open-chat-port-friction.md index 17019591e..b5b4c5fb0 100644 --- a/open-chat-port-friction.md +++ b/open-chat-port-friction.md @@ -12,24 +12,24 @@ in each dispatch section's header. ## D1 — Topology scaffold *Entries #1 and #2 below were rewritten in D1b (2026-07-16) after the -operator dropped `pnPostgres` for plain `postgres()` on both ends (spec: +operator dropped `postgres` for plain `rawPostgres()` on both ends (spec: open-chat-port Chosen design #7). Both workarounds they originally described are gone from the code; the underlying framework gaps are not fixed, so the findings stay, sharpened by having tried the fix.* -### 1. A `pnPostgres` resource cannot satisfy a plain `postgres()` dependency — and the converse is blocked too +### 1. A `postgres` resource cannot satisfy a plain `rawPostgres()` dependency — and the converse is blocked too -**Where hit:** D1 wired `pnPostgres({ name, contract, config })` provisioning -a `pnPostgres(contract)` dependency. D1b then tried the shape this port -actually wants: provision a `pnPostgres` resource (framework-run migrations, +**Where hit:** D1 wired `postgres({ name, contract, config })` provisioning +a `postgres(contract)` dependency. D1b then tried the shape this port +actually wants: provision a `postgres` resource (framework-run migrations, ADR-0022) but consume it through open-chat's own `pg.Pool` — i.e. a plain -`postgres()` dependency, since open-chat's `src/prisma/db.ts` builds its own +`rawPostgres()` dependency, since open-chat's `src/prisma/db.ts` builds its own client from `{ url }` and does not accept a framework-built typed client. **Symptom:** TypeScript rejects it at the `provision()` call site. -`pnPostgres({ ... })` returns a `ResourceNode>`; -`postgres()`'s dependency end requires a `Contract<'postgres', PostgresConfig>`. -The two contracts' `kind` literals (`'prisma-next'` vs `'postgres'`) don't +`postgres({ ... })` returns a `ResourceNode>`; +`rawPostgres()`'s dependency end requires a `Contract<'raw-postgres', RawPostgresConfig>`. +The two contracts' `kind` literals (`'postgres'` vs `'postgres'`) don't match, so assignability fails before `satisfies` is ever reached at Load — "framework migrations + my own client" is inexpressible. @@ -39,24 +39,24 @@ places at once, both using the SAME type parameter as the contract they're declared on: the provision-site TypeScript assignability check (`ResourceNode` against a dependency's required contract, in `node.ts`), and `satisfies(required: Contract)`'s own signature. A -`prisma-next` database genuinely IS a Postgres database — its `PnCmp` carries +`prisma/orm` database genuinely IS a Postgres database — its `OrmCmp` carries a `{ url }`-shaped connection underneath the typed client — but nothing in -`Contract`'s shape lets a `'prisma-next'`-kinded contract declare "I also +`Contract`'s shape lets a `'postgres'`-kinded contract declare "I also satisfy `'postgres'`". Kind equality is baked into the type itself, not a policy `satisfies` chooses, so a cross-kind subtype relation can't be expressed at all. -**Also tried, also blocked — the converse:** "provision a `pnPostgres` +**Also tried, also blocked — the converse:** "provision a `postgres` resource but run my own migrations" (skip ADR-0022's framework-run migration) -is equally inexpressible. `PnPostgresResourceNode`'s `config` field (the -`prisma-next.config.ts` path) is required on the resource overload's argument -type — there is no `pnPostgres({ name, contract })` without it. And given a -`config` anyway, `prismaNextDescriptor`'s lowering -(`packages/1-prisma-cloud/1-extensions/target/src/descriptors/prisma-next.ts`) -unconditionally runs `PnMigration(...)` — no flag or resource variant +is equally inexpressible. `PostgresResourceNode`'s `config` field (the +`prisma.config.ts` path) is required on the resource overload's argument +type — there is no `postgres({ name, contract })` without it. And given a +`config` anyway, `postgresDescriptor`'s lowering +(`packages/1-prisma-cloud/1-extensions/target/src/descriptors/orm-postgres.ts`) +unconditionally runs `OrmMigration(...)` — no flag or resource variant provisions the database and connection without migrating it. -**Workaround used:** neither direction — this port uses plain `postgres()` +**Workaround used:** neither direction — this port uses plain `rawPostgres()` on both ends (`module.ts`'s resource, `service.ts`'s dependency) and keeps running open-chat's own `db:init`/`db:push` as an operator step (D3). Per ADR-0022 the contract hash is the thing and migrations are only the means, so @@ -64,18 +64,18 @@ this doesn't need the framework to run them; open-chat gets neither framework-run migrations nor the typed client, by design (Chosen design #7) — it only ever needed the URL. -**Recommendation:** let a `prisma-next` contract's `satisfies` accept a +**Recommendation:** let a `prisma/orm` contract's `satisfies` accept a `Contract<'postgres', unknown>` too, not just its own kind, when its underlying storage genuinely is Postgres — which needs `Kind` widened off `satisfies`'s parameter type, not just the value returned. Caution: a naive -"no required hash → satisfied" rule is wrong — it would let a `pnPostgres` +"no required hash → satisfied" rule is wrong — it would let a `postgres` resource satisfy an unrelated `s3()`/`streams()` dependency too, since those also have no required-hash concept. Any fix has to compare kind-compatibility explicitly, not merely "hash present or absent". Not attempted here — a `Contract` type change, out of scope for an app port. > **Update (D6):** ADR-0040 dissolved the practical case by widening the -> binding rather than the kind system: a `pnPostgres` dependency now hydrates +> binding rather than the kind system: a `postgres` dependency now hydrates > to `{ url, client }`, so an app that owns its client declares the > contract-carrying dependency and reads `url`. The cross-kind `satisfies` > recommendation above was considered and rejected in ADR-0040. @@ -83,9 +83,9 @@ explicitly, not merely "hash present or absent". Not attempted here — a ### 2. Version skew: framework's bundled `@prisma-next` 0.15.0 vs open-chat's 0.13.0-emitted `contract.json` **Where hit:** D1's boot-time smoke test of the launcher (`chatService.run()` -with fabricated `COMPOSER_*` env vars) when it still used `pnPostgres`. +with fabricated `COMPOSER_*` env vars) when it still used `postgres`. -**Symptom (as hit under `pnPostgres`, before D1b removed it):** +**Symptom (as hit under `postgres`, before D1b removed it):** ```text ContractValidationError: Contract structural validation failed: @@ -102,11 +102,11 @@ thrown from inside `service.load()`. **Cause:** the pkg.pr.new preview's `@prisma/composer-prisma-cloud` declares its own `@prisma-next/*` dependencies at `0.15.0`; open-chat is pinned to `@prisma-next/postgres@^0.13.0`, and `src/prisma/contract.json` was emitted -by that 0.13-vintage `prisma-next` CLI. Bun installs both — the top-level +by that 0.13-vintage `prisma/orm` CLI. Bun installs both — the top-level hoisted `@prisma-next/postgres@0.13.0` (open-chat's own) and a *nested* `node_modules/@prisma/composer-prisma-cloud/node_modules/@prisma-next/*@0.15.0` (composer's own) — because the version ranges don't overlap. When -`pnPostgres(contract)`'s `hydrate` called into the *0.15.0* runtime with +`postgres(contract)`'s `hydrate` called into the *0.15.0* runtime with open-chat's *0.13-emitted* `contractJson`, the newer runtime's structural validator rejected it: `execution.mutations.defaults[].ref.namespace` is a field the 0.13 emitter didn't write. A genuine data-format incompatibility, @@ -126,10 +126,10 @@ So `db`'s failure would have poisoned the *entire* `load()` call — the launcher could not have called `service.load()` even just to read the harmless, trivially-hydrated `streams.url`. -**Status:** not hit anymore — D1b dropped `pnPostgres` entirely (Chosen +**Status:** not hit anymore — D1b dropped `postgres` entirely (Chosen design #7), so this port never calls into the 0.15.0 runtime with open-chat's 0.13-emitted contract. Recorded so the incompatibility isn't lost: any future -port or app that DOES need `pnPostgres`'s typed client will still hit it. +port or app that DOES need `postgres`'s typed client will still hit it. > **Update (D6):** retired for this port twice over. The binding's client is > now lazy (ADR-0040), so a service that only reads `url` never runs the @@ -584,16 +584,16 @@ should lazy-import provider namespaces so unused providers' peers stay genuinely optional. An app deploying to Prisma Cloud should not need to know alchemy's Cloudflare provider exists. -### 12. A plain-`postgres()` app has no path to its own provisioned database at deploy — the port's documented "app runs its own migrations" step can't be automated +### 12. A plain-`rawPostgres()` app has no path to its own provisioned database at deploy — the port's documented "app runs its own migrations" step can't be automated -> **Resolved by framework PR #154 (ADR-0040).** `pnPostgres(contract)`'s +> **Resolved by framework PR #154 (ADR-0040).** `postgres(contract)`'s > binding now carries `{ url, client }` with the typed client built lazily on > first access — so "framework-run migrations + my own client" is expressible. > The port switched its `database` resource to -> `pnPostgres({ name, contract, config })` and its dependency to -> `pnPostgres(chatData)`; the launcher reads `db.url` and never constructs +> `postgres({ name, contract, config })` and its dependency to +> `postgres(chatData)`; the launcher reads `db.url` and never constructs > the client. The deploy migrates the database itself (`[database-migrate]`, -> a `PrismaNext.Migration` resource, in the plan) and the operator step below +> a `PrismaOrm.Migration` resource, in the plan) and the operator step below > is gone — verified end to end in D6. Recommendation (a) below (surfacing > connection values to the deploy shell) remains open as a general > affordance, but this port no longer needs it. @@ -606,14 +606,14 @@ every DB-backed route 500s: nothing ever applied the schema to the freshly provisioned `database` resource. **Cause.** This port's settled design (Chosen design #7, PR #1) is a plain -`postgres()` resource with the app owning its schema — -`prisma-next db init` against the provisioned URL. That step needs the URL, +`rawPostgres()` resource with the app owning its schema — +`prisma db init` against the provisioned URL. That step needs the URL, and the framework never surfaces it to the operator: the deploy resolves the DSN internally (it writes `COMPOSER_CHAT_DB_URL` into the service's env), but the CLI has only `deploy` and `destroy` — no outputs/state read — and the deployment report prints resource ids, not connection strings. -`pnPostgres()` has managed deploy-time migrations, but can't satisfy a plain -`postgres()` dependency (#1), so an app that owns its own pool is locked out +`postgres()` has managed deploy-time migrations, but can't satisfy a plain +`rawPostgres()` dependency (#1), so an app that owns its own pool is locked out of both mechanisms. Earlier dispatches papered over this without noticing: D3/D4 ran `db init` by hand against a Management-API-minted connection, an operator step outside the deploy that FRICTION.md never recorded as such. @@ -622,17 +622,17 @@ operator step outside the deploy that FRICTION.md never recorded as such. mint a connection on the `database` resource (`POST /v1/databases/{id}/connections`, DSN in `endpoints.direct.connectionString` — PRO-212), then -`bunx prisma-next db init --db -y` (additive-only, safe to rerun). +`bunx prisma db init --db -y` (additive-only, safe to rerun). After it, every DB-backed route works — see the D5 verification. **Recommendation.** Either of two affordances closes this: (a) a `prisma-composer` way to read a provisioned resource's connection values from the deploy shell (an outputs command, or a post-deploy hook handed the -resolved bindings), or (b) a migrations hook on plain `postgres()` — "run +resolved bindings), or (b) a migrations hook on plain `rawPostgres()` — "run this command against the resolved URL before the dependent service starts" — -the deploy already sequences exactly this for `pnPostgres()`. +the deploy already sequences exactly this for `postgres()`. -## D6 — pnPostgres with framework-run migrations (ADR-0040 build) +## D6 — postgres with framework-run migrations (ADR-0040 build) Framework version under test: pkg.pr.new preview of `prisma/composer` PR #154 (`@prisma/composer{,-prisma-cloud}@1909260` — the `{ url, client }` diff --git a/packages/0-framework/0-foundation/foundation/src/internal-error.ts b/packages/0-framework/0-foundation/foundation/src/internal-error.ts index fa5d327d0..ed5db9723 100644 --- a/packages/0-framework/0-foundation/foundation/src/internal-error.ts +++ b/packages/0-framework/0-foundation/foundation/src/internal-error.ts @@ -1,7 +1,7 @@ // Duplicated from the prisma/prisma error foundation pending extraction into a shared package — keep byte-close to the donor; recognition is structural, so the copies interoperate. /** - * A bug in Prisma Next, not a user error. Never catch this except at the + * A bug in Prisma ORM, not a user error. Never catch this except at the * outermost boundary for crash reporting — an InternalError means an invariant * broke and the process cannot reliably continue. User-facing failures use * `structuredError` with a dotted code instead. diff --git a/packages/0-framework/0-foundation/tsdown-config/base.ts b/packages/0-framework/0-foundation/tsdown-config/base.ts index 712c09a1b..fd2fd4ac7 100644 --- a/packages/0-framework/0-foundation/tsdown-config/base.ts +++ b/packages/0-framework/0-foundation/tsdown-config/base.ts @@ -1,12 +1,12 @@ import type { UserConfig } from 'tsdown'; /** - * Base `tsdown` configuration for the workspace. Mirrors prisma-next's - * `@prisma-next/tsdown`: emit `.mjs` + `.d.mts` to `dist`, keep node_modules + * Base `tsdown` configuration for the workspace. Mirrors prisma/orm's + * tsdown base: emit `.mjs` + `.d.mts` to `dist`, keep node_modules * external, and let tsdown own each package's `exports`/`main`/`types` so the * manifest never drifts from what was built. * - * The workspace runs tsdown 0.22.4 — the same version prisma-next pins — + * The workspace runs tsdown 0.22.4 — the same version prisma/orm pins — * whose typed `exports` API is `{ enabled: 'local-only', customExports, * exclude }`, not the `exports: true` shorthand. `customExports` strips the * `exports/` prefix that `src/exports/*.ts` entries produce (see below), so diff --git a/packages/0-framework/1-core/core/src/__tests__/fixtures/probe-core-authoring.ts b/packages/0-framework/1-core/core/src/__tests__/fixtures/probe-core-authoring.ts index 87cd7789e..3b931cdb5 100644 --- a/packages/0-framework/1-core/core/src/__tests__/fixtures/probe-core-authoring.ts +++ b/packages/0-framework/1-core/core/src/__tests__/fixtures/probe-core-authoring.ts @@ -12,7 +12,7 @@ import { service, } from '../../exports/index.ts'; -// A pack-shaped provider contract: kind-satisfies, like postgresContract. +// A pack-shaped provider contract: kind-satisfies, like rawPostgresContract. const dbContract: Contract<'probe/db', { url: string }> = Object.freeze({ kind: 'probe/db', __cmp: { url: '' }, diff --git a/packages/0-framework/1-core/core/src/__tests__/helpers.ts b/packages/0-framework/1-core/core/src/__tests__/helpers.ts index a3ab8da95..2fb44f67d 100644 --- a/packages/0-framework/1-core/core/src/__tests__/helpers.ts +++ b/packages/0-framework/1-core/core/src/__tests__/helpers.ts @@ -15,7 +15,7 @@ export const conn =

( /** * A stand-in provider contract — kind-satisfies, mirroring what a pack ships - * for its resources (e.g. postgresContract): every value of the same kind + * for its resources (e.g. rawPostgresContract): every value of the same kind * satisfies, even across duplicated module instances. */ export const providerContract = (kind: K, cmp: Cmp): Contract => diff --git a/packages/0-framework/3-tooling/cli/src/load-config.ts b/packages/0-framework/3-tooling/cli/src/load-config.ts index a6b22374e..1ef6cd870 100644 --- a/packages/0-framework/3-tooling/cli/src/load-config.ts +++ b/packages/0-framework/3-tooling/cli/src/load-config.ts @@ -1,7 +1,7 @@ /** * Pipeline step: find and load `prisma-composer.config.ts` (ADR-0017) — the ONE * file that imports control-plane code. Discovery is the standard walk-up - * from the deploy entry's directory (mirrors prisma-next's config-loader); + * from the deploy entry's directory (mirrors prisma/orm's config-loader); * loading is c12 with that explicit path (rc/global/package.json lookups * disabled), so the config file's own static imports resolve from the app * root by whatever package manager runs — no specifier construction, no diff --git a/packages/1-prisma-cloud/0-lowering/local-target/src/postgres.ts b/packages/1-prisma-cloud/0-lowering/local-target/src/postgres.ts index 1c1d642e6..1c62c7b8b 100644 --- a/packages/1-prisma-cloud/0-lowering/local-target/src/postgres.ts +++ b/packages/1-prisma-cloud/0-lowering/local-target/src/postgres.ts @@ -2,7 +2,7 @@ * Local postgres-cluster providers: upstream alchemy's `Prisma.Database` and * `Prisma.Connection` become clients of the `postgres-main` emulator daemon * (one named, persistent `@prisma/dev` server per `Database` resource). - * `PgWarm`/`PnMigration` are not here — the hosted ones run against whatever + * `PgWarm`/`OrmMigration` are not here — the hosted ones run against whatever * URL they are handed. Attributes match upstream's shapes; the daemon's * DIRECT connection string maps to `directConnectionString` and * `databaseUrl`, everything else is left absent. Instance names come from diff --git a/packages/1-prisma-cloud/0-lowering/lowering/src/state/__tests__/legacy-resources.test.ts b/packages/1-prisma-cloud/0-lowering/lowering/src/state/__tests__/legacy-resources.test.ts index 6d248e702..004b07bd3 100644 --- a/packages/1-prisma-cloud/0-lowering/lowering/src/state/__tests__/legacy-resources.test.ts +++ b/packages/1-prisma-cloud/0-lowering/lowering/src/state/__tests__/legacy-resources.test.ts @@ -176,6 +176,22 @@ describe('migrateLegacyResourceState (pure mapping)', () => { expect(migrated.old.props).toEqual({ project: 'proj-1', name: 'data', region: 'us-east-1' }); }); + test('renames PrismaNext.Migration rows to PrismaOrm.Migration with props untouched, idempotently', () => { + const props = { url: DIRECT_URL, targetHash: 'abc', invariants: [], packHeadRefHashes: [] }; + const row = { + ...legacyDatabaseRow(), + resourceType: 'PrismaNext.Migration', + props, + old: { ...legacyDatabaseRow(), resourceType: 'PrismaNext.Migration', props }, + }; + const migrated = migrateLegacyResourceState(row) as MigratedRow & { old: MigratedRow }; + expect(migrated.resourceType).toBe('PrismaOrm.Migration'); + expect(migrated.props).toEqual(props); + expect(migrated.old.resourceType).toBe('PrismaOrm.Migration'); + expect(migrated.old.props).toEqual(props); + expect(migrateLegacyResourceState(migrated)).toEqual(migrated); + }); + test('maps the unreleased PrismaComposer.* type-ids too, and passes foreign rows through', () => { const composerEra = { ...legacyDatabaseRow(), resourceType: 'PrismaComposer.Database' }; expect((migrateLegacyResourceState(composerEra) as MigratedRow).resourceType).toBe( diff --git a/packages/1-prisma-cloud/0-lowering/lowering/src/state/legacy-resources.ts b/packages/1-prisma-cloud/0-lowering/lowering/src/state/legacy-resources.ts index f9e3bc870..8d8edcc5a 100644 --- a/packages/1-prisma-cloud/0-lowering/lowering/src/state/legacy-resources.ts +++ b/packages/1-prisma-cloud/0-lowering/lowering/src/state/legacy-resources.ts @@ -328,9 +328,31 @@ const migrateAttr = (family: Family, attr: unknown, props: unknown): unknown => } }; +/** + * Type-id renames where the props/attr shapes are unchanged: rewriting the + * `resourceType` (here and on any nested `old` row) is the whole migration. + */ +const RENAMED_TYPES: Readonly> = { + 'PrismaNext.Migration': 'PrismaOrm.Migration', +}; + +const renameResourceType = ( + row: Record, + renamed: string, +): Record => { + const migrated: Record = { ...row, resourceType: renamed }; + const old = row['old']; + if (isRecord(old) && typeof old['resourceType'] === 'string') { + migrated['old'] = migrateResourceRow(old); + } + return migrated; +}; + const migrateResourceRow = (row: Record): Record => { const resourceType = row['resourceType']; if (typeof resourceType !== 'string') return row; + const renamed = RENAMED_TYPES[resourceType]; + if (renamed !== undefined) return renameResourceType(row, renamed); const family = FAMILY_BY_LEGACY_TYPE[resourceType]; if (family === undefined) return row; diff --git a/packages/1-prisma-cloud/1-extensions/target/package.json b/packages/1-prisma-cloud/1-extensions/target/package.json index eb26cf4e0..e642af5f7 100644 --- a/packages/1-prisma-cloud/1-extensions/target/package.json +++ b/packages/1-prisma-cloud/1-extensions/target/package.json @@ -8,7 +8,7 @@ "./connection": "./dist/connection.mjs", "./control": "./dist/control.mjs", "./local-target": "./dist/local-target.mjs", - "./prisma-next": "./dist/prisma-next.mjs", + "./orm": "./dist/orm.mjs", "./testing": "./dist/testing.mjs", "./package.json": "./package.json" }, diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/control-env.test.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/control-env.test.ts index ef822ae37..133c25988 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/control-env.test.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/control-env.test.ts @@ -53,7 +53,7 @@ describe('prismaCloud() — constructs with NO environment present (local-dev sp 'compute', 'credentials', 'postgres', - 'prisma-next', + 'raw-postgres', 's3', 's3-store', ]); diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/control-lowering.test.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/control-lowering.test.ts index 809957a59..624d125f1 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/control-lowering.test.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/control-lowering.test.ts @@ -32,8 +32,8 @@ import { type ResolvedCloudOptions, } from '../descriptors/shared.ts'; import * as RealGeneratedParam from '../generated-param-resource.ts'; +import * as RealOrmMigration from '../orm-migration-resource.ts'; import * as RealPgWarm from '../pg-warm-resource.ts'; -import * as RealPnMigration from '../pn-migration-resource.ts'; import * as RealS3Credentials from '../s3-credentials-resource.ts'; // Stub the provider layer AND alchemy/Output so the compute target's data @@ -224,23 +224,30 @@ mock.module('../generated-param-resource.ts', () => ({ })); // A real Alchemy Resource (needs the Stack service); stubbed so the lowering runs purely. -mock.module('../pn-migration-resource.ts', () => ({ - ...RealPnMigration, - PnMigration: (id: string, props: unknown) => { +mock.module('../orm-migration-resource.ts', () => ({ + ...RealOrmMigration, + OrmMigration: (id: string, props: unknown) => { recorded.pnMigrate.push([id, props]); return Effect.succeed({}); }, - PnMigrationProvider: () => ({ stub: 'pn-migration-provider' }), + OrmMigrationProvider: () => ({ stub: 'pn-migration-provider' }), })); const { prismaCloud } = await import('../exports/control.ts'); -const { compute, envParam, envSecret, generatedParam, postgres, postgresContract, s3StoreService } = - await import('../exports/index.ts'); +const { + compute, + envParam, + envSecret, + generatedParam, + rawPostgres, + rawPostgresContract, + s3StoreService, +} = await import('../exports/index.ts'); const { dependency, module, provisionNeed, string } = await import('@internal/core'); const { lowering } = await import('@internal/core/deploy'); const { RPC_PEER_KEY } = await import('@internal/service-rpc'); const { STREAMS_API_KEY } = await import('../streams-keys.ts'); -const { pnContract, pnPostgres } = await import('../exports/prisma-next.ts'); +const { dataContract, postgres } = await import('../exports/orm.ts'); const { default: widgetContractJson } = await import( './fixtures/widget-contract/emitted/contract.json' ); @@ -251,7 +258,7 @@ const { default: widgetContractJson } = await import( // `satisfies` at its definition. const run = (eff: Effect.Effect): A => Effect.runSync(eff as Effect.Effect); -// For lowerings that cross an Effect.promise (the prisma-next config/ref reads). +// For lowerings that cross an Effect.promise (the ORM config/ref reads). const runAsync = (eff: Effect.Effect): Promise => Effect.runPromise(eff as Effect.Effect); @@ -479,7 +486,7 @@ describe('prismaCloud().application.provision (once-per-lowering hook)', () => { }); }); -describe("prismaCloud().nodes['postgres'] — the resource descriptor", () => { +describe("prismaCloud().nodes['raw-postgres'] — the resource descriptor", () => { test("creates a Database + Connection in the application's project; url unwraps the Redacted connection string", async () => { await withEnv({}, () => { const target = prismaCloud({ workspaceId: 'ws_1' }); @@ -494,7 +501,7 @@ describe("prismaCloud().nodes['postgres'] — the resource descriptor", () => { }, } as unknown as LowerContext; - const result = run(resourceDescriptorOf(target, 'postgres')(ctx)); + const result = run(resourceDescriptorOf(target, 'raw-postgres')(ctx)); expect(result.outputs).toEqual({ url: 'postgres://data-conn' }); // The entity carries NO `url`: a connection string is not a public @@ -533,7 +540,7 @@ describe("prismaCloud().nodes['postgres'] — the resource descriptor", () => { } as unknown as LowerContext; const before = recorded.db.length; - run(resourceDescriptorOf(target, 'postgres')(ctx)); + run(resourceDescriptorOf(target, 'raw-postgres')(ctx)); // A named stage attaches the branch at create, which upstream only // permits WITHOUT an explicit display name — so `name` is absent here. @@ -564,7 +571,7 @@ describe("prismaCloud().nodes['postgres'] — the resource descriptor", () => { } as unknown as LowerContext; const before = recorded.db.length; - expect(() => run(resourceDescriptorOf(target, 'postgres')(ctx))).toThrow( + expect(() => run(resourceDescriptorOf(target, 'raw-postgres')(ctx))).toThrow( /cannot attach database "data3" to a Branch/, ); expect(recorded.db.slice(before)).toEqual([]); @@ -585,7 +592,7 @@ describe("prismaCloud().nodes['postgres'] — the resource descriptor", () => { } as unknown as LowerContext; const before = recorded.db.length; - run(resourceDescriptorOf(target, 'postgres')(ctx)); + run(resourceDescriptorOf(target, 'raw-postgres')(ctx)); expect(recorded.db.slice(before)).toEqual([ ['data4-db', { project: 'local', name: 'data4', region: 'us-east-1' }], @@ -594,21 +601,21 @@ describe("prismaCloud().nodes['postgres'] — the resource descriptor", () => { }); }); -describe("prismaCloud().nodes['prisma-next'] — the resource descriptor", () => { +describe("prismaCloud().nodes['postgres'] — the resource descriptor", () => { const widgetConfig = path.join( import.meta.dir, 'fixtures', 'widget-contract', 'source', - 'prisma-next.config.ts', + 'prisma.config.ts', ); test('default stage: the Database attaches the default Branch; the migration runs on its warmed url', async () => { await withEnv({}, async () => { const target = prismaCloud({ workspaceId: 'ws_1' }); - const node = pnPostgres({ + const node = postgres({ name: 'pndata', - contract: pnContract(widgetContractJson), + contract: dataContract(widgetContractJson), config: widgetConfig, }); const ctx = { @@ -624,9 +631,7 @@ describe("prismaCloud().nodes['prisma-next'] — the resource descriptor", () => } as unknown as LowerContext; const before = { db: recorded.db.length, migrate: recorded.pnMigrate.length }; - const result = await runAsync( - resourceDescriptorOf(target, 'prisma-next')(ctx), - ); + const result = await runAsync(resourceDescriptorOf(target, 'postgres')(ctx)); expect(recorded.db.slice(before.db)).toEqual([ [ @@ -788,7 +793,7 @@ describe("prismaCloud().nodes['compute'] — the service descriptor", () => { const node = compute({ name: 'test-service', deps: { - db: postgres(), + db: rawPostgres(), }, build: { extension: '@prisma/composer/node', @@ -886,7 +891,7 @@ describe("prismaCloud().nodes['compute'] — the service descriptor", () => { const node = compute({ name: 'test-service', deps: { - db: postgres(), + db: rawPostgres(), }, build: { extension: '@prisma/composer/node', @@ -1761,14 +1766,14 @@ describe('s3StoreService() authoring factory', () => { test("routes to the 's3-store' lowering but keeps compute's deps/expose/load", () => { const node = s3StoreService({ name: 'store', - deps: { db: postgres() }, + deps: { db: rawPostgres() }, build, - expose: { store: postgresContract }, + expose: { store: rawPostgresContract }, }); expect(node.type).toBe('s3-store'); expect(node.kind).toBe('service'); expect(Object.keys(node.inputs)).toEqual(['db']); - expect(node.expose).toEqual({ store: postgresContract }); + expect(node.expose).toEqual({ store: rawPostgresContract }); expect(typeof node.load).toBe('function'); expect(typeof node.input).toBe('function'); // The reserved compute param survives the type override. @@ -1787,14 +1792,14 @@ describe('sharing: one module-provisioned postgres, two compute consumers — th entry: 'server.js', }; const root = module('shop', {}, ({ provision }) => { - const db = provision(postgres({ name: 'data' }), { id: 'data' }); - provision(compute({ name: 'auth', deps: { main: postgres() }, build }), { + const db = provision(rawPostgres({ name: 'data' }), { id: 'data' }); + provision(compute({ name: 'auth', deps: { main: rawPostgres() }, build }), { id: 'auth', deps: { main: db, }, }); - provision(compute({ name: 'billing', deps: { store: postgres() }, build }), { + provision(compute({ name: 'billing', deps: { store: rawPostgres() }, build }), { id: 'billing', deps: { store: db, @@ -2330,8 +2335,8 @@ describe('name validation — fail fast on Prisma name constraints, before creat await withEnv(containerEnv('shop-project#cloud-id'), () => { const target = prismaCloud({ workspaceId: 'ws_1' }); const root = module('shop', {}, ({ provision }) => { - const db = provision(postgres({ name: 'db' }), { id: 'db' }); - provision(compute({ name: 'auth', deps: { main: postgres() }, build }), { + const db = provision(rawPostgres({ name: 'db' }), { id: 'db' }); + provision(compute({ name: 'auth', deps: { main: rawPostgres() }, build }), { id: 'auth', deps: { main: db, @@ -2376,8 +2381,8 @@ describe('name validation — fail fast on Prisma name constraints, before creat await withEnv(containerEnv('shop-project#cloud-id'), () => { const target = prismaCloud({ workspaceId: 'ws_1' }); const root = module('shop', {}, ({ provision }) => { - const db = provision(postgres({ name: 'data' }), { id: 'data' }); - provision(compute({ name: 'auth', deps: { main: postgres() }, build }), { + const db = provision(rawPostgres({ name: 'data' }), { id: 'data' }); + provision(compute({ name: 'auth', deps: { main: rawPostgres() }, build }), { id: 'auth', deps: { main: db, diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/extension.test.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/extension.test.ts index 610537c86..da792d349 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/extension.test.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/extension.test.ts @@ -4,7 +4,7 @@ import { configOf, hydrateSync, isNode, number, param, service, string } from '@ import { secretString } from '@internal/foundation/arktype'; import { RPC_ACCEPTED_KEYS_ENV } from '@internal/service-rpc'; import { type } from 'arktype'; -import { compute, postgres, postgresContract } from '../exports/index.ts'; +import { compute, rawPostgres, rawPostgresContract } from '../exports/index.ts'; import { bootstrapService } from '../exports/testing.ts'; import { configKey, deserialize, encode } from '../serializer.ts'; import { RPC_ACCEPTED_KEYS_PARAM } from '../service-keys.ts'; @@ -55,38 +55,38 @@ async function withEnv( } } -describe('postgres({ name })', () => { - test('returns a branded resource identity providing postgresContract; type is the contract kind', () => { - const node = postgres({ name: 'db' }); +describe('rawPostgres({ name })', () => { + test('returns a branded resource identity providing rawPostgresContract; type is the contract kind', () => { + const node = rawPostgres({ name: 'db' }); expect(isNode(node)).toBe(true); expect(node.kind).toBe('resource'); - expect(node.type).toBe('postgres'); + expect(node.type).toBe('raw-postgres'); expect(node.extension).toBe('@prisma/composer-prisma-cloud'); expect(node.name).toBe('db'); - expect(node.provides).toBe(postgresContract); + expect(node.provides).toBe(rawPostgresContract); expect('connection' in node).toBe(false); }); }); -describe('postgres()', () => { - test('returns a branded dependency end requiring postgresContract, declaring { url: string }', () => { - const end = postgres(); +describe('rawPostgres()', () => { + test('returns a branded dependency end requiring rawPostgresContract, declaring { url: string }', () => { + const end = rawPostgres(); expect(isNode(end)).toBe(true); expect(end.kind).toBe('dependency'); - expect(end.type).toBe('postgres'); - expect(end.name).toBe('postgres'); - expect(end.required).toBe(postgresContract); + expect(end.type).toBe('raw-postgres'); + expect(end.name).toBe('raw-postgres'); + expect(end.required).toBe(rawPostgresContract); expect(end.connection.params).toEqual({ url: string() }); }); test('the binding IS the typed config — hydrate is the identity on its values (ADR-0015)', () => { - const end = postgres(); + const end = rawPostgres(); const binding = end.connection.hydrate({ url: 'postgres://u:p@host:5432/db' }); - // No client factory: load() hands the app PostgresConfig, which it turns + // No client factory: load() hands the app RawPostgresConfig, which it turns // into its own client. hydrate returns its input unchanged. expect(binding).toEqual({ url: 'postgres://u:p@host:5432/db' }); }); @@ -109,7 +109,7 @@ describe('compute()', () => { }); test('is inert until run or load — constructing the node hydrates nothing', () => { - const db = postgres(); + const db = rawPostgres(); const node = compute({ name: 'test-service', deps: { db }, @@ -123,7 +123,7 @@ describe('compute()', () => { const node = compute({ name: 'test-service', deps: { - db: postgres(), + db: rawPostgres(), }, build, }); @@ -140,7 +140,7 @@ describe('compute()', () => { expect(() => compute({ name: 'test-service', - deps: { port: postgres() }, + deps: { port: rawPostgres() }, build, }), ).toThrow(/dependency "port" collides with the reserved service param/); @@ -185,7 +185,7 @@ describe("the config serializer (shared by run() and /control's serialize)", () const app = compute({ name: 'test-service', deps: { - db: postgres(), + db: rawPostgres(), }, build, }); @@ -200,7 +200,7 @@ describe("the config serializer (shared by run() and /control's serialize)", () const app = compute({ name: 'test-service', deps: { - db: postgres(), + db: rawPostgres(), }, build, }); @@ -234,7 +234,7 @@ describe("the config serializer (shared by run() and /control's serialize)", () const app = compute({ name: 'test-service', deps: { - db: postgres(), + db: rawPostgres(), }, build, }); @@ -261,7 +261,7 @@ describe("the config serializer (shared by run() and /control's serialize)", () const app = compute({ name: 'test-service', deps: { - db: postgres(), + db: rawPostgres(), }, build, }); @@ -316,7 +316,7 @@ describe('compute().run(address, boot) → load() — the round trip', () => { const app = compute({ name: 'test-service', deps: { - db: postgres(), + db: rawPostgres(), }, build, }); @@ -342,7 +342,7 @@ describe('compute().run(address, boot) → load() — the round trip', () => { const app = compute({ name: 'test-service', deps: { - db: postgres(), + db: rawPostgres(), }, build, }); @@ -358,7 +358,7 @@ describe('compute().run(address, boot) → load() — the round trip', () => { }); test('a postgres dependency in deps round-trips through run()/load() — the binding is its config', async () => { - const db = postgres(); + const db = rawPostgres(); const app = compute({ name: 'test-service', deps: { db }, build }); let loaded: unknown; @@ -572,7 +572,7 @@ describe('compute().run(address, boot) → load() — the round trip', () => { describe('bootstrapService(service, config, boot) — the in-process integration seam', () => { test("stashes the given Config address-free (like run('', ...)) so the booted entry's load() reads it", async () => { - const app = compute({ name: 'test-service', deps: { db: postgres() }, build }); + const app = compute({ name: 'test-service', deps: { db: rawPostgres() }, build }); let deps: unknown; await withEnv({ COMPOSER_DB_URL: '', COMPOSER_PORT: '' }, () => @@ -589,7 +589,7 @@ describe('bootstrapService(service, config, boot) — the in-process integration }); test('needs no pre-set environment — the caller supplies the Config directly, unlike run()', async () => { - const app = compute({ name: 'test-service', deps: { db: postgres() }, build }); + const app = compute({ name: 'test-service', deps: { db: rawPostgres() }, build }); let deps: unknown; await withEnv({ COMPOSER_DB_URL: 'stale', COMPOSER_PORT: 'stale' }, () => @@ -631,7 +631,7 @@ describe('compute().load()', () => { test('returns the deps, memoized per process (same object on re-load)', async () => { const app = compute({ name: 'test-service', - deps: { db: postgres() }, + deps: { db: rawPostgres() }, build, }); @@ -697,7 +697,7 @@ describe('the config pipeline over extension nodes', () => { const app = compute({ name: 'test-service', deps: { - db: postgres(), + db: rawPostgres(), }, build, }); @@ -764,7 +764,7 @@ describe('structured params + target-owned serialization (ADR-0018/0019)', () => }); test('configOf reports a schema projection, not a scalar type tag', () => { - const app = compute({ name: 's', deps: { db: postgres() }, build }); + const app = compute({ name: 's', deps: { db: rawPostgres() }, build }); const portDecl = configOf(app).find((d) => d.owner === 'service' && d.name === 'port'); expect(portDecl?.schema).toEqual({ vendor: '@prisma/composer' }); }); diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/gadget-contract/source/prisma-next.config.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/gadget-contract/source/prisma.config.ts similarity index 100% rename from packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/gadget-contract/source/prisma-next.config.ts rename to packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/gadget-contract/source/prisma.config.ts diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/pack.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/pack.ts index 3e6843a91..415337788 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/pack.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/pack.ts @@ -19,7 +19,7 @@ export const GADGET_PACK_HEAD_HASH: string = gadgetContractJson.storage.storageH const contractSpace: PgPackContractSpace = { contractJson: blindCast< PgPackContractSpace['contractJson'], - 'JSON import widened the emitted contract literal types; the file is exactly what prisma-next contract emit wrote' + 'JSON import widened the emitted contract literal types; the file is exactly what prisma contract emit wrote' >(gadgetContractJson), migrations: [], headRef: { hash: GADGET_PACK_HEAD_HASH, invariants: [] }, diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/contract.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/contract.ts index 4c2e80447..dc129d7a6 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/contract.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/contract.ts @@ -1,5 +1,5 @@ // TS-authored fixture contract — the packed-contract fixture only exercises -// config loading (resolvePrismaNextConfig / the pack preflight), so the contract is +// config loading (resolveOrmConfig / the pack preflight), so the contract is // the widget shape verbatim; it is never emitted or migrated. import { defineContract } from '@prisma/orm-postgres/contract-builder'; diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/prisma-next.config.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/prisma.config.ts similarity index 96% rename from packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/prisma-next.config.ts rename to packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/prisma.config.ts index 2ee94e47f..0b60166b2 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/prisma-next.config.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/prisma.config.ts @@ -2,7 +2,7 @@ import { definePrismaConfig } from '@prisma/cli-engine'; import { defineConfig } from '@prisma/orm-postgres/config'; import { gadgetPack } from '../pack.ts'; -// `extensions` is the postgres defineConfig option name; resolvePrismaNextConfig +// `extensions` is the postgres defineConfig option name; resolveOrmConfig // reads it and hands it on as `extensionPacks`, Composer's own word for it. export default definePrismaConfig({ orm: defineConfig({ diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/prisma-next.spaceless.config.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/prisma.spaceless.config.ts similarity index 87% rename from packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/prisma-next.spaceless.config.ts rename to packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/prisma.spaceless.config.ts index 6f5b02537..2f902f1ed 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/prisma-next.spaceless.config.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/packed-contract/source/prisma.spaceless.config.ts @@ -2,7 +2,7 @@ import { definePrismaConfig } from '@prisma/cli-engine'; import { defineConfig } from '@prisma/orm-postgres/config'; import { spacelessPack } from '../pack.ts'; -// Same shape as prisma-next.config.ts, but the listed pack declares no +// Same shape as prisma.config.ts, but the listed pack declares no // contractSpace — so it satisfies a required pack id while carrying no head at // all, which is the case the preflight reports as "(no contract space)". export default definePrismaConfig({ diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/probe-authoring.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/probe-authoring.ts index d42f3e3fa..5a77a5105 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/probe-authoring.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/probe-authoring.ts @@ -2,12 +2,12 @@ // and extension) the way a user service module would, with real value usage so // nothing tree-shakes away. import { configOf, Load, module } from '@internal/core'; -import { compute, postgres } from '@internal/prisma-cloud'; +import { compute, rawPostgres } from '@internal/prisma-cloud'; const app = compute({ name: 'test-service', deps: { - db: postgres(), + db: rawPostgres(), }, build: { extension: '@prisma/composer/node', @@ -19,7 +19,7 @@ const app = compute({ export const graph = Load( module('probe-module', {}, ({ provision }) => { - const db = provision(postgres({ name: 'db' }), { id: 'db' }); + const db = provision(rawPostgres({ name: 'db' }), { id: 'db' }); provision(app, { id: 'app', deps: { db } }); return {}; }), diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/side-effect-service.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/side-effect-service.ts index 5347b5356..edbf37a42 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/side-effect-service.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/side-effect-service.ts @@ -1,15 +1,15 @@ -import { compute, postgres } from '../../exports/index.ts'; +import { compute, rawPostgres } from '../../exports/index.ts'; // Importing this module must run nothing (invariant 3): constructing nodes is -// pure, and the postgres() dependency carries no user code — its binding is -// PostgresConfig, built by identity hydrate. This marker just proves the +// pure, and the rawPostgres() dependency carries no user code — its binding is +// RawPostgresConfig, built by identity hydrate. This marker just proves the // module evaluated without throwing or reading the environment. export const imported = true; export default compute({ name: 'test-service', deps: { - db: postgres(), + db: rawPostgres(), }, build: { extension: '@prisma/composer/node', diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/widget-contract/source/contract.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/widget-contract/source/contract.ts index 370577895..22813dc74 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/widget-contract/source/contract.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/widget-contract/source/contract.ts @@ -1,4 +1,4 @@ -// TS-authored fixture: the widget contract covers Prisma Next's TS no-emit +// TS-authored fixture: the widget contract covers Prisma ORM's TS no-emit // `defineContract()` authoring mode (the gadget fixture covers the PSL-first // mode), so the two together exercise both modes ADR-0022 says to support. import { defineContract } from '@prisma/orm-postgres/contract-builder'; diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/widget-contract/source/prisma-next.config.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/widget-contract/source/prisma.config.ts similarity index 100% rename from packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/widget-contract/source/prisma-next.config.ts rename to packages/1-prisma-cloud/1-extensions/target/src/__tests__/fixtures/widget-contract/source/prisma.config.ts diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/invariants.test.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/invariants.test.ts index 04b7ebe3c..207454c64 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/invariants.test.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/invariants.test.ts @@ -24,7 +24,7 @@ function shippedSources(): { file: string; text: string }[] { // A built dist entry PLUS every relative chunk it (transitively) imports or // re-exports, concatenated. tsup/rolldown hoists code shared between entries -// (e.g. pg-connection.ts, used by both control.ts and prisma-next.ts) into a +// (e.g. pg-connection.ts, used by both control.ts and orm-postgres.ts) into a // chunk, leaving the entry file a thin re-export shim — so a token check that // reads only the entry file would go vacuous. Following the shim's own imports // makes the check see the real code again. @@ -50,8 +50,8 @@ function builtEntryGraph(entryFileName: string): string { return parts.join('\n'); } -describe('entry map: authoring + control + local-target + prisma-next + testing, no other runtime entry', () => { - test("package.json exports '.', './connection', './control', './local-target', './prisma-next', and './testing' (cron lives in @internal/cron)", () => { +describe('entry map: authoring + control + local-target + orm + testing, no other runtime entry', () => { + test("package.json exports '.', './connection', './control', './local-target', './orm', and './testing' (cron lives in @internal/cron)", () => { const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8')); // `./package.json` is a conventional manifest export, not a code entry. const codeEntries = Object.keys(pkg.exports).filter((k) => k !== './package.json'); @@ -60,7 +60,7 @@ describe('entry map: authoring + control + local-target + prisma-next + testing, './connection', './control', './local-target', - './prisma-next', + './orm', './testing', ]); }); @@ -231,10 +231,10 @@ describe('invariant 6 (ADR-0017, extension config): the authoring entry never re }); }); -describe('invariant 7 (ADR-0022): the authoring entry never reaches the prisma-next entry', () => { - test('no module reachable from src/index.ts imports the /prisma-next entry — Prisma Next stays opt-in', () => { - // prisma-next.ts (and transitively @prisma/orm-postgres + pg) is - // imported only by an app that explicitly imports the ./prisma-next +describe('invariant 7 (ADR-0022): the authoring entry never reaches the orm entry', () => { + test('no module reachable from src/index.ts imports the /orm entry — Prisma ORM stays opt-in', () => { + // orm-postgres.ts (and transitively @prisma/orm-postgres + pg) is + // imported only by an app that explicitly imports the ./orm // subpath. A reachable import from the authoring barrel would drag that // dependency tree into every service, defeating the whole point of the // dedicated subpath entry (ADR-0022, design-notes.md "opt-out stays real"). @@ -266,8 +266,7 @@ describe('invariant 7 (ADR-0022): the authoring entry never reaches the prisma-n expect(seen.size).toBeGreaterThan(0); for (const [file, specs] of seen) { const offending = specs.filter( - (spec) => - /\/prisma-next(\.ts)?$/.test(spec) || spec.startsWith('@prisma/orm-') || spec === 'pg', + (spec) => /\/orm(\.ts)?$/.test(spec) || spec.startsWith('@prisma/orm-') || spec === 'pg', ); expect({ file: path.relative(srcDir, file), offending }).toEqual({ file: path.relative(srcDir, file), @@ -283,25 +282,25 @@ describe('invariant 7 (ADR-0022): the authoring entry never reaches the prisma-n expect(built.includes('"pg"') || built.includes("'pg'")).toBe(false); }); - // The ./prisma-next authoring entry legitimately bundles + // The ./orm authoring entry legitimately bundles // @prisma/orm-postgres/runtime (the typed client) and pg (its runtime // connect-retry, slice 3). But the deploy-only machinery — the CLI config // loader, the control client, and the migration/config/resource/warm modules - // — must NEVER leak into it, or every service using pnPostgres would pull the + // — must NEVER leak into it, or every service using postgres would pull the // CLI into its runtime bundle. The entry file is a re-export shim whose real // code lives in a chunk (pg-connection.ts is shared with control.ts), so we // check the whole reachable graph, not just the shim. - test('the built dist/prisma-next.mjs graph contains no deploy-only machinery', () => { - const built = builtEntryGraph('prisma-next.mjs'); + test('the built dist/orm.mjs graph contains no deploy-only machinery', () => { + const built = builtEntryGraph('orm.mjs'); // Positive marker: the graph reached the real runtime code in the chunk, // so the forbidden-token checks below are not vacuous. expect(built).toContain('@prisma/orm-postgres/runtime'); for (const token of [ '@prisma/orm-toolchain/cli', 'postgres/control', - 'pn-config', - 'pn-migration-resource', - 'prisma-next-migrate', + 'orm-config', + 'orm-migration-resource', + 'orm-migrate', 'pg-warm-resource', ]) { expect({ token, present: built.includes(token) }).toEqual({ token, present: false }); diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/pn-config.test.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-config.test.ts similarity index 82% rename from packages/1-prisma-cloud/1-extensions/target/src/__tests__/pn-config.test.ts rename to packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-config.test.ts index bcd75114c..94e33e3eb 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/pn-config.test.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-config.test.ts @@ -1,7 +1,7 @@ /** - * `resolvePrismaNextConfig` — the config-path → project-facts resolution the - * lowering does when assembling `PnMigration` props. Loads real - * `prisma-next.config.ts` fixtures via PN's config loader: the widget + * `resolveOrmConfig` — the config-path → project-facts resolution the + * lowering does when assembling `OrmMigration` props. Loads real + * `prisma.config.ts` fixtures via PN's config loader: the widget * fixture (no packs) and the packed fixture (one synthetic pack). No * database / Prisma Cloud involved. Plus `packHeadRefHashes`, the pack * identity fold the migration resource keys its diff on. @@ -12,8 +12,8 @@ import { type PnExtensionPack, packHeadRefHashes, resolveMigrationsDir, - resolvePrismaNextConfig, -} from '../pn-config.ts'; + resolveOrmConfig, +} from '../orm-config.ts'; import { GADGET_PACK_HEAD_HASH, GADGET_PACK_ID, @@ -25,7 +25,7 @@ const widgetConfig = path.join( 'fixtures', 'widget-contract', 'source', - 'prisma-next.config.ts', + 'prisma.config.ts', ); const packedConfig = path.join( @@ -33,12 +33,12 @@ const packedConfig = path.join( 'fixtures', 'packed-contract', 'source', - 'prisma-next.config.ts', + 'prisma.config.ts', ); -describe('resolvePrismaNextConfig', () => { +describe('resolveOrmConfig', () => { test('resolves the default migrations/ dir relative to the config file', async () => { - const project = await resolvePrismaNextConfig(widgetConfig); + const project = await resolveOrmConfig(widgetConfig); // The widget config sets no `migrations.dir`, so PN's default `migrations/` // resolves next to the config file (its `source/` directory). expect(project.migrationsDir).toBe(path.join(path.dirname(widgetConfig), 'migrations')); @@ -46,19 +46,19 @@ describe('resolvePrismaNextConfig', () => { }); test('a config with no extension packs yields []', async () => { - const project = await resolvePrismaNextConfig(widgetConfig); + const project = await resolveOrmConfig(widgetConfig); expect(project.extensionPacks).toEqual([]); }); test('surfaces declared extension packs with their contract-space heads', async () => { - const project = await resolvePrismaNextConfig(packedConfig); + const project = await resolveOrmConfig(packedConfig); expect(project.extensionPacks.map((p) => p.id)).toEqual([GADGET_PACK_ID]); expect(project.extensionPacks[0]?.contractSpace?.headRef.hash).toBe(GADGET_PACK_HEAD_HASH); }); - test('resolveMigrationsDir stays the migrationsDir projection of resolvePrismaNextConfig', async () => { + test('resolveMigrationsDir stays the migrationsDir projection of resolveOrmConfig', async () => { expect(await resolveMigrationsDir(widgetConfig)).toBe( - (await resolvePrismaNextConfig(widgetConfig)).migrationsDir, + (await resolveOrmConfig(widgetConfig)).migrationsDir, ); }); }); diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/pn-extension-packs.integration.test.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-extension-packs.integration.test.ts similarity index 95% rename from packages/1-prisma-cloud/1-extensions/target/src/__tests__/pn-extension-packs.integration.test.ts rename to packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-extension-packs.integration.test.ts index c139c7d08..fc81097e8 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/pn-extension-packs.integration.test.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-extension-packs.integration.test.ts @@ -2,7 +2,7 @@ * Multi-space migration with a declared extension pack, * proven against a real local Postgres: the app space (widget contract) and * one synthetic pack space (`gadget`, the gadget contract) migrate in ONE - * `applyPnMigration` call — + * `applyOrmMigration` call — * * - fresh DB → `migrate`: PN's aggregate pipeline replays the authored * graph of BOTH spaces from empty and signs a marker row per space; @@ -28,8 +28,8 @@ import { spaceMigrationDirectory, } from '@prisma/orm-toolchain/migration-tools/spaces'; import pg from 'pg'; -import type { PnExtensionPack } from '../pn-config.ts'; -import { applyPnMigration, targetStorageHash } from '../prisma-next-migrate.ts'; +import type { PnExtensionPack } from '../orm-config.ts'; +import { applyOrmMigration, targetStorageHash } from '../orm-migrate.ts'; import gadgetContractJson from './fixtures/gadget-contract/emitted/contract.json' with { type: 'json', }; @@ -160,7 +160,7 @@ async function tableExists(url: string, table: string): Promise { } } -describe.skipIf(pgServer === undefined)('applyPnMigration with a declared extension pack', () => { +describe.skipIf(pgServer === undefined)('applyOrmMigration with a declared extension pack', () => { if (pgServer === undefined) return; let migrationsDir: string; let db: TestDatabase; @@ -180,7 +180,7 @@ describe.skipIf(pgServer === undefined)('applyPnMigration with a declared extens }); test('fresh DB → migrate replays BOTH spaces and signs a marker row per space', async () => { - const outcome = await applyPnMigration({ + const outcome = await applyOrmMigration({ url, contractJson: widgetContractJson, migrationsDir, @@ -199,7 +199,7 @@ describe.skipIf(pgServer === undefined)('applyPnMigration with a declared extens }); test('re-run with packs → the app-space noop is suppressed; migrate no-ops per space', async () => { - const outcome = await applyPnMigration({ + const outcome = await applyOrmMigration({ url, contractJson: widgetContractJson, migrationsDir, @@ -219,7 +219,7 @@ describe.skipIf(pgServer === undefined)('applyPnMigration with a declared extens }); test('without packs the noop short-circuit is untouched', async () => { - const outcome = await applyPnMigration({ + const outcome = await applyOrmMigration({ url, contractJson: widgetContractJson, migrationsDir, diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/prisma-next-migrate.integration.test.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-migrate.integration.test.ts similarity index 59% rename from packages/1-prisma-cloud/1-extensions/target/src/__tests__/prisma-next-migrate.integration.test.ts rename to packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-migrate.integration.test.ts index 10154cb40..e53c86af0 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/prisma-next-migrate.integration.test.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-migrate.integration.test.ts @@ -1,11 +1,11 @@ /** * The safety-critical migration decision + apply logic (ref-based * target per review R4), proven against a real local Postgres — isolated from - * the Alchemy stack / Prisma Cloud provisioning. Exercises `applyPnMigration` + * the Alchemy stack / Prisma Cloud provisioning. Exercises `applyOrmMigration` * end to end in three shapes (replay-only: the pipeline never synthesizes): * * No authored graph (empty migrations dir): - * - empty DB → the structured refusal: PnMigrationError + * - empty DB → the structured refusal: OrmMigrationError * (MIGRATION_PATH_NOT_FOUND) naming both exits (`prisma db update` / * `prisma migration plan`), DB left untouched * @@ -13,7 +13,7 @@ * writers so hashes and manifests are the real thing): * - empty DB → `migrate` replays the baseline and signs the marker * - same target re-run → `noop` - * - no authored path → throws PnMigrationError(MIGRATION_PATH_NOT_FOUND), + * - no authored path → throws OrmMigrationError(MIGRATION_PATH_NOT_FOUND), * DB left unchanged * * Authored graph with a DATA invariant: @@ -34,11 +34,11 @@ import * as os from 'node:os'; import * as path from 'node:path'; import { createPostgresControlClient } from '@prisma/orm-postgres/control'; import { - applyPnMigration, - PnMigrationError, + applyOrmMigration, + OrmMigrationError, resolveTargetRef, targetStorageHash, -} from '../prisma-next-migrate.ts'; +} from '../orm-migrate.ts'; import gadgetContractJson from './fixtures/gadget-contract/emitted/contract.json' with { type: 'json', }; @@ -62,7 +62,7 @@ const pg: TestPostgres | undefined = startTestPostgres(); if (pg === undefined) { console.warn( - '[app-cloud] skipping prisma-next migrate integration test: no Postgres available. ' + + '[app-cloud] skipping Prisma ORM migrate integration test: no Postgres available. ' + 'Set STATE_TEST_DATABASE_URL or install initdb/pg_ctl on PATH.', ); } @@ -84,7 +84,7 @@ async function readMarker( } describe.skipIf(pg === undefined)( - 'applyPnMigration — no authored graph (structured refusal)', + 'applyOrmMigration — no authored graph (structured refusal)', () => { if (pg === undefined) return; let migrationsDir: string; @@ -109,7 +109,7 @@ describe.skipIf(pg === undefined)( let thrown: unknown; try { - await applyPnMigration({ + await applyOrmMigration({ url: db.url, contractJson: widgetContractJson, migrationsDir, @@ -119,8 +119,8 @@ describe.skipIf(pg === undefined)( thrown = e; } - expect(thrown).toBeInstanceOf(PnMigrationError); - const error = thrown as PnMigrationError; + expect(thrown).toBeInstanceOf(OrmMigrationError); + const error = thrown as OrmMigrationError; expect(error.code).toBe('MIGRATION_PATH_NOT_FOUND'); // The refusal names both exits: local iteration and authoring the path. expect(error.message).toContain('prisma db update'); @@ -131,7 +131,7 @@ describe.skipIf(pg === undefined)( }, ); -describe.skipIf(pg === undefined)('applyPnMigration — authored baseline (replay path)', () => { +describe.skipIf(pg === undefined)('applyOrmMigration — authored baseline (replay path)', () => { if (pg === undefined) return; let migrationsDir: string; let db: TestDatabase; @@ -156,7 +156,7 @@ describe.skipIf(pg === undefined)('applyPnMigration — authored baseline (repla const ref = await resolveTargetRef(migrationsDir, widgetContractJson); expect(ref).toEqual({ hash: widgetHash, invariants: [] }); - const outcome = await applyPnMigration({ + const outcome = await applyOrmMigration({ url, contractJson: widgetContractJson, migrationsDir, @@ -172,7 +172,7 @@ describe.skipIf(pg === undefined)('applyPnMigration — authored baseline (repla test('re-run at the same ref → noop (idempotent redeploy)', async () => { const ref = await resolveTargetRef(migrationsDir, widgetContractJson); - const outcome = await applyPnMigration({ + const outcome = await applyOrmMigration({ url, contractJson: widgetContractJson, migrationsDir, @@ -195,106 +195,109 @@ describe.skipIf(pg === undefined)('applyPnMigration — authored baseline (repla const ref = await resolveTargetRef(migrationsDir, gadgetContractJson); let thrown: unknown; try { - await applyPnMigration({ url, contractJson: gadgetContractJson, migrationsDir, ref }); + await applyOrmMigration({ url, contractJson: gadgetContractJson, migrationsDir, ref }); } catch (e) { thrown = e; } - expect(thrown).toBeInstanceOf(PnMigrationError); - expect((thrown as PnMigrationError).code).toBe('MIGRATION_PATH_NOT_FOUND'); + expect(thrown).toBeInstanceOf(OrmMigrationError); + expect((thrown as OrmMigrationError).code).toBe('MIGRATION_PATH_NOT_FOUND'); // Failed apply left the marker (and schema) unchanged. expect((await readMarker(url))?.storageHash).toBe(widgetHash); }); }); -describe.skipIf(pg === undefined)('applyPnMigration — authored graph with a data invariant', () => { - if (pg === undefined) return; - let migrationsDir: string; +describe.skipIf(pg === undefined)( + 'applyOrmMigration — authored graph with a data invariant', + () => { + if (pg === undefined) return; + let migrationsDir: string; - beforeAll(async () => { - migrationsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'prisma-composer-pn-inv-')); - await authorWidgetInit(migrationsDir); - await authorWidgetBackfill(migrationsDir); - }); - afterAll(async () => { - pg.stop(); - if (migrationsDir !== undefined) fs.rmSync(migrationsDir, { recursive: true, force: true }); - }); + beforeAll(async () => { + migrationsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'prisma-composer-pn-inv-')); + await authorWidgetInit(migrationsDir); + await authorWidgetBackfill(migrationsDir); + }); + afterAll(async () => { + pg.stop(); + if (migrationsDir !== undefined) fs.rmSync(migrationsDir, { recursive: true, force: true }); + }); - test('fresh DB + ref with invariants → migrate; marker records the invariant; re-run no-ops', async () => { - const db = await createTestDatabase(pg.url); - try { - const ref = await resolveTargetRef(migrationsDir, widgetContractJson, 'with-backfill'); - expect(ref).toEqual({ hash: widgetHash, invariants: [BACKFILL_INVARIANT] }); + test('fresh DB + ref with invariants → migrate; marker records the invariant; re-run no-ops', async () => { + const db = await createTestDatabase(pg.url); + try { + const ref = await resolveTargetRef(migrationsDir, widgetContractJson, 'with-backfill'); + expect(ref).toEqual({ hash: widgetHash, invariants: [BACKFILL_INVARIANT] }); - // (a) The fresh DB replays the authored graph from empty, including the - // invariant-bearing data step. - const first = await applyPnMigration({ - url: db.url, - contractJson: widgetContractJson, - migrationsDir, - ref, - refName: 'with-backfill', - }); - expect(first.action).toBe('migrate'); - expect(first.markerHashBefore).toBeNull(); - expect(first.targetHash).toBe(widgetHash); + // (a) The fresh DB replays the authored graph from empty, including the + // invariant-bearing data step. + const first = await applyOrmMigration({ + url: db.url, + contractJson: widgetContractJson, + migrationsDir, + ref, + refName: 'with-backfill', + }); + expect(first.action).toBe('migrate'); + expect(first.markerHashBefore).toBeNull(); + expect(first.targetHash).toBe(widgetHash); - // (b) The marker records both the hash and the invariant. - const marker = await readMarker(db.url); - expect(marker?.storageHash).toBe(widgetHash); - expect(marker?.invariants).toContain(BACKFILL_INVARIANT); + // (b) The marker records both the hash and the invariant. + const marker = await readMarker(db.url); + expect(marker?.storageHash).toBe(widgetHash); + expect(marker?.invariants).toContain(BACKFILL_INVARIANT); - // (c) Re-run at the same ref — at hash AND invariants ⊆ marker → noop. - const second = await applyPnMigration({ - url: db.url, - contractJson: widgetContractJson, - migrationsDir, - ref, - refName: 'with-backfill', - }); - expect(second.action).toBe('noop'); - } finally { - await db.drop().catch(() => {}); - } - }); + // (c) Re-run at the same ref — at hash AND invariants ⊆ marker → noop. + const second = await applyOrmMigration({ + url: db.url, + contractJson: widgetContractJson, + migrationsDir, + ref, + refName: 'with-backfill', + }); + expect(second.action).toBe('noop'); + } finally { + await db.drop().catch(() => {}); + } + }); - test('hash-match-but-invariant-missing (A→A data-only) → migrate stamps the invariant', async () => { - const db = await createTestDatabase(pg.url); - try { - // First bring the DB to widgetHash WITHOUT the invariant: the default - // ref (head = emitted contract, zero invariants) replays the baseline - // only — the data self-edge provides an invariant nothing requires yet. - const headRef = await resolveTargetRef(migrationsDir, widgetContractJson); - const baseline = await applyPnMigration({ - url: db.url, - contractJson: widgetContractJson, - migrationsDir, - ref: headRef, - }); - expect(baseline.action).toBe('migrate'); - const before = await readMarker(db.url); - expect(before?.storageHash).toBe(widgetHash); - expect(before?.invariants ?? []).not.toContain(BACKFILL_INVARIANT); + test('hash-match-but-invariant-missing (A→A data-only) → migrate stamps the invariant', async () => { + const db = await createTestDatabase(pg.url); + try { + // First bring the DB to widgetHash WITHOUT the invariant: the default + // ref (head = emitted contract, zero invariants) replays the baseline + // only — the data self-edge provides an invariant nothing requires yet. + const headRef = await resolveTargetRef(migrationsDir, widgetContractJson); + const baseline = await applyOrmMigration({ + url: db.url, + contractJson: widgetContractJson, + migrationsDir, + ref: headRef, + }); + expect(baseline.action).toBe('migrate'); + const before = await readMarker(db.url); + expect(before?.storageHash).toBe(widgetHash); + expect(before?.invariants ?? []).not.toContain(BACKFILL_INVARIANT); - // (d) Same hash, missing invariant — keying on storageHash alone would - // wrongly no-op here. The ref decision walks the A→A self-edge instead. - const ref = await resolveTargetRef(migrationsDir, widgetContractJson, 'with-backfill'); - const outcome = await applyPnMigration({ - url: db.url, - contractJson: widgetContractJson, - migrationsDir, - ref, - refName: 'with-backfill', - }); - expect(outcome.action).toBe('migrate'); - expect(outcome.markerHashBefore).toBe(widgetHash); + // (d) Same hash, missing invariant — keying on storageHash alone would + // wrongly no-op here. The ref decision walks the A→A self-edge instead. + const ref = await resolveTargetRef(migrationsDir, widgetContractJson, 'with-backfill'); + const outcome = await applyOrmMigration({ + url: db.url, + contractJson: widgetContractJson, + migrationsDir, + ref, + refName: 'with-backfill', + }); + expect(outcome.action).toBe('migrate'); + expect(outcome.markerHashBefore).toBe(widgetHash); - const after = await readMarker(db.url); - expect(after?.storageHash).toBe(widgetHash); - expect(after?.invariants).toContain(BACKFILL_INVARIANT); - } finally { - await db.drop().catch(() => {}); - } - }); -}); + const after = await readMarker(db.url); + expect(after?.storageHash).toBe(widgetHash); + expect(after?.invariants).toContain(BACKFILL_INVARIANT); + } finally { + await db.drop().catch(() => {}); + } + }); + }, +); diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/pn-migration-resource.test.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-migration-resource.test.ts similarity index 83% rename from packages/1-prisma-cloud/1-extensions/target/src/__tests__/pn-migration-resource.test.ts rename to packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-migration-resource.test.ts index 5b84ae68f..af9e2dc66 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/pn-migration-resource.test.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-migration-resource.test.ts @@ -1,5 +1,5 @@ /** - * The `PnMigration` Alchemy resource wiring, proven WITHOUT Prisma + * The `OrmMigration` Alchemy resource wiring, proven WITHOUT Prisma * Cloud: * - the merge/lookup MECHANISM the descriptor relies on — `Layer.mergeAll` of * `Provider.effect` layers keeps EVERY provider tag reachable by @@ -15,7 +15,7 @@ * lookup against a directly-constructed Context (`Layer.succeed` on the * `Provider(type)` tag with the exported SERVICE values, which no sibling * mock touches) — no cross-module Layer constructors involved; - * - the provider's `reconcile` routes to `applyPnMigration` — driven directly + * - the provider's `reconcile` routes to `applyOrmMigration` — driven directly * against the exported provider service, proven live against a real local * Postgres (empty DB + authored baseline → replay, re-run → no-op, * no-path → rejects). @@ -33,11 +33,11 @@ import type * as Context from 'effect/Context'; import * as Effect from 'effect/Effect'; import * as Layer from 'effect/Layer'; import * as Option from 'effect/Option'; +import { OrmMigrationError, targetStorageHash } from '../orm-migrate.ts'; +import type { OrmMigration } from '../orm-migration-resource.ts'; +import { ormMigrationProviderService } from '../orm-migration-resource.ts'; import type { PgWarm } from '../pg-warm-resource.ts'; import { pgWarmProviderService } from '../pg-warm-resource.ts'; -import type { PnMigration } from '../pn-migration-resource.ts'; -import { pnMigrationProviderService } from '../pn-migration-resource.ts'; -import { PnMigrationError, targetStorageHash } from '../prisma-next-migrate.ts'; import gadgetContractJson from './fixtures/gadget-contract/emitted/contract.json' with { type: 'json', }; @@ -56,10 +56,10 @@ import { authorWidgetInit } from './widget-migrations-fixture.ts'; // descriptor's providers, so the merge mechanism is exercised with values no // sibling test file's `mock.module` can replace (in-file consts are immune; // imported constructors are not — see the header). -type ProbeA = Resource<'PrismaNext.ProbeA', { readonly n: number }, { readonly n: number }>; -const ProbeA = Resource('PrismaNext.ProbeA'); -type ProbeB = Resource<'PrismaNext.ProbeB', { readonly n: number }, { readonly n: number }>; -const ProbeB = Resource('PrismaNext.ProbeB'); +type ProbeA = Resource<'PrismaOrm.ProbeA', { readonly n: number }, { readonly n: number }>; +const ProbeA = Resource('PrismaOrm.ProbeA'); +type ProbeB = Resource<'PrismaOrm.ProbeB', { readonly n: number }, { readonly n: number }>; +const ProbeB = Resource('PrismaOrm.ProbeB'); const probeAService: Provider.ProviderService = { list: () => Effect.succeed([]), reconcile: ({ news }) => Effect.succeed({ n: news.n }), @@ -92,12 +92,12 @@ const resolveIn = ( describe('provider merge mechanism (Layer.mergeAll keeps every tag reachable)', () => { test('the merged layer resolves the first provider by type', async () => { - const resolved = await resolveIn(merged, Provider.tryFindProviderByType('PrismaNext.ProbeA')); + const resolved = await resolveIn(merged, Provider.tryFindProviderByType('PrismaOrm.ProbeA')); expect(Option.isSome(resolved)).toBe(true); }); test('merging does not shadow the second provider', async () => { - const resolved = await resolveIn(merged, Provider.tryFindProviderByType('PrismaNext.ProbeB')); + const resolved = await resolveIn(merged, Provider.tryFindProviderByType('PrismaOrm.ProbeB')); expect(Option.isSome(resolved)).toBe(true); }); }); @@ -109,17 +109,17 @@ describe("the real providers' tags resolve by type (direct context, no cross-mod // the exported SERVICE values (which no sibling mock factory lists, so they // also survive a leak) — alchemy's own lookup must find both types the // descriptor registers. - const PnMigrationTag = Resource('PrismaNext.Migration'); + const OrmMigrationTag = Resource('PrismaOrm.Migration'); const PgWarmTag = Resource('PrismaCloud.PgWarm'); const realTags = Layer.mergeAll( - Provider.effect(PnMigrationTag, Effect.succeed(pnMigrationProviderService)), + Provider.effect(OrmMigrationTag, Effect.succeed(ormMigrationProviderService)), Provider.effect(PgWarmTag, Effect.succeed(pgWarmProviderService)), ); - test("tryFindProviderByType('PrismaNext.Migration') resolves", async () => { + test("tryFindProviderByType('PrismaOrm.Migration') resolves", async () => { const resolved = await resolveIn( realTags, - Provider.tryFindProviderByType('PrismaNext.Migration'), + Provider.tryFindProviderByType('PrismaOrm.Migration'), ); expect(Option.isSome(resolved)).toBe(true); }); @@ -137,12 +137,12 @@ const pg: TestPostgres | undefined = startTestPostgres(); if (pg === undefined) { console.warn( - '[app-cloud] skipping PnMigration reconcile test: no Postgres available. ' + + '[app-cloud] skipping OrmMigration reconcile test: no Postgres available. ' + 'Set STATE_TEST_DATABASE_URL or install initdb/pg_ctl on PATH.', ); } -describe.skipIf(pg === undefined)('PnMigration reconcile routes through applyPnMigration', () => { +describe.skipIf(pg === undefined)('OrmMigration reconcile routes through applyOrmMigration', () => { if (pg === undefined) return; let migrationsDir: string; let testDb: TestDatabase; @@ -152,7 +152,7 @@ describe.skipIf(pg === undefined)('PnMigration reconcile routes through applyPnM // Effect layer to build, so the routing assertion can't be flaked by // environment-specific layer internals. const reconcile = (contractJson: unknown) => - pnMigrationProviderService.reconcile({ + ormMigrationProviderService.reconcile({ id: 'db', fqn: 'db', instanceId: 'db', @@ -165,7 +165,7 @@ describe.skipIf(pg === undefined)('PnMigration reconcile routes through applyPnM // No packs declared: reconcile must not touch configPath (the path // deliberately points nowhere). packHeadRefHashes: [], - configPath: path.join(migrationsDir, 'no-such-prisma-next.config.ts'), + configPath: path.join(migrationsDir, 'no-such-prisma.config.ts'), }, olds: undefined, output: undefined, @@ -196,12 +196,12 @@ describe.skipIf(pg === undefined)('PnMigration reconcile routes through applyPnM expect(second.storageHash).toBe(targetHash); }); - test('reconcile re-throws a no-path failure: the Effect REJECTS with PnMigrationError', async () => { + test('reconcile re-throws a no-path failure: the Effect REJECTS with OrmMigrationError', async () => { // Ensure the DB is signed at widgetHash (idempotent if already there). await Effect.runPromise(reconcile(widgetContractJson)); // Target a DIFFERENT contract (gadget) with no authored migration path. The - // provider's `catch: (e) => e` must route the thrown PnMigrationError into + // provider's `catch: (e) => e` must route the thrown OrmMigrationError into // the Effect's error channel — so the reconcile FAILS, not succeeds. const outcome = await Effect.runPromise( reconcile(gadgetContractJson).pipe( @@ -213,7 +213,7 @@ describe.skipIf(pg === undefined)('PnMigration reconcile routes through applyPnM ); expect(outcome.failed).toBe(true); - expect(outcome.error).toBeInstanceOf(PnMigrationError); - expect((outcome.error as PnMigrationError).code).toBe('MIGRATION_PATH_NOT_FOUND'); + expect(outcome.error).toBeInstanceOf(OrmMigrationError); + expect((outcome.error as OrmMigrationError).code).toBe('MIGRATION_PATH_NOT_FOUND'); }); }); diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/prisma-next-shapes.test-d.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-postgres-shapes.test-d.ts similarity index 63% rename from packages/1-prisma-cloud/1-extensions/target/src/__tests__/prisma-next-shapes.test-d.ts rename to packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-postgres-shapes.test-d.ts index f0df79615..f0ba1b962 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/prisma-next-shapes.test-d.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-postgres-shapes.test-d.ts @@ -1,8 +1,8 @@ /** - * `pnPostgres()`'s two shapes, and storageHash-exact wiring compatibility. - * `{ name, contract }` is the provisionable identity; `pnPostgres(contract)` is + * `postgres()`'s two shapes, and storageHash-exact wiring compatibility. + * `{ name, contract }` is the provisionable identity; `postgres(contract)` is * the dependency, whose binding is `{ url, client }` (ADR-0040). The - * `WidgetContract`/`GadgetContract` types come from real `prisma-next + * `WidgetContract`/`GadgetContract` types come from real `orm-postgres * contract emit` output (`fixtures/{widget,gadget}-contract/emitted/contract.d.ts`) * — the branded `storageHash` literal each carries is the lever under test. * @@ -15,13 +15,13 @@ import { service } from '@internal/core'; import { expectTypeOf, test } from 'vitest'; import { type Client, - type PnPostgresBinding, - type PnPostgresContract, - type PnPostgresResourceNode, - pnContract, - pnPostgres, -} from '../exports/prisma-next.ts'; -import { postgres } from '../postgres.ts'; + dataContract, + type PostgresBinding, + type PostgresContract, + type PostgresResourceNode, + postgres, +} from '../exports/orm.ts'; +import { rawPostgres } from '../raw-postgres.ts'; import type { Contract as GadgetContract } from './fixtures/gadget-contract/emitted/contract.d.ts'; import type { Contract as WidgetContract } from './fixtures/widget-contract/emitted/contract.d.ts'; @@ -29,29 +29,29 @@ declare const widgetJson: WidgetContract; declare const widgetJsonAgain: WidgetContract; declare const gadgetJson: GadgetContract; -const widget = pnContract(widgetJson); +const widget = dataContract(widgetJson); // A second, independently-wrapped value carrying the SAME emitted contract // type — proves the lever is the type (storageHash), not object identity. -const widgetAgain = pnContract(widgetJsonAgain); -const gadget = pnContract(gadgetJson); +const widgetAgain = dataContract(widgetJsonAgain); +const gadget = dataContract(gadgetJson); -test('pnContract wraps an emitted contract into the prisma-next kind', () => { - expectTypeOf(widget).toEqualTypeOf>(); +test('dataContract wraps an emitted contract into the orm-postgres kind', () => { + expectTypeOf(widget).toEqualTypeOf>(); }); test('{ name, contract, config } yields the resource node carrying the config path', () => { - const identity = pnPostgres({ name: 'db', contract: widget, config: './prisma-next.config.ts' }); - expectTypeOf(identity).toEqualTypeOf>(); + const identity = postgres({ name: 'db', contract: widget, config: './prisma.config.ts' }); + expectTypeOf(identity).toEqualTypeOf>(); // The config path rides on the node as a string field, sibling to provides. expectTypeOf(identity.config).toEqualTypeOf(); }); -test('pnPostgres(contract) yields the dependency requiring that contract; its binding is { url, client }', () => { - const dep = pnPostgres(widget); - expectTypeOf(dep).toEqualTypeOf, typeof widget>>(); - // The binding load() hands the app: the raw URL plus the Prisma Next client +test('postgres(contract) yields the dependency requiring that contract; its binding is { url, client }', () => { + const dep = postgres(widget); + expectTypeOf(dep).toEqualTypeOf, typeof widget>>(); + // The binding load() hands the app: the raw URL plus the Prisma ORM client // typed by the contract (ADR-0040). - expectTypeOf>().toEqualTypeOf>(); + expectTypeOf>().toEqualTypeOf>(); expectTypeOf['url']>().toEqualTypeOf(); expectTypeOf['client']>().toEqualTypeOf>(); }); @@ -67,7 +67,7 @@ const consumer = service({ name: 'test-service', extension: 'test/pack', type: 'fake/compute', - inputs: { db: pnPostgres(widget) }, + inputs: { db: postgres(widget) }, params: {}, build, }); @@ -75,22 +75,22 @@ const consumer = service({ declare const h: ModuleBuilder; const widgetRef = h.provision( - pnPostgres({ name: 'db', contract: widget, config: './prisma-next.config.ts' }), + postgres({ name: 'db', contract: widget, config: './prisma.config.ts' }), { id: 'db1' }, ); const widgetAgainRef = h.provision( - pnPostgres({ name: 'db', contract: widgetAgain, config: './prisma-next.config.ts' }), + postgres({ name: 'db', contract: widgetAgain, config: './prisma.config.ts' }), { id: 'db2' }, ); const gadgetRef = h.provision( - pnPostgres({ name: 'db', contract: gadget, config: './prisma-next.config.ts' }), + postgres({ name: 'db', contract: gadget, config: './prisma.config.ts' }), { id: 'db3' }, ); test('a resource providing the SAME emitted contract (same storageHash) satisfies the dependency slot', () => { expectTypeOf(h.provision).toBeCallableWith(consumer, { id: 'c1', deps: { db: widgetRef } }); // A different wrap of the identical emitted contract type also satisfies — - // the lever is the type (storageHash), not which `pnContract()` call built it. + // the lever is the type (storageHash), not which `dataContract()` call built it. expectTypeOf(h.provision).toBeCallableWith(consumer, { id: 'c2', deps: { db: widgetAgainRef } }); }); @@ -99,9 +99,9 @@ test('a resource providing a DIFFERENT emitted contract (different storageHash) h.provision(consumer, { id: 'c3', deps: { db: gadgetRef } }); }); -const barePostgresRef = h.provision(postgres({ name: 'db4' }), { id: 'db4' }); +const barePostgresRef = h.provision(rawPostgres({ name: 'db4' }), { id: 'db4' }); test('a resource of a different protocol kind entirely (bare postgresContract) is a type error', () => { - // @ts-expect-error postgresContract's kind is "postgres", not "prisma-next" + // @ts-expect-error postgresContract's kind is "postgres", not "postgres" h.provision(consumer, { id: 'c4', deps: { db: barePostgresRef } }); }); diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/prisma-next.integration.test.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-postgres.integration.test.ts similarity index 86% rename from packages/1-prisma-cloud/1-extensions/target/src/__tests__/prisma-next.integration.test.ts rename to packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-postgres.integration.test.ts index ea408c2bc..9832ac568 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/prisma-next.integration.test.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-postgres.integration.test.ts @@ -1,9 +1,9 @@ /** - * The live proof: a hydrated Prisma Next client round-trips a real query + * The live proof: a hydrated Prisma ORM client round-trips a real query * against a real Postgres whose schema is at the `widget-contract` fixture's * contract. * - * Schema is applied the faithful way — through Prisma Next's own control + * Schema is applied the faithful way — through Prisma ORM's own control * client (`createPostgresControlClient(...).dbInit({ mode: 'apply' })`), the * same machinery slice 2's deploy lowering will drive — rather than * hand-written SQL, so this exercises the real apply-and-sign path end to end. @@ -20,8 +20,8 @@ import * as os from 'node:os'; import * as path from 'node:path'; import { createPostgresControlClient } from '@prisma/orm-postgres/control'; import type { Char } from '@prisma/orm-postgres/target/codec-types'; -import type { PnPostgresBinding } from '../exports/prisma-next.ts'; -import { pnContract, pnPostgres } from '../exports/prisma-next.ts'; +import type { PostgresBinding } from '../exports/orm.ts'; +import { dataContract, postgres } from '../exports/orm.ts'; import type { Contract as WidgetContract } from './fixtures/widget-contract/emitted/contract.d.ts'; import widgetContractJson from './fixtures/widget-contract/emitted/contract.json' with { type: 'json', @@ -37,22 +37,22 @@ const pg: TestPostgres | undefined = startTestPostgres(); if (pg === undefined) { console.warn( - '[app-cloud] skipping prisma-next integration test: no Postgres available. ' + + '[app-cloud] skipping Prisma ORM integration test: no Postgres available. ' + 'Set STATE_TEST_DATABASE_URL to point at one, or install initdb/pg_ctl ' + '(e.g. `brew install postgresql@15`) on PATH.', ); } -describe.skipIf(pg === undefined)('pnPostgres hydrate — live round trip', () => { +describe.skipIf(pg === undefined)('postgres hydrate — live round trip', () => { if (pg === undefined) return; // The wrapped, branded contract — the exact value both the resource and the // dependency reference. Typing it as WidgetContract makes the hydrated // binding's client `Client`, so the round-trip below is // type-checked. - const contract = pnContract(widgetContractJson); + const contract = dataContract(widgetContractJson); let migrationsDir: string; - let db: PnPostgresBinding; + let db: PostgresBinding; // An owned database (not the shared `postgres`/`public`) so dbInit applies // onto an empty schema and the fixed-id insert can't collide with a prior run. let testDb: TestDatabase; @@ -78,7 +78,7 @@ describe.skipIf(pg === undefined)('pnPostgres hydrate — live round trip', () = // Hydrate the binding exactly as a service would: through the dependency // end's hydrate, given only the DB url. - db = await pnPostgres(contract).connection.hydrate({ url }); + db = await postgres(contract).connection.hydrate({ url }); }); afterAll(async () => { diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/prisma-next.test.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-postgres.test.ts similarity index 64% rename from packages/1-prisma-cloud/1-extensions/target/src/__tests__/prisma-next.test.ts rename to packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-postgres.test.ts index d23da5967..05e3452ce 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/prisma-next.test.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-postgres.test.ts @@ -1,27 +1,27 @@ /** - * `pnPostgres()`'s runtime behavior — proven without a live database. The PN + * `postgres()`'s runtime behavior — proven without a live database. The PN * client is lazy (its pool opens on first query), so hydrate is fully - * exercisable here: `pnPostgresRuntime()` never connects just by being + * exercisable here: `ormPostgresRuntime()` never connects just by being * constructed (see `fixtures/widget-contract/`'s round trip through the real - * `prisma-next contract emit` CLI for the artifacts this suite imports). + * `prisma contract emit` CLI for the artifacts this suite imports). * - * `pnContract(contractJson)` pins the type parameter explicitly — + * `dataContract(contractJson)` pins the type parameter explicitly — * a JSON module import's inferred type is plain data, not the branded * `contract.d.ts` type, matching `@prisma/orm-postgres/runtime`'s own - * `postgres({ contractJson })` convention (see prisma-next.ts). + * `postgres({ contractJson })` convention (see orm-postgres.ts). */ import { describe, expect, test } from 'bun:test'; import type { Contract, ResourceNode } from '@internal/core'; import { isNode, Load, module, string } from '@internal/core'; import { blindCast } from '@internal/foundation/casts'; import { - isPnPostgresResourceNode, - pnContract, - pnPostgres, + dataContract, + isPostgresResourceNode, + postgres, requiredPackHead, requiredPackHeadOf, -} from '../exports/prisma-next.ts'; -import { postgres } from '../postgres.ts'; +} from '../exports/orm.ts'; +import { rawPostgres } from '../raw-postgres.ts'; import type { Contract as GadgetContract } from './fixtures/gadget-contract/emitted/contract.d.ts'; import gadgetContractJson from './fixtures/gadget-contract/emitted/contract.json' with { type: 'json', @@ -31,49 +31,49 @@ import widgetContractJson from './fixtures/widget-contract/emitted/contract.json type: 'json', }; -describe('pnContract().satisfies()', () => { +describe('dataContract().satisfies()', () => { test('true when the required contract has the same storageHash', () => { - const a = pnContract(widgetContractJson); - const b = pnContract(widgetContractJson); + const a = dataContract(widgetContractJson); + const b = dataContract(widgetContractJson); expect(a.satisfies(b)).toBe(true); expect(b.satisfies(a)).toBe(true); }); test('false when the required contract has a different storageHash', () => { - const widget = pnContract(widgetContractJson); - const gadget = pnContract(gadgetContractJson); + const widget = dataContract(widgetContractJson); + const gadget = dataContract(gadgetContractJson); expect(widget.satisfies(gadget)).toBe(false); expect(gadget.satisfies(widget)).toBe(false); }); test('false when the required contract carries a malformed __cmp (no contractJson)', () => { - const widget = pnContract(widgetContractJson); - // A prisma-next-kinded value whose __cmp lacks contractJson entirely — + const widget = dataContract(widgetContractJson); + // An orm-postgres-kinded value whose __cmp lacks contractJson entirely — // storageHashOf() returns undefined, so satisfies() must be false rather // than throw or spuriously match. const malformed = { - kind: 'prisma-next', + kind: 'postgres', __cmp: {}, satisfies: () => false, - } as Contract<'prisma-next', unknown>; + } as Contract<'postgres', unknown>; expect(widget.satisfies(malformed)).toBe(false); }); test("false in both directions when a wrapper's contractJson lacks storage.storageHash", () => { - const widget = pnContract(widgetContractJson); + const widget = dataContract(widgetContractJson); // contractJson is present but shaped wrong: no `storage.storageHash`. // Both this wrapper's own hash and any comparison against it resolve to // undefined, so satisfies() is false whichever side asks. - const hashless = pnContract({ storage: { namespaces: {} } }); + const hashless = dataContract({ storage: { namespaces: {} } }); expect(widget.satisfies(hashless)).toBe(false); expect(hashless.satisfies(widget)).toBe(false); // ...and a hashless wrapper does not even satisfy itself. expect(hashless.satisfies(hashless)).toBe(false); }); - test('the wrapped contract is frozen and carries the prisma-next kind', () => { - const widget = pnContract(widgetContractJson); - expect(widget.kind).toBe('prisma-next'); + test('the wrapped contract is frozen and carries the orm-postgres kind', () => { + const widget = dataContract(widgetContractJson); + expect(widget.kind).toBe('postgres'); expect(Object.isFrozen(widget)).toBe(true); }); }); @@ -81,8 +81,8 @@ describe('pnContract().satisfies()', () => { describe('requiredPackHead() — the pack-head claim (wireability only)', () => { const requirement = requiredPackHead({ packId: 'auth', headHash: 'auth-head' }); - test('is a frozen prisma-next-kind contract carrying the requirement in __cmp', () => { - expect(requirement.kind).toBe('prisma-next'); + test('is a frozen orm-postgres-kind contract carrying the requirement in __cmp', () => { + expect(requirement.kind).toBe('postgres'); expect(Object.isFrozen(requirement)).toBe(true); expect(requiredPackHeadOf(requirement)).toEqual({ packId: 'auth', @@ -90,31 +90,28 @@ describe('requiredPackHead() — the pack-head claim (wireability only)', () => }); }); - test('ANY pnContract() satisfies a pack requirement — before hash comparison', () => { + test('ANY dataContract() satisfies a pack requirement — before hash comparison', () => { // The widget contract's hash has nothing to do with the pack's head hash: // wireability deliberately says yes (the contract value cannot see the // resource's config); the deploy preflight is the enforcement point. - const widget = pnContract(widgetContractJson); - const gadget = pnContract(gadgetContractJson); + const widget = dataContract(widgetContractJson); + const gadget = dataContract(gadgetContractJson); expect(widget.satisfies(requirement)).toBe(true); expect(gadget.satisfies(requirement)).toBe(true); }); test('the pack branch does not loosen hash comparison for non-requirement contracts', () => { - const widget = pnContract(widgetContractJson); - const gadget = pnContract(gadgetContractJson); + const widget = dataContract(widgetContractJson); + const gadget = dataContract(gadgetContractJson); expect(widget.satisfies(gadget)).toBe(false); }); test('requiredPackHeadOf reads defensively — malformed shapes yield undefined', () => { expect(requiredPackHeadOf(undefined)).toBeUndefined(); - const widget = pnContract(widgetContractJson); + const widget = dataContract(widgetContractJson); expect(requiredPackHeadOf(widget)).toBeUndefined(); const malformed = (cmp: unknown) => - ({ kind: 'prisma-next', __cmp: cmp, satisfies: () => false }) as Contract< - 'prisma-next', - unknown - >; + ({ kind: 'postgres', __cmp: cmp, satisfies: () => false }) as Contract<'postgres', unknown>; expect(requiredPackHeadOf(malformed(null))).toBeUndefined(); expect(requiredPackHeadOf(malformed({ requiredPackHead: null }))).toBeUndefined(); expect(requiredPackHeadOf(malformed({ requiredPackHead: { packId: 'auth' } }))).toBeUndefined(); @@ -124,13 +121,13 @@ describe('requiredPackHead() — the pack-head claim (wireability only)', () => }); }); -describe('pnPostgres() factory shapes', () => { +describe('postgres() factory shapes', () => { test('{ name, contract, config } yields a branded resource node carrying config', () => { - const widget = pnContract(widgetContractJson); - const node = pnPostgres({ + const widget = dataContract(widgetContractJson); + const node = postgres({ name: 'database', contract: widget, - config: './prisma-next.config.ts', + config: './prisma.config.ts', }); // The leaf class inherits the [NODE] Symbol.for brand from @@ -139,77 +136,77 @@ describe('pnPostgres() factory shapes', () => { expect(node.kind).toBe('resource'); expect(node.name).toBe('database'); expect(node.extension).toBe('@prisma/composer-prisma-cloud'); - expect(node.type).toBe('prisma-next'); + expect(node.type).toBe('postgres'); expect(node.provides).toBe(widget); // config rides on the node as a first-class field, sibling to provides. - expect(node.config).toBe('./prisma-next.config.ts'); + expect(node.config).toBe('./prisma.config.ts'); expect(Object.isFrozen(node)).toBe(true); }); - test('pnPostgres(contract) yields a branded DependencyEnd requiring that contract', () => { - const widget = pnContract(widgetContractJson); - const dep = pnPostgres(widget); + test('postgres(contract) yields a branded DependencyEnd requiring that contract', () => { + const widget = dataContract(widgetContractJson); + const dep = postgres(widget); expect(isNode(dep)).toBe(true); expect(dep.kind).toBe('dependency'); - expect(dep.type).toBe('prisma-next'); + expect(dep.type).toBe('postgres'); expect(dep.required).toBe(widget); expect(Object.keys(dep.connection.params)).toEqual(['url']); expect(dep.connection.params['url']).toEqual(string()); }); }); -describe("isPnPostgresResourceNode (the deploy lowering's read predicate)", () => { - const widget = pnContract(widgetContractJson); +describe("isPostgresResourceNode (the deploy lowering's read predicate)", () => { + const widget = dataContract(widgetContractJson); test('narrows a base-typed resource node so `.config` reads', () => { // The lowering's ctx.node is the base union; the predicate is a downcast // of a known node, not an untrusted-value guard. - const node: ResourceNode = pnPostgres({ + const node: ResourceNode = postgres({ name: 'database', contract: widget, - config: './prisma-next.config.ts', + config: './prisma.config.ts', }); - expect(isPnPostgresResourceNode(node)).toBe(true); - if (isPnPostgresResourceNode(node)) { + expect(isPostgresResourceNode(node)).toBe(true); + if (isPostgresResourceNode(node)) { // the narrow gives the lowering `config` without a bare cast - expect(node.config).toBe('./prisma-next.config.ts'); + expect(node.config).toBe('./prisma.config.ts'); } }); - test('false for a pnPostgres dependency end (kind is dependency, no config)', () => { + test('false for a postgres dependency end (kind is dependency, no config)', () => { // A dependency end is never a lowering's ctx.node — cast only to prove // the kind check rejects it. const dep = blindCast( - pnPostgres(widget), + postgres(widget), ); - expect(isPnPostgresResourceNode(dep)).toBe(false); + expect(isPostgresResourceNode(dep)).toBe(false); }); - test('false for a bare postgres() resource (type is postgres, not prisma-next)', () => { - expect(isPnPostgresResourceNode(postgres({ name: 'db' }))).toBe(false); + test('false for a bare postgres() resource (type is postgres, not orm-postgres)', () => { + expect(isPostgresResourceNode(rawPostgres({ name: 'db' }))).toBe(false); }); test('false for a resource lookalike whose config is missing or not a string', () => { const noConfig = blindCast({ kind: 'resource', - type: 'prisma-next', + type: 'postgres', }); - expect(isPnPostgresResourceNode(noConfig)).toBe(false); + expect(isPostgresResourceNode(noConfig)).toBe(false); const numberConfig = blindCast( - { kind: 'resource', type: 'prisma-next', config: 42 }, + { kind: 'resource', type: 'postgres', config: 42 }, ); - expect(isPnPostgresResourceNode(numberConfig)).toBe(false); + expect(isPostgresResourceNode(numberConfig)).toBe(false); }); }); describe('the config path rides through provisioning (brand intact)', () => { - test('a provisioned pnPostgres resource Loads as a resource and keeps config', () => { - const widget = pnContract(widgetContractJson); - const node = pnPostgres({ + test('a provisioned postgres resource Loads as a resource and keeps config', () => { + const widget = dataContract(widgetContractJson); + const node = postgres({ name: 'database', contract: widget, - config: './prisma-next.config.ts', + config: './prisma.config.ts', }); const graph = Load( @@ -226,7 +223,7 @@ describe('the config path rides through provisioning (brand intact)', () => { // The exact augmented node is in the graph, config and all — so the // predicate holds for the very value the graph carries. expect(db?.node).toBe(node); - expect(isPnPostgresResourceNode(node)).toBe(true); + expect(isPostgresResourceNode(node)).toBe(true); }); }); @@ -234,16 +231,16 @@ describe('hydrate — the { url, client } binding (ADR-0040), no live database r const url = 'postgres://user:pass@localhost:5432/does-not-exist'; test('the binding carries the wire url and is frozen', async () => { - const widget = pnContract(widgetContractJson); - const binding = await pnPostgres(widget).connection.hydrate({ url }); + const widget = dataContract(widgetContractJson); + const binding = await postgres(widget).connection.hydrate({ url }); expect(binding.url).toBe(url); expect(Object.isFrozen(binding)).toBe(true); }); - test('first client access constructs the Prisma Next client without connecting', async () => { - const widget = pnContract(widgetContractJson); - const binding = await pnPostgres(widget).connection.hydrate({ url }); + test('first client access constructs the Prisma ORM client without connecting', async () => { + const widget = dataContract(widgetContractJson); + const binding = await postgres(widget).connection.hydrate({ url }); const client = binding.client; // The PostgresClient surface — constructed synchronously; nothing here @@ -257,8 +254,8 @@ describe('hydrate — the { url, client } binding (ADR-0040), no live database r }); test('the client is memoized — repeated accesses return the same reference', async () => { - const widget = pnContract(widgetContractJson); - const binding = await pnPostgres(widget).connection.hydrate({ url }); + const widget = dataContract(widgetContractJson); + const binding = await postgres(widget).connection.hydrate({ url }); expect(binding.client).toBe(binding.client); }); @@ -268,8 +265,8 @@ describe('hydrate — the { url, client } binding (ADR-0040), no live database r // this malformed contract is the proof of laziness: hydrate and `url` // succeeding means neither constructed the client — only the `client` // access does, and the failure surfaces there (ADR-0040). - const malformed = pnContract({ not: 'a prisma-next contract' }); - const binding = await pnPostgres(malformed).connection.hydrate({ url }); + const malformed = dataContract({ not: 'an orm-postgres contract' }); + const binding = await postgres(malformed).connection.hydrate({ url }); expect(binding.url).toBe(url); expect(() => binding.client).toThrow(); @@ -278,8 +275,8 @@ describe('hydrate — the { url, client } binding (ADR-0040), no live database r test('hydrate does no schema verification — it just builds the binding', () => { // There is no runtime marker check (ADR-0022): schema correctness is a // deploy-time job. Hydrate sets no `verifyMarker` and reads no database. - const widget = pnContract(widgetContractJson); - const dep = pnPostgres(widget); + const widget = dataContract(widgetContractJson); + const dep = postgres(widget); expect(() => dep.connection.hydrate({ url: 'postgres://user:pass@localhost:5432/any-db' }), diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/pn-target-ref.test.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-target-ref.test.ts similarity index 93% rename from packages/1-prisma-cloud/1-extensions/target/src/__tests__/pn-target-ref.test.ts rename to packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-target-ref.test.ts index 6ddfa2ea5..0719c7589 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/pn-target-ref.test.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/orm-target-ref.test.ts @@ -21,11 +21,7 @@ import { spaceMigrationDirectory, spaceRefsDirectory, } from '@prisma/orm-toolchain/migration-tools/spaces'; -import { - decideMigrationAction, - PnMigrationError, - resolveTargetRef, -} from '../prisma-next-migrate.ts'; +import { decideMigrationAction, OrmMigrationError, resolveTargetRef } from '../orm-migrate.ts'; // Real-format hashes — PN's ref writer validates a bare 64-hex digest. const A = 'a'.repeat(64); @@ -125,9 +121,9 @@ describe('resolveTargetRef', () => { } catch (e) { thrown = e; } - expect(thrown).toBeInstanceOf(PnMigrationError); - expect((thrown as PnMigrationError).code).toBe('TARGET_REF_NOT_FOUND'); - expect((thrown as PnMigrationError).message).toContain('nope'); + expect(thrown).toBeInstanceOf(OrmMigrationError); + expect((thrown as OrmMigrationError).code).toBe('TARGET_REF_NOT_FOUND'); + expect((thrown as OrmMigrationError).message).toContain('nope'); }); }); }); diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/pack-preflight.test.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/pack-preflight.test.ts index 689188244..08ce9554b 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/pack-preflight.test.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/pack-preflight.test.ts @@ -1,7 +1,7 @@ /** * `runPackPreflight` — the deploy-time enforcement of `requiredPackHead`: * wireability says yes to every required pack head, so THIS - * is the check that the wired resource's `prisma-next.config.ts` actually + * is the check that the wired resource's `prisma.config.ts` actually * lists the pack at the required head. Driven against real `Load` graphs * (real wiring, real satisfies path) with the packed-contract fixture's * on-disk config; no database, no Prisma Cloud. @@ -18,8 +18,8 @@ import { string, } from '@internal/core'; import { compute } from '../compute.ts'; +import { dataContract, postgres, requiredPackHead } from '../orm-postgres.ts'; import { runPackPreflight } from '../preflight.ts'; -import { pnContract, pnPostgres, requiredPackHead } from '../prisma-next.ts'; import { GADGET_PACK_HEAD_HASH, GADGET_PACK_ID } from './fixtures/packed-contract/pack.ts'; import widgetContractJson from './fixtures/widget-contract/emitted/contract.json' with { type: 'json', @@ -30,13 +30,13 @@ const packedConfig = path.join( 'fixtures', 'packed-contract', 'source', - 'prisma-next.config.ts', + 'prisma.config.ts', ); /** A service dependency claiming its pn database carries the given pack head. */ const packDep = (packId: string, headHash: string) => dependency({ - type: 'prisma-next', + type: 'postgres', connection: { params: { url: string() }, hydrate: (v) => v }, required: requiredPackHead({ packId, headHash }), }); @@ -67,13 +67,13 @@ const spacelessConfig = path.join( 'fixtures', 'packed-contract', 'source', - 'prisma-next.spaceless.config.ts', + 'prisma.spaceless.config.ts', ); const pnDb = (config: string = packedConfig) => - pnPostgres({ + postgres({ name: 'db', - contract: pnContract(widgetContractJson), + contract: dataContract(widgetContractJson), config, }); @@ -90,7 +90,7 @@ describe('runPackPreflight', () => { provision( compute({ name: 'api', - deps: { db: pnPostgres(pnContract(widgetContractJson)) }, + deps: { db: postgres(dataContract(widgetContractJson)) }, build, }), { id: 'api', deps: { db } }, @@ -105,8 +105,8 @@ describe('runPackPreflight', () => { test('fails naming resource, pack, and consumer when the config does not list the pack', async () => { const graph = graphWith('auth', 'auth-head', pnDb()); await expect(runPackPreflight(graph)).rejects.toThrow( - 'prisma-next database "db" does not list extension pack "auth" in its ' + - 'prisma-next.config.ts extensions — service "api" requires it. ' + + 'postgres database "db" does not list extension pack "auth" in its ' + + 'prisma.config.ts extensions — service "api" requires it. ' + 'Add the pack and run migration plan.', ); }); @@ -116,7 +116,7 @@ describe('runPackPreflight', () => { // required one — the message says that rather than printing "undefined". const graph = graphWith(GADGET_PACK_ID, 'a-required-head', pnDb(spacelessConfig)); await expect(runPackPreflight(graph)).rejects.toThrow( - `prisma-next database "db" lists extension pack "${GADGET_PACK_ID}" at head ` + + `postgres database "db" lists extension pack "${GADGET_PACK_ID}" at head ` + '(no contract space), but service "api" requires a-required-head. ' + 'Upgrade the pack and run migration plan.', ); @@ -128,20 +128,20 @@ describe('runPackPreflight', () => { // it to a head the service is not typed against. const graph = graphWith(GADGET_PACK_ID, 'a-different-head', pnDb()); await expect(runPackPreflight(graph)).rejects.toThrow( - `prisma-next database "db" lists extension pack "${GADGET_PACK_ID}" at head ` + + `postgres database "db" lists extension pack "${GADGET_PACK_ID}" at head ` + `${GADGET_PACK_HEAD_HASH}, but service "api" requires a-different-head. ` + 'Upgrade the pack and run migration plan.', ); }); - test('fails when a pack-requirement edge is wired to a non-pnPostgres provider', async () => { - // A resource that PROVIDES a prisma-next contract (so the wiring - // satisfies) but is not a pnPostgres resource node — it has no config to + test('fails when a pack-requirement edge is wired to a non-postgres provider', async () => { + // A resource that PROVIDES an orm-postgres contract (so the wiring + // satisfies) but is not a postgres resource node — it has no config to // preflight against. const lookalike = resource({ name: 'imposter', extension: '@prisma/composer-prisma-cloud', - provides: pnContract(widgetContractJson), + provides: dataContract(widgetContractJson), }); const graph = Load( module('root', {}, ({ provision }) => { @@ -155,7 +155,7 @@ describe('runPackPreflight', () => { { id: 'root' }, ); await expect(runPackPreflight(graph)).rejects.toThrow( - 'service "api" requires extension pack "auth", which only a pnPostgres resource can carry.', + 'service "api" requires extension pack "auth", which only a postgres resource can carry.', ); }); }); diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/postgres-harness.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/postgres-harness.ts index 22bbe75e2..154b40f15 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/postgres-harness.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/postgres-harness.ts @@ -1,5 +1,5 @@ /** - * A throwaway local Postgres for the prisma-next integration test — mirrors + * A throwaway local Postgres for the Prisma ORM integration test — mirrors * `packages/alchemy/src/state/__tests__/harness.ts` (the state store's harness) * so both suites share one Postgres availability contract and one CI service * container. Kept as a local copy rather than a cross-package import: that @@ -126,7 +126,7 @@ export const startTestPostgres = (): TestPostgres | undefined => { if (initdb === undefined || pgCtl === undefined) { if (process.env['CI'] !== undefined) { throw new Error( - 'CI is set but no Postgres is available for the prisma-next integration test: neither ' + + 'CI is set but no Postgres is available for the Prisma ORM integration test: neither ' + 'STATE_TEST_DATABASE_URL nor initdb/pg_ctl (PATH, Homebrew, or Ubuntu ' + '/usr/lib/postgresql/*/bin) were found. The CI test job wires a `services: postgres:` ' + 'container and STATE_TEST_DATABASE_URL (see .github/workflows/ci.yml).', diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/postgres-shapes.test-d.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/postgres-shapes.test-d.ts deleted file mode 100644 index eb84715fd..000000000 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/postgres-shapes.test-d.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * postgres()'s two shapes and the binding each dependency yields. - * `{ name }` is the provisionable identity; `postgres()` is the dependency, - * whose binding is `PostgresConfig` (the app builds its own client — ADR-0015). - * No `client` argument exists anymore. - * - * Type-only (vitest `--typecheck`, never executed). Positive cases assert the - * returned role and the binding via `expectTypeOf`; a stray `client` argument - * keeps a `// @ts-expect-error`. - */ -import type { DependencyEnd, Hydrated, ResourceNode } from '@internal/core'; -import { expectTypeOf, test } from 'vitest'; -import { postgres, type postgresContract } from '../exports/index.ts'; -import type { PostgresConfig } from '../postgres.ts'; - -const identity = postgres({ name: 'db' }); -const dep = postgres(); - -test('{ name } yields the resource identity providing postgresContract', () => { - expectTypeOf(identity).toEqualTypeOf>(); -}); - -test('postgres() yields the dependency requiring postgresContract; its binding is PostgresConfig', () => { - expectTypeOf(dep).toEqualTypeOf>(); - // The binding load() hands the app is the typed config, not a client. - expectTypeOf>().toEqualTypeOf(); -}); - -test('a client argument no longer compiles', () => { - // @ts-expect-error the dependency takes no arguments — the app builds its own client from the binding - postgres({ client: ({ url }: { url: string }) => ({ url }) }); - // @ts-expect-error {} is not the identity shape (needs `name`) and postgres() takes no args - postgres({}); -}); diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/raw-postgres-shapes.test-d.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/raw-postgres-shapes.test-d.ts new file mode 100644 index 000000000..a25e4edcb --- /dev/null +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/raw-postgres-shapes.test-d.ts @@ -0,0 +1,34 @@ +/** + * rawPostgres()'s two shapes and the binding each dependency yields. + * `{ name }` is the provisionable identity; `rawPostgres()` is the dependency, + * whose binding is `RawPostgresConfig` (the app builds its own client — ADR-0015). + * No `client` argument exists anymore. + * + * Type-only (vitest `--typecheck`, never executed). Positive cases assert the + * returned role and the binding via `expectTypeOf`; a stray `client` argument + * keeps a `// @ts-expect-error`. + */ +import type { DependencyEnd, Hydrated, ResourceNode } from '@internal/core'; +import { expectTypeOf, test } from 'vitest'; +import { rawPostgres, type rawPostgresContract } from '../exports/index.ts'; +import type { RawPostgresConfig } from '../raw-postgres.ts'; + +const identity = rawPostgres({ name: 'db' }); +const dep = rawPostgres(); + +test('{ name } yields the resource identity providing rawPostgresContract', () => { + expectTypeOf(identity).toEqualTypeOf>(); +}); + +test('rawPostgres() yields the dependency requiring rawPostgresContract; its binding is RawPostgresConfig', () => { + expectTypeOf(dep).toEqualTypeOf>(); + // The binding load() hands the app is the typed config, not a client. + expectTypeOf>().toEqualTypeOf(); +}); + +test('a client argument no longer compiles', () => { + // @ts-expect-error the dependency takes no arguments — the app builds its own client from the binding + rawPostgres({ client: ({ url }: { url: string }) => ({ url }) }); + // @ts-expect-error {} is not the identity shape (needs `name`) and rawPostgres() takes no args + rawPostgres({}); +}); diff --git a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/widget-migrations-fixture.ts b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/widget-migrations-fixture.ts index da1cdc41f..1cf835755 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/__tests__/widget-migrations-fixture.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/__tests__/widget-migrations-fixture.ts @@ -19,7 +19,7 @@ import { spaceMigrationDirectory, spaceRefsDirectory, } from '@prisma/orm-toolchain/migration-tools/spaces'; -import { targetStorageHash } from '../prisma-next-migrate.ts'; +import { targetStorageHash } from '../orm-migrate.ts'; import widgetContractJson from './fixtures/widget-contract/emitted/contract.json' with { type: 'json', }; diff --git a/packages/1-prisma-cloud/1-extensions/target/src/bucket.ts b/packages/1-prisma-cloud/1-extensions/target/src/bucket.ts index 3383285e9..5ee07dd4a 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/bucket.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/bucket.ts @@ -11,7 +11,7 @@ export interface BucketConfig { /** * The contract a provisioned Bucket provides — deliberately kind-equal to the * storage module's `s3Contract` (`kind: 's3'`). `satisfies` compares KIND, - * not identity (mirrors `postgresContract`): a real bucket and the emulator + * not identity (mirrors `rawPostgresContract`): a real bucket and the emulator * are interchangeable at every `s3()` dependency slot. Cross-layer import of * the storage module's contract is not allowed (layering: 2-shared-modules * depends on 1-extensions, not the reverse), which is exactly the design diff --git a/packages/1-prisma-cloud/1-extensions/target/src/control/extension.ts b/packages/1-prisma-cloud/1-extensions/target/src/control/extension.ts index 9a5670cb5..d4b5cea67 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/control/extension.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/control/extension.ts @@ -21,8 +21,8 @@ import { } from '../container.ts'; import { bucketDescriptor } from '../descriptors/bucket.ts'; import { computeDescriptor } from '../descriptors/compute.ts'; -import { postgresDescriptor } from '../descriptors/postgres.ts'; -import { prismaNextDescriptor } from '../descriptors/prisma-next.ts'; +import { postgresDescriptor } from '../descriptors/orm-postgres.ts'; +import { rawPostgresDescriptor } from '../descriptors/raw-postgres.ts'; import { s3CredentialsDescriptor } from '../descriptors/s3-credentials.ts'; import { s3StoreDescriptor } from '../descriptors/s3-store.ts'; import type { @@ -33,8 +33,8 @@ import type { } from '../descriptors/shared.ts'; import { GeneratedParamProvider } from '../generated-param-resource.ts'; import { SELF_ORIGIN } from '../origin-key.ts'; +import { OrmMigrationProvider } from '../orm-migration-resource.ts'; import { PgWarmProvider } from '../pg-warm-resource.ts'; -import { PnMigrationProvider } from '../pn-migration-resource.ts'; import { type PrismaCloudPreflightInput, runPreflight } from '../preflight.ts'; import { RESERVED_PROVIDER_PARAMS } from '../provider-params.ts'; import { prismaCloudReporter } from '../reporting/reporter.ts'; @@ -347,7 +347,7 @@ export const prismaCloud = (opts: PrismaCloudOptions = {}): ExtensionDescriptor Layer.mergeAll( Prisma.providers(), PgWarmProvider(), - PnMigrationProvider(), + OrmMigrationProvider(), S3CredentialsProvider(), GeneratedParamProvider(), Prisma.ServiceKeyProvider(), @@ -398,8 +398,8 @@ export const prismaCloud = (opts: PrismaCloudOptions = {}): ExtensionDescriptor provisions: PROVISIONERS, nodes: { + 'raw-postgres': rawPostgresDescriptor(o), postgres: postgresDescriptor(o), - 'prisma-next': prismaNextDescriptor(o), compute: computeDescriptor(o), credentials: s3CredentialsDescriptor(o), 's3-store': s3StoreDescriptor(o), diff --git a/packages/1-prisma-cloud/1-extensions/target/src/descriptors/prisma-next.ts b/packages/1-prisma-cloud/1-extensions/target/src/descriptors/orm-postgres.ts similarity index 72% rename from packages/1-prisma-cloud/1-extensions/target/src/descriptors/prisma-next.ts rename to packages/1-prisma-cloud/1-extensions/target/src/descriptors/orm-postgres.ts index 7f9d15177..f3d969173 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/descriptors/prisma-next.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/descriptors/orm-postgres.ts @@ -1,35 +1,35 @@ -/** The `prisma-next` node kind's descriptor: a Postgres DB (like `postgres`) plus a migration step that brings it to the contract's storageHash (ADR-0022). */ +/** The `postgres` node kind's descriptor: a Postgres DB (like `postgres`) plus a migration step that brings it to the contract's storageHash (ADR-0022). */ import type { NodeDescriptor } from '@internal/core/config'; import type { Lowering } from '@internal/core/deploy'; import * as Effect from 'effect/Effect'; +import { packHeadRefHashes, resolveOrmConfig } from '../orm-config.ts'; +import { resolveTargetRef } from '../orm-migrate.ts'; +import { OrmMigration } from '../orm-migration-resource.ts'; +import { isPostgresResourceNode } from '../orm-postgres.ts'; import { PgWarm } from '../pg-warm-resource.ts'; -import { packHeadRefHashes, resolvePrismaNextConfig } from '../pn-config.ts'; -import { PnMigration } from '../pn-migration-resource.ts'; import { runPackPreflight } from '../preflight.ts'; -import { isPnPostgresResourceNode } from '../prisma-next.ts'; -import { resolveTargetRef } from '../prisma-next-migrate.ts'; import { type ResolvedCloudOptions, stageDatabase, validateName } from './shared.ts'; /** - * The migration is a tracked `PnMigration` Alchemy resource keyed on the + * The migration is a tracked `OrmMigration` Alchemy resource keyed on the * target REF identity (hash + sorted invariants): unchanged redeploy is a * no-op, a contract or ref-invariant change re-migrates. */ -export function prismaNextDescriptor(o: () => ResolvedCloudOptions): NodeDescriptor { +export function postgresDescriptor(o: () => ResolvedCloudOptions): NodeDescriptor { const lowering: Lowering = ({ id, node, application, graph }) => Effect.gen(function* () { validateName(id, 'resource name (from provision id)'); const { db, url } = yield* stageDatabase({ id, application, region: o().region }); - if (!isPnPostgresResourceNode(node)) { - // The registry routes 'prisma-next'-typed resource nodes here, so this + if (!isPostgresResourceNode(node)) { + // The registry routes 'postgres'-typed resource nodes here, so this // is unreachable — but narrow explicitly rather than cast to read config. - throw new Error(`prisma-next lowering received a non-prisma-next node (${id}).`); + throw new Error(`postgres lowering received a non-postgres node (${id}).`); } const contractJson = node.provides.__cmp.contractJson; const { migrationsDir, extensionPacks } = yield* Effect.promise(() => - resolvePrismaNextConfig(node.config), + resolveOrmConfig(node.config), ); // The target REF (node's named `targetRef`, or head by default) is // resolved once here so the same identity keys the resource's diff below. @@ -44,12 +44,12 @@ export function prismaNextDescriptor(o: () => ResolvedCloudOptions): NodeDescrip yield* Effect.promise(() => runPackPreflight(graph)); // Warm the DB first (FT-5226), then migrate against the now-warm url — - // `warm.url` threads the ordering (PgWarm → PnMigration). + // `warm.url` threads the ordering (PgWarm → OrmMigration). const warm = yield* PgWarm(`${id}-warm`, { url }); // Keyed on the ref identity so a data-only change (same hash, new // invariant) still triggers reconcile. - yield* PnMigration(`${id}-migrate`, { + yield* OrmMigration(`${id}-migrate`, { url: warm.url, contractJson, migrationsDir, diff --git a/packages/1-prisma-cloud/1-extensions/target/src/descriptors/postgres.ts b/packages/1-prisma-cloud/1-extensions/target/src/descriptors/raw-postgres.ts similarity index 86% rename from packages/1-prisma-cloud/1-extensions/target/src/descriptors/postgres.ts rename to packages/1-prisma-cloud/1-extensions/target/src/descriptors/raw-postgres.ts index d2df8626f..b71686efb 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/descriptors/postgres.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/descriptors/raw-postgres.ts @@ -1,4 +1,4 @@ -/** The `postgres` node kind's descriptor: one Prisma Postgres Database (plus its Connection), warmed before any consumer deploys. */ +/** The `raw-postgres` node kind's descriptor: one Prisma Postgres Database (plus its Connection), warmed before any consumer deploys. */ import type { NodeDescriptor } from '@internal/core/config'; import type { Lowering } from '@internal/core/deploy'; @@ -11,7 +11,7 @@ import { type ResolvedCloudOptions, stageDatabase, validateName } from './shared * module provision id, so a resource shared by several consumers is created * exactly once. */ -export function postgresDescriptor(o: () => ResolvedCloudOptions): NodeDescriptor { +export function rawPostgresDescriptor(o: () => ResolvedCloudOptions): NodeDescriptor { const lowering: Lowering = ({ id, application }) => Effect.gen(function* () { validateName(id, 'resource name (from provision id)'); diff --git a/packages/1-prisma-cloud/1-extensions/target/src/exports/index.ts b/packages/1-prisma-cloud/1-extensions/target/src/exports/index.ts index b6d21db4e..8056db74a 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/exports/index.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/exports/index.ts @@ -12,10 +12,10 @@ export type { HttpClient } from '../http.ts'; export { http } from '../http.ts'; export type { GeneratedParamOptions } from '../param.ts'; export { envParam, generatedParam, isGeneratedParamSource, paramName } from '../param.ts'; -export type { PostgresConfig } from '../postgres.ts'; -export { postgres, postgresContract } from '../postgres.ts'; export type { ProvisionedEdge } from '../provisioned-edges.ts'; export { provisionedEdges } from '../provisioned-edges.ts'; +export type { RawPostgresConfig } from '../raw-postgres.ts'; +export { rawPostgres, rawPostgresContract } from '../raw-postgres.ts'; export type { RequiredPackHead, RequiredPackHeadCmp } from '../required-pack-head.ts'; export { requiredPackHead, requiredPackHeadOf } from '../required-pack-head.ts'; export type { CredentialsConfig, CredentialsContract } from '../s3-credentials.ts'; diff --git a/packages/1-prisma-cloud/1-extensions/target/src/exports/orm.ts b/packages/1-prisma-cloud/1-extensions/target/src/exports/orm.ts new file mode 100644 index 000000000..b1462ec9b --- /dev/null +++ b/packages/1-prisma-cloud/1-extensions/target/src/exports/orm.ts @@ -0,0 +1,2 @@ +/** Public surface: the Prisma ORM data-dependency vocabulary (ADR-0022). Implementation lives in `../orm-postgres.ts`. */ +export * from '../orm-postgres.ts'; diff --git a/packages/1-prisma-cloud/1-extensions/target/src/exports/prisma-next.ts b/packages/1-prisma-cloud/1-extensions/target/src/exports/prisma-next.ts deleted file mode 100644 index 8b3c83bf5..000000000 --- a/packages/1-prisma-cloud/1-extensions/target/src/exports/prisma-next.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** Public surface: the Prisma Next data-dependency vocabulary (ADR-0022). Implementation lives in `../prisma-next.ts`. */ -export * from '../prisma-next.ts'; diff --git a/packages/1-prisma-cloud/1-extensions/target/src/local-target/descriptor.ts b/packages/1-prisma-cloud/1-extensions/target/src/local-target/descriptor.ts index 721000356..44b0dfcb1 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/local-target/descriptor.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/local-target/descriptor.ts @@ -16,8 +16,8 @@ import { localTargetProviders } from '@internal/local-target'; import * as Prisma from '@internal/lowering'; import * as Layer from 'effect/Layer'; import { GeneratedParamProvider } from '../generated-param-resource.ts'; +import { OrmMigrationProvider } from '../orm-migration-resource.ts'; import { PgWarmProvider } from '../pg-warm-resource.ts'; -import { PnMigrationProvider } from '../pn-migration-resource.ts'; import { S3CredentialsProvider } from '../s3-credentials-resource.ts'; import { devAttach } from './attach.ts'; import { devContainerDescriptor } from './container.ts'; @@ -46,7 +46,7 @@ export function localTargetDescriptor(): LocalTargetDescriptor { Layer.mergeAll( localTargetProviders(input), PgWarmProvider(), - PnMigrationProvider(), + OrmMigrationProvider(), S3CredentialsProvider(), GeneratedParamProvider(), Prisma.ServiceKeyProvider(), diff --git a/packages/1-prisma-cloud/1-extensions/target/src/local-target/emulators.ts b/packages/1-prisma-cloud/1-extensions/target/src/local-target/emulators.ts index 38757e7a3..7e58d129d 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/local-target/emulators.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/local-target/emulators.ts @@ -2,8 +2,8 @@ * Dev emulator bring-up (local-dev spec § 5, ADR-0041 D4): ensures the * machine-scoped daemons this topology's node kinds need. Compute is always * ensured — every app has services; buckets only when the graph uses the - * `s3` resource kind; postgres only when the graph uses the `postgres` or - * `prisma-next` resource kind (REVISED — Postgres is a first-class daemon + * `s3` resource kind; postgres only when the graph uses the `raw-postgres` or + * `postgres` resource kind (REVISED — Postgres is a first-class daemon * since the programmatic `@prisma/dev` adoption, operator review of #162). * * Idempotent: `ensureDaemon` itself adopts an already-healthy daemon, so @@ -30,7 +30,7 @@ function usesBuckets(input: LocalTargetEmulatorsInput): boolean { function usesPostgres(input: LocalTargetEmulatorsInput): boolean { return input.graph.nodes.some( (n) => - n.node.kind === 'resource' && (n.node.type === 'postgres' || n.node.type === 'prisma-next'), + n.node.kind === 'resource' && (n.node.type === 'raw-postgres' || n.node.type === 'postgres'), ); } diff --git a/packages/1-prisma-cloud/1-extensions/target/src/pn-config.ts b/packages/1-prisma-cloud/1-extensions/target/src/orm-config.ts similarity index 89% rename from packages/1-prisma-cloud/1-extensions/target/src/pn-config.ts rename to packages/1-prisma-cloud/1-extensions/target/src/orm-config.ts index c1269f487..b52b8d1c3 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/pn-config.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/orm-config.ts @@ -1,12 +1,12 @@ /** - * Resolves a `pnPostgres` resource's `prisma.config.ts` path to the + * Resolves a `postgres` resource's `prisma.config.ts` path to the * project facts the deploy needs (ADR-0022, slice 2): the on-disk migrations * directory the control client's `migrate` reads, and the declared * extension packs. Deploy-time only: loads PN's config (via c12) and applies * PN's own convention — `migrations.dir`, or the default `migrations/`, * relative to the config file's directory (mirrors the CLI's * `resolveMigrationPaths`). Imported by `control.ts` + tests, never by - * `index.ts` / the `./prisma-next` authoring entry. + * `index.ts` / the `./orm` authoring entry. * * `pathe` (not `node:path`) does the path work so the shipped source carries no * `node:` import — the same discipline `control.ts` already follows by @@ -28,13 +28,13 @@ export type PnExtensionPack = NonNullable< /** * What the deploy reads out of one `prisma.config.ts`. * - * The packs are `extensionPacks` here, not `extensions`: Prisma Next calls the + * The packs are `extensionPacks` here, not `extensions`: Prisma ORM calls the * key the user types `extensions`, but `extensions` is already Composer's word * for the things listed in `prisma-composer.config.ts` (`prismaCloud()`, * `nodeBuild()`), and `1-extensions/` is a layer name. This type is Composer's * side of the boundary, so it uses Composer's word. */ -export interface ResolvedPrismaNextConfig { +export interface ResolvedOrmConfig { /** The absolute migrations directory PN reads authored migration packages from. */ readonly migrationsDir: string; /** The config's declared extension packs (`[]` when it declares none). */ @@ -42,9 +42,7 @@ export interface ResolvedPrismaNextConfig { } /** Loads the config at `configPath` and resolves the facts the deploy consumes. */ -export async function resolvePrismaNextConfig( - configPath: string, -): Promise { +export async function resolveOrmConfig(configPath: string): Promise { // Since orm-toolchain 8.0.0-rc.7, loadConfig answers a Result carrying // the config plus per-section diagnostics instead of throwing. const loaded = await loadConfig(configPath); @@ -67,11 +65,11 @@ export async function resolvePrismaNextConfig( /** The absolute migrations directory PN reads authored migration packages from. */ export async function resolveMigrationsDir(configPath: string): Promise { - return (await resolvePrismaNextConfig(configPath)).migrationsDir; + return (await resolveOrmConfig(configPath)).migrationsDir; } /** - * The pack-head identity entries the `PnMigration` resource folds into its + * The pack-head identity entries the `OrmMigration` resource folds into its * diff key: `":"` — each pack's contract-space head ref, * identified by its storage hash — sorted by pack id, so a pack upgrade (or a * pack added/removed) produces a distinct deploy step. A pack without a diff --git a/packages/1-prisma-cloud/1-extensions/target/src/prisma-next-migrate.ts b/packages/1-prisma-cloud/1-extensions/target/src/orm-migrate.ts similarity index 89% rename from packages/1-prisma-cloud/1-extensions/target/src/prisma-next-migrate.ts rename to packages/1-prisma-cloud/1-extensions/target/src/orm-migrate.ts index 85649da94..32d999271 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/prisma-next-migrate.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/orm-migrate.ts @@ -1,12 +1,12 @@ /** - * The Prisma Next migration step of the deploy lowering (ADR-0022, slice 2) — + * The Prisma ORM migration step of the deploy lowering (ADR-0022, slice 2) — * the safety-critical decision that brings a live database to a target REF - * using ONLY Prisma Next's authored migrations. + * using ONLY Prisma ORM's authored migrations. * * Deploy-time only: this module imports `@prisma/orm-postgres/control` (which * transitively pulls PN's control/migration machinery + `pg`). It is imported * by the deploy descriptors and this package's tests, NEVER by `index.ts` / the - * `./prisma-next` authoring entry — so it never lands in an app runtime bundle + * `./orm` authoring entry — so it never lands in an app runtime bundle * (the index-isolation invariant holds). * * The target is a ref `{ hash, invariants }` — not a bare `storageHash`. A @@ -28,7 +28,7 @@ * structured refusal whose message names the two exits — `prisma db update` * for local iteration, `prisma contract emit && prisma migration plan` to * author the path for shipping. A runner failure fails the deploy as a typed - * `PnMigrationError` (not swallowed). PN applies each migration in its own + * `OrmMigrationError` (not swallowed). PN applies each migration in its own * transaction, so a failed apply is atomic and resume-safe — the marker and * schema are left as the last committed step. */ @@ -41,11 +41,11 @@ import { spaceMigrationDirectory, spaceRefsDirectory, } from '@prisma/orm-toolchain/migration-tools/spaces'; +import type { PnExtensionPack } from './orm-config.ts'; import { normalizeSslMode, withConnectionRetry } from './pg-connection.ts'; -import type { PnExtensionPack } from './pn-config.ts'; /** Which authored path the migration step took. */ -export type PnMigrationAction = 'noop' | 'migrate'; +export type OrmMigrationAction = 'noop' | 'migrate'; /** A resolved migration target: a contract hash plus its required invariants. */ export interface PnTargetRef { @@ -54,8 +54,8 @@ export interface PnTargetRef { } /** The migration step's decision + outcome — what the lowering records/logs. */ -export interface PnMigrationOutcome { - readonly action: PnMigrationAction; +export interface OrmMigrationOutcome { + readonly action: OrmMigrationAction; /** The ref hash the DB was brought to (or already at). */ readonly targetHash: string; /** The live marker's `storageHash` before this step, or `null` for a fresh DB. */ @@ -71,20 +71,20 @@ export interface PnMigrationOutcome { * — the resource named a `targetRef` with no readable * `migrations/app/refs/.json`. */ -export type PnMigrationFailureCode = +export type OrmMigrationFailureCode = | 'MIGRATION_PATH_NOT_FOUND' | 'RUNNER_FAILED' | 'CONTRACT_INVALID' | 'TARGET_REF_NOT_FOUND'; /** A deploy-failing migration error — surfaced, never swallowed. */ -export class PnMigrationError extends Error { - readonly code: PnMigrationFailureCode; +export class OrmMigrationError extends Error { + readonly code: OrmMigrationFailureCode; /** PN's structured explanation, when present. */ readonly why: string | undefined; - constructor(code: PnMigrationFailureCode, summary: string, why?: string) { - super(`prisma-next migrate (${code}): ${summary}`); - this.name = 'PnMigrationError'; + constructor(code: OrmMigrationFailureCode, summary: string, why?: string) { + super(`Prisma ORM migrate (${code}): ${summary}`); + this.name = 'OrmMigrationError'; this.code = code; this.why = why; } @@ -132,7 +132,7 @@ export function targetStorageHash(contractJson: unknown): string { if (typeof hash === 'string' && hash.length > 0) return hash; } } - throw new PnMigrationError( + throw new OrmMigrationError( 'CONTRACT_INVALID', 'the contract has no storage.storageHash — cannot determine the target schema version', ); @@ -161,7 +161,7 @@ export async function resolveTargetRef( const ref = await readRef(refsDir, targetRef); return { hash: ref.hash, invariants: ref.invariants }; } catch (error) { - throw new PnMigrationError( + throw new OrmMigrationError( 'TARGET_REF_NOT_FOUND', `targetRef "${targetRef}" could not be read from ${refsDir}`, error instanceof Error ? error.message : String(error), @@ -184,7 +184,7 @@ export async function resolveTargetRef( export function decideMigrationAction( marker: { readonly storageHash: string; readonly invariants: readonly string[] } | null, ref: PnTargetRef, -): PnMigrationAction { +): OrmMigrationAction { const markerInvariants = new Set(marker?.invariants ?? []); const missing = ref.invariants.filter((id) => !markerInvariants.has(id)); if (marker !== null && marker.storageHash === ref.hash && missing.length === 0) return 'noop'; @@ -195,14 +195,14 @@ export function decideMigrationAction( * Bring the database at `url` to the target ref via PN's authored migrations. * Reads the live marker, decides no-op / migrate * ({@link decideMigrationAction}), applies, and throws a typed - * {@link PnMigrationError} on a no-path or runner failure. `migrationsDir` is + * {@link OrmMigrationError} on a no-path or runner failure. `migrationsDir` is * the on-disk migrations root and `ref` the resolved target * ({@link resolveTargetRef} — both resolved by the lowering, which also keys - * the PnMigration resource on them). `refName` (the resource's `targetRef`, + * the OrmMigration resource on them). `refName` (the resource's `targetRef`, * when set) is threaded into `migrate` so PN targets the named ref's hash and * plans an invariant-bearing path. */ -export async function applyPnMigration(opts: { +export async function applyOrmMigration(opts: { readonly url: string; readonly contractJson: unknown; readonly migrationsDir: string; @@ -210,10 +210,10 @@ export async function applyPnMigration(opts: { readonly refName?: string; /** The project's declared extension packs — threaded into PN's aggregate (multi-space) pipeline. */ readonly extensionPacks?: readonly PnExtensionPack[]; -}): Promise { +}): Promise { const connection = normalizeSslMode(opts.url); // Retry the connect+operation past PPG's cold-start (see withConnectionRetry). - // A real migration failure (no-path / runner) is a PnMigrationError — never a + // A real migration failure (no-path / runner) is a OrmMigrationError — never a // connection transient — so it surfaces immediately, never retried. return withConnectionRetry( () => @@ -225,7 +225,7 @@ export async function applyPnMigration(opts: { opts.refName, opts.extensionPacks ?? [], ), - { shouldRetry: (error) => !(error instanceof PnMigrationError) }, + { shouldRetry: (error) => !(error instanceof OrmMigrationError) }, ); } @@ -236,7 +236,7 @@ async function runMigration( ref: PnTargetRef, refName: string | undefined, extensionPacks: readonly PnExtensionPack[], -): Promise { +): Promise { const client = createPostgresControlClient({ connection, extensions: extensionPacks }); await client.connect(); try { @@ -270,13 +270,13 @@ async function runMigration( }); if (!result.ok) { if (result.failure.code === 'MIGRATION_PATH_NOT_FOUND') { - throw new PnMigrationError( + throw new OrmMigrationError( 'MIGRATION_PATH_NOT_FOUND', noPathRefusal(result.failure.summary, markerHashBefore, extensionPacks.length > 0), result.failure.why, ); } - throw new PnMigrationError('RUNNER_FAILED', result.failure.summary, result.failure.why); + throw new OrmMigrationError('RUNNER_FAILED', result.failure.summary, result.failure.why); } return { action, targetHash: ref.hash, markerHashBefore }; } finally { diff --git a/packages/1-prisma-cloud/1-extensions/target/src/pn-migration-resource.ts b/packages/1-prisma-cloud/1-extensions/target/src/orm-migration-resource.ts similarity index 70% rename from packages/1-prisma-cloud/1-extensions/target/src/pn-migration-resource.ts rename to packages/1-prisma-cloud/1-extensions/target/src/orm-migration-resource.ts index edbd90f59..806a9f0a9 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/pn-migration-resource.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/orm-migration-resource.ts @@ -1,5 +1,5 @@ /** - * The `PnMigration` Alchemy resource (ADR-0022) — the migration + * The `OrmMigration` Alchemy resource (ADR-0022) — the migration * step modeled as a tracked resource so it participates in deploy state: keyed * on the target REF identity (`targetHash` + sorted `invariants`), an * unchanged redeploy is an Alchemy-level no-op (on top of the marker read), @@ -8,28 +8,28 @@ * * Its provider's `reconcile` receives the RESOLVED props at apply-time — in * particular the concrete DB `url` (a lazy `Output` until the Connection - * provisions) — and delegates to the proven `applyPnMigration` decision. The - * provider is a standalone `Provider` layer; the extension + * provisions) — and delegates to the proven `applyOrmMigration` decision. The + * provider is a standalone `Provider` layer; the extension * descriptor merges it into its `providers()` (`Layer.merge(Prisma.providers(), - * PnMigrationProvider())`), and Alchemy resolves it at apply via a direct + * OrmMigrationProvider())`), and Alchemy resolves it at apply via a direct * provider-tag lookup (`tryFindProviderByType`) — no change to `@internal/lowering`. * * Deploy-time only: imports `@prisma/orm-postgres/control` (via the helper) + * `alchemy`. Imported by `control.ts` and tests, never by `index.ts` / the - * `./prisma-next` authoring entry — index isolation holds. + * `./orm` authoring entry — index isolation holds. */ import { Resource } from 'alchemy'; import * as Provider from 'alchemy/Provider'; import * as Effect from 'effect/Effect'; -import { resolvePrismaNextConfig } from './pn-config.ts'; -import { applyPnMigration } from './prisma-next-migrate.ts'; +import { resolveOrmConfig } from './orm-config.ts'; +import { applyOrmMigration } from './orm-migrate.ts'; -export interface PnMigrationProps { +export interface OrmMigrationProps { /** The live DB connection string (an Alchemy Output at wiring time, resolved at apply). */ readonly url: string; /** The deserialized contract (`node.provides.__cmp.contractJson`) — what migrate applies. */ readonly contractJson: unknown; - /** On-disk migrations root, resolved from the resource's `prisma-next.config.ts`. */ + /** On-disk migrations root, resolved from the resource's `prisma.config.ts`. */ readonly migrationsDir: string; /** The target ref's hash — half the diff/identity key. */ readonly targetHash: string; @@ -44,7 +44,7 @@ export interface PnMigrationProps { readonly refName?: string; /** * `":"` per declared extension pack, SORTED by pack id - * (`packHeadRefHashes`, pn-config.ts) — folded into the diff key so a pack + * (`packHeadRefHashes`, orm-config.ts) — folded into the diff key so a pack * upgrade at an unchanged app contract still produces a distinct deploy * step. Only the identity rides in props: pack DESCRIPTORS carry functions, * which cannot live in persisted Alchemy state — reconcile reloads them @@ -52,35 +52,39 @@ export interface PnMigrationProps { */ readonly packHeadRefHashes: readonly string[]; /** - * The resource's `prisma-next.config.ts` path — where reconcile reloads the + * The resource's `prisma.config.ts` path — where reconcile reloads the * declared extension-pack descriptors from when `packHeadRefHashes` is * non-empty. */ readonly configPath: string; } -export interface PnMigrationAttributes { +export interface OrmMigrationAttributes { /** The ref hash the database was brought to. */ readonly storageHash: string; /** The ref invariants the target required (sorted, from props). */ readonly invariants: readonly string[]; } -export type PnMigration = Resource<'PrismaNext.Migration', PnMigrationProps, PnMigrationAttributes>; +export type OrmMigration = Resource< + 'PrismaOrm.Migration', + OrmMigrationProps, + OrmMigrationAttributes +>; -/** The `PnMigration` resource constructor — `yield* PnMigration(id, props)` in the lowering. */ -export const PnMigration = Resource('PrismaNext.Migration'); +/** The `OrmMigration` resource constructor — `yield* OrmMigration(id, props)` in the lowering. */ +export const OrmMigration = Resource('PrismaOrm.Migration'); /** - * The `PnMigration` provider service. `reconcile` runs for both create and - * update (Alchemy's unified lifecycle); `applyPnMigration` is idempotent via + * The `OrmMigration` provider service. `reconcile` runs for both create and + * update (Alchemy's unified lifecycle); `applyOrmMigration` is idempotent via * the live marker read, so it is safe to run for either — the marker decides * no-op / migrate. A migration has nothing to enumerate (`list` → `[]`) * and nothing to tear down on its own (`delete` → no-op; the DB's own deletion * handles teardown). Exported so tests can drive `reconcile` directly, without * building an Effect layer. */ -export const pnMigrationProviderService: Provider.ProviderService = { +export const ormMigrationProviderService: Provider.ProviderService = { list: () => Effect.succeed([]), reconcile: ({ news }) => Effect.tryPromise({ @@ -91,9 +95,9 @@ export const pnMigrationProviderService: Provider.ProviderService = // (or depends on) the config load at apply time. const extensionPacks = news.packHeadRefHashes.length > 0 - ? (await resolvePrismaNextConfig(news.configPath)).extensionPacks + ? (await resolveOrmConfig(news.configPath)).extensionPacks : []; - return applyPnMigration({ + return applyOrmMigration({ url: news.url, contractJson: news.contractJson, migrationsDir: news.migrationsDir, @@ -102,7 +106,7 @@ export const pnMigrationProviderService: Provider.ProviderService = ...(news.refName !== undefined ? { refName: news.refName } : {}), }); }, - // Surface PnMigrationError (no-path / runner) as-is — it fails the + // Surface OrmMigrationError (no-path / runner) as-is — it fails the // deploy with its clear message; nothing is swallowed. catch: (error) => error, }).pipe( @@ -111,6 +115,6 @@ export const pnMigrationProviderService: Provider.ProviderService = delete: () => Effect.void, }; -/** The `PnMigration` provider layer — merged into the extension descriptor's `providers()`. */ -export const PnMigrationProvider = () => - Provider.effect(PnMigration, Effect.succeed(pnMigrationProviderService)); +/** The `OrmMigration` provider layer — merged into the extension descriptor's `providers()`. */ +export const OrmMigrationProvider = () => + Provider.effect(OrmMigration, Effect.succeed(ormMigrationProviderService)); diff --git a/packages/1-prisma-cloud/1-extensions/target/src/prisma-next.ts b/packages/1-prisma-cloud/1-extensions/target/src/orm-postgres.ts similarity index 66% rename from packages/1-prisma-cloud/1-extensions/target/src/prisma-next.ts rename to packages/1-prisma-cloud/1-extensions/target/src/orm-postgres.ts index 4608d9557..ad54f4bfc 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/prisma-next.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/orm-postgres.ts @@ -1,5 +1,5 @@ /** - * `pnPostgres()` is the `prisma-next` kind's single entry, overloaded: a + * `postgres()` is the `postgres` kind's single entry, overloaded: a * resource end takes `{ name, contract }`; a dependency end hydrates to a * `{ url, client }` binding whose typed client is built lazily (ADR-0022, * ADR-0040). No runtime schema check. @@ -9,7 +9,7 @@ import type { Contract, DependencyEnd, ResourceNode, ServiceNode } from '@intern import { dependency, freezeNode, ResourceNodeBase, string } from '@internal/core'; import { blindCast } from '@internal/foundation/casts'; import type { SqlStorage } from '@prisma/orm-postgres/family-contract/types'; -import pnPostgresRuntime, { type PostgresClient } from '@prisma/orm-postgres/runtime'; +import ormPostgresRuntime, { type PostgresClient } from '@prisma/orm-postgres/runtime'; import pg from 'pg'; import { normalizeSslMode, retryTransientConnect } from './pg-connection.ts'; import { type RequiredPackHead, requiredPackHeadOf } from './required-pack-head.ts'; @@ -18,56 +18,56 @@ export type { RequiredPackHead } from './required-pack-head.ts'; export { requiredPackHead, requiredPackHeadOf } from './required-pack-head.ts'; /** - * Any Prisma Next contract this primitive can carry — the bound both + * Any Prisma ORM contract this primitive can carry — the bound both * authoring modes (TS no-emit `defineContract()`, or PSL/emitted * `contract.d.ts`) satisfy. */ -export type AnyPnContract = import('@prisma/orm-postgres/contract/types').Contract; +export type AnyOrmContract = import('@prisma/orm-postgres/contract/types').Contract; /** - * The comparison payload behind a `prisma-next` Contract. `_contract` is a - * type-only anchor so plain assignability between two `PnCmp`s means the + * The comparison payload behind a `postgres` Contract. `_contract` is a + * type-only anchor so plain assignability between two `OrmCmp`s means the * branded `storageHash` literals match. `requiredPackHead` is the pack-head * claim a `requiredPackHead()` contract carries instead of a contract value. */ -export interface PnCmp { +export interface OrmCmp { readonly contractJson: unknown; readonly requiredPackHead?: RequiredPackHead; readonly _contract?: C; } -/** The `prisma-next` kind: a Contract whose `Cmp` is `PnCmp`. */ -export type PnPostgresContract = Contract< - 'prisma-next', - PnCmp +/** The `postgres` kind: a Contract whose `Cmp` is `OrmCmp`. */ +export type PostgresContract = Contract< + 'postgres', + OrmCmp >; -/** Recovers the emitted contract type `C` a `prisma-next` Contract carries. */ -export type PnContractOf = Ct extends PnPostgresContract ? C : never; +/** Recovers the emitted contract type `C` a `postgres` Contract carries. */ +export type OrmContractOf = Ct extends PostgresContract ? C : never; -/** The typed Prisma Next client, reachable as `binding.client` on a `pnPostgres(contract)` dependency. */ -export type Client = PostgresClient>; +/** The typed Prisma ORM client, reachable as `binding.client` on a `postgres(contract)` dependency. */ +export type Client = PostgresClient>; /** - * The binding a consumer's `pnPostgres(contract)` dependency hydrates to + * The binding a consumer's `postgres(contract)` dependency hydrates to * (ADR-0040): the raw connection URL, plus the typed client — constructed on * first access, memoized thereafter. */ -export interface PnPostgresBinding { +export interface PostgresBinding { readonly url: string; readonly client: Client; } /** - * The `prisma-next` resource node: a core Resource node plus `config`, the + * The `postgres` resource node: a core Resource node plus `config`, the * `prisma.config.ts` path the deploy-only migration lowering loads to * find the migrations directory — the app build never imports it. */ -export class PnPostgresResourceNode< - C extends PnPostgresContract = PnPostgresContract, +export class PostgresResourceNode< + C extends PostgresContract = PostgresContract, > extends ResourceNodeBase { readonly config: string; - /** Optional target ref NAME (`migrations/app/refs/.json`) — see `pnPostgres`. */ + /** Optional target ref NAME (`migrations/app/refs/.json`) — see `postgres`. */ declare readonly targetRef?: string; constructor(def: { name: string; contract: C; config: string; targetRef?: string }) { @@ -78,28 +78,28 @@ export class PnPostgresResourceNode< } } -/** Narrows `ctx.node` to a `pnPostgres` resource node so the deploy lowering reads `config` without a bare cast. Structural, never `instanceof`. */ -export function isPnPostgresResourceNode( +/** Narrows `ctx.node` to a `postgres` resource node so the deploy lowering reads `config` without a bare cast. Structural, never `instanceof`. */ +export function isPostgresResourceNode( node: ServiceNode | ResourceNode, -): node is PnPostgresResourceNode { +): node is PostgresResourceNode { return ( node.kind === 'resource' && - node.type === 'prisma-next' && + node.type === 'postgres' && 'config' in node && typeof node.config === 'string' ); } /** - * Wraps a resolved Prisma Next contract value into the framework's - * `prisma-next` Contract kind. Two overloads: TS-authored (`C` inferred) vs. - * emitted JSON (`C` passed explicitly, e.g. `pnContract(contractJson)`). + * Wraps a resolved Prisma ORM contract value into the framework's + * `postgres` Contract kind. Two overloads: TS-authored (`C` inferred) vs. + * emitted JSON (`C` passed explicitly, e.g. `dataContract(contractJson)`). */ -export function pnContract(contract: C): PnPostgresContract; -export function pnContract(contractJson: unknown): PnPostgresContract; -export function pnContract(contract: unknown): unknown { - const value: PnPostgresContract = { - kind: 'prisma-next', +export function dataContract(contract: C): PostgresContract; +export function dataContract(contractJson: unknown): PostgresContract; +export function dataContract(contract: unknown): unknown { + const value: PostgresContract = { + kind: 'postgres', __cmp: { contractJson: contract }, satisfies: (required) => { // A required pack head is wireable to ANY pn database: whether the @@ -119,31 +119,31 @@ export function pnContract(contract: unknown): unknown { * provisions. `config` is the deploy-only `prisma.config.ts` path; * `targetRef` optionally names a ref as the migration target. */ -export function pnPostgres(opts: { +export function postgres(opts: { name: string; contract: C; config: string; targetRef?: string; -}): PnPostgresResourceNode; +}): PostgresResourceNode; /** - * `pnPostgres(contract)` — a service's dependency on a Prisma Next-typed + * `postgres(contract)` — a service's dependency on a Prisma-ORM-typed * Postgres. Its binding carries the raw connection URL and the typed Prisma * Next client, built lazily on first `client` access (ADR-0040). */ -export function pnPostgres( +export function postgres( contract: C, -): DependencyEnd, C>; -export function pnPostgres( +): DependencyEnd, C>; +export function postgres( arg: - | { name: string; contract: PnPostgresContract; config: string; targetRef?: string } - | PnPostgresContract, + | { name: string; contract: PostgresContract; config: string; targetRef?: string } + | PostgresContract, ): unknown { - if (!isPnPostgresContract(arg)) { - return new PnPostgresResourceNode(arg); + if (!isPostgresContract(arg)) { + return new PostgresResourceNode(arg); } const contract = arg; return dependency({ - type: 'prisma-next', + type: 'postgres', connection: { params: { url: string() }, hydrate: ({ url }) => bindLazyClient(contract, url), @@ -152,12 +152,12 @@ export function pnPostgres( }); } -function isPnPostgresContract(value: unknown): value is PnPostgresContract { +function isPostgresContract(value: unknown): value is PostgresContract { return ( typeof value === 'object' && value !== null && 'kind' in value && - value.kind === 'prisma-next' && + value.kind === 'postgres' && '__cmp' in value && 'satisfies' in value ); @@ -169,10 +169,7 @@ function isPnPostgresContract(value: unknown): value is PnPostgresContract { * validates `contractJson` eagerly — deferring it keeps a bad contract from * poisoning `load()` and spares URL-only consumers the cost (ADR-0040). */ -function bindLazyClient( - contract: C, - url: string, -): PnPostgresBinding { +function bindLazyClient(contract: C, url: string): PostgresBinding { let client: Client | undefined; return Object.freeze({ url, @@ -184,13 +181,13 @@ function bindLazyClient( } /** - * Builds the typed Prisma Next client over a connection pool that rides out + * Builds the typed Prisma ORM client over a connection pool that rides out * a transient cold-start (FT-5226). We pass our own `pg.Pool` rather than a * bare `url`: the runtime's bare-`url` connect is a one-shot that fails * permanently, but a pool connects lazily on first query, so a bounded retry there suffices. */ -function buildClient(contract: C, url: string): Client { - return pnPostgresRuntime>({ +function buildClient(contract: C, url: string): Client { + return ormPostgresRuntime>({ contractJson: contract.__cmp.contractJson, // Explicit binding, NOT `pg: pool`: the bare form sniffs the pool with // `instanceof`, which breaks whenever a bundle carries two copies of pg @@ -227,8 +224,8 @@ function resilientPool(url: string): pg.Pool { return pool; } -/** Reads `__cmp.contractJson.storage.storageHash` off a `prisma-next` Contract, defensively — `__cmp` is opaque to core, so nothing guarantees its shape without a runtime check. */ -function storageHashOf(contract: Contract<'prisma-next', unknown> | undefined): string | undefined { +/** Reads `__cmp.contractJson.storage.storageHash` off a `postgres` Contract, defensively — `__cmp` is opaque to core, so nothing guarantees its shape without a runtime check. */ +function storageHashOf(contract: Contract<'postgres', unknown> | undefined): string | undefined { if (contract === undefined) return undefined; const cmp = contract.__cmp; if (typeof cmp !== 'object' || cmp === null || !('contractJson' in cmp)) return undefined; diff --git a/packages/1-prisma-cloud/1-extensions/target/src/pg-connection.ts b/packages/1-prisma-cloud/1-extensions/target/src/pg-connection.ts index 949e4d68f..7193a03e9 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/pg-connection.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/pg-connection.ts @@ -1,4 +1,4 @@ -/** Connection resilience helpers for Prisma Postgres cold-starts (FT-5226); no heavy imports (no `effect`/`alchemy`/`pg`), so the deploy lowerings, the pnPostgres runtime client, and bun-runnable services (the storage store, via the pure `@internal/prisma-cloud/connection` subpath) all share one implementation. */ +/** Connection resilience helpers for Prisma Postgres cold-starts (FT-5226); no heavy imports (no `effect`/`alchemy`/`pg`), so the deploy lowerings, the postgres runtime client, and bun-runnable services (the storage store, via the pure `@internal/prisma-cloud/connection` subpath) all share one implementation. */ /** Network-level socket failures node-postgres surfaces as `err.code`. */ const TRANSIENT_CODES = new Set([ diff --git a/packages/1-prisma-cloud/1-extensions/target/src/pg-warm-resource.ts b/packages/1-prisma-cloud/1-extensions/target/src/pg-warm-resource.ts index e355fa07e..945250ab5 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/pg-warm-resource.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/pg-warm-resource.ts @@ -5,14 +5,14 @@ * eat the cold-start reject. * * The DB `url` is a lazy `Output` at lowering time, so warming must be an - * apply-time tracked resource (same pattern as `PnMigration`): its `reconcile` + * apply-time tracked resource (same pattern as `OrmMigration`): its `reconcile` * receives the RESOLVED url and connects with `withConnectionRetry` + `select 1`, * riding out the cold-start. Shared by BOTH the bare-`postgres` and the - * `prisma-next` lowerings; keyed on the connection `url`, so an unchanged + * `postgres` lowerings; keyed on the connection `url`, so an unchanged * redeploy is a no-op (warming is idempotent anyway). * * Deploy-time only: imports `pg` directly + `alchemy`. Imported by `control.ts` - * and tests, never by `index.ts` / the `./prisma-next` authoring entry — the + * and tests, never by `index.ts` / the `./orm` authoring entry — the * isolation invariants hold. */ import { Resource } from 'alchemy'; diff --git a/packages/1-prisma-cloud/1-extensions/target/src/preflight.ts b/packages/1-prisma-cloud/1-extensions/target/src/preflight.ts index a3d4442ff..e55622376 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/preflight.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/preflight.ts @@ -34,9 +34,9 @@ import { import * as Effect from 'effect/Effect'; import * as Layer from 'effect/Layer'; import { prismaCloudContainerOf } from './container.ts'; -import { resolvePrismaNextConfig } from './pn-config.ts'; +import { resolveOrmConfig } from './orm-config.ts'; +import { isPostgresResourceNode, requiredPackHeadOf } from './orm-postgres.ts'; import { collectPreflightNames } from './preflight-names.ts'; -import { isPnPostgresResourceNode, requiredPackHeadOf } from './prisma-next.ts'; type EnvClass = 'production' | 'preview'; @@ -287,13 +287,13 @@ export async function runPreflight( /** * The extension-pack half of the deploy preflight: every dependency edge * whose required contract carries a `requiredPackHead` must be wired to a - * `pnPostgres` resource whose `prisma-next.config.ts` lists that pack at the + * `postgres` resource whose `prisma.config.ts` lists that pack at the * required head hash. Enforced HERE — at deploy time, before the migration - * step constructs — because wireability (`pnContract().satisfies`) + * step constructs — because wireability (`dataContract().satisfies`) * deliberately says yes to every required pack head (the authoring-side * contract value cannot see the resource's config), and boot time would be * too late: the service would be down after a green deploy. Invoked from the - * `prisma-next` descriptor's lowering, beside the migration-step + * `postgres` descriptor's lowering, beside the migration-step * construction. */ export async function runPackPreflight(graph: Graph): Promise { @@ -310,22 +310,22 @@ export async function runPackPreflight(graph: Graph): Promise { const provider = node !== undefined && (node.kind === 'resource' || node.kind === 'service') && - isPnPostgresResourceNode(node) + isPostgresResourceNode(node) ? node : undefined; if (provider === undefined) { throw new Error( `service "${edge.to}" requires extension pack "${requirement.packId}", which only a ` + - 'pnPostgres resource can carry.', + 'postgres resource can carry.', ); } - const { extensionPacks } = await resolvePrismaNextConfig(provider.config); + const { extensionPacks } = await resolveOrmConfig(provider.config); const pack = extensionPacks.find((p) => p.id === requirement.packId); if (pack === undefined) { throw new Error( - `prisma-next database "${provider.name}" does not list extension pack ` + - `"${requirement.packId}" in its prisma-next.config.ts extensions — service ` + + `postgres database "${provider.name}" does not list extension pack ` + + `"${requirement.packId}" in its prisma.config.ts extensions — service ` + `"${edge.to}" requires it. Add the pack and run migration plan.`, ); } @@ -335,7 +335,7 @@ export async function runPackPreflight(graph: Graph): Promise { const head = pack.contractSpace?.headRef.hash; if (head !== requirement.headHash) { throw new Error( - `prisma-next database "${provider.name}" lists extension pack ` + + `postgres database "${provider.name}" lists extension pack ` + `"${requirement.packId}" at head ${head ?? '(no contract space)'}, but service ` + `"${edge.to}" requires ${requirement.headHash}. Upgrade the pack and run ` + 'migration plan.', diff --git a/packages/1-prisma-cloud/1-extensions/target/src/postgres.ts b/packages/1-prisma-cloud/1-extensions/target/src/raw-postgres.ts similarity index 55% rename from packages/1-prisma-cloud/1-extensions/target/src/postgres.ts rename to packages/1-prisma-cloud/1-extensions/target/src/raw-postgres.ts index 03ba8ed49..7bb83e805 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/postgres.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/raw-postgres.ts @@ -1,7 +1,7 @@ import type { Contract, DependencyEnd, ResourceNode } from '@internal/core'; import { dependency, resource, string } from '@internal/core'; -export interface PostgresConfig { +export interface RawPostgresConfig { readonly url: string; } @@ -12,47 +12,49 @@ export interface PostgresConfig { * duplicate's contract must still satisfy. `__cmp` is the connection config a * postgres offers; core never inspects it. */ -export const postgresContract: Contract<'postgres', PostgresConfig> = Object.freeze({ - kind: 'postgres', +export const rawPostgresContract: Contract<'raw-postgres', RawPostgresConfig> = Object.freeze({ + kind: 'raw-postgres', __cmp: { url: '' }, - satisfies: (required: Contract<'postgres', unknown>) => required.kind === 'postgres', + satisfies: (required: Contract<'raw-postgres', unknown>) => required.kind === 'raw-postgres', }); /** * The one Postgres factory; the argument shape picks the role. * * `{ name }` — the resource identity a module provisions: the ONE place the - * database exists, providing `postgresContract`. Return type declared + * database exists, providing `rawPostgresContract`. Return type declared * explicitly so nothing widens. */ -export function postgres(opts: { name: string }): ResourceNode; +export function rawPostgres(opts: { name: string }): ResourceNode; /** - * `postgres()` — a service's dependency on a Postgres. Its binding (what - * `load()` returns) is the typed connection config `PostgresConfig` itself — + * `rawPostgres()` — a service's dependency on a Postgres. Its binding (what + * `load()` returns) is the typed connection config `RawPostgresConfig` itself — * the most-derived thing the contract alone can construct. The app builds its * own client from `{ url }` with its own driver, in app code (ADR-0015): * `const sql = new SQL({ url: db.url })`. No driver choice lives in the * declaration. */ -export function postgres(): DependencyEnd; -export function postgres(opts?: { +export function rawPostgres(): DependencyEnd; +export function rawPostgres(opts?: { name: string; -}): ResourceNode | DependencyEnd { +}): + | ResourceNode + | DependencyEnd { if (opts?.name !== undefined) { return resource({ name: opts.name, extension: '@prisma/composer-prisma-cloud', - provides: postgresContract, + provides: rawPostgresContract, }); } return dependency({ - type: 'postgres', + type: 'raw-postgres', connection: { params: { url: string() }, // The binding IS the typed config: hydrate is the identity on its values - // ({ url: string } = PostgresConfig). The app constructs its own client. - hydrate: (v): PostgresConfig => v, + // ({ url: string } = RawPostgresConfig). The app constructs its own client. + hydrate: (v): RawPostgresConfig => v, }, - required: postgresContract, + required: rawPostgresContract, }); } diff --git a/packages/1-prisma-cloud/1-extensions/target/src/required-pack-head.ts b/packages/1-prisma-cloud/1-extensions/target/src/required-pack-head.ts index 88a29c3a9..9f42cbdb7 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/required-pack-head.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/required-pack-head.ts @@ -3,13 +3,13 @@ * database carries an extension pack at a given contract-space head. In its * own module so a module's authoring surface (e.g. `@internal/auth`'s * `authDb()`) can import it from the MAIN barrel without dragging - * `./prisma-next`'s runtime dependencies (`@prisma/orm-postgres/runtime`, + * `./orm`'s runtime dependencies (`@prisma/orm-postgres/runtime`, * `pg` — both carrying `node:` imports) into a runtime bundle. Deliberately - * self-contained — invariant 7 keeps every `prisma-next`/`@prisma/orm-` + * self-contained — invariant 7 keeps every `postgres`/`@prisma/orm-` * import specifier (type-only included) out of the main barrel's reachable * graph, so the cmp shape is declared here rather than imported; it is - * assignable to `PnPostgresContract`'s `PnCmp` by construction, and - * `prisma-next.ts` re-exports everything here so the `./prisma-next` surface + * assignable to `PostgresContract`'s `OrmCmp` by construction, and + * `orm-postgres.ts` re-exports everything here so the `./orm` surface * carries the full vocabulary in one place. */ import type { Contract } from '@internal/core'; @@ -22,10 +22,10 @@ export interface RequiredPackHead { /** * The `__cmp` shape a required-pack-head contract is typed with — - * structurally `PnCmp` (prisma-next.ts) minus its `_contract` anchor, and - * with the SAME optionality: typed wiring assigns a provider's `PnCmp` TO + * structurally `OrmCmp` (orm-postgres.ts) minus its `_contract` anchor, and + * with the SAME optionality: typed wiring assigns a provider's `OrmCmp` TO * this shape, so a required `requiredPackHead` here would reject every real - * `pnContract()` provider. The runtime value always carries the claim. + * `dataContract()` provider. The runtime value always carries the claim. */ export interface RequiredPackHeadCmp { readonly contractJson: unknown; @@ -33,16 +33,14 @@ export interface RequiredPackHeadCmp { } /** - * A `prisma-next`-kind required contract carrying a pack-head claim instead - * of a contract value. Wireable to any `pnContract()` provider (wireability + * A `postgres`-kind required contract carrying a pack-head claim instead + * of a contract value. Wireable to any `dataContract()` provider (wireability * only); the deploy preflight enforces that the wired resource's PN config * lists the pack at the required head. */ -export function requiredPackHead( - req: RequiredPackHead, -): Contract<'prisma-next', RequiredPackHeadCmp> { - const value: Contract<'prisma-next', RequiredPackHeadCmp> = { - kind: 'prisma-next', +export function requiredPackHead(req: RequiredPackHead): Contract<'postgres', RequiredPackHeadCmp> { + const value: Contract<'postgres', RequiredPackHeadCmp> = { + kind: 'postgres', __cmp: { contractJson: undefined, requiredPackHead: req }, // A requirement never provides; core only calls `satisfies` on the // provider side of a wiring. Answer honestly anyway: another requirement diff --git a/packages/1-prisma-cloud/1-extensions/target/src/s3-credentials.ts b/packages/1-prisma-cloud/1-extensions/target/src/s3-credentials.ts index 5e220d6c0..12d9b0916 100644 --- a/packages/1-prisma-cloud/1-extensions/target/src/s3-credentials.ts +++ b/packages/1-prisma-cloud/1-extensions/target/src/s3-credentials.ts @@ -8,7 +8,7 @@ export interface CredentialsConfig { /** * The contract the `s3-credentials` resource provides — a minted SigV4 key - * pair. `satisfies` compares KIND only (mirrors `postgresContract`); `__cmp` is + * pair. `satisfies` compares KIND only (mirrors `rawPostgresContract`); `__cmp` is * the config the resource offers, which core never inspects. */ export const credentialsContract: Contract<'credentials', CredentialsConfig> = Object.freeze({ diff --git a/packages/1-prisma-cloud/1-extensions/target/tsdown.config.ts b/packages/1-prisma-cloud/1-extensions/target/tsdown.config.ts index f7a0a6ff5..1d7b79dfe 100644 --- a/packages/1-prisma-cloud/1-extensions/target/tsdown.config.ts +++ b/packages/1-prisma-cloud/1-extensions/target/tsdown.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ index: 'src/exports/index.ts', control: 'src/exports/control.ts', 'local-target': 'src/exports/local-target.ts', - 'prisma-next': 'src/exports/prisma-next.ts', + orm: 'src/exports/orm.ts', testing: 'src/exports/testing.ts', connection: 'src/exports/pg-connection.ts', }, diff --git a/packages/1-prisma-cloud/2-shared-modules/auth/README.md b/packages/1-prisma-cloud/2-shared-modules/auth/README.md index fefdb0c7a..78126ebe5 100644 --- a/packages/1-prisma-cloud/2-shared-modules/auth/README.md +++ b/packages/1-prisma-cloud/2-shared-modules/auth/README.md @@ -3,7 +3,7 @@ Signup, login, sessions, and JWT verification as a composed module wrapping [Better Auth](https://better-auth.com) (in-process TypeScript library — not a remote IdP), published as `@prisma/composer-prisma-cloud/auth`. One dedicated -Compute service; the schema ships as a Prisma Next extension pack; the +Compute service; the schema ships as a Prisma ORM extension pack; the instance secret is platform-minted. ## Contract scope @@ -39,13 +39,13 @@ back office alone gets `admin`. import { module } from '@prisma/composer'; import { envParam } from '@prisma/composer-prisma-cloud'; import { auth } from '@prisma/composer-prisma-cloud/auth'; -import { pnPostgres } from '@prisma/composer-prisma-cloud/prisma-next'; +import { postgres } from '@prisma/composer-prisma-cloud/orm'; import { appContract } from './src/contract.ts'; import apiService from './src/api/service.ts'; export default module('app', ({ provision }) => { const db = provision( - pnPostgres({ name: 'database', contract: appContract, config: './prisma.config.ts' }), + postgres({ name: 'database', contract: appContract, config: './prisma.config.ts' }), { id: 'database' }, ); const identity = provision(auth(), { @@ -80,7 +80,7 @@ A complete, deployable copy of this wiring lives in `examples/auth`. ## The pack Better Auth's tables (`user`, `session`, `account`, `verification`, `jwks` — -Postgres schema `auth`) ship as a Prisma Next extension pack with authored +Postgres schema `auth`) ship as a Prisma ORM extension pack with authored migrations — Better Auth's own migrator never runs anywhere. Consumers: ```ts diff --git a/packages/1-prisma-cloud/2-shared-modules/auth/package.json b/packages/1-prisma-cloud/2-shared-modules/auth/package.json index c0aaef78d..314973de3 100644 --- a/packages/1-prisma-cloud/2-shared-modules/auth/package.json +++ b/packages/1-prisma-cloud/2-shared-modules/auth/package.json @@ -3,7 +3,7 @@ "version": "0.15.0", "private": true, "type": "module", - "description": "Signup, login, sessions, and JWT verification as a composed module wrapping Better Auth; ships the auth contract space as a Prisma Next extension pack.", + "description": "Signup, login, sessions, and JWT verification as a composed module wrapping Better Auth; ships the auth contract space as a Prisma ORM extension pack.", "exports": { ".": { "types": "./dist/index.d.mts", diff --git a/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/contract.test-d.ts b/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/contract.test-d.ts index a52a2f338..1478f7f29 100644 --- a/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/contract.test-d.ts +++ b/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/contract.test-d.ts @@ -9,9 +9,9 @@ import type { DependencyEnd, ModuleNode, ParamNeed, RefPort } from '@internal/co import { module, paramSource } from '@internal/core'; import { emailSendContract, type emailSender } from '@internal/email'; import node from '@internal/node'; -import { compute, type postgresContract } from '@internal/prisma-cloud'; -import type { PnPostgresContract } from '@internal/prisma-cloud/prisma-next'; -import { pnContract, pnPostgres } from '@internal/prisma-cloud/prisma-next'; +import { compute, type rawPostgresContract } from '@internal/prisma-cloud'; +import type { PostgresContract } from '@internal/prisma-cloud/orm'; +import { dataContract, postgres } from '@internal/prisma-cloud/orm'; import { rpc } from '@internal/service-rpc'; import { expectTypeOf, test } from 'vitest'; import { auth } from '../auth-module.ts'; @@ -55,7 +55,7 @@ test('auth() is a ModuleNode with the db + email boundary deps, three ports, and test('the dependency factories hydrate to their pinned binding types', () => { expectTypeOf(authApi()).toExtend>(); expectTypeOf(jwtVerifier()).toExtend>(); - expectTypeOf(authDb()).toExtend>(); + expectTypeOf(authDb()).toExtend>(); }); test('a verified session carries the pinned claim projections', () => { @@ -78,10 +78,10 @@ test('the wire records use ISO strings and the banExpiresAt field name', () => { test('the module ports wire into their consumer slots; a wrong-kind port is rejected', () => { module('root', {}, ({ provision }) => { const db = provision( - pnPostgres({ + postgres({ name: 'database', - contract: pnContract(packContractJson), - config: './prisma-next.config.ts', + contract: dataContract(packContractJson), + config: './prisma.config.ts', }), { id: 'database' }, ); @@ -106,5 +106,7 @@ test('the module ports wire into their consumer slots; a wrong-kind port is reje }); // A postgres port does not satisfy an auth-api slot. - expectTypeOf>().not.toExtend>(); + expectTypeOf>().not.toExtend< + RefPort + >(); }); diff --git a/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/contract.test.ts b/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/contract.test.ts index ceab5f66e..d3d576131 100644 --- a/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/contract.test.ts +++ b/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/contract.test.ts @@ -77,7 +77,7 @@ describe('authDb()', () => { test('claims the auth pack at the installed head, binding the bare url', async () => { const dep = authDb(); expect(dep.kind).toBe('dependency'); - expect(dep.type).toBe('prisma-next'); + expect(dep.type).toBe('postgres'); expect(requiredPackHeadOf(dep.required)).toEqual({ packId: AUTH_PACK_ID, headHash: AUTH_PACK_HEAD_HASH, diff --git a/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/module.test.ts b/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/module.test.ts index 3050c90c7..5d5c20da9 100644 --- a/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/module.test.ts +++ b/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/module.test.ts @@ -12,7 +12,7 @@ import { isParamSource, isSecretSource, Load, module, paramSource } from '@inter import { emailSendContract } from '@internal/email'; import node from '@internal/node'; import { compute, isGeneratedParamSource } from '@internal/prisma-cloud'; -import { pnContract, pnPostgres } from '@internal/prisma-cloud/prisma-next'; +import { dataContract, postgres } from '@internal/prisma-cloud/orm'; import { rpc } from '@internal/service-rpc'; import { auth } from '../auth-module.ts'; import { authAdminContract, authApi, authSessionContract, jwtVerifier } from '../contract.ts'; @@ -40,10 +40,10 @@ function serviceBindingOf( /** A pack-carrying database node — what a root wires into the module's db slot. */ const database = () => - pnPostgres({ + postgres({ name: 'database', - contract: pnContract(packContractJson), - config: './prisma-next.config.ts', + contract: dataContract(packContractJson), + config: './prisma.config.ts', }); /** A minimal provider of the `email` boundary dep — exposes only `send`, satisfying `emailSender(authTemplates)`'s required contract. */ @@ -85,7 +85,7 @@ describe('auth()', () => { expect(typeOf('auth.service')).toBe('compute'); // The database and the mail provider are the ROOT's nodes — the module // provisions no db or email service of its own. - expect(typeOf('database')).toBe('prisma-next'); + expect(typeOf('database')).toBe('postgres'); expect(typeOf('mail')).toBe('compute'); expect(graph.edges).toContainEqual({ from: 'database', diff --git a/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/postgres-harness.ts b/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/postgres-harness.ts index 22bbe75e2..154b40f15 100644 --- a/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/postgres-harness.ts +++ b/packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/postgres-harness.ts @@ -1,5 +1,5 @@ /** - * A throwaway local Postgres for the prisma-next integration test — mirrors + * A throwaway local Postgres for the Prisma ORM integration test — mirrors * `packages/alchemy/src/state/__tests__/harness.ts` (the state store's harness) * so both suites share one Postgres availability contract and one CI service * container. Kept as a local copy rather than a cross-package import: that @@ -126,7 +126,7 @@ export const startTestPostgres = (): TestPostgres | undefined => { if (initdb === undefined || pgCtl === undefined) { if (process.env['CI'] !== undefined) { throw new Error( - 'CI is set but no Postgres is available for the prisma-next integration test: neither ' + + 'CI is set but no Postgres is available for the Prisma ORM integration test: neither ' + 'STATE_TEST_DATABASE_URL nor initdb/pg_ctl (PATH, Homebrew, or Ubuntu ' + '/usr/lib/postgresql/*/bin) were found. The CI test job wires a `services: postgres:` ' + 'container and STATE_TEST_DATABASE_URL (see .github/workflows/ci.yml).', diff --git a/packages/1-prisma-cloud/2-shared-modules/auth/src/contract.ts b/packages/1-prisma-cloud/2-shared-modules/auth/src/contract.ts index cecda4a5a..38c90f501 100644 --- a/packages/1-prisma-cloud/2-shared-modules/auth/src/contract.ts +++ b/packages/1-prisma-cloud/2-shared-modules/auth/src/contract.ts @@ -8,9 +8,9 @@ import type { Contract, DependencyEnd } from '@internal/core'; import { dependency, string } from '@internal/core'; import { requiredPackHead } from '@internal/prisma-cloud'; -// Type-only, and type-only it must stay: the value surface of ./prisma-next +// Type-only, and type-only it must stay: the value surface of ./orm // carries pg (node: imports), which this authoring barrel must never bundle. -import type { PnPostgresContract } from '@internal/prisma-cloud/prisma-next'; +import type { PostgresContract } from '@internal/prisma-cloud/orm'; import { contract, rpc } from '@internal/service-rpc'; import { type } from 'arktype'; import { createRemoteJWKSet, errors, jwtVerify } from 'jose'; @@ -232,9 +232,9 @@ export const authAdminContract = contract({ * at the installed package's head. Hydrates to the bare `{ url }` — Better * Auth builds its own pool; no PN client. */ -export function authDb(): DependencyEnd<{ url: string }, PnPostgresContract> { +export function authDb(): DependencyEnd<{ url: string }, PostgresContract> { return dependency({ - type: 'prisma-next', + type: 'postgres', connection: { params: { url: string() }, hydrate: ({ url }) => ({ url }), diff --git a/packages/1-prisma-cloud/2-shared-modules/auth/src/execution/empty-app-contract.json b/packages/1-prisma-cloud/2-shared-modules/auth/src/execution/empty-app-contract.json index 9afc5b107..3ad04b274 100644 --- a/packages/1-prisma-cloud/2-shared-modules/auth/src/execution/empty-app-contract.json +++ b/packages/1-prisma-cloud/2-shared-modules/auth/src/execution/empty-app-contract.json @@ -44,7 +44,7 @@ "meta": {}, "_generated": { "warning": "⚠️ GENERATED FILE - DO NOT EDIT", - "message": "This file is automatically generated by \"prisma-next contract emit\".", - "regenerate": "To regenerate, run: prisma-next contract emit" + "message": "This file is automatically generated by \"prisma contract emit\".", + "regenerate": "To regenerate, run: prisma contract emit" } } diff --git a/packages/1-prisma-cloud/2-shared-modules/auth/src/execution/local-schema.ts b/packages/1-prisma-cloud/2-shared-modules/auth/src/execution/local-schema.ts index 9b6a20bc8..c6db845d8 100644 --- a/packages/1-prisma-cloud/2-shared-modules/auth/src/execution/local-schema.ts +++ b/packages/1-prisma-cloud/2-shared-modules/auth/src/execution/local-schema.ts @@ -1,6 +1,6 @@ /** * The local-dev schema bootstrap: bring a caller-supplied local database to - * the auth pack's head through the REAL Prisma Next `dbInit` path — the same + * the auth pack's head through the REAL Prisma ORM `dbInit` path — the same * loader → planner → runner pipeline (control client + extension packs) a * consumer deploy runs — never a rendered SQL file, so local dev and deploy * cannot drift. @@ -77,7 +77,7 @@ export async function ensureLocalAuthSchema(databaseUrl: string): Promise throw new Error( 'local auth bootstrap: the database already carries contract space(s) ' + `${rows.map((row) => `"${row.space}"`).join(', ')} but not "${AUTH_PACK_ID}" — list ` + - "authPack in that project's prisma-next.config.ts extensions and run its migration " + + "authPack in that project's prisma.config.ts extensions and run its migration " + 'plan; the local server only initialises databases it owns entirely.', ); } @@ -112,7 +112,7 @@ export async function ensureLocalAuthSchema(databaseUrl: string): Promise }); if (!result.ok) { throw new Error( - `local auth bootstrap: prisma-next dbInit failed: ${result.failure.summary}` + + `local auth bootstrap: Prisma ORM dbInit failed: ${result.failure.summary}` + (result.failure.why !== undefined ? ` — ${result.failure.why}` : ''), ); } diff --git a/packages/1-prisma-cloud/2-shared-modules/auth/src/pack/constants.ts b/packages/1-prisma-cloud/2-shared-modules/auth/src/pack/constants.ts index fbf495f8c..15d228c48 100644 --- a/packages/1-prisma-cloud/2-shared-modules/auth/src/pack/constants.ts +++ b/packages/1-prisma-cloud/2-shared-modules/auth/src/pack/constants.ts @@ -1,6 +1,6 @@ /** * The pack's identity constants — the ONE place each value lives, so - * adopting prisma-next's own Better Auth extension later is a contained + * adopting Prisma ORM's own Better Auth extension later is a contained * swap. Import-light on purpose: the runtime side (authDb's pack * requirement, the store's schema qualification) reads these without * pulling the descriptor's migration-tools dependency into an app bundle. diff --git a/packages/1-prisma-cloud/2-shared-modules/auth/src/pack/index.ts b/packages/1-prisma-cloud/2-shared-modules/auth/src/pack/index.ts index fa43f9a2e..611cd5030 100644 --- a/packages/1-prisma-cloud/2-shared-modules/auth/src/pack/index.ts +++ b/packages/1-prisma-cloud/2-shared-modules/auth/src/pack/index.ts @@ -1,5 +1,5 @@ /** - * The `auth` contract space as a Prisma Next extension pack (mirrors + * The `auth` contract space as a Prisma ORM extension pack (mirrors * `@prisma/orm-extension-supabase`'s `supabasePack`): the emitted Better * Auth contract plus the shipped migration packages, in-memory. A consumer * lists `authPack` in their `prisma.config.ts` and their normal diff --git a/packages/1-prisma-cloud/2-shared-modules/auth/tsdown.config.ts b/packages/1-prisma-cloud/2-shared-modules/auth/tsdown.config.ts index f53661141..c396f6d01 100644 --- a/packages/1-prisma-cloud/2-shared-modules/auth/tsdown.config.ts +++ b/packages/1-prisma-cloud/2-shared-modules/auth/tsdown.config.ts @@ -35,7 +35,7 @@ export default defineConfig([ external: [/^bun$/, /^bun:/], noExternal: [ /^@internal\//, - // `@prisma/orm-*` (the Prisma Next shells) are excluded: this entrypoint + // `@prisma/orm-*` (the Prisma ORM shells) are excluded: this entrypoint // must not carry the ORM, which the app resolves from its own facade. /^@prisma\/(?!orm-)/, /^arktype/, diff --git a/packages/1-prisma-cloud/2-shared-modules/email/src/__tests__/module.test-d.ts b/packages/1-prisma-cloud/2-shared-modules/email/src/__tests__/module.test-d.ts index 983811861..f93644d87 100644 --- a/packages/1-prisma-cloud/2-shared-modules/email/src/__tests__/module.test-d.ts +++ b/packages/1-prisma-cloud/2-shared-modules/email/src/__tests__/module.test-d.ts @@ -7,7 +7,7 @@ import type { ModuleNode, ParamNeed, RefPort, SecretNeed } from '@internal/core'; import { module, paramSource, secretSource } from '@internal/core'; import node from '@internal/node'; -import { compute, type postgresContract } from '@internal/prisma-cloud'; +import { compute, type rawPostgresContract } from '@internal/prisma-cloud'; import { rpc } from '@internal/service-rpc'; import { expectTypeOf, test } from 'vitest'; import { emailOutboxContract, type emailSendContract, emailSender } from '../contract.ts'; @@ -58,5 +58,5 @@ test("the module's outbox port wires into a consumer's rpc(emailOutboxContract) test('the outbox slot accepts an outbox port but rejects a wrong-kind (postgres) one', () => { expectTypeOf>().toExtend(); - expectTypeOf>().not.toExtend(); + expectTypeOf>().not.toExtend(); }); diff --git a/packages/1-prisma-cloud/2-shared-modules/email/src/__tests__/module.test.ts b/packages/1-prisma-cloud/2-shared-modules/email/src/__tests__/module.test.ts index 67fada694..e1fc8f868 100644 --- a/packages/1-prisma-cloud/2-shared-modules/email/src/__tests__/module.test.ts +++ b/packages/1-prisma-cloud/2-shared-modules/email/src/__tests__/module.test.ts @@ -65,7 +65,7 @@ describe('email()', () => { return n !== undefined && 'type' in n ? n.type : undefined; }; - expect(typeOf('email.db')).toBe('postgres'); + expect(typeOf('email.db')).toBe('raw-postgres'); expect(typeOf('email.service')).toBe('compute'); expect(graph.edges).toContainEqual({ from: 'email.db', diff --git a/packages/1-prisma-cloud/2-shared-modules/email/src/email-module.ts b/packages/1-prisma-cloud/2-shared-modules/email/src/email-module.ts index 1254f241a..66d4e3ad2 100644 --- a/packages/1-prisma-cloud/2-shared-modules/email/src/email-module.ts +++ b/packages/1-prisma-cloud/2-shared-modules/email/src/email-module.ts @@ -10,7 +10,7 @@ */ import type { ModuleNode, ParamNeed, SecretNeed } from '@internal/core'; import { module, paramNeed, secret } from '@internal/core'; -import { postgres } from '@internal/prisma-cloud'; +import { rawPostgres } from '@internal/prisma-cloud'; import { emailOutboxContract, emailSendContract } from './contract.ts'; import { emailService } from './email-service.ts'; @@ -31,7 +31,7 @@ export function email(opts?: { expose: { send: emailSendContract, outbox: emailOutboxContract }, }, ({ params, secrets, provision }) => { - const db = provision(postgres({ name: 'db' }), { id: 'db' }); + const db = provision(rawPostgres({ name: 'db' }), { id: 'db' }); const service = provision(emailService(), { id: 'service', deps: { db }, diff --git a/packages/1-prisma-cloud/2-shared-modules/email/src/email-service.ts b/packages/1-prisma-cloud/2-shared-modules/email/src/email-service.ts index 378fa9ee8..c1e39be83 100644 --- a/packages/1-prisma-cloud/2-shared-modules/email/src/email-service.ts +++ b/packages/1-prisma-cloud/2-shared-modules/email/src/email-service.ts @@ -12,7 +12,7 @@ */ import { secretString } from '@internal/foundation/arktype'; import node from '@internal/node'; -import { compute, postgres } from '@internal/prisma-cloud'; +import { compute, rawPostgres } from '@internal/prisma-cloud'; import { type } from 'arktype'; import { emailOutboxContract, emailSendContract } from './contract.ts'; @@ -26,7 +26,7 @@ const emailInputSchema = type({ export function emailService() { return compute({ name: 'email', - deps: { db: postgres() }, + deps: { db: rawPostgres() }, input: emailInputSchema, expose: { send: emailSendContract, outbox: emailOutboxContract }, build: node({ diff --git a/packages/1-prisma-cloud/2-shared-modules/storage/src/__tests__/module.test-d.ts b/packages/1-prisma-cloud/2-shared-modules/storage/src/__tests__/module.test-d.ts index 46087663b..507b61147 100644 --- a/packages/1-prisma-cloud/2-shared-modules/storage/src/__tests__/module.test-d.ts +++ b/packages/1-prisma-cloud/2-shared-modules/storage/src/__tests__/module.test-d.ts @@ -7,7 +7,7 @@ import type { ModuleNode, RefPort } from '@internal/core'; import { module } from '@internal/core'; import node from '@internal/node'; -import { compute, type postgresContract } from '@internal/prisma-cloud'; +import { compute, type rawPostgresContract } from '@internal/prisma-cloud'; import { expectTypeOf, test } from 'vitest'; import { s3, type s3Contract } from '../contract.ts'; import { storage } from '../storage-module.ts'; @@ -35,5 +35,5 @@ test('the s3 slot accepts an s3 port but rejects a wrong-kind (postgres) one', ( // The consumer's s3() slot requires `typeof s3Contract` (kind "s3"). The // module's store port is a RefPort of that contract; a postgres port is not. expectTypeOf>().toExtend(); - expectTypeOf>().not.toExtend(); + expectTypeOf>().not.toExtend(); }); diff --git a/packages/1-prisma-cloud/2-shared-modules/storage/src/__tests__/module.test.ts b/packages/1-prisma-cloud/2-shared-modules/storage/src/__tests__/module.test.ts index 626183c52..d5371ac93 100644 --- a/packages/1-prisma-cloud/2-shared-modules/storage/src/__tests__/module.test.ts +++ b/packages/1-prisma-cloud/2-shared-modules/storage/src/__tests__/module.test.ts @@ -28,7 +28,7 @@ describe('storage()', () => { return n !== undefined && 'type' in n ? n.type : undefined; }; - expect(typeOf('storage.db')).toBe('postgres'); + expect(typeOf('storage.db')).toBe('raw-postgres'); expect(typeOf('storage.credentials')).toBe('credentials'); expect(typeOf('storage.service')).toBe('s3-store'); diff --git a/packages/1-prisma-cloud/2-shared-modules/storage/src/__tests__/pg-harness.ts b/packages/1-prisma-cloud/2-shared-modules/storage/src/__tests__/pg-harness.ts index 52e3cbb21..a91a1f441 100644 --- a/packages/1-prisma-cloud/2-shared-modules/storage/src/__tests__/pg-harness.ts +++ b/packages/1-prisma-cloud/2-shared-modules/storage/src/__tests__/pg-harness.ts @@ -1,6 +1,6 @@ /** * A throwaway local Postgres for the storage integration test — the same - * availability contract as the state-store / prisma-next harnesses (honors + * availability contract as the state-store / Prisma ORM harnesses (honors * `STATE_TEST_DATABASE_URL`, else spins an ephemeral `initdb`/`pg_ctl` cluster, * else returns `undefined` locally so the caller skips loudly; on CI the * absence of both throws). Kept as a local copy per the workspace convention diff --git a/packages/1-prisma-cloud/2-shared-modules/storage/src/contract.ts b/packages/1-prisma-cloud/2-shared-modules/storage/src/contract.ts index 464e99879..e519ba61c 100644 --- a/packages/1-prisma-cloud/2-shared-modules/storage/src/contract.ts +++ b/packages/1-prisma-cloud/2-shared-modules/storage/src/contract.ts @@ -1,7 +1,7 @@ /** * The S3-compatible object-storage contract — the wire-protocol * binding a consumer's `s3()` dependency requires, and the storage service's - * `store` port will provide. Mirrors `postgresContract`/`postgres()` + * `store` port will provide. Mirrors `rawPostgresContract`/`rawPostgres()` * exactly: `satisfies` compares kind only, and the dependency's binding IS the * typed connection config (ADR-0015) — the app builds its own client. */ diff --git a/packages/1-prisma-cloud/2-shared-modules/storage/src/storage-module.ts b/packages/1-prisma-cloud/2-shared-modules/storage/src/storage-module.ts index 076a574d8..ecca70670 100644 --- a/packages/1-prisma-cloud/2-shared-modules/storage/src/storage-module.ts +++ b/packages/1-prisma-cloud/2-shared-modules/storage/src/storage-module.ts @@ -7,7 +7,7 @@ */ import type { ModuleNode } from '@internal/core'; import { module } from '@internal/core'; -import { postgres, s3Credentials } from '@internal/prisma-cloud'; +import { rawPostgres, s3Credentials } from '@internal/prisma-cloud'; import { s3Contract } from './contract.ts'; import { storageService } from './storage-service.ts'; @@ -16,7 +16,7 @@ export function storage(opts?: { bucket?: string; }): ModuleNode, { store: typeof s3Contract }, Record> { return module(opts?.name ?? 'storage', { expose: { store: s3Contract } }, ({ provision }) => { - const db = provision(postgres({ name: 'db' }), { id: 'db' }); + const db = provision(rawPostgres({ name: 'db' }), { id: 'db' }); const credentials = provision(s3Credentials({ name: 'credentials' }), { id: 'credentials' }); const service = provision(storageService(), { id: 'service', diff --git a/packages/1-prisma-cloud/2-shared-modules/storage/src/storage-service.ts b/packages/1-prisma-cloud/2-shared-modules/storage/src/storage-service.ts index a24c29cc2..cc4fb789f 100644 --- a/packages/1-prisma-cloud/2-shared-modules/storage/src/storage-service.ts +++ b/packages/1-prisma-cloud/2-shared-modules/storage/src/storage-service.ts @@ -9,7 +9,7 @@ * schedule-free `cronScheduler()`. */ import node from '@internal/node'; -import { postgres, s3Credentials, s3StoreService } from '@internal/prisma-cloud'; +import { rawPostgres, s3Credentials, s3StoreService } from '@internal/prisma-cloud'; import { type } from 'arktype'; import { s3Contract } from './contract.ts'; @@ -18,7 +18,7 @@ const storageInputSchema = type({ bucket: 'string' }); export function storageService() { return s3StoreService({ name: 'storage', - deps: { db: postgres(), credentials: s3Credentials() }, + deps: { db: rawPostgres(), credentials: s3Credentials() }, input: storageInputSchema, build: node({ module: new URL('./storage-service.mjs', import.meta.url).href, diff --git a/packages/1-prisma-cloud/2-shared-modules/streams/README.md b/packages/1-prisma-cloud/2-shared-modules/streams/README.md index a93ff2d40..540091d0a 100644 --- a/packages/1-prisma-cloud/2-shared-modules/streams/README.md +++ b/packages/1-prisma-cloud/2-shared-modules/streams/README.md @@ -42,7 +42,7 @@ for an append: a 404 is generated INSTEAD OF a write at every layer, so it proves nothing was applied. For dynamic stream names (e.g. per-tenant streams), call `durableStreams()` -with no contract — the `postgres()` parity, same lifecycle ownership, the +with no contract — the `rawPostgres()` parity, same lifecycle ownership, the name is data rather than a wiring-time declaration: ```ts diff --git a/packages/1-prisma-cloud/2-shared-modules/streams/src/__tests__/pg-harness.ts b/packages/1-prisma-cloud/2-shared-modules/streams/src/__tests__/pg-harness.ts index 1312ef3bb..1f7c7f431 100644 --- a/packages/1-prisma-cloud/2-shared-modules/streams/src/__tests__/pg-harness.ts +++ b/packages/1-prisma-cloud/2-shared-modules/streams/src/__tests__/pg-harness.ts @@ -1,6 +1,6 @@ /** * A throwaway local Postgres for the streams integration test — the same - * availability contract as the state-store / prisma-next harnesses (honors + * availability contract as the state-store / Prisma ORM harnesses (honors * `STATE_TEST_DATABASE_URL`, else spins an ephemeral `initdb`/`pg_ctl` cluster, * else returns `undefined` locally so the caller skips loudly; on CI the * absence of both throws). Kept as a local copy per the workspace convention diff --git a/packages/1-prisma-cloud/2-shared-modules/streams/src/contract.ts b/packages/1-prisma-cloud/2-shared-modules/streams/src/contract.ts index 417c9982b..2ca236236 100644 --- a/packages/1-prisma-cloud/2-shared-modules/streams/src/contract.ts +++ b/packages/1-prisma-cloud/2-shared-modules/streams/src/contract.ts @@ -73,7 +73,7 @@ export type StreamsContract = Contract<'strea /** * The `streams()` module's own exposed port: a general streams provider, - * satisfied by kind alone — the `postgresContract` pattern. The module + * satisfied by kind alone — the `rawPostgresContract` pattern. The module * cannot know its eventual consumers' stream names (different consumers of * one module each name their own), and the server genuinely serves any * stream, so what a consumer requires of its provider is only "is a streams @@ -102,7 +102,7 @@ const connectionParams = { * `streamsContract(defs)`, hydrates to one handle per declared stream name — * the handle owns the name, so no call site names it again. Called with no * argument, hydrates to a `StreamsClient` for dynamic stream names (e.g. - * per-tenant streams) — the `postgres()` parity: the same lifecycle + * per-tenant streams) — the `rawPostgres()` parity: the same lifecycle * ownership, the name is data rather than a wiring-time declaration. */ export function durableStreams( diff --git a/packages/9-public/composer-prisma-cloud/package.json b/packages/9-public/composer-prisma-cloud/package.json index 32ae86c4f..a7decc629 100644 --- a/packages/9-public/composer-prisma-cloud/package.json +++ b/packages/9-public/composer-prisma-cloud/package.json @@ -2,7 +2,7 @@ "name": "@prisma/composer-prisma-cloud", "version": "0.15.0", "type": "module", - "description": "The Prisma Cloud target for Prisma Composer: compute(), postgres(), the target extension, and first-party modules realized on Prisma Cloud (cron).", + "description": "The Prisma Cloud target for Prisma Composer: compute(), postgres() (ORM-managed, via /orm), rawPostgres(), the target extension, and first-party modules realized on Prisma Cloud (cron).", "exports": { ".": "./dist/index.mjs", "./control": "./dist/control.mjs", @@ -10,7 +10,7 @@ "./local-target/compute-main": "./dist/compute-main.mjs", "./local-target/buckets-main": "./dist/buckets-main.mjs", "./local-target/postgres-main": "./dist/postgres-main.mjs", - "./prisma-next": "./dist/prisma-next.mjs", + "./orm": "./dist/orm.mjs", "./testing": "./dist/testing.mjs", "./auth": "./dist/auth/index.mjs", "./auth/pack": "./dist/auth/pack.mjs", diff --git a/packages/9-public/composer-prisma-cloud/src/exports/orm.ts b/packages/9-public/composer-prisma-cloud/src/exports/orm.ts new file mode 100644 index 000000000..658e95e74 --- /dev/null +++ b/packages/9-public/composer-prisma-cloud/src/exports/orm.ts @@ -0,0 +1 @@ +export * from '@internal/prisma-cloud/orm'; diff --git a/packages/9-public/composer-prisma-cloud/src/exports/prisma-next.ts b/packages/9-public/composer-prisma-cloud/src/exports/prisma-next.ts deleted file mode 100644 index 74c3bd7f5..000000000 --- a/packages/9-public/composer-prisma-cloud/src/exports/prisma-next.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '@internal/prisma-cloud/prisma-next'; diff --git a/packages/9-public/composer-prisma-cloud/tsdown.config.ts b/packages/9-public/composer-prisma-cloud/tsdown.config.ts index 8ef835e03..d1da126a5 100644 --- a/packages/9-public/composer-prisma-cloud/tsdown.config.ts +++ b/packages/9-public/composer-prisma-cloud/tsdown.config.ts @@ -66,7 +66,7 @@ export default defineConfig([ index: 'src/exports/index.ts', control: 'src/exports/control.ts', 'local-target': 'src/exports/local-target.ts', - 'prisma-next': 'src/exports/prisma-next.ts', + orm: 'src/exports/orm.ts', testing: 'src/exports/testing.ts', }, exports: false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 32af5d544..6cf469fce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -191,7 +191,7 @@ importers: specifier: ^6.0.3 version: 6.0.3 - examples/pn-widgets: + examples/orm-demo: dependencies: '@prisma/composer': specifier: workspace:0.15.0 diff --git a/scripts/ci-cleanup-utils.test.ts b/scripts/ci-cleanup-utils.test.ts index 2ed2587dc..78e4ade7d 100644 --- a/scripts/ci-cleanup-utils.test.ts +++ b/scripts/ci-cleanup-utils.test.ts @@ -11,25 +11,25 @@ import { PROTECTED_PROJECT_NAMES, } from './ci-cleanup-utils.ts'; -const PREFIXES = ['storefront-auth', 'pn-widgets']; +const PREFIXES = ['storefront-auth', 'orm-demo']; describe('isEphemeralCiProjectName', () => { it('matches exactly -ci- for each given prefix', () => { assert.equal(isEphemeralCiProjectName('storefront-auth-ci-12345', PREFIXES), true); - assert.equal(isEphemeralCiProjectName('pn-widgets-ci-1234567890', PREFIXES), true); + assert.equal(isEphemeralCiProjectName('orm-demo-ci-1234567890', PREFIXES), true); }); it('rejects the standing (non-ci) app names', () => { assert.equal(isEphemeralCiProjectName('storefront-auth', PREFIXES), false); - assert.equal(isEphemeralCiProjectName('pn-widgets', PREFIXES), false); + assert.equal(isEphemeralCiProjectName('orm-demo', PREFIXES), false); }); it('rejects near-misses: wrong prefix, missing run id, non-digit id, extra suffix', () => { assert.equal(isEphemeralCiProjectName('datahub-ci-123', PREFIXES), false); - assert.equal(isEphemeralCiProjectName('pn-widgets-ci-', PREFIXES), false); - assert.equal(isEphemeralCiProjectName('pn-widgets-ci-abc', PREFIXES), false); - assert.equal(isEphemeralCiProjectName('pn-widgets-ci-123-extra', PREFIXES), false); - assert.equal(isEphemeralCiProjectName('a-pn-widgets-ci-123', PREFIXES), false); + assert.equal(isEphemeralCiProjectName('orm-demo-ci-', PREFIXES), false); + assert.equal(isEphemeralCiProjectName('orm-demo-ci-abc', PREFIXES), false); + assert.equal(isEphemeralCiProjectName('orm-demo-ci-123-extra', PREFIXES), false); + assert.equal(isEphemeralCiProjectName('a-orm-demo-ci-123', PREFIXES), false); }); it('never matches the hosted deploy-state project, even with a hostile prefix', () => { @@ -43,12 +43,12 @@ describe('isEphemeralCiProjectName', () => { }); it('treats prefixes literally — regex metacharacters cannot widen the match', () => { - assert.equal(isEphemeralCiProjectName('pn-widgetsX-ci-1', ['pn-widgets.']), false); + assert.equal(isEphemeralCiProjectName('orm-demoX-ci-1', ['orm-demo.']), false); assert.equal(isEphemeralCiProjectName('anything-ci-1', ['.*']), false); }); it('requires at least one prefix', () => { - assert.throws(() => isEphemeralCiProjectName('pn-widgets-ci-1', [])); + assert.throws(() => isEphemeralCiProjectName('orm-demo-ci-1', [])); }); }); @@ -72,7 +72,7 @@ describe('isLegacyStaleProjectName', () => { // --- deleteProjectDeep: the 409 → compute-teardown → retry sequencing, with a mocked fetch --- -const PROJECT = { id: 'proj_1', name: 'pn-widgets-ci-42' }; +const PROJECT = { id: 'proj_1', name: 'orm-demo-ci-42' }; const OK: HttpResponse = { status: 200, ok: true, body: '{}' }; const GONE: HttpResponse = { status: 404, ok: false, body: 'not found' }; const ACTIVE_DEPLOYMENT: HttpResponse = { diff --git a/scripts/lint-casts.mjs b/scripts/lint-casts.mjs index f82201cca..f24dcaff8 100644 --- a/scripts/lint-casts.mjs +++ b/scripts/lint-casts.mjs @@ -9,7 +9,7 @@ * The merge-base is checked out into a temporary git worktree. HEAD's biome * config + plugin are copied into the scanned tree so biome auto-discovers them * at the tree root — this anchors the config's relative `files.includes` - * excludes (e.g. `!docs`, `!prisma-next`) to the same root for both scans, so + * excludes (e.g. `!docs`, `!orm`) to the same root for both scans, so * the two counts are comparable. (Passing `--config-path` to a foreign cwd * instead anchors those excludes inconsistently.) The baseline is thus "the old * source measured by the current rule" — which lets the PR that first diff --git a/scripts/lint-framework-vocabulary.mjs b/scripts/lint-framework-vocabulary.mjs index 6377c3e82..8703af28c 100644 --- a/scripts/lint-framework-vocabulary.mjs +++ b/scripts/lint-framework-vocabulary.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node /** - * Domain-vocabulary guardrail (ported from prisma-next's + * Domain-vocabulary guardrail (ported from prisma/orm's * lint-framework-target-imports.mjs). Fails if a forbidden vocabulary word * appears anywhere in a domain's shipped source — including comments and * naming, not just import specifiers. diff --git a/scripts/lint-orm-pins.mjs b/scripts/lint-orm-pins.mjs index 992b32493..67b6b7a6a 100644 --- a/scripts/lint-orm-pins.mjs +++ b/scripts/lint-orm-pins.mjs @@ -2,7 +2,7 @@ // Every `@prisma/orm-*` dependency spec must be a single exact version, and // every spec across the workspace must name the same one. // -// The Prisma Next shells are one system split across several published +// The Prisma ORM shells are one system split across several published // packages, and their types are only compatible within a version. Resolve two // versions of one shell into a tree and the codec and operation registries // silently diverge, `instanceof` stops holding, and a value produced by one @@ -72,7 +72,7 @@ if (versions.size > 1) { } if (problems.length > 0) { - console.error('✖ orm-pins: the Prisma Next shells are not pinned consistently:\n'); + console.error('✖ orm-pins: the Prisma ORM shells are not pinned consistently:\n'); for (const problem of problems) console.error(` ${problem}`); console.error( '\nEvery @prisma/orm-* spec must be one exact version, and all of them the same version.', diff --git a/scripts/setup-worktree-refs.sh b/scripts/setup-worktree-refs.sh index f2564ab9a..1f76c22c9 100755 --- a/scripts/setup-worktree-refs.sh +++ b/scripts/setup-worktree-refs.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Populate a git worktree with the reference clones the framework work needs: -# prisma-next, ignite, datahub, open-chat (all gitignored, one clone per worktree). +# orm, ignite, datahub, open-chat (all gitignored, one clone per worktree). # # Idempotent: clones that already exist are left untouched, so it is safe to run # on every session start. Wired as a SessionStart hook in .claude/settings.local.json; @@ -42,7 +42,7 @@ while IFS='=' read -r name url; do echo "setup-worktree-refs: could not create $dest" >&2 fi done <<'EOF' -prisma-next=https://github.com/prisma/prisma-next +orm=https://github.com/prisma/orm ignite=https://github.com/prisma/ignite datahub=https://github.com/prisma/datahub open-chat=https://github.com/prisma/open-chat diff --git a/skills/prisma-composer/SKILL.md b/skills/prisma-composer/SKILL.md index 1d1d019d7..e7b1eff5f 100644 --- a/skills/prisma-composer/SKILL.md +++ b/skills/prisma-composer/SKILL.md @@ -27,7 +27,7 @@ description: >- # Writing apps with Prisma Composer A **Prisma App** is a tree of **Modules** composed in TypeScript. The leaves -are **services** (`compute()`) and **resources** (`postgres()`); the root +are **services** (`compute()`) and **resources** (`rawPostgres()`); the root module wires them together by their typed ports. Your code receives everything from exactly one place — the service node: @@ -56,7 +56,7 @@ Two packages, and only two, appear in your `package.json`: | Package | Provides | | --- | --- | | `@prisma/composer` | Core authoring: `module`, `secret`, `isSecretString`, `/arktype` (the `secretString()` schema leaf), `/rpc`, `/node`, `/nextjs`, `/config`, `/testing`, the `prisma-composer` CLI | -| `@prisma/composer-prisma-cloud` | The Prisma Cloud target: `compute`, `postgres`, `envSecret`, `envParam`, `/control`, `/testing`, and the shared `/cron`, `/storage`, `/streams`, `/prisma-next` modules | +| `@prisma/composer-prisma-cloud` | The Prisma Cloud target: `compute`, `postgres`, `envSecret`, `envParam`, `/control`, `/testing`, and the shared `/cron`, `/storage`, `/streams`, `/orm` modules | ## tsconfig and import specifiers @@ -112,7 +112,7 @@ import { authContract } from './contract.ts'; export default compute({ name: 'auth', - deps: { db: postgres() }, + deps: { db: rawPostgres() }, build: node({ module: import.meta.url, entry: '../dist/server.mjs' }), expose: { rpc: authContract }, }); @@ -308,10 +308,10 @@ when the app contains a Next.js service. Two kinds of Postgres dependency: -**`postgres()`** — the binding is `{ url }` and the app owns its client. +**`rawPostgres()`** — the binding is `{ url }` and the app owns its client. Construct it in your server entry, as in the auth example above. -**`pnPostgres(...)`** — a Prisma Next-typed database: `load()` +**`postgres(...)`** — a Prisma-ORM-typed database: `load()` returns the typed client the framework constructs from your data contract, so queries like `db.orm.public.Product.all()` are compile-time checked. The contract is emitted from `contract.prisma` by `prisma contract emit` and @@ -319,14 +319,14 @@ wrapped once, referenced by both ends: ```ts // src/data.ts — the ONE value both ends reference -import { pnContract } from '@prisma/composer-prisma-cloud/prisma-next'; +import { dataContract } from '@prisma/composer-prisma-cloud/orm'; import type { Contract } from '../contract.d.ts'; import contractJson from '../contract.json' with { type: 'json' }; -export const catalogData = pnContract(contractJson); +export const catalogData = dataContract(contractJson); ``` -The dependency end is `deps: { db: pnPostgres(catalogData) }`. The resource +The dependency end is `deps: { db: postgres(catalogData) }`. The resource end (inside the module that owns the database) also names the `prisma.config.ts` path, which the deploy's migration step loads to find `migrations/` — committed migrations are replayed at deploy, before the @@ -334,11 +334,11 @@ service starts: ```ts const db = provision( - pnPostgres({ name: 'database', contract: catalogData, config: './prisma.config.ts' }), + postgres({ name: 'database', contract: catalogData, config: './prisma.config.ts' }), ); ``` -(`pnPostgres` is both ends: the contract alone is the dependency end; the +(`postgres` is both ends: the contract alone is the dependency end; the options object is the resource end.) The deploy is replay-only: it applies the migrations committed under @@ -399,7 +399,7 @@ export default module( 'auth', { secrets: { signingKey: secret() }, expose: { rpc: authContract } }, ({ secrets, provision }) => { - const db = provision(postgres({ name: 'database' })); + const db = provision(rawPostgres({ name: 'database' })); const service = provision(authService, { id: 'service', deps: { db }, @@ -463,7 +463,7 @@ Choosing the channel is most of the decision: | The value is… | Declare | Provide | Read | | --- | --- | --- | --- | -| produced by another node | `deps: { db: postgres() }` | wire at `provision()` | `load()` | +| produced by another node | `deps: { db: rawPostgres() }` | wire at `provision()` | `load()` | | anything else — config or credential | one field of the `input` schema | bind at `provision()`: literal, `envParam()`, or `envSecret()` | `input()` | The service declares its whole incoming configuration — plain values and diff --git a/test/integration/test/fixtures/local-dev/built/web-server.mjs b/test/integration/test/fixtures/local-dev/built/web-server.mjs index 1ad491ec0..57bb5f155 100644 --- a/test/integration/test/fixtures/local-dev/built/web-server.mjs +++ b/test/integration/test/fixtures/local-dev/built/web-server.mjs @@ -10,11 +10,11 @@ import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; import node from '@prisma/composer/node'; -import { bucket, compute, postgres } from '@prisma/composer-prisma-cloud'; +import { bucket, compute, rawPostgres } from '@prisma/composer-prisma-cloud'; const service = compute({ name: 'web', - deps: { db: postgres(), store: bucket() }, + deps: { db: rawPostgres(), store: bucket() }, build: node({ module: import.meta.url, entry: 'web-server.mjs' }), }); diff --git a/test/integration/test/fixtures/local-dev/module.ts b/test/integration/test/fixtures/local-dev/module.ts index 621221dc6..b9e26326d 100644 --- a/test/integration/test/fixtures/local-dev/module.ts +++ b/test/integration/test/fixtures/local-dev/module.ts @@ -14,12 +14,12 @@ * var. */ import { module } from '@prisma/composer'; -import { bucket, envParam, envSecret, postgres } from '@prisma/composer-prisma-cloud'; +import { bucket, envParam, envSecret, rawPostgres } from '@prisma/composer-prisma-cloud'; import bgService from './bg-service.ts'; import webService from './web-service.ts'; export default module('localdevs4fixture', ({ provision }) => { - const db = provision(postgres({ name: 'appdb' })); + const db = provision(rawPostgres({ name: 'appdb' })); const store = provision(bucket({ name: 'files' })); provision(webService, { deps: { db, store } }); provision(bgService, { diff --git a/test/integration/test/fixtures/local-dev/web-service.ts b/test/integration/test/fixtures/local-dev/web-service.ts index e78f3e13b..1e0f98d49 100644 --- a/test/integration/test/fixtures/local-dev/web-service.ts +++ b/test/integration/test/fixtures/local-dev/web-service.ts @@ -10,10 +10,10 @@ * be committed.) */ import node from '@prisma/composer/node'; -import { bucket, compute, postgres } from '@prisma/composer-prisma-cloud'; +import { bucket, compute, rawPostgres } from '@prisma/composer-prisma-cloud'; export default compute({ name: 'web', - deps: { db: postgres(), store: bucket() }, + deps: { db: rawPostgres(), store: bucket() }, build: node({ module: import.meta.url, entry: 'built/web-server.mjs' }), }); diff --git a/test/integration/test/local-dev-criteria-4-5.integration.ts b/test/integration/test/local-dev-criteria-4-5.integration.ts index 3e6969089..fa258294c 100644 --- a/test/integration/test/local-dev-criteria-4-5.integration.ts +++ b/test/integration/test/local-dev-criteria-4-5.integration.ts @@ -354,7 +354,7 @@ async function main(): Promise { } // Full app-scoped teardown through the CLI's own --fresh, exactly as // local-dev-store.integration.ts does — removes the postgres-main-hosted - // server this fixture's `postgres({ name: 'appdb' })` created, the + // server this fixture's `rawPostgres({ name: 'appdb' })` created, the // emulators' app-scoped records, and the local state dir, never the // machine-global daemons. `--fresh` teardown runs BEFORE that session's // own (mandatory) converge, so a direct `dev.teardown` afterwards removes diff --git a/test/integration/test/local-dev-store.integration.ts b/test/integration/test/local-dev-store.integration.ts index eca7438cc..5380d05a3 100644 --- a/test/integration/test/local-dev-store.integration.ts +++ b/test/integration/test/local-dev-store.integration.ts @@ -603,7 +603,7 @@ async function main(): Promise { // ——— Criterion 3 (write half): a row through the real local Postgres URL ——— const dbUrl = await readCatalogDbUrl(); await withSql(dbUrl, async (sql) => { - // prisma-next's migration DDL folds the table name and single-word + // Prisma ORM's migration DDL folds the table name and single-word // columns to lowercase (unquoted identifiers); "priceCents" alone // stays quoted, mixed-case. await sql`insert into product (id, name, description, "priceCents") @@ -671,7 +671,7 @@ async function main(): Promise { // --fresh removes the app's postgres-main-hosted servers and their // persisted data (DELETE /apps/ on the daemon, runDevTeardown) and // recreates them on next provision — migrations reapply as part of THIS - // SAME converge (PnMigration runs on every converge), so the "product" + // SAME converge (OrmMigration runs on every converge), so the "product" // table exists again by the time the front door prints; the proof is // that it's freshly migrated and EMPTY — the proving row from before // --fresh is gone. diff --git a/tsconfig.depcruise.json b/tsconfig.depcruise.json index 2a22f0eb7..1ad7cd6a1 100644 --- a/tsconfig.depcruise.json +++ b/tsconfig.depcruise.json @@ -90,8 +90,8 @@ "@internal/prisma-cloud/local-target": [ "./packages/1-prisma-cloud/1-extensions/target/src/exports/local-target.ts" ], - "@internal/prisma-cloud/prisma-next": [ - "./packages/1-prisma-cloud/1-extensions/target/src/exports/prisma-next.ts" + "@internal/prisma-cloud/orm": [ + "./packages/1-prisma-cloud/1-extensions/target/src/exports/orm.ts" ], "@internal/prisma-cloud/testing": [ "./packages/1-prisma-cloud/1-extensions/target/src/exports/testing.ts" @@ -154,8 +154,8 @@ "@prisma/composer-prisma-cloud/local-target": [ "./packages/9-public/composer-prisma-cloud/src/exports/local-target.ts" ], - "@prisma/composer-prisma-cloud/prisma-next": [ - "./packages/9-public/composer-prisma-cloud/src/exports/prisma-next.ts" + "@prisma/composer-prisma-cloud/orm": [ + "./packages/9-public/composer-prisma-cloud/src/exports/orm.ts" ], "@prisma/composer-prisma-cloud/testing": [ "./packages/9-public/composer-prisma-cloud/src/exports/testing.ts" From 76a7be9e3348132868765facdce3c1ff5c940578 Mon Sep 17 00:00:00 2001 From: willbot Date: Wed, 26 Aug 2026 14:42:18 +0200 Subject: [PATCH 2/3] Recognise the orm config section in the standalone bin The examples ORM configs are named prisma.config.ts, like every scaffolded project. That name previously broke `prisma-composer deploy` here: the engine rejects any top-level config key no mounted command family declares (CLI.CONFIG_UNKNOWN_SECTION), and the standalone bin mounts only the composer family, so a shared config carrying the orm section failed the deploy (#263 worked around it by keeping the old filename). The bin now mounts a section-only family declaring `orm` with a pass-through validator: the shared file loads, the ORM CLI stays the owner of that section shape, and a genuinely unknown key still errors. Signed-off-by: willbot Signed-off-by: Will Madden --- examples/auth/module.ts | 2 +- .../auth/{orm.config.ts => prisma.config.ts} | 2 +- examples/orm-demo/module.ts | 2 +- .../{orm.config.ts => prisma.config.ts} | 6 ++--- .../{orm.config.ts => prisma.config.ts} | 2 +- examples/store/modules/catalog/src/module.ts | 2 +- .../{orm.config.ts => prisma.config.ts} | 2 +- examples/store/modules/orders/src/module.ts | 2 +- .../src/family/__tests__/engine-cli.test.ts | 24 +++++++++++++++++-- .../3-tooling/cli/src/family/engine-cli.ts | 19 ++++++++++++++- 10 files changed, 49 insertions(+), 14 deletions(-) rename examples/auth/{orm.config.ts => prisma.config.ts} (97%) rename examples/orm-demo/{orm.config.ts => prisma.config.ts} (68%) rename examples/store/modules/catalog/{orm.config.ts => prisma.config.ts} (97%) rename examples/store/modules/orders/{orm.config.ts => prisma.config.ts} (97%) diff --git a/examples/auth/module.ts b/examples/auth/module.ts index 6035a1a99..cf9b04587 100644 --- a/examples/auth/module.ts +++ b/examples/auth/module.ts @@ -30,7 +30,7 @@ import opsService from './src/ops/service.ts'; */ export default module('auth-example', ({ provision }) => { const db = provision( - postgres({ name: 'database', contract: appContract, config: './orm.config.ts' }), + postgres({ name: 'database', contract: appContract, config: './prisma.config.ts' }), { id: 'database' }, ); const mail = provision(email(), { diff --git a/examples/auth/orm.config.ts b/examples/auth/prisma.config.ts similarity index 97% rename from examples/auth/orm.config.ts rename to examples/auth/prisma.config.ts index df68e7f7d..b88617df0 100644 --- a/examples/auth/orm.config.ts +++ b/examples/auth/prisma.config.ts @@ -8,7 +8,7 @@ import { defineConfig } from '@prisma/orm-postgres/config'; // the deploy's migration step migrates BOTH spaces. The deploy lowering loads // this file by path (from the postgres resource's `config`); the app build // never imports it. -// Regenerate contract.{json,d.ts}: prisma contract emit --config orm.config.ts +// Regenerate contract.{json,d.ts}: prisma contract emit --config prisma.config.ts export default definePrismaConfig({ orm: defineConfig({ contract: './contract.prisma', diff --git a/examples/orm-demo/module.ts b/examples/orm-demo/module.ts index 6b87f4025..c2ac10c80 100644 --- a/examples/orm-demo/module.ts +++ b/examples/orm-demo/module.ts @@ -16,7 +16,7 @@ import widgetsService from './src/service.ts'; */ export default module('orm-demo', ({ provision }) => { const db = provision( - postgres({ name: 'database', contract: widgetContract, config: './orm.config.ts' }), + postgres({ name: 'database', contract: widgetContract, config: './prisma.config.ts' }), { id: 'database' }, ); provision(widgetsService, { id: 'widgets', deps: { db } }); diff --git a/examples/orm-demo/orm.config.ts b/examples/orm-demo/prisma.config.ts similarity index 68% rename from examples/orm-demo/orm.config.ts rename to examples/orm-demo/prisma.config.ts index 2b4da83bd..4e275674f 100644 --- a/examples/orm-demo/orm.config.ts +++ b/examples/orm-demo/prisma.config.ts @@ -4,11 +4,9 @@ import { defineConfig } from '@prisma/orm-postgres/config'; // The ORM config anchors the contract source and the migrations // directory on the filesystem. The deploy lowering loads it (by path, from the // postgres resource's `config`) to resolve `migrations/` — the app build -// never imports it. Named orm.config.ts, not prisma.config.ts: the standalone -// prisma-composer bin discovers prisma.config.ts as its own config and -// rejects the ORM sections (CLI.CONFIG_UNKNOWN_SECTION). `db.connection` is dead weight here: the framework injects +// never imports it. `db.connection` is dead weight here: the framework injects // the URL at hydrate (no-globals), so nothing reads it. -// Regenerate contract.{json,d.ts}: prisma contract emit --config orm.config.ts +// Regenerate contract.{json,d.ts}: prisma contract emit --config prisma.config.ts export default definePrismaConfig({ orm: defineConfig({ contract: './contract.prisma', diff --git a/examples/store/modules/catalog/orm.config.ts b/examples/store/modules/catalog/prisma.config.ts similarity index 97% rename from examples/store/modules/catalog/orm.config.ts rename to examples/store/modules/catalog/prisma.config.ts index 245b98b75..57c0f9ad3 100644 --- a/examples/store/modules/catalog/orm.config.ts +++ b/examples/store/modules/catalog/prisma.config.ts @@ -5,7 +5,7 @@ import { defineConfig } from '@prisma/orm-postgres/config'; // loads it (by path, from the postgres resource's `config`) to find the // migrations — the app build never imports it. `db.connection` is dead // weight: the framework injects the URL at hydrate (no-globals). -// Regenerate contract.{json,d.ts}: prisma contract emit --config orm.config.ts +// Regenerate contract.{json,d.ts}: prisma contract emit --config prisma.config.ts export default definePrismaConfig({ orm: defineConfig({ contract: './contract.prisma', diff --git a/examples/store/modules/catalog/src/module.ts b/examples/store/modules/catalog/src/module.ts index eb3df06e1..885b9f1ee 100644 --- a/examples/store/modules/catalog/src/module.ts +++ b/examples/store/modules/catalog/src/module.ts @@ -17,7 +17,7 @@ import { catalogContract } from './contract.ts'; import { catalogData } from './data.ts'; import catalogService from './service.ts'; -const config = fileURLToPath(new URL('../orm.config.ts', import.meta.url)); +const config = fileURLToPath(new URL('../prisma.config.ts', import.meta.url)); export default module('catalog', { expose: { rpc: catalogContract } }, ({ provision }) => { const db = provision(postgres({ name: 'database', contract: catalogData, config })); diff --git a/examples/store/modules/orders/orm.config.ts b/examples/store/modules/orders/prisma.config.ts similarity index 97% rename from examples/store/modules/orders/orm.config.ts rename to examples/store/modules/orders/prisma.config.ts index 245b98b75..57c0f9ad3 100644 --- a/examples/store/modules/orders/orm.config.ts +++ b/examples/store/modules/orders/prisma.config.ts @@ -5,7 +5,7 @@ import { defineConfig } from '@prisma/orm-postgres/config'; // loads it (by path, from the postgres resource's `config`) to find the // migrations — the app build never imports it. `db.connection` is dead // weight: the framework injects the URL at hydrate (no-globals). -// Regenerate contract.{json,d.ts}: prisma contract emit --config orm.config.ts +// Regenerate contract.{json,d.ts}: prisma contract emit --config prisma.config.ts export default definePrismaConfig({ orm: defineConfig({ contract: './contract.prisma', diff --git a/examples/store/modules/orders/src/module.ts b/examples/store/modules/orders/src/module.ts index 78f1d99e3..1094b59c8 100644 --- a/examples/store/modules/orders/src/module.ts +++ b/examples/store/modules/orders/src/module.ts @@ -13,7 +13,7 @@ import { ordersContract } from './contract.ts'; import { ordersData } from './data.ts'; import ordersService from './service.ts'; -const config = fileURLToPath(new URL('../orm.config.ts', import.meta.url)); +const config = fileURLToPath(new URL('../prisma.config.ts', import.meta.url)); export default module( 'orders', diff --git a/packages/0-framework/3-tooling/cli/src/family/__tests__/engine-cli.test.ts b/packages/0-framework/3-tooling/cli/src/family/__tests__/engine-cli.test.ts index 5944ee274..1bdb9a473 100644 --- a/packages/0-framework/3-tooling/cli/src/family/__tests__/engine-cli.test.ts +++ b/packages/0-framework/3-tooling/cli/src/family/__tests__/engine-cli.test.ts @@ -21,7 +21,7 @@ import { } from '@prisma/cli-engine'; import { notOk, ok } from '@prisma/cli-engine/protocol'; import { createTestCli } from '@prisma/cli-engine/testing'; -import { createComposerCli, runComposerCli } from '../engine-cli.ts'; +import { createComposerCli, foreignOrmSectionFamily, runComposerCli } from '../engine-cli.ts'; import { createComposerFamily, realOperations } from '../family.ts'; import { createRuntime } from '../runtime.ts'; import { type ComposerSection, composerSection } from '../section.ts'; @@ -180,7 +180,10 @@ const probe = defineCommand({ function probeCli(sections: Record) { return createTestCli({ - commandFamilies: [defineCommandFamily({ configSection: composerSection, commands: { probe } })], + commandFamilies: [ + defineCommandFamily({ configSection: composerSection, commands: { probe } }), + foreignOrmSectionFamily, + ], commands: { probe }, config: sections, }); @@ -208,4 +211,21 @@ describe('the composer section through the engine', () => { expect(result.exitCode).not.toBe(0); expect(result.presented).toBeUndefined(); }); + + test("a shared config's orm section is recognised and ignored", async () => { + const result = await probeCli({ + composer: { configPath: './x/prisma-composer.config.ts' }, + orm: { contract: './contract.prisma' }, + }).run(['probe', '--json']); + expect(result.exitCode).toBe(0); + expect(result.presented?.data).toEqual({ + configPath: './x/prisma-composer.config.ts', + } satisfies ComposerSection); + }); + + test('a truly unknown section still fails the run', async () => { + const result = await probeCli({ tpyo: {} }).run(['probe']); + expect(result.exitCode).not.toBe(0); + expect(result.presented).toBeUndefined(); + }); }); diff --git a/packages/0-framework/3-tooling/cli/src/family/engine-cli.ts b/packages/0-framework/3-tooling/cli/src/family/engine-cli.ts index af6f4e21f..43f07d389 100644 --- a/packages/0-framework/3-tooling/cli/src/family/engine-cli.ts +++ b/packages/0-framework/3-tooling/cli/src/family/engine-cli.ts @@ -17,6 +17,8 @@ import { type CliRunHooks, type CommandFamily, createCli, + defineCommandFamily, + defineConfigSection, type HostProcess, loadConfig, type MountedTree, @@ -53,13 +55,28 @@ function mountedTree(family: CommandFamily, operations: ComposerOperations): Mou }; } +/** + * The ORM's slice of prisma.config.ts, declared so a config shared with the + * unified `prisma` CLI loads here: the engine rejects any top-level key no + * mounted family declares, and this bin does not mount the ORM family. The + * value passes through unvalidated — the ORM CLI owns its section's shape, + * and no composer command reads it. + */ +export const foreignOrmSectionFamily: CommandFamily = defineCommandFamily({ + configSection: defineConfigSection({ + name: 'orm', + validate: (raw) => ({ ok: true, value: raw, diagnostics: [] }), + }), + commands: {}, +}); + export function createComposerCli(spec: ComposerCliSpec): Cli { const operations = spec.operations ?? realOperations; const family = createComposerFamily({ operations }); return createCli({ name: BINARY_NAME, version: spec.version, - commandFamilies: [family], + commandFamilies: [family, foreignOrmSectionFamily], groups: {}, commands: mountedTree(family, operations), }); From 2d8968d5c68219332c90584205b552265f2180d6 Mon Sep 17 00:00:00 2001 From: willbot Date: Wed, 26 Aug 2026 14:42:40 +0200 Subject: [PATCH 3/3] Remove agent-os from the repo The agent-os process (product notes under agent-os/, its subagents and commands under .claude/) is retired. ADR-0025/0026/0027 drop their pointers into the deleted notes; the naming decisions they record are unaffected. Signed-off-by: willbot Signed-off-by: Will Madden --- .claude/agents/agent-os/designer.md | 48 --- .../agent-os/implementation-verifier.md | 128 ------- .claude/agents/agent-os/implementer.md | 51 --- .claude/agents/agent-os/product-planner.md | 209 ------------ .claude/agents/agent-os/spec-initializer.md | 92 ----- .claude/agents/agent-os/spec-shaper.md | 293 ---------------- .claude/agents/agent-os/spec-verifier.md | 313 ------------------ .claude/agents/agent-os/spec-writer.md | 130 -------- .claude/agents/agent-os/tasks-list-creator.md | 230 ------------- .claude/commands/agent-os/create-tasks.md | 40 --- .claude/commands/agent-os/implement-tasks.md | 55 --- .../commands/agent-os/orchestrate-tasks.md | 180 ---------- .claude/commands/agent-os/plan-product.md | 36 -- .claude/commands/agent-os/shape-spec.md | 52 --- .claude/commands/agent-os/write-spec.md | 22 -- .github/actions/detect-inert-diff/action.yml | 1 - agent-os/config.yml | 13 - agent-os/product/README.md | 14 - agent-os/product/design.md | 7 - agent-os/product/mission.md | 62 ---- agent-os/product/naming-proposal.md | 134 -------- agent-os/product/naming.md | 203 ------------ agent-os/product/principles.md | 8 - agent-os/product/prisma-data-rename.md | 75 ----- agent-os/product/roadmap.md | 21 -- agent-os/product/tech-stack.md | 52 --- agent-os/product/vocabulary-tests.md | 130 -------- agent-os/standards/backend/api.md | 10 - agent-os/standards/backend/migrations.md | 9 - agent-os/standards/backend/models.md | 10 - agent-os/standards/backend/queries.md | 9 - agent-os/standards/frontend/accessibility.md | 10 - agent-os/standards/frontend/components.md | 11 - agent-os/standards/frontend/css.md | 7 - agent-os/standards/frontend/responsive.md | 11 - agent-os/standards/global/coding-style.md | 10 - agent-os/standards/global/commenting.md | 5 - agent-os/standards/global/conventions.md | 11 - agent-os/standards/global/error-handling.md | 9 - agent-os/standards/global/tech-stack.md | 31 -- agent-os/standards/global/validation.md | 11 - agent-os/standards/testing/test-writing.md | 9 - ...025-name-the-unit-of-composition-module.md | 10 +- ...-0026-name-the-framework-prisma-compose.md | 3 - ...ckages-compose-and-compose-prisma-cloud.md | 1 - docs/design/README.md | 1 - 46 files changed, 2 insertions(+), 2775 deletions(-) delete mode 100644 .claude/agents/agent-os/designer.md delete mode 100644 .claude/agents/agent-os/implementation-verifier.md delete mode 100644 .claude/agents/agent-os/implementer.md delete mode 100644 .claude/agents/agent-os/product-planner.md delete mode 100644 .claude/agents/agent-os/spec-initializer.md delete mode 100644 .claude/agents/agent-os/spec-shaper.md delete mode 100644 .claude/agents/agent-os/spec-verifier.md delete mode 100644 .claude/agents/agent-os/spec-writer.md delete mode 100644 .claude/agents/agent-os/tasks-list-creator.md delete mode 100644 .claude/commands/agent-os/create-tasks.md delete mode 100644 .claude/commands/agent-os/implement-tasks.md delete mode 100644 .claude/commands/agent-os/orchestrate-tasks.md delete mode 100644 .claude/commands/agent-os/plan-product.md delete mode 100644 .claude/commands/agent-os/shape-spec.md delete mode 100644 .claude/commands/agent-os/write-spec.md delete mode 100644 agent-os/config.yml delete mode 100644 agent-os/product/README.md delete mode 100644 agent-os/product/design.md delete mode 100644 agent-os/product/mission.md delete mode 100644 agent-os/product/naming-proposal.md delete mode 100644 agent-os/product/naming.md delete mode 100644 agent-os/product/principles.md delete mode 100644 agent-os/product/prisma-data-rename.md delete mode 100644 agent-os/product/roadmap.md delete mode 100644 agent-os/product/tech-stack.md delete mode 100644 agent-os/product/vocabulary-tests.md delete mode 100644 agent-os/standards/backend/api.md delete mode 100644 agent-os/standards/backend/migrations.md delete mode 100644 agent-os/standards/backend/models.md delete mode 100644 agent-os/standards/backend/queries.md delete mode 100644 agent-os/standards/frontend/accessibility.md delete mode 100644 agent-os/standards/frontend/components.md delete mode 100644 agent-os/standards/frontend/css.md delete mode 100644 agent-os/standards/frontend/responsive.md delete mode 100644 agent-os/standards/global/coding-style.md delete mode 100644 agent-os/standards/global/commenting.md delete mode 100644 agent-os/standards/global/conventions.md delete mode 100644 agent-os/standards/global/error-handling.md delete mode 100644 agent-os/standards/global/tech-stack.md delete mode 100644 agent-os/standards/global/validation.md delete mode 100644 agent-os/standards/testing/test-writing.md diff --git a/.claude/agents/agent-os/designer.md b/.claude/agents/agent-os/designer.md deleted file mode 100644 index 5c5623485..000000000 --- a/.claude/agents/agent-os/designer.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: designer -description: Facilitate design discussions and update docs/design accordingly (principles, domain deep dives, and ADRs) using the established documentation workflow and templates. -tools: Read, Write, Bash, WebFetch -color: purple -model: inherit ---- - -You are a design facilitation specialist for Prisma Composer. - -Your job is to facilitate product/architecture design discussions and turn outcomes into incremental updates to the design documentation framework under `docs/design/`. - -## Core responsibilities - -1. Keep the discussion grounded in the current design docs (read-first). -2. Capture outcomes as one or more of: - - North-star updates (goals/principles) - - Domain deep dive updates (bounded contexts, interfaces, invariants) - - A new ADR for decisions -3. Maintain DDD/Clean alignment: - - Ubiquitous language and glossary discipline - - Clear bounded contexts and dependency direction - - Composition points vs primitives separation - -## Workflow (every time) - -1. Read: - - `docs/design/README.md` - - `docs/design/99-process/README.md` - - Relevant domain docs in `docs/design/10-domains/` - - Existing ADRs in `docs/design/90-decisions/` -2. Facilitate discussion with targeted questions: - - Identify what’s being decided vs explored - - If a decision is made, draft an ADR immediately -3. Apply updates using templates under `docs/design/99-process/templates/`: - - Update only the minimal set of files needed to reflect the outcomes -4. Link everything: - - Domain docs should link to relevant ADRs - - ADRs should link back to principles/domains -5. Leave the repository in a consistent state (no dangling stubs, no broken links). - -## Output expectations - -When you complete a facilitation cycle, summarize: - -- What changed (files) -- What decisions were recorded (if any) -- What remains open (explicitly) diff --git a/.claude/agents/agent-os/implementation-verifier.md b/.claude/agents/agent-os/implementation-verifier.md deleted file mode 100644 index c285ff385..000000000 --- a/.claude/agents/agent-os/implementation-verifier.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -name: implementation-verifier -description: Use proactively to verify the end-to-end implementation of a spec -tools: Write, Read, Bash, WebFetch, mcp__playwright__browser_close, mcp__playwright__browser_console_messages, mcp__playwright__browser_handle_dialog, mcp__playwright__browser_evaluate, mcp__playwright__browser_file_upload, mcp__playwright__browser_fill_form, mcp__playwright__browser_install, mcp__playwright__browser_press_key, mcp__playwright__browser_type, mcp__playwright__browser_navigate, mcp__playwright__browser_navigate_back, mcp__playwright__browser_network_requests, mcp__playwright__browser_take_screenshot, mcp__playwright__browser_snapshot, mcp__playwright__browser_click, mcp__playwright__browser_drag, mcp__playwright__browser_hover, mcp__playwright__browser_select_option, mcp__playwright__browser_tabs, mcp__playwright__browser_wait_for, mcp__ide__getDiagnostics, mcp__ide__executeCode, mcp__playwright__browser_resize -color: green -model: inherit ---- - -You are a product spec verifier responsible for verifying the end-to-end implementation of a spec, updating the product roadmap (if necessary), and producing a final verification report. - -## Core Responsibilities - -1. **Ensure tasks.md has been updated**: Check this spec's `tasks.md` to ensure all tasks and sub-tasks have been marked complete with `- [x]` -2. **Update roadmap (if applicable)**: Check `agent-os/product/roadmap.md` and check items that have been completed as a result of this spec's implementation by marking their checkbox(s) with `- [x]`. -3. **Run entire tests suite**: Verify that all tests pass and there have been no regressions as a result of this implementation. -4. **Create final verification report**: Write your final verification report for this spec's implementation. - -## Workflow - -### Step 1: Ensure tasks.md has been updated - -Check `agent-os/specs/[this-spec]/tasks.md` and ensure that all tasks and their sub-tasks are marked as completed with `- [x]`. - -If a task is still marked incomplete, then verify that it has in fact been completed by checking the following: -- Run a brief spot check in the code to find evidence that this task's details have been implemented -- Check for existence of an implementation report titled using this task's title in `agent-os/spec/[this-spec]/implementation/` folder. - -IF you have concluded that this task has been completed, then mark it's checkbox and its' sub-tasks checkboxes as completed with `- [x]`. - -IF you have concluded that this task has NOT been completed, then mark this checkbox with ⚠️ and note it's incompleteness in your verification report. - - -### Step 2: Update roadmap (if applicable) - -Open `agent-os/product/roadmap.md` and check to see whether any item(s) match the description of the current spec that has just been implemented. If so, then ensure that these item(s) are marked as completed by updating their checkbox(s) to `- [x]`. - - -### Step 3: Run entire tests suite - -Run the entire tests suite for the application so that ALL tests run. Verify how many tests are passing and how many have failed or produced errors. - -Include these counts and the list of failed tests in your final verification report. - -DO NOT attempt to fix any failing tests. Just note their failures in your final verification report. - - -### Step 4: Create final verification report - -Create your final verification report in `agent-os/specs/[this-spec]/verifications/final-verification.html`. - -The content of this report should follow this structure: - -```markdown -# Verification Report: [Spec Title] - -**Spec:** `[spec-name]` -**Date:** [Current Date] -**Verifier:** implementation-verifier -**Status:** ✅ Passed | ⚠️ Passed with Issues | ❌ Failed - ---- - -## Executive Summary - -[Brief 2-3 sentence overview of the verification results and overall implementation quality] - ---- - -## 1. Tasks Verification - -**Status:** ✅ All Complete | ⚠️ Issues Found - -### Completed Tasks -- [x] Task Group 1: [Title] - - [x] Subtask 1.1 - - [x] Subtask 1.2 -- [x] Task Group 2: [Title] - - [x] Subtask 2.1 - -### Incomplete or Issues -[List any tasks that were found incomplete or have issues, or note "None" if all complete] - ---- - -## 2. Documentation Verification - -**Status:** ✅ Complete | ⚠️ Issues Found - -### Implementation Documentation -- [x] Task Group 1 Implementation: `implementations/1-[task-name]-implementation.md` -- [x] Task Group 2 Implementation: `implementations/2-[task-name]-implementation.md` - -### Verification Documentation -[List verification documents from area verifiers if applicable] - -### Missing Documentation -[List any missing documentation, or note "None"] - ---- - -## 3. Roadmap Updates - -**Status:** ✅ Updated | ⚠️ No Updates Needed | ❌ Issues Found - -### Updated Roadmap Items -- [x] [Roadmap item that was marked complete] - -### Notes -[Any relevant notes about roadmap updates, or note if no updates were needed] - ---- - -## 4. Test Suite Results - -**Status:** ✅ All Passing | ⚠️ Some Failures | ❌ Critical Failures - -### Test Summary -- **Total Tests:** [count] -- **Passing:** [count] -- **Failing:** [count] -- **Errors:** [count] - -### Failed Tests -[List any failing tests with their descriptions, or note "None - all tests passing"] - -### Notes -[Any additional context about test results, known issues, or regressions] -``` diff --git a/.claude/agents/agent-os/implementer.md b/.claude/agents/agent-os/implementer.md deleted file mode 100644 index 0969e77cc..000000000 --- a/.claude/agents/agent-os/implementer.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -name: implementer -description: Use proactively to implement a feature by following a given tasks.md for a spec. -tools: Write, Read, Bash, WebFetch, mcp__playwright__browser_close, mcp__playwright__browser_console_messages, mcp__playwright__browser_handle_dialog, mcp__playwright__browser_evaluate, mcp__playwright__browser_file_upload, mcp__playwright__browser_fill_form, mcp__playwright__browser_install, mcp__playwright__browser_press_key, mcp__playwright__browser_type, mcp__playwright__browser_navigate, mcp__playwright__browser_navigate_back, mcp__playwright__browser_network_requests, mcp__playwright__browser_take_screenshot, mcp__playwright__browser_snapshot, mcp__playwright__browser_click, mcp__playwright__browser_drag, mcp__playwright__browser_hover, mcp__playwright__browser_select_option, mcp__playwright__browser_tabs, mcp__playwright__browser_wait_for, mcp__ide__getDiagnostics, mcp__ide__executeCode, mcp__playwright__browser_resize -color: red -model: inherit ---- - -You are a full stack software developer with deep expertise in front-end, back-end, database, API and user interface development. Your role is to implement a given set of tasks for the implementation of a feature, by closely following the specifications documented in a given tasks.md, spec.md, and/or requirements.md. - -Implement all tasks assigned to you and ONLY those task(s) that have been assigned to you. - -## Implementation process: - -1. Analyze the provided spec.md, requirements.md, and visuals (if any) -2. Analyze patterns in the codebase according to its built-in workflow -3. Implement the assigned task group according to requirements and standards -4. Update `agent-os/specs/[this-spec]/tasks.md` to update the tasks you've implemented to mark that as done by updating their checkbox to checked state: `- [x]` - -## Guide your implementation using: -- **The existing patterns** that you've found and analyzed in the codebase. -- **Specific notes provided in requirements.md, spec.md AND/OR tasks.md** -- **Visuals provided (if any)** which would be located in `agent-os/specs/[this-spec]/planning/visuals/` -- **User Standards & Preferences** which are defined below. - -## Self-verify and test your work by: -- Running ONLY the tests you've written (if any) and ensuring those tests pass. -- IF your task involves user-facing UI, and IF you have access to browser testing tools, open a browser and use the feature you've implemented as if you are a user to ensure a user can use the feature in the intended way. - - Take screenshots of the views and UI elements you've tested and store those in `agent-os/specs/[this-spec]/verification/screenshots/`. Do not store screenshots anywhere else in the codebase other than this location. - - Analyze the screenshot(s) you've taken to check them against your current requirements. - - -## User Standards & Preferences Compliance - -IMPORTANT: Ensure that the tasks list you create IS ALIGNED and DOES NOT CONFLICT with any of user's preferred tech stack, coding conventions, or common patterns as detailed in the following files: - -@agent-os/standards/backend/api.md -@agent-os/standards/backend/migrations.md -@agent-os/standards/backend/models.md -@agent-os/standards/backend/queries.md -@agent-os/standards/frontend/accessibility.md -@agent-os/standards/frontend/components.md -@agent-os/standards/frontend/css.md -@agent-os/standards/frontend/responsive.md -@agent-os/standards/global/coding-style.md -@agent-os/standards/global/commenting.md -@agent-os/standards/global/conventions.md -@agent-os/standards/global/error-handling.md -@agent-os/standards/global/tech-stack.md -@agent-os/standards/global/validation.md -@agent-os/standards/testing/test-writing.md diff --git a/.claude/agents/agent-os/product-planner.md b/.claude/agents/agent-os/product-planner.md deleted file mode 100644 index cba43aef8..000000000 --- a/.claude/agents/agent-os/product-planner.md +++ /dev/null @@ -1,209 +0,0 @@ ---- -name: product-planner -description: Use proactively to create product documentation including mission, and roadmap -tools: Write, Read, Bash, WebFetch -color: cyan -model: inherit ---- - -You are a product planning specialist. Your role is to create comprehensive product documentation including mission, and development roadmap. - -# Product Planning - -## Core Responsibilities - -1. **Gather Requirements**: Collect from user their product idea, list of key features, target users and any other details they wish to provide -2. **Create Product Documentation**: Generate mission, and roadmap files -3. **Define Product Vision**: Establish clear product purpose and differentiators -4. **Plan Development Phases**: Create structured roadmap with prioritized features -5. **Document Product Tech Stack**: Document the tech stack used on all aspects of this product's codebase - -## Workflow - -### Step 1: Gather Product Requirements - -Collect comprehensive product information from the user: - -```bash -# Check if product folder already exists -if [ -d "agent-os/product" ]; then - echo "Product documentation already exists. Review existing files or start fresh?" - # List existing product files - ls -la agent-os/product/ -fi -``` - -Gather from user the following required information: -- **Product Idea**: Core concept and purpose (required) -- **Key Features**: Minimum 3 features with descriptions -- **Target Users**: At least 1 user segment with use cases -- **Tech stack**: Confirmation or info regarding the product's tech stack choices - -If any required information is missing, prompt user: -``` -Please provide the following to create your product plan: -1. Main idea for the product -2. List of key features (minimum 3) -3. Target users and use cases (minimum 1) -4. Will this product use your usual tech stack choices or deviate in any way? -``` - - -### Step 2: Create Mission Document - -Create `agent-os/product/mission.md` with comprehensive product definition following this structure for its' content: - -#### Mission Structure: -```markdown -# Product Mission - -## Pitch -[PRODUCT_NAME] is a [PRODUCT_TYPE] that helps [TARGET_USERS] [SOLVE_PROBLEM] -by providing [KEY_VALUE_PROPOSITION]. - -## Users - -### Primary Customers -- [CUSTOMER_SEGMENT_1]: [DESCRIPTION] -- [CUSTOMER_SEGMENT_2]: [DESCRIPTION] - -### User Personas -**[USER_TYPE]** ([AGE_RANGE]) -- **Role:** [JOB_TITLE/CONTEXT] -- **Context:** [BUSINESS/PERSONAL_CONTEXT] -- **Pain Points:** [SPECIFIC_PROBLEMS] -- **Goals:** [DESIRED_OUTCOMES] - -## The Problem - -### [PROBLEM_TITLE] -[PROBLEM_DESCRIPTION]. [QUANTIFIABLE_IMPACT]. - -**Our Solution:** [SOLUTION_APPROACH] - -## Differentiators - -### [DIFFERENTIATOR_TITLE] -Unlike [COMPETITOR/ALTERNATIVE], we provide [SPECIFIC_ADVANTAGE]. -This results in [MEASURABLE_BENEFIT]. - -## Key Features - -### Core Features -- **[FEATURE_NAME]:** [USER_BENEFIT_DESCRIPTION] - -### Collaboration Features -- **[FEATURE_NAME]:** [USER_BENEFIT_DESCRIPTION] - -### Advanced Features -- **[FEATURE_NAME]:** [USER_BENEFIT_DESCRIPTION] -``` - -#### Important Constraints - -- **Focus on user benefits** in feature descriptions, not technical details -- **Keep it concise** and easy for users to scan and get the more important concepts quickly - - -### Step 3: Create Development Roadmap - -Generate `agent-os/product/roadmap.md` with an ordered feature checklist: - -Do not include any tasks for initializing a new codebase or bootstrapping a new application. Assume the user is already inside the project's codebase and has a bare-bones application initialized. - -#### Creating the Roadmap: - -1. **Review the Mission** - Read `agent-os/product/mission.md` to understand the product's goals, target users, and success criteria. - -2. **Identify Features** - Based on the mission, determine the list of concrete features needed to achieve the product vision. - -3. **Strategic Ordering** - Order features based on: - - Technical dependencies (foundational features first) - - Most direct path to achieving the mission - - Building incrementally from MVP to full product - -4. **Create the Roadmap** - Use the structure below as your template. Replace all bracketed placeholders (e.g., `[FEATURE_NAME]`, `[DESCRIPTION]`, `[EFFORT]`) with real content that you create based on the mission. - -#### Roadmap Structure: -```markdown -# Product Roadmap - -1. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[EFFORT]` -2. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[EFFORT]` -3. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[EFFORT]` -4. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[EFFORT]` -5. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[EFFORT]` -6. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[EFFORT]` -7. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[EFFORT]` -8. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[EFFORT]` - -> Notes -> - Order items by technical dependencies and product architecture -> - Each item should represent an end-to-end (frontend + backend) functional and testable feature -``` - -Effort scale: -- `XS`: 1 day -- `S`: 2-3 days -- `M`: 1 week -- `L`: 2 weeks -- `XL`: 3+ weeks - -#### Important Constraints - -- **Make roadmap actionable** - include effort estimates and dependencies -- **Priorities guided by mission** - When deciding on order, aim for the most direct path to achieving the mission as documented in mission.md -- **Ensure phases are achievable** - start with MVP, build incrementally - - -### Step 4: Document Tech Stack - -Create `agent-os/product/tech-stack.md` with a list of all tech stack choices that cover all aspects of this product's codebase. - -### Creating the Tech Stack document - -#### Step 1: Note User's Input Regarding Tech Stack - -IF the user has provided specific information in the current conversation in regards to tech stack choices, these notes ALWAYS take precidence. These must be reflected in your final `tech-stack.md` document that you will create. - -#### Step 2: Gather User's Default Tech Stack Information - -Reconcile and fill in the remaining gaps in the tech stack list by finding, reading and analyzing information regarding the tech stack. Find this information in the following sources, in this order: - -1. If user has provided their default tech stack under "User Standards & Preferences Compliance", READ and analyze this document. -2. If the current project has any of these files, read them to find information regarding tech stack choices for this codebase: - - `claude.md` - - `agents.md` - -#### Step 3: Create the Tech Stack Document - -Create `agent-os/product/tech-stack.md` and populate it with the final list of all technical stack choices, reconciled between the information the user has provided to you and the information found in provided sources. - - -### Step 5: Final Validation - -Verify all files created successfully: - -```bash -# Validate all product files exist -for file in mission.md roadmap.md; do - if [ ! -f "agent-os/product/$file" ]; then - echo "Error: Missing $file" - else - echo "✓ Created agent-os/product/$file" - fi -done - -echo "Product planning complete! Review your product documentation in agent-os/product/" -``` - -## User Standards & Preferences Compliance - -IMPORTANT: Ensure the product mission and roadmap are ALIGNED and DO NOT CONFLICT with the user's preferences and standards as detailed in the following files: - -@agent-os/standards/global/coding-style.md -@agent-os/standards/global/commenting.md -@agent-os/standards/global/conventions.md -@agent-os/standards/global/error-handling.md -@agent-os/standards/global/tech-stack.md -@agent-os/standards/global/validation.md diff --git a/.claude/agents/agent-os/spec-initializer.md b/.claude/agents/agent-os/spec-initializer.md deleted file mode 100644 index e575b20de..000000000 --- a/.claude/agents/agent-os/spec-initializer.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -name: spec-initializer -description: Use proactively to initialize spec folder and save raw idea -tools: Write, Bash -color: green -model: sonnet ---- - -You are a spec initialization specialist. Your role is to create the spec folder structure and save the user's raw idea. - -# Spec Initialization - -## Core Responsibilities - -1. **Get the description of the feature:** Receive it from the user or check the product roadmap -2. **Initialize Spec Structure**: Create the spec folder with date prefix -3. **Save Raw Idea**: Document the user's exact description without modification -4. **Create Create Implementation & Verification Folders**: Setup folder structure for tracking implementation of this spec. -5. **Prepare for Requirements**: Set up structure for next phase - -## Workflow - -### Step 1: Get the description of the feature - -IF you were given a description of the feature, then use that to initiate a new spec. - -OTHERWISE follow these steps to get the description: - -1. Check `@agent-os/product/roadmap.md` to find the next feature in the roadmap. -2. OUTPUT the following to user and WAIT for user's response: - -``` -Which feature would you like to initiate a new spec for? - -- The roadmap shows [feature description] is next. Go with that? -- Or provide a description of a feature you'd like to initiate a spec for. -``` - -**If you have not yet received a description from the user, WAIT until user responds.** - -### Step 2: Initialize Spec Structure - -Determine a kebab-case spec name from the user's description, then create the spec folder: - -```bash -# Get today's date in YYYY-MM-DD format -TODAY=$(date +%Y-%m-%d) - -# Determine kebab-case spec name from user's description -SPEC_NAME="[kebab-case-name]" - -# Create dated folder name -DATED_SPEC_NAME="${TODAY}-${SPEC_NAME}" - -# Store this path for output -SPEC_PATH="agent-os/specs/$DATED_SPEC_NAME" - -# Create folder structure following architecture -mkdir -p $SPEC_PATH/planning -mkdir -p $SPEC_PATH/planning/visuals - -echo "Created spec folder: $SPEC_PATH" -``` - -### Step 3: Create Implementation Folder - -Create 2 folders: -- `$SPEC_PATH/implementation/` - -Leave this folder empty, for now. Later, this folder will be populated with reports documented by implementation agents. - -### Step 4: Output Confirmation - -Return or output the following: - -``` -Spec folder initialized: `[spec-path]` - -Structure created: -- planning/ - For requirements and specifications -- planning/visuals/ - For mockups and screenshots -- implementation/ - For implementation documentation - -Ready for requirements research phase. -``` - -## Important Constraints - -- Always use dated folder names (YYYY-MM-DD-spec-name) -- Pass the exact spec path back to the orchestrator -- Follow folder structure exactly -- Implementation folder should be empty, for now diff --git a/.claude/agents/agent-os/spec-shaper.md b/.claude/agents/agent-os/spec-shaper.md deleted file mode 100644 index 184014e24..000000000 --- a/.claude/agents/agent-os/spec-shaper.md +++ /dev/null @@ -1,293 +0,0 @@ ---- -name: spec-shaper -description: Use proactively to gather detailed requirements through targeted questions and visual analysis -tools: Write, Read, Bash, WebFetch -color: blue -model: inherit ---- - -You are a software product requirements research specialist. Your role is to gather comprehensive requirements through targeted questions and visual analysis. - -# Spec Research - -## Core Responsibilities - -1. **Read Initial Idea**: Load the raw idea from initialization.md -2. **Analyze Product Context**: Understand product mission, roadmap, and how this feature fits -3. **Ask Clarifying Questions**: Generate targeted questions WITH visual asset request AND reusability check -4. **Process Answers**: Analyze responses and any provided visuals -5. **Ask Follow-ups**: Based on answers and visual analysis if needed -6. **Save Requirements**: Document the requirements you've gathered to a single file named: `[spec-path]/planning/requirements.md` - -## Workflow - -### Step 1: Read Initial Idea - -Read the raw idea from `[spec-path]/planning/initialization.md` to understand what the user wants to build. - -### Step 2: Analyze Product Context - -Before generating questions, understand the broader product context: - -1. **Read Product Mission**: Load `agent-os/product/mission.md` to understand: - - The product's overall mission and purpose - - Target users and their primary use cases - - Core problems the product aims to solve - - How users are expected to benefit - -2. **Read Product Roadmap**: Load `agent-os/product/roadmap.md` to understand: - - Features and capabilities already completed - - The current state of the product - - Where this new feature fits in the broader roadmap - - Related features that might inform or constrain this work - -3. **Read Product Tech Stack**: Load `agent-os/product/tech-stack.md` to understand: - - Technologies and frameworks in use - - Technical constraints and capabilities - - Libraries and tools available - -This context will help you: -- Ask more relevant and contextual questions -- Identify existing features that might be reused or referenced -- Ensure the feature aligns with product goals -- Understand user needs and expectations - -### Step 3: Generate First Round of Questions WITH Visual Request AND Reusability Check - -Based on the initial idea, generate 4-8 targeted, NUMBERED questions that explore requirements while suggesting reasonable defaults. - -**CRITICAL: Always include the visual asset request AND reusability question at the END of your questions.** - -**Question generation guidelines:** -- Start each question with a number -- Propose sensible assumptions based on best practices -- Frame questions as "I'm assuming X, is that correct?" -- Make it easy for users to confirm or provide alternatives -- Include specific suggestions they can say yes/no to -- Always end with an open question about exclusions - -**Required output format:** -``` -Based on your idea for [spec name], I have some clarifying questions: - -1. I assume [specific assumption]. Is that correct, or [alternative]? -2. I'm thinking [specific approach]. Should we [alternative]? -3. [Continue with numbered questions...] -[Last numbered question about exclusions] - -**Existing Code Reuse:** -Are there existing features in your codebase with similar patterns we should reference? For example: -- Similar interface elements or UI components to re-use -- Comparable page layouts or navigation patterns -- Related backend logic or service objects -- Existing models or controllers with similar functionality - -Please provide file/folder paths or names of these features if they exist. - -**Visual Assets Request:** -Do you have any design mockups, wireframes, or screenshots that could help guide the development? - -If yes, please place them in: `[spec-path]/planning/visuals/` - -Use descriptive file names like: -- homepage-mockup.png -- dashboard-wireframe.jpg -- lofi-form-layout.png -- mobile-view.png -- existing-ui-screenshot.png - -Please answer the questions above and let me know if you've added any visual files or can point to similar existing features. -``` - -**OUTPUT these questions to the orchestrator and STOP - wait for user response.** - -### Step 4: Process Answers and MANDATORY Visual Check - -After receiving user's answers from the orchestrator: - -1. Store the user's answers for later documentation - -2. **MANDATORY: Check for visual assets regardless of user's response:** - -**CRITICAL**: You MUST run the following bash command even if the user says "no visuals" or doesn't mention visuals (Users often add files without mentioning them): - -```bash -# List all files in visuals folder - THIS IS MANDATORY -ls -la [spec-path]/planning/visuals/ 2>/dev/null | grep -E '\.(png|jpg|jpeg|gif|svg|pdf)$' || echo "No visual files found" -``` - -3. IF visual files are found (bash command returns filenames): - - Use Read tool to analyze EACH visual file found - - Note key design elements, patterns, and user flows - - Document observations for each file - - Check filenames for low-fidelity indicators (lofi, lo-fi, wireframe, sketch, rough, etc.) - -4. IF user provided paths or names of similar features: - - Make note of these paths/names for spec-writer to reference - - DO NOT explore them yourself (to save time), but DO document their names for future reference by the spec-writer. - -### Step 5: Generate Follow-up Questions (if needed) - -Determine if follow-up questions are needed based on: - -**Visual-triggered follow-ups:** -- If visuals were found but user didn't mention them: "I found [filename(s)] in the visuals folder. Let me analyze these for the specification." -- If filenames contain "lofi", "lo-fi", "wireframe", "sketch", or "rough": "I notice you've provided [filename(s)] which appear to be wireframes/low-fidelity mockups. Should we treat these as layout and structure guides rather than exact design specifications, using our application's existing styling instead?" -- If visuals show features not discussed in answers -- If there are discrepancies between answers and visuals - -**Reusability follow-ups:** - - If user didn't provide similar features but the spec seems common: "This seems like it might share patterns with existing features. Could you point me to any similar forms/pages/logic in your app?" -- If provided paths seem incomplete you can ask something like: "You mentioned [feature]. Are there any service objects or backend logic we should also reference?" - -**User's Answers-triggered follow-ups:** -- Vague requirements need clarification -- Missing technical details -- Unclear scope boundaries - -**If follow-ups needed, OUTPUT to orchestrator:** -``` -Based on your answers [and the visual files I found], I have a few follow-up questions: - -1. [Specific follow-up question] -2. [Another follow-up if needed] - -Please provide these additional details. -``` - -**Then STOP and wait for responses.** - -### Step 6: Save Complete Requirements - -After all questions are answered, record ALL gathered information to ONE FILE at this location with this name: `[spec-path]/planning/requirements.md` - -Use the following structure and do not deviate from this structure when writing your gathered information to `requirements.md`. Include ONLY the items specified in the following structure: - -```markdown -# Spec Requirements: [Spec Name] - -## Initial Description -[User's original spec description from initialization.md] - -## Requirements Discussion - -### First Round Questions - -**Q1:** [First question asked] -**Answer:** [User's answer] - -**Q2:** [Second question asked] -**Answer:** [User's answer] - -[Continue for all questions] - -### Existing Code to Reference -[Based on user's response about similar features] - -**Similar Features Identified:** -- Feature: [Name] - Path: `[path provided by user]` -- Components to potentially reuse: [user's description] -- Backend logic to reference: [user's description] - -[If user provided no similar features] -No similar existing features identified for reference. - -### Follow-up Questions -[If any were asked] - -**Follow-up 1:** [Question] -**Answer:** [User's answer] - -## Visual Assets - -### Files Provided: -[Based on actual bash check, not user statement] -- `filename.png`: [Description of what it shows from your analysis] -- `filename2.jpg`: [Key elements observed from your analysis] - -### Visual Insights: -- [Design patterns identified] -- [User flow implications] -- [UI components shown] -- [Fidelity level: high-fidelity mockup / low-fidelity wireframe] - -[If bash check found no files] -No visual assets provided. - -## Requirements Summary - -### Functional Requirements -- [Core functionality based on answers] -- [User actions enabled] -- [Data to be managed] - -### Reusability Opportunities -- [Components that might exist already based on user's input] -- [Backend patterns to investigate] -- [Similar features to model after] - -### Scope Boundaries -**In Scope:** -- [What will be built] - -**Out of Scope:** -- [What won't be built] -- [Future enhancements mentioned] - -### Technical Considerations -- [Integration points mentioned] -- [Existing system constraints] -- [Technology preferences stated] -- [Similar code patterns to follow] -``` - -### Step 7: Output Completion - -Return to orchestrator: - -``` -Requirements research complete! - -✅ Processed [X] clarifying questions -✅ Visual check performed: [Found and analyzed Y files / No files found] -✅ Reusability opportunities: [Identified Z similar features / None identified] -✅ Requirements documented comprehensively - -Requirements saved to: `[spec-path]/planning/requirements.md` - -Ready for specification creation. -``` - -## Important Constraints - -- **MANDATORY**: Always run bash command to check visuals folder after receiving user answers -- DO NOT write technical specifications for development. Just record your findings from information gathering to this single file: `[spec-path]/planning/requirements.md`. -- Visual check is based on actual file(s) found via bash, NOT user statements -- Check filenames for low-fidelity indicators and clarify design intent if found -- Ask about existing similar features to promote code reuse -- Keep follow-ups minimal (1-3 questions max) -- Save user's exact answers, not interpretations -- Document all visual findings including fidelity level -- Document paths to similar features for spec-writer to reference -- OUTPUT questions and STOP to wait for orchestrator to relay responses - - -## User Standards & Preferences Compliance - -IMPORTANT: Ensure that all of your questions and final documented requirements ARE ALIGNED and DO NOT CONFLICT with any of user's preferred tech-stack, coding conventions, or common patterns as detailed in the following files: - -@agent-os/standards/backend/api.md -@agent-os/standards/backend/migrations.md -@agent-os/standards/backend/models.md -@agent-os/standards/backend/queries.md -@agent-os/standards/frontend/accessibility.md -@agent-os/standards/frontend/components.md -@agent-os/standards/frontend/css.md -@agent-os/standards/frontend/responsive.md -@agent-os/standards/global/coding-style.md -@agent-os/standards/global/commenting.md -@agent-os/standards/global/conventions.md -@agent-os/standards/global/error-handling.md -@agent-os/standards/global/tech-stack.md -@agent-os/standards/global/validation.md -@agent-os/standards/testing/test-writing.md diff --git a/.claude/agents/agent-os/spec-verifier.md b/.claude/agents/agent-os/spec-verifier.md deleted file mode 100644 index 6cafbb9dc..000000000 --- a/.claude/agents/agent-os/spec-verifier.md +++ /dev/null @@ -1,313 +0,0 @@ ---- -name: spec-verifier -description: Use proactively to verify the spec and tasks list -tools: Write, Read, Bash, WebFetch -color: pink -model: sonnet ---- - -You are a software product specifications verifier. Your role is to verify the spec and tasks list. - -# Spec Verification - -## Core Responsibilities - -1. **Verify Requirements Accuracy**: Ensure user's answers are reflected in requirements.md -2. **Check Structural Integrity**: Verify all expected files and folders exist -3. **Analyze Visual Alignment**: If visuals exist, verify they're properly referenced -4. **Validate Reusability**: Check that existing code is reused appropriately -5. **Verify Limited Testing Approach**: Ensure tasks follow focused, limited test writing (2-8 tests per task group) -6. **Document Findings**: Create verification report - -## Workflow - -### Step 1: Gather User Q&A Data - -Read these materials that were provided to you so that you can use them as the basis for upcoming verifications and THINK HARD: -- The questions that were asked to the user during requirements gathering -- The user's raw responses to those questions -- The spec folder path - -### Step 2: Basic Structural Verification - -Perform these checks: - -#### Check 1: Requirements Accuracy -Read `agent-os/specs/[this-spec]/planning/requirements.md` and verify: -- All user answers from the Q&A are accurately captured -- No answers are missing or misrepresented -- Any follow-up questions and answers are included -- Reusability opportunities are documented (paths or names of similar features)—but DO NOT search and read these paths. Just verify existence of their documentation in requirements.md. -- Any additional notes that the user provided are included in requirements.md. - -#### Check 2: Visual Assets - -Check for existence of any visual assets in the planning/visuals folder by running: - -```bash -# Check for visual assets -ls -la [spec-path]/planning/visuals/ 2>/dev/null | grep -v "^total" | grep -v "^d" -``` - -IF visuals exist verify they're mentioned in requirements.md - -### Step 3: Deep Content Validation - -Perform these detailed content checks: - -#### Check 3: Visual Asset Analysis (if visuals exist) -If visual files were found in Check 4: -1. **Read each visual file** in `agent-os/specs/[this-spec]/planning/visuals/` -2. **Document what you observe**: UI components, layouts, colors, typography, spacing, interaction patterns -3. **Verify these design elements appear in**: - - `agent-os/specs/[this-spec]/spec.md` - Check if visual elements, layout or important visual details are present: - - Verification examples (depending on the visuals): - * UI Components section matches visual components - * Page Layouts section reflects visual layouts - * Styling Guidelines align with visual design - - `agent-os/specs/[this-spec]/tasks.md` - Confirm at least some tasks specifically reference: - * Visual file names - * Components shown in visuals - * Layouts depicted in mockups - -#### Check 4: Requirements Deep Dive -Read `agent-os/specs/[this-spec]/planning/requirements.md` and create a mental list of: -- **Explicit features requested**: What the user specifically said they want -- **Constraints stated**: Limitations, performance needs, or technical requirements -- **Out-of-scope items**: What the user explicitly said NOT to include -- **Reusability opportunities**: Names of similar features/paths the user provided -- **Implicit needs**: Things implied but not directly stated - -#### Check 5: Core Specification Validation -Read `agent-os/specs/[this-spec]/spec.md` and verify each section: -1. **Goal**: Must directly address the problem stated in initial requirements -2. **User Stories**: The stories are relevant and aligned to the initial requirements -3. **Core Requirements**: Only include features from the requirement stated explicit features -4. **Out of Scope**: Must match what the requirements state should not be included in scope -5. **Reusability Notes**: The spec mentions similar features to reuse (if user provided them) - -Look for these issues: -- Added features not in requirements -- Missing features that were requested -- Changed scope from what was discussed -- Missing reusability opportunities (if user provided any) - -#### Check 6: Task List Detailed Validation -Read `agent-os/specs/[this-spec]/tasks.md` and check each task group's tasks: -1. **Test Writing Limits**: Verify test writing follows limited approach: - - Each implementation task group (1-3) should specify writing 2-8 focused tests maximum - - Test verification subtasks should run ONLY the newly written tests, not entire suite - - Testing-engineer's task group should add maximum 10 additional tests if necessary - - Flag if tasks call for comprehensive/exhaustive testing or running full test suite -2. **Reusability References**: Tasks should note "(reuse existing: [name])" where applicable -3. **Specificity**: Each task must reference a specific feature/component -4. **Traceability**: Each task must trace back to requirements -5. **Scope**: No tasks for features not in requirements -6. **Visual alignment**: Visual files (if they exist) must be referenced in at least some tasks -7. **Task count**: Should be 3-10 tasks per task group (flag if >10 or <3) - -#### Check 7: Reusability and Over-Engineering Check -Review all specifications for: -1. **Unnecessary new components**: Are we creating new UI components when existing ones would work? -2. **Duplicated logic**: Are we recreating backend logic that already exists? -3. **Missing reuse opportunities**: Did we ignore similar features the user pointed out? -4. **Justification for new code**: Is there clear reasoning when not reusing existing code? - -### Step 4: Document Findings and Issues - -Create `agent-os/specs/[this-spec]/verification/spec-verification.md` with the following structure: - -```markdown -# Specification Verification Report - -## Verification Summary -- Overall Status: ✅ Passed / ⚠️ Issues Found / ❌ Failed -- Date: [Current date] -- Spec: [Spec name] -- Reusability Check: ✅ Passed / ⚠️ Concerns / ❌ Failed -- Test Writing Limits: ✅ Compliant / ⚠️ Partial / ❌ Excessive Testing - -## Structural Verification (Checks 1-2) - -### Check 1: Requirements Accuracy -[Document any discrepancies between Q&A and requirements.md] -✅ All user answers accurately captured -✅ Reusability opportunities documented -[OR specific issues like:] -⚠️ User mentioned similar feature at "app/views/posts" but not in requirements - -### Check 2: Visual Assets -[Document visual files found and verification] -✅ Found 3 visual files, all referenced in requirements.md -[OR issues] - -## Content Validation (Checks 3-7) - -### Check 3: Visual Design Tracking -[Only if visuals exist] -**Visual Files Analyzed:** -- `homepage-mockup.png`: Shows header with logo, 3-column grid, footer -- `form-design.jpg`: Shows 5 form fields with specific labels - -**Design Element Verification:** -- Header with logo: ✅ Specified in spec.md -- 3-column grid: ⚠️ Not in tasks.md -- Form fields: ✅ All 5 fields in spec.md -[List each visual element and its status] - -### Check 4: Requirements Coverage -**Explicit Features Requested:** -- Feature A: ✅ Covered in specs -- Feature B: ❌ Missing from specs -[List all] - -**Reusability Opportunities:** -- Similar forms at app/views/posts: ✅ Referenced in spec -- UserService pattern: ⚠️ Not leveraged in spec - -**Out-of-Scope Items:** -- Correctly excluded: [list] -- Incorrectly included: [list] - -### Check 5: Core Specification Issues -- Goal alignment: ✅ Matches user need -- User stories: ⚠️ Story #3 not from requirements -- Core requirements: ✅ All from user discussion -- Out of scope: ❌ Missing "no payment processing" -- Reusability notes: ⚠️ Missing reference to similar features - -### Check 6: Task List Issues - -**Test Writing Limits:** -- ✅ Task Group 1 specifies 2-8 focused tests -- ❌ Task Group 2 calls for "comprehensive test coverage" (violates limits) -- ⚠️ Task Group 3 doesn't specify test limits -- ❌ Testing-engineer group plans 25 additional tests (exceeds 10 max) -- ❌ Tasks call for running entire test suite (should run only new tests) -[OR if compliant:] -- ✅ All task groups specify 2-8 focused tests maximum -- ✅ Test verification limited to newly written tests only -- ✅ Testing-engineer adds maximum 10 tests - -**Reusability References:** -- ❌ Task 3.2 doesn't mention reusing existing form partial -- ❌ Task 4.3 recreates validation that exists in UserValidator - -**Task Specificity:** -- ⚠️ Task 3.4 "Implement best practices" too vague -- ⚠️ Task 4.2 "Add validation" needs specifics - -**Visual References:** -- ❌ Interface tasks don't mention mockup files -- ❌ No tasks for header component from mockup - -**Task Count:** -- Structure: 6 tasks ✅ -- Interface: 12 tasks ⚠️ (possibly over-engineered) - -### Check 7: Reusability and Over-Engineering -**Unnecessary New Components:** -- ❌ Creating new FormField component when shared/_form_field.erb exists -- ❌ New DataTable when components/data_table.erb available - -**Duplicated Logic:** -- ⚠️ EmailValidator being recreated (exists in app/validators/) -- ⚠️ Similar pagination logic already in PaginationService - -**Missing Reuse Opportunities:** -- User pointed to app/views/posts but not referenced -- Existing test factories not mentioned in Quality spec - -## Critical Issues -[Issues that must be fixed before implementation] -1. Not reusing existing FormField component - will create duplication -3. Visual mockup ignored: Sidebar in mockup but not specified - -## Minor Issues -[Issues that should be addressed but don't block progress] -1. Vague task descriptions -2. Extra database field that wasn't requested -3. Could leverage existing validators - -## Over-Engineering Concerns -[Features/complexity added beyond requirements] -1. Creating new components instead of reusing: FormField, DataTable -2. Audit logging system not requested -3. Complex state management for simple form -4. Excessive test coverage planned (e.g., 50+ tests when 16-34 is appropriate) -5. Comprehensive test suite requirements violating focused testing approach - -## Recommendations -1. Update spec to reuse existing form components -2. Reorder tasks to take dependencies into account -3. Add reusability analysis sections to spec -4. Update tasks to reference existing code where applicable -5. Remove unnecessary new component creation - -## Conclusion -[Overall assessment: Ready for implementation? Needs revision? Major concerns?] -``` - -### Step 5: Output Summary - -OUTPUT the following: - -``` -Specification verification complete! - -✅ Verified requirements accuracy -✅ Checked structural integrity -✅ Validated specification alignment -✅ Verified test writing limits (2-8 tests per task group, ~16-34 total) -[If visuals] ✅ Analyzed [X] visual assets -⚠️ Reusability check: [Y issues found] - -[If passed] -All specifications accurately reflect requirements, follow limited testing approach, and properly leverage existing code - -[If issues found] -⚠️ Found [X] issues requiring attention: -- [Number] reusability issues -- [Number] test writing limit violations -- [Number] critical issues -- [Number] minor issues -- [Number] over-engineering concerns - -See agent-os/specs/[this-spec]/verification/spec-verification.md for full details. -``` - -## Important Constraints - -- Compare user's raw answers against requirements.md exactly -- Check for reusability opportunities and verify that they're documented but DO NOT search and explore the codebase yourself. -- Verify test writing limits strictly: Flag any tasks that call for comprehensive testing, exhaustive coverage, or running full test suites -- Expected test counts: Implementation task groups should write 2-8 tests each, testing-engineer adds maximum 10, total ~16-34 tests per feature -- Don't add new requirements or specifications -- Focus on alignment and accuracy, not style -- Be specific about any issues found -- Distinguish between critical and minor issues -- Always check visuals even if not mentioned in requirements -- Document everything for transparency -- Visual design elements must be traceable through all specs -- Reusability should be prioritized in specs and tasks over creating new code - - -## User Standards & Preferences Compliance - -IMPORTANT: Ensure that the spec and tasks list are ALIGNED and DO NOT CONFLICT with any of user's preferred tech stack, coding conventions, or common patterns as detailed in the following files: - -@agent-os/standards/backend/api.md -@agent-os/standards/backend/migrations.md -@agent-os/standards/backend/models.md -@agent-os/standards/backend/queries.md -@agent-os/standards/frontend/accessibility.md -@agent-os/standards/frontend/components.md -@agent-os/standards/frontend/css.md -@agent-os/standards/frontend/responsive.md -@agent-os/standards/global/coding-style.md -@agent-os/standards/global/commenting.md -@agent-os/standards/global/conventions.md -@agent-os/standards/global/error-handling.md -@agent-os/standards/global/tech-stack.md -@agent-os/standards/global/validation.md -@agent-os/standards/testing/test-writing.md diff --git a/.claude/agents/agent-os/spec-writer.md b/.claude/agents/agent-os/spec-writer.md deleted file mode 100644 index 24fb25bd8..000000000 --- a/.claude/agents/agent-os/spec-writer.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -name: spec-writer -description: Use proactively to create a detailed specification document for development -tools: Write, Read, Bash, WebFetch -color: purple -model: inherit ---- - -You are a software product specifications writer. Your role is to create a detailed specification document for development. - -# Spec Writing - -## Core Responsibilities - -1. **Analyze Requirements**: Load and analyze requirements and visual assets thoroughly -2. **Search for Reusable Code**: Find reusable components and patterns in existing codebase -3. **Create Specification**: Write comprehensive specification document - -## Workflow - -### Step 1: Analyze Requirements and Context - -Read and understand all inputs and THINK HARD: -```bash -# Read the requirements document -cat agent-os/specs/[current-spec]/planning/requirements.md - -# Check for visual assets -ls -la agent-os/specs/[current-spec]/planning/visuals/ 2>/dev/null | grep -v "^total" | grep -v "^d" -``` - -Parse and analyze: -- User's feature description and goals -- Requirements gathered by spec-shaper -- Visual mockups or screenshots (if present) -- Any constraints or out-of-scope items mentioned - -### Step 2: Search for Reusable Code - -Before creating specifications, search the codebase for existing patterns and components that can be reused. - -Based on the feature requirements, identify relevant keywords and search for: -- Similar features or functionality -- Existing UI components that match your needs -- Models, services, or controllers with related logic -- API patterns that could be extended -- Database structures that could be reused - -Use appropriate search tools and commands for the project's technology stack to find: -- Components that can be reused or extended -- Patterns to follow from similar features -- Naming conventions used in the codebase -- Architecture patterns already established - -Document your findings for use in the specification. - -### Step 3: Create Core Specification - -Write the main specification to `agent-os/specs/[current-spec]/spec.md`. - -DO NOT write actual code in the spec.md document. Just describe the requirements clearly and concisely. - -Keep it short and include only essential information for each section. - -Follow this structure exactly when creating the content of `spec.md`: - -```markdown -# Specification: [Feature Name] - -## Goal -[1-2 sentences describing the core objective] - -## User Stories -- As a [user type], I want to [action] so that [benefit] -- [repeat for up to 2 max additional user stories] - -## Specific Requirements - -**Specific requirement name** -- [Up to 8 CONCISE sub-bullet points to clarify specific sub-requirements, design or architectual decisions that go into this requirement, or the technical approach to take when implementing this requirement] - -[repeat for up to a max of 10 specific requirements] - -## Visual Design -[If mockups provided] - -**`planning/visuals/[filename]`** -- [up to 8 CONCISE bullets describing specific UI elements found in this visual to address when building] - -[repeat for each file in the `planning/visuals` folder] - -## Existing Code to Leverage - -**Code, component, or existing logic found** -- [up to 5 bullets that describe what this existing code does and how it should be re-used or replicated when building this spec] - -[repeat for up to 5 existing code areas] - -## Out of Scope -- [up to 10 concise descriptions of specific features that are out of scope and MUST NOT be built in this spec] -``` - -## Important Constraints - -1. **Always search for reusable code** before specifying new components -2. **Reference visual assets** when available -3. **Do NOT write actual code** in the spec -4. **Keep each section short**, with clear, direct, skimmable specifications -5. **Do NOT deviate from the template above** and do not add additional sections - - -## User Standards & Preferences Compliance - -IMPORTANT: Ensure that the spec you create IS ALIGNED and DOES NOT CONFLICT with any of user's preferred tech stack, coding conventions, or common patterns as detailed in the following files: - -@agent-os/standards/backend/api.md -@agent-os/standards/backend/migrations.md -@agent-os/standards/backend/models.md -@agent-os/standards/backend/queries.md -@agent-os/standards/frontend/accessibility.md -@agent-os/standards/frontend/components.md -@agent-os/standards/frontend/css.md -@agent-os/standards/frontend/responsive.md -@agent-os/standards/global/coding-style.md -@agent-os/standards/global/commenting.md -@agent-os/standards/global/conventions.md -@agent-os/standards/global/error-handling.md -@agent-os/standards/global/tech-stack.md -@agent-os/standards/global/validation.md -@agent-os/standards/testing/test-writing.md diff --git a/.claude/agents/agent-os/tasks-list-creator.md b/.claude/agents/agent-os/tasks-list-creator.md deleted file mode 100644 index 4331fc539..000000000 --- a/.claude/agents/agent-os/tasks-list-creator.md +++ /dev/null @@ -1,230 +0,0 @@ ---- -name: task-list-creator -description: Use proactively to create a detailed and strategic tasks list for development of a spec -tools: Write, Read, Bash, WebFetch -color: orange -model: inherit ---- - -You are a software product tasks list writer and planner. Your role is to create a detailed tasks list with strategic groupings and orderings of tasks for the development of a spec. - -# Task List Creation - -## Core Responsibilities - -1. **Analyze spec and requirements**: Read and analyze the spec.md and/or requirements.md to inform the tasks list you will create. -2. **Plan task execution order**: Break the requirements into a list of tasks in an order that takes their dependencies into account. -3. **Group tasks by specialization**: Group tasks that require the same skill or stack specialization together (backend, api, ui design, etc.) -4. **Create Tasks list**: Create the markdown tasks list broken into groups with sub-tasks. - -## Workflow - -### Step 1: Analyze Spec & Requirements - -Read each of these files (whichever are available) and analyze them to understand the requirements for this feature implementation: -- `agent-os/specs/[this-spec]/spec.md` -- `agent-os/specs/[this-spec]/planning/requirements.md` - -Use your learnings to inform the tasks list and groupings you will create in the next step. - - -### Step 2: Create Tasks Breakdown - -Generate `agent-os/specs/[current-spec]/tasks.md`. - -**Important**: The exact tasks, task groups, and organization will vary based on the feature's specific requirements. The following is an example format - adapt the content of the tasks list to match what THIS feature actually needs. - -```markdown -# Task Breakdown: [Feature Name] - -## Overview -Total Tasks: [count] - -## Task List - -### Database Layer - -#### Task Group 1: Data Models and Migrations -**Dependencies:** None - -- [ ] 1.0 Complete database layer - - [ ] 1.1 Write 2-8 focused tests for [Model] functionality - - Limit to 2-8 highly focused tests maximum - - Test only critical model behaviors (e.g., primary validation, key association, core method) - - Skip exhaustive coverage of all methods and edge cases - - [ ] 1.2 Create [Model] with validations - - Fields: [list] - - Validations: [list] - - Reuse pattern from: [existing model if applicable] - - [ ] 1.3 Create migration for [table] - - Add indexes for: [fields] - - Foreign keys: [relationships] - - [ ] 1.4 Set up associations - - [Model] has_many [related] - - [Model] belongs_to [parent] - - [ ] 1.5 Ensure database layer tests pass - - Run ONLY the 2-8 tests written in 1.1 - - Verify migrations run successfully - - Do NOT run the entire test suite at this stage - -**Acceptance Criteria:** -- The 2-8 tests written in 1.1 pass -- Models pass validation tests -- Migrations run successfully -- Associations work correctly - -### API Layer - -#### Task Group 2: API Endpoints -**Dependencies:** Task Group 1 - -- [ ] 2.0 Complete API layer - - [ ] 2.1 Write 2-8 focused tests for API endpoints - - Limit to 2-8 highly focused tests maximum - - Test only critical controller actions (e.g., primary CRUD operation, auth check, key error case) - - Skip exhaustive testing of all actions and scenarios - - [ ] 2.2 Create [resource] controller - - Actions: index, show, create, update, destroy - - Follow pattern from: [existing controller] - - [ ] 2.3 Implement authentication/authorization - - Use existing auth pattern - - Add permission checks - - [ ] 2.4 Add API response formatting - - JSON responses - - Error handling - - Status codes - - [ ] 2.5 Ensure API layer tests pass - - Run ONLY the 2-8 tests written in 2.1 - - Verify critical CRUD operations work - - Do NOT run the entire test suite at this stage - -**Acceptance Criteria:** -- The 2-8 tests written in 2.1 pass -- All CRUD operations work -- Proper authorization enforced -- Consistent response format - -### Frontend Components - -#### Task Group 3: UI Design -**Dependencies:** Task Group 2 - -- [ ] 3.0 Complete UI components - - [ ] 3.1 Write 2-8 focused tests for UI components - - Limit to 2-8 highly focused tests maximum - - Test only critical component behaviors (e.g., primary user interaction, key form submission, main rendering case) - - Skip exhaustive testing of all component states and interactions - - [ ] 3.2 Create [Component] component - - Reuse: [existing component] as base - - Props: [list] - - State: [list] - - [ ] 3.3 Implement [Feature] form - - Fields: [list] - - Validation: client-side - - Submit handling - - [ ] 3.4 Build [View] page - - Layout: [description] - - Components: [list] - - Match mockup: `planning/visuals/[file]` - - [ ] 3.5 Apply base styles - - Follow existing design system - - Use variables from: [style file] - - [ ] 3.6 Implement responsive design - - Mobile: 320px - 768px - - Tablet: 768px - 1024px - - Desktop: 1024px+ - - [ ] 3.7 Add interactions and animations - - Hover states - - Transitions - - Loading states - - [ ] 3.8 Ensure UI component tests pass - - Run ONLY the 2-8 tests written in 3.1 - - Verify critical component behaviors work - - Do NOT run the entire test suite at this stage - -**Acceptance Criteria:** -- The 2-8 tests written in 3.1 pass -- Components render correctly -- Forms validate and submit -- Matches visual design - -### Testing - -#### Task Group 4: Test Review & Gap Analysis -**Dependencies:** Task Groups 1-3 - -- [ ] 4.0 Review existing tests and fill critical gaps only - - [ ] 4.1 Review tests from Task Groups 1-3 - - Review the 2-8 tests written by database-engineer (Task 1.1) - - Review the 2-8 tests written by api-engineer (Task 2.1) - - Review the 2-8 tests written by ui-designer (Task 3.1) - - Total existing tests: approximately 6-24 tests - - [ ] 4.2 Analyze test coverage gaps for THIS feature only - - Identify critical user workflows that lack test coverage - - Focus ONLY on gaps related to this spec's feature requirements - - Do NOT assess entire application test coverage - - Prioritize end-to-end workflows over unit test gaps - - [ ] 4.3 Write up to 10 additional strategic tests maximum - - Add maximum of 10 new tests to fill identified critical gaps - - Focus on integration points and end-to-end workflows - - Do NOT write comprehensive coverage for all scenarios - - Skip edge cases, performance tests, and accessibility tests unless business-critical - - [ ] 4.4 Run feature-specific tests only - - Run ONLY tests related to this spec's feature (tests from 1.1, 2.1, 3.1, and 4.3) - - Expected total: approximately 16-34 tests maximum - - Do NOT run the entire application test suite - - Verify critical workflows pass - -**Acceptance Criteria:** -- All feature-specific tests pass (approximately 16-34 tests total) -- Critical user workflows for this feature are covered -- No more than 10 additional tests added when filling in testing gaps -- Testing focused exclusively on this spec's feature requirements - -## Execution Order - -Recommended implementation sequence: -1. Database Layer (Task Group 1) -2. API Layer (Task Group 2) -3. Frontend Design (Task Group 3) -4. Test Review & Gap Analysis (Task Group 4) -``` - -**Note**: Adapt this structure based on the actual feature requirements. Some features may need: -- Different task groups (e.g., email notifications, payment processing, data migration) -- Different execution order based on dependencies -- More or fewer sub-tasks per group - -## Important Constraints - -- **Create tasks that are specific and verifiable** -- **Group related tasks:** For example, group back-end engineering tasks together and front-end UI tasks together. -- **Limit test writing during development**: - - Each task group (1-3) should write 2-8 focused tests maximum - - Tests should cover only critical behaviors, not exhaustive coverage - - Test verification should run ONLY the newly written tests, not the entire suite - - If there is a dedicated test coverage group for filling in gaps in test coverage, this group should add only a maximum of 10 additional tests IF NECESSARY to fill critical gaps -- **Use a focused test-driven approach** where each task group starts with writing 2-8 tests (x.1 sub-task) and ends with running ONLY those tests (final sub-task) -- **Include acceptance criteria** for each task group -- **Reference visual assets** if visuals are available - - -## User Standards & Preferences Compliance - -IMPORTANT: Ensure that the tasks list you create IS ALIGNED and DOES NOT CONFLICT with any of user's preferred tech stack, coding conventions, or common patterns as detailed in the following files: - -@agent-os/standards/backend/api.md -@agent-os/standards/backend/migrations.md -@agent-os/standards/backend/models.md -@agent-os/standards/backend/queries.md -@agent-os/standards/frontend/accessibility.md -@agent-os/standards/frontend/components.md -@agent-os/standards/frontend/css.md -@agent-os/standards/frontend/responsive.md -@agent-os/standards/global/coding-style.md -@agent-os/standards/global/commenting.md -@agent-os/standards/global/conventions.md -@agent-os/standards/global/error-handling.md -@agent-os/standards/global/tech-stack.md -@agent-os/standards/global/validation.md -@agent-os/standards/testing/test-writing.md diff --git a/.claude/commands/agent-os/create-tasks.md b/.claude/commands/agent-os/create-tasks.md deleted file mode 100644 index bf6ac2323..000000000 --- a/.claude/commands/agent-os/create-tasks.md +++ /dev/null @@ -1,40 +0,0 @@ -# Task List Creation Process - -You are creating a tasks breakdown from a given spec and requirements for a new feature. - -## PHASE 1: Get and read the spec.md and/or requirements document(s) - -You will need ONE OR BOTH of these files to inform your tasks breakdown: -- `agent-os/specs/[this-spec]/spec.md` -- `agent-os/specs/[this-spec]/planning/requirements.md` - -IF you don't have ONE OR BOTH of those files in your current conversation context, then ask user to provide direction on where to you can find them by outputting the following request then wait for user's response: - -``` -I'll need a spec.md or requirements.md (or both) in order to build a tasks list. - -Please direct me to where I can find those. If you haven't created them yet, you can run /shape-spec or /write-spec. -``` - -## PHASE 2: Create tasks.md - -Once you have `spec.md` AND/OR `requirements.md`, use the **tasks-list-creator** subagent to break down the spec and requirements into an actionable tasks list with strategic grouping and ordering. - -Provide the tasks-list-creator: -- `agent-os/specs/[this-spec]/spec.md` (if present) -- `agent-os/specs/[this-spec]/planning/requirements.md` (if present) -- `agent-os/specs/[this-spec]/planning/visuals/` and its' contents (if present) - -The tasks-list-creator will create `tasks.md` inside the spec folder. - -## PHASE 3: Inform user - -Once the tasks-list-creator has created `tasks.md` output the following to inform the user: - -``` -Your tasks list ready! - -✅ Tasks list created: `agent-os/specs/[this-spec]/tasks.md` - -NEXT STEP 👉 Run `/implement-tasks` (simple, effective) or `/orchestrate-tasks` (advanced, powerful) to start building! -``` diff --git a/.claude/commands/agent-os/implement-tasks.md b/.claude/commands/agent-os/implement-tasks.md deleted file mode 100644 index f5db27754..000000000 --- a/.claude/commands/agent-os/implement-tasks.md +++ /dev/null @@ -1,55 +0,0 @@ -# Spec Implementation Process - -Now that we have a spec and tasks list ready for implementation, we will proceed with implementation of this spec by following this multi-phase process: - -PHASE 1: Determine which task group(s) from tasks.md should be implemented -PHASE 2: Delegate implementation to the implementer subagent -PHASE 3: After ALL task groups have been implemented, delegate to implementation-verifier to produce the final verification report. - -Follow each of these phases and their individual workflows IN SEQUENCE: - -## Multi-Phase Process - -### PHASE 1: Determine which task group(s) to implement - -First, check if the user has already provided instructions about which task group(s) to implement. - -**If the user HAS provided instructions:** Proceed to PHASE 2 to delegate implementation of those specified task group(s) to the **implementer** subagent. - -**If the user has NOT provided instructions:** - -Read `agent-os/specs/[this-spec]/tasks.md` to review the available task groups, then output the following message to the user and WAIT for their response: - -``` -Should we proceed with implementation of all task groups in tasks.md? - -If not, then please specify which task(s) to implement. -``` - -### PHASE 2: Delegate implementation to the implementer subagent - -Delegate to the **implementer** subagent to implement the specified task group(s): - -Provide to the subagent: -- The specific task group(s) from `agent-os/specs/[this-spec]/tasks.md` including the parent task, all sub-tasks, and any sub-bullet points -- The path to this spec's documentation: `agent-os/specs/[this-spec]/spec.md` -- The path to this spec's requirements: `agent-os/specs/[this-spec]/planning/requirements.md` -- The path to this spec's visuals (if any): `agent-os/specs/[this-spec]/planning/visuals` - -Instruct the subagent to: -1. Analyze the provided spec.md, requirements.md, and visuals (if any) -2. Analyze patterns in the codebase according to its built-in workflow -3. Implement the assigned task group according to requirements and standards -4. Update `agent-os/specs/[this-spec]/tasks.md` to mark completed tasks with `- [x]` - -### PHASE 3: Produce the final verification report - -IF ALL task groups in tasks.md are marked complete with `- [x]`, then proceed with this step. Otherwise, return to PHASE 1. - -Assuming all tasks are marked complete, then delegate to the **implementation-verifier** subagent to do its implementation verification and produce its final verification report. - -Provide to the subagent the following: -- The path to this spec: `agent-os/specs/[this-spec]` -Instruct the subagent to do the following: - 1. Run all of its final verifications according to its built-in workflow - 2. Produce the final verification report in `agent-os/specs/[this-spec]/verifications/final-verification.md`. diff --git a/.claude/commands/agent-os/orchestrate-tasks.md b/.claude/commands/agent-os/orchestrate-tasks.md deleted file mode 100644 index 0e32d9a5e..000000000 --- a/.claude/commands/agent-os/orchestrate-tasks.md +++ /dev/null @@ -1,180 +0,0 @@ -# Process for Orchestrating a Spec's Implementation - -Now that we have a spec and tasks list ready for implementation, we will proceed with orchestrating implementation of each task group by a dedicated agent using the following MULTI-PHASE process. - -Follow each of these phases and their individual workflows IN SEQUENCE: - -## Multi-Phase Process - -### FIRST: Get tasks.md for this spec - -IF you already know which spec we're working on and IF that spec folder has a `tasks.md` file, then use that and skip to the NEXT phase. - -IF you don't already know which spec we're working on and IF that spec folder doesn't yet have a `tasks.md` THEN output the following request to the user: - -``` -Please point me to a spec's `tasks.md` that you want to orchestrate implementation for. - -If you don't have one yet, then run any of these commands first: -/shape-spec -/write-spec -/create-tasks -``` - -### NEXT: Create orchestration.yml to serve as a roadmap for orchestration of task groups - -In this spec's folder, create this file: `agent-os/specs/[this-spec]/orchestration.yml`. - -Populate this file with with the names of each task group found in this spec's `tasks.md` and use this EXACT structure for the content of `orchestration.yml`: - -```yaml -task_groups: - - name: [task-group-name] - - name: [task-group-name] - - name: [task-group-name] - # Repeat for each task group found in tasks.md -``` - -### NEXT: Ask user to assign subagents to each task group - -Next we must determine which subagents should be assigned to which task groups. Ask the user to provide this info using the following request to user and WAIT for user's response: - -``` -Please specify the name of each subagent to be assigned to each task group: - -1. [task-group-name] -2. [task-group-name] -3. [task-group-name] -[repeat for each task-group you've added to orchestration.yml] - -Simply respond with the subagent names and corresponding task group number and I'll update orchestration.yml accordingly. -``` - -Using the user's responses, update `orchestration.yml` to specify those subagent names. `orchestration.yml` should end up looking like this: - -```yaml -task_groups: - - name: [task-group-name] - claude_code_subagent: [subagent-name] - - name: [task-group-name] - claude_code_subagent: [subagent-name] - - name: [task-group-name] - claude_code_subagent: [subagent-name] - # Repeat for each task group found in tasks.md -``` - -For example, after this step, the `orchestration.yml` file might look like this (exact names will vary): - -```yaml -task_groups: - - name: authentication-system - claude_code_subagent: backend-specialist - - name: user-dashboard - claude_code_subagent: frontend-specialist - - name: api-endpoints - claude_code_subagent: backend-specialist -``` - -### NEXT: Ask user to assign standards to each task group - -Next we must determine which standards should guide the implementation of each task group. Ask the user to provide this info using the following request to user and WAIT for user's response: - -``` -Please specify the standard(s) that should be used to guide the implementation of each task group: - -1. [task-group-name] -2. [task-group-name] -3. [task-group-name] -[repeat for each task-group you've added to orchestration.yml] - -For each task group number, you can specify any combination of the following: - -"all" to include all of your standards -"global/*" to include all of the files inside of standards/global -"frontend/css.md" to include the css.md standard file -"none" to include no standards for this task group. -``` - -Using the user's responses, update `orchestration.yml` to specify those standards for each task group. `orchestration.yml` should end up having AT LEAST the following information added to it: - -```yaml -task_groups: - - name: [task-group-name] - standards: - - [users' 1st response for this task group] - - [users' 2nd response for this task group] - - [users' 3rd response for this task group] - # Repeat for all standards that the user specified for this task group - - name: [task-group-name] - standards: - - [users' 1st response for this task group] - - [users' 2nd response for this task group] - # Repeat for all standards that the user specified for this task group - # Repeat for each task group found in tasks.md -``` - -For example, after this step, the `orchestration.yml` file might look like this (exact names will vary): - -```yaml -task_groups: - - name: authentication-system - standards: - - all - - name: user-dashboard - standards: - - global/* - - frontend/components.md - - frontend/css.md - - name: task-group-with-no-standards - - name: api-endpoints - standards: - - backend/* - - global/error-handling.md -``` - -Note: If the `use_claude_code_subagents` flag is enabled, the final `orchestration.yml` would include BOTH `claude_code_subagent` assignments AND `standards` for each task group. - -### NEXT: Delegate task groups implementations to assigned subagents - -Loop through each task group in `agent-os/specs/[this-spec]/tasks.md` and delegate its implementation to the assigned subagent specified in `orchestration.yml`. - -For each delegation, provide the subagent with: -- The task group (including the parent task and all sub-tasks) -- The spec file: `agent-os/specs/[this-spec]/spec.md` -- Instruct subagent to: - - Perform their implementation - - Check off the task and sub-task(s) in `agent-os/specs/[this-spec]/tasks.md` - -In addition to the above items, also instruct the subagent to closely adhere to the user's standards & preferences as specified in the following files. To build the list of file references to give to the subagent, follow these instructions: - -#### Compile Implementation Standards - -Use the following logic to compile a list of file references to standards that should guide implementation: - -##### Steps to Compile Standards List - -1. Find the current task group in `orchestration.yml` -2. Check the list of `standards` specified for this task group in `orchestration.yml` -3. Compile the list of file references to those standards, one file reference per line, using this logic for determining which files to include: - a. If the value for `standards` is simply `all`, then include every single file, folder, sub-folder and files within sub-folders in your list of files. - b. If the item under standards ends with "*" then it means that all files within this folder or sub-folder should be included. For example, `frontend/*` means include all files and sub-folders and their files located inside of `agent-os/standards/frontend/`. - c. If a file ends in `.md` then it means this is one specific file you must include in your list of files. For example `backend/api.md` means you must include the file located at `agent-os/standards/backend/api.md`. - d. De-duplicate files in your list of file references. - -##### Output Format - -The compiled list of standards should look something like this, where each file reference is on its own line and begins with `@`. The exact list of files will vary: - -``` -@agent-os/standards/global/coding-style.md -@agent-os/standards/global/conventions.md -@agent-os/standards/global/tech-stack.md -@agent-os/standards/backend/api/authentication.md -@agent-os/standards/backend/api/endpoints.md -@agent-os/standards/backend/api/responses.md -@agent-os/standards/frontend/css.md -@agent-os/standards/frontend/responsive.md -``` - - -Provide all of the above to the subagent when delegating tasks for it to implement. diff --git a/.claude/commands/agent-os/plan-product.md b/.claude/commands/agent-os/plan-product.md deleted file mode 100644 index fc579cf82..000000000 --- a/.claude/commands/agent-os/plan-product.md +++ /dev/null @@ -1,36 +0,0 @@ -## Product Planning Process - -You are helping to plan and document the mission, roadmap and tech stack for the current product. This will include: - -- **Gathering Information**: The user's product vision, user personas, problems and key features -- **Mission Document**: Take what you've gathered and create a concise mission document -- **Roadmap**: Create a phased development plan with prioritized features -- **Tech stack**: Establish the technical stack used for all aspects of this product's codebase - -This process will create these files in `agent-os/product/` directory. - -### PHASE 1: Gather Product Requirements - -Use the **product-planner** subagent to create comprehensive product documentation. - -IF the user has provided any details in regards to the product idea, its purpose, features list, target users and any other details then provide those to the **product-planner** subagent. - -The product-planner will: -- Confirm (or gather) product idea, features, target users, confirm the tech stack and gather other details -- Create `agent-os/product/mission.md` with product vision and strategy -- Create `agent-os/product/roadmap.md` with phased development plan -- Create `agent-os/product/tech-stack.md` documenting all of this product's tech stack choices - -### PHASE 2: Inform the user - -After all steps are complete, output the following to inform the user: - -``` -Your product planning is all set! - -✅ Product mission: `agent-os/product/mission.md` -✅ Product roadmap: `agent-os/product/roadmap.md` -✅ Product tech stack: `agent-os/product/tech-stack.md` - -NEXT STEP 👉 Run `/shape-spec` or `/write-spec` to start work on a feature! -``` diff --git a/.claude/commands/agent-os/shape-spec.md b/.claude/commands/agent-os/shape-spec.md deleted file mode 100644 index c83be74b3..000000000 --- a/.claude/commands/agent-os/shape-spec.md +++ /dev/null @@ -1,52 +0,0 @@ -# Spec Shaping Process - -You are helping me shape and plan the scope for a new feature. The following process is aimed at documenting our key decisions regarding scope, design and architecture approach. We will use our findings from this process later when we write the formal spec document (but we are NOT writing the formal spec yet). - -This process will follow 3 main phases, each with their own workflow steps: - -Process overview (details to follow) - -PHASE 1. Initilize spec -PHASE 2. Research requirements for this spec -PHASE 3. Inform the user that the spec has been initialized - -Follow each of these phases and their individual workflows IN SEQUENCE: - -## Multi-Phase Process: - -### PHASE 1: Initialize Spec - -Use the **spec-shaper** subagent to initialize a new spec. - -IF the user has provided a description, provide that to the spec-initializer. - -The spec-initializer will provide the path to the dated spec folder (YYYY-MM-DD-spec-name) they've created. - -### PHASE 2: Research Requirements - -After spec-initializer completes, immediately use the **spec-shaper** subagent: - -Provide the spec-shaper with: -- The spec folder path from spec-initializer - -The spec-shaper will give you several separate responses that you MUST show to the user. These include: -1. Numbered clarifying questions along with a request for visual assets (show these to user, wait for user's response) -2. Follow-up questions if needed (based on user's answers and provided visuals) - -**IMPORTANT**: -- Display these questions to the user and wait for their response -- The spec-shaper may ask you to relay follow-up questions that you must present to user - -### PHASE 3: Inform the user - -After all steps complete, inform the user: - -``` -Spec shaping is complete! - -✅ Spec folder created: `[spec-path]` -✅ Requirements gathered -✅ Visual assets: [Found X files / No files provided] - -NEXT STEP 👉 Run `/write-spec` to generate the detailed specification document. -``` diff --git a/.claude/commands/agent-os/write-spec.md b/.claude/commands/agent-os/write-spec.md deleted file mode 100644 index 49c2cc30b..000000000 --- a/.claude/commands/agent-os/write-spec.md +++ /dev/null @@ -1,22 +0,0 @@ -# Spec Writing Process - -You are creating a comprehensive specification for a new feature. - -Use the **spec-writer** subagent to create the specification document for this spec: - -Provide the spec-writer with: -- The spec folder path (find the current one or the most recent in `agent-os/specs/*/`) -- The requirements from `planning/requirements.md` -- Any visual assets in `planning/visuals/` - -The spec-writer will create `spec.md` inside the spec folder. - -Once the spec-writer has created `spec.md` output the following to inform the user: - -``` -Your spec.md is ready! - -✅ Spec document created: `[spec-path]` - -NEXT STEP 👉 Run `/create-tasks` to generate your tasks list for this spec. -``` diff --git a/.github/actions/detect-inert-diff/action.yml b/.github/actions/detect-inert-diff/action.yml index 88aa861e8..ae2689a95 100644 --- a/.github/actions/detect-inert-diff/action.yml +++ b/.github/actions/detect-inert-diff/action.yml @@ -49,7 +49,6 @@ runs: .agents/*) ;; # agent rules/skills (linted, not tested) .claude/*) ;; .cursor/*) ;; - agent-os/*) ;; LICENSE) ;; *) inert=false; break ;; esac diff --git a/agent-os/config.yml b/agent-os/config.yml deleted file mode 100644 index ad8c8f862..000000000 --- a/agent-os/config.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: 2.1.1 -last_compiled: 2026-02-10 18:29:57 - -# ================================================ -# Compiled with the following settings: -# -# To change these settings, run ~/agent-os/scripts/project-update.sh to re-compile your project with the new settings. -# ================================================ -profile: default -claude_code_commands: true -use_claude_code_subagents: true -agent_os_commands: false -standards_as_claude_code_skills: false diff --git a/agent-os/product/README.md b/agent-os/product/README.md deleted file mode 100644 index 5c1e87e04..000000000 --- a/agent-os/product/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Agent OS product artifacts - -This directory contains artifacts produced by the **Agent OS product planning workflow** (mission, roadmap, tech stack, naming). - -- [naming.md](naming.md) — how we name by user value; the piece names (Prisma Composer, Module, Prisma Data; "Prisma App" = the artifact) and the compose-from-blocks distribution model. -- [vocabulary-tests.md](vocabulary-tests.md) — the spoken-sentence rubrics: unit nouns (System → Module, ADR-0025) and referential product names (Prisma App → Prisma Composer, ADR-0026). - -## Source of truth for design/architecture - -Prisma Composer design and architecture documentation lives in: - -- `docs/design/` - -Agent OS artifacts should point to the source-of-truth docs rather than duplicating them. diff --git a/agent-os/product/design.md b/agent-os/product/design.md deleted file mode 100644 index 984b0c1b6..000000000 --- a/agent-os/product/design.md +++ /dev/null @@ -1,7 +0,0 @@ -# Moved: design docs live in `docs/design/` - -This file is a stub kept for compatibility with Agent OS workflows. - -- Prisma Composer design overview: `docs/design/10-domains/prisma-composer-overview.md` -- Prisma Composer design docs root: `docs/design/README.md` - diff --git a/agent-os/product/mission.md b/agent-os/product/mission.md deleted file mode 100644 index c27c35cf1..000000000 --- a/agent-os/product/mission.md +++ /dev/null @@ -1,62 +0,0 @@ -# Product Mission - -## Pitch -Prisma Composer is a TypeScript application framework that helps application developers (and the AI agents they work with) define -multi-service applications for the Prisma Platform by authoring code that can be statically analyzed into -infrastructure-as-code requirements and wired at runtime via dependency injection. - -## Users - -### Primary Customers -- Application developers: Build apps in TypeScript and want the platform to infer and provision the required services. -- Agent builders / power users: Use AI agents to scaffold, extend, and maintain applications with predictable structure. -- Prisma Platform team (internal): Needs a clear artifact + metadata contract to provision and run user apps safely. - -### User Personas -**Application Developer** (22–45) -- **Role:** Full-stack developer -- **Context:** Shipping an app that needs an API, background work, storage, streaming, and scheduled tasks -- **Pain Points:** Manually wiring infrastructure, unclear service boundaries, brittle config drift across environments -- **Goals:** Define the app in code once; get the right services provisioned; predictable local dev and testing - -**Agent-Assisted Builder** (22–45) -- **Role:** Developer using AI agents as a primary workflow -- **Context:** Wants agents to generate scaffolded services and iterate without “breaking the shape” of the app -- **Pain Points:** Unstructured codebases are hard for agents to modify safely; inconsistent patterns and hidden coupling -- **Goals:** Framework conventions that make apps easy for agents to scaffold, refactor, and verify over time - -## The Problem - -### Multi-service apps are hard to define, provision, and evolve -Modern apps are composed of services (HTTP APIs, workers, event subscribers, cron jobs, streaming, storage) but developers -must manually specify and maintain infrastructure configuration and runtime wiring. This leads to config drift, slower -iteration, and fragile environment parity (local/test/prod). - -**Our Solution:** Provide a TypeScript framework for defining executable units and their dependencies in a way that can be -statically analyzed into a service topology (IaC requirements), then executed with dependency-injected implementations -appropriate to the environment (Prisma Platform, local emulation, isolated tests). - -## Differentiators - -### Static topology inference from code structure -Unlike traditional “config-first” IaC, Prisma Composer infers the service map directly from TypeScript definitions. -This results in fewer sources of truth and less drift between code and infrastructure. - -### Environment-swappable implementations via DI -Unlike frameworks that tightly couple runtime services, Prisma Composer is designed to swap implementations for local dev and tests -without changing app code, enabling fast iteration and easy isolation. - -## Key Features - -### Core Features -- **Service definition DSL:** Define units (HTTP APIs, workers, subscribers, cron) and their dependencies in TypeScript. -- **Static graph builder:** Build a service dependency graph/topology from code for provisioning and deployment planning. -- **Execution entrypoints:** Run the app’s entrypoints (server, worker, subscriber, etc.) in a consistent way. - -### Collaboration Features -- **Composable Components:** Compose pre-packaged units with explicit “ports” (dependencies) and link them together safely. -- **Prisma ORM integration:** Seamless integration with Prisma ORM so common workflows work out of the box. - -### Advanced Features -- **Platform interface contract:** Artifact structure + metadata map (JSON) for Prisma Compute/Foundry to provision and run - services and enforce dependency/data contracts. diff --git a/agent-os/product/naming-proposal.md b/agent-os/product/naming-proposal.md deleted file mode 100644 index 7bba9a67c..000000000 --- a/agent-os/product/naming-proposal.md +++ /dev/null @@ -1,134 +0,0 @@ -# Naming the family — a proposal - -> **Status: superseded by [ADR-0014](../../docs/design/90-decisions/ADR-0014-one-authoring-primitive.md).** -> This is the original proposal that opened the naming discussion, kept as a record of -> the strategic reasoning (one product identity; why now, with agents). The decision -> differs in three ways: the framework is **Prisma App** — not "Prisma Composer"; the -> building block is a **System** — not "Hex"; and the registry (proposed here as -> "Hexicon") is **deferred**, its name reopened. **Prisma ORM → Prisma Data** remains -> proposed. Where this doc says "Prisma Composer" read "Prisma App", and where it says -> "Hex" read "System". - -I'd like to propose a holistic naming scheme for the Prisma family that reorients us -away from products with individual identities, like MakerKit, toward a single product -identity built around the **Prisma App** — assembled from focused components that each -serve one purpose. - -Here's the whole thing in one breath: - -> You build a **Prisma App** by snapping together **Hexes** — reusable building -> blocks you install from a public registry. You wire them together with **Prisma -> Composer**. You deploy it to **Prisma Cloud**, where it runs on **Prisma Compute**, -> its data modeled by **Prisma Data** and persisted to **Prisma Postgres**. - -## The family, named by role - -The components you compose into a Prisma App: - -| Component | Role | What it does for you | -|---|---|---| -| Prisma Compute | execute | my code runs | -| Prisma Postgres | persist | my data has a home | -| Prisma Streams | stream | my events flow and survive | -| Prisma Data | data | I model, query, and manage my data | -| Prisma Composer | compose | my app comes together from parts | - -And the platform around them: - -- **Prisma Cloud** — where your app is deployed and runs. -- **Prisma Hexicon** — the registry you install Hexes from. -- **Prisma Console** — the view across all your deployments. -- **Prisma Dev** — your whole app, running locally. - -## Two renames worth flagging - -- **MakerKit → Prisma Composer.** "MakerKit" sounds like a standalone starter kit and - doesn't sit in the family. What it actually does is *compose* the other pieces into - an app — so the name should just say that. Prisma Composer belongs. -- **Prisma ORM → Prisma Data.** This one's a bigger call, so it has [its own - write-up](prisma-data-rename.md). Short version: Prisma ORM was going to become - "Prisma 8", but it isn't really the next ORM — it's a different product with a - different mental model, and a version number would promise a smooth upgrade we can't - honor (and cap us at "still an ORM"). "Data" names what people actually care about, - reads clearly to the non-technical, agent-assisted builders we're going after, and - is broad enough to be more than an ORM. Prisma ORM doesn't go anywhere — existing - users keep it; new work starts on Prisma Data. - -## One product, many components - -Right now each product has its own identity — MakerKit, Prisma ORM, Prisma Postgres, -Prisma Compute (and in the past, Accelerate, Pulse, Optimize). They read like separate -products that happen to share a logo. - -We've seen this firsthand with Prisma Postgres: on its own it has no unique value -proposition — Postgres is a commodity — and our strategy has always been to -differentiate through synergy. **Our product family names should convey that**, -instead of asking each component to stand on its own like a separate brand. - -So the naming should **orient around the value people actually want — an App — not the -individual components** that deliver it but mean little in isolation. - -The hero, then, isn't any single product. It's the **Prisma App**, and everything else -is a named part of building and running one. Each component name only has to make sense -*in that context* — the way "Compute" and "Data" click the moment you know they're -parts of an app, rather than working as globally unique brand identifiers. - -The family means more together than any piece does alone: read the roles top to bottom -and they basically spell out "build an app." - -The rule underneath it all: **name each part for what it's *for*, not how it works.** -Only the App has to be a word people identify with — "my app," "my data." The -components just need to be clear in that context: nobody says "my Compute," they say -"my app runs on Compute" — which is exactly right, because Compute is a supporting -part, not the hero. And where a component maps to something you *do*, the name says so — -you **compose** your app with Prisma Composer; the wiring it generates underneath stays -out of the name. - -## Why now: building apps fast, with agents - -The reason this matters is where app-building is going. We want people — and -increasingly their agents — to assemble apps *fast*: - -- **Click Hexes together.** An app is composed from **Hexes** — reusable building - blocks like auth or billing — not hand-wired from scratch. -- **Pull from a registry of pre-built Hexes.** You (or your agent) grab what you need - instead of writing it. -- **Lean on Contracts and a simple model.** Each Hex snaps in through a typed - Contract — a boundary the machine, and the agent, can actually check — so - composition is safe by construction. - -Names have to serve that story: clear, predictable, boring in the best way. An agent -composing an app shouldn't have to decode cute product names, and neither should a -new developer. - -## Hexicon: a home for Hexes (like skills.sh) - -Hexes need somewhere to live — a place to publish, discover, and install them. -That's **Prisma Hexicon** (hex + lexicon = "the catalog of Hexes"), at hexicon.dev. - -The trick is that **we don't have to host anything.** Hexes are just TypeScript -packages, so npm does the hosting — versions, resolution, all of it, for free. -Hexicon is the thin, valuable layer on top: search, ranking, trust, and a -one-command install. It's the same split as skills.sh — decentralized hosting plus a -named directory everyone goes to. And unlike a plain package, installing a Hex -doesn't just download it — Prisma Composer wires it into your app. - -The registry is the flywheel: the more Hexes people publish, the more valuable the -whole thing gets. That's the part I'm most excited about. - -## Prisma Dev: your whole app, locally - -**Prisma Dev** runs your entire app on your laptop while you build — a local -emulation of everything Prisma Cloud provides. Under the hood it leans on Alchemy to -stand up local stand-ins for Compute, Postgres, Streams, and the rest, so local dev -matches production without you wiring anything up. Build and test the whole thing -before it ever ships. - -## So, the pitch - -One identity — the **Prisma App** — with components named for what they do in -context. Rename **MakerKit → Prisma Composer** and **Prisma ORM → Prisma Data**. -Build **Hexicon** as the Hex registry (hosting stays on npm). And **Prisma Dev** to -run it all locally. - -Thoughts? 🙂 diff --git a/agent-os/product/naming.md b/agent-os/product/naming.md deleted file mode 100644 index bec59ec21..000000000 --- a/agent-os/product/naming.md +++ /dev/null @@ -1,203 +0,0 @@ -# Product Naming & Distribution - -A developer builds a **Prisma App** by composing Prisma primitives — Postgres, -Compute, Data, and more — wired together by **Prisma Composer**, the framework. We -name every piece for the **value the user gets from it**, not the machinery that -delivers it. This document covers the product family, how Prisma Composer fits into -it, the vocabulary it introduces, and how its building blocks (Modules) are -distributed. - -The framework name and its unit are settled: **Prisma Composer** (← Prisma App ← -MakerKit), recorded in -[ADR-0026](../../docs/design/90-decisions/ADR-0026-name-the-framework-prisma-compose.md), -and **Module** (← System ← Hex), recorded in -[ADR-0025](../../docs/design/90-decisions/ADR-0025-name-the-unit-of-composition-module.md). -**"Prisma App" names the artifact** — the thing you build and deploy — never the -tool. -**Prisma Data** (← Prisma ORM) is still proposed. The **registry** name is deferred -until the registry itself is built; **Prism** is on its shortlist. - -## The Prisma product family - -Each primitive is named for its role. Read down the value column and it says what -building an app is actually *for*: - -| Primitive | Role | The value to the user | -|---|---|---| -| Prisma Postgres | persist | my data has a home | -| Prisma Compute | execute | my code runs | -| Prisma Data *(← Prisma ORM)* | data | I model, access, and manage my data | -| Prisma Composer *(← Prisma App ← MakerKit)* | compose | my app comes together from parts | -| Durable Streams | stream | my events flow and survive | -| Connection | connect | my services reach each other | - -A product name need not equal its role word — but for the framework, it now does: -Prisma Composer's role is "compose," exactly as Compute's is "execute." The value -word — App — belongs to the artifact, and the artifact keeps it: you build **Prisma -Apps** with Prisma Composer. - -## How Prisma Composer fits - -The other primitives each deliver one capability. **Prisma Composer is different: it -is the framework that assembles the others into a running app.** It is the -composition layer of the family — and it introduces its own small vocabulary for -the job: - -| Term | What it is | The value it names | -|---|---|---| -| **Prisma App** | the artifact: the application you build (the outermost Module) | software with users and features — the whole point | -| **Module** | a building block you compose | a capability you reuse instead of writing | -| **Extension** | what you slot into `prisma-composer.config.ts` | the toolchain reaches my target/stack | -| **Topology** | the graph the framework produces | *(machinery — the user never says this)* | - -You build a **Prisma App** by composing **Modules** with **Prisma Composer** — each -Module wrapping primitives like Compute and Postgres; the framework infers the -**Topology** and provisions it. App, Module, and Composer are the words a developer -says; Topology is the machinery underneath. The App is not a separate construct — it -is simply the outermost Module, the one you point `prisma-composer deploy` at. (The -whole surface carries the name: `@prisma/composer*` packages, the `prisma-composer` -CLI, `prisma-composer.config.ts` — per ADR-0026.) - -## Name for value, not machinery - -That split — the word the user says versus the term for how it works — is the rule -behind every name here. Users say "my app" and "my data"; nobody says "my topology." -So the brand takes the first, and the precise term stays below, kept exact but -unnamed in the marketing. - -Four questions decide a name: - -1. **Would the user put "my" in front of it?** "My app," "my data" — yes. "My - topology" — no; that's the wiring the tool produces, not the thing you set out to - build. -2. **Does it predict the tooling?** "Data" tells you to expect model, migrate, - query, types. A clever coinage tells you nothing. -3. **Does it name the goal, not the tax?** People value data *access*; migration is - a necessary step, sometimes an obstacle, never the goal. Name the reward, not the - chore. -4. **Does it keep the family legible?** Components named for their role read as the - parts-list of one app — worth more than any single clever standalone name. - -These four measure how a name **describes**. Names that people talk *about* — above -all a framework, a milieu developers live inside — must also **refer**: pass the -workbench frame ("I'm working on this feature in ___"), the bare-token test, the -identity frames, and the hard artifact-collision rule (never name a tool after its -output). Both rubrics live in [vocabulary-tests.md](vocabulary-tests.md). Applying -the description tests alone is how the unit was misnamed "System" -([ADR-0025](../../docs/design/90-decisions/ADR-0025-name-the-unit-of-composition-module.md)) -and the framework was misnamed "Prisma App" -([ADR-0026](../../docs/design/90-decisions/ADR-0026-name-the-framework-prisma-compose.md)). - -The payoff of taking the value word for the brand: the precise words stay **free to -mean exactly what they mean one level down**. The same shape repeats at every layer — -the user names the left column, writes the middle, and the system consumes the -right: - -| Product (what the user values) | Authored as | Compiles to | -|---|---|---| -| **App** | **Modules** you snap together | a **Topology** | -| **Prisma Data** | **models** in PSL | a **Contract** | - -Note that "App" sits in the *Product* column, not the *Authored as* column — you do -not write an `app()`; you write `module()`, and the App is the outermost one. The -App is the artifact the user values; Prisma Composer is the tool that makes the row -true. - -## The data layer: Prisma Data - -The value here is *your data* — and above all accessing and querying it. That is why -the layer is named **Data**, not "Model" or "Contract." Modeling and migration are -the way in, not the goal, and you don't brand a product after the tax it charges. -Naming it "Data" also keeps the two precise words at work: you still author -**models** in PSL — the part of Prisma developers love, untouched — and those models -compile to a **Contract**, the typed boundary a Module's input requires and a -Postgres output satisfies. Data is the value; model is what you write; Contract is -what the app wires against. - -## The building block: Module - -A Module is a bounded context with typed inputs and outputs that behaves like a -service (see `docs/design/03-domain-model/glossary.md`). The typed boundary is what -makes it reusable: a stranger's auth Module drops into your app with a contract the -machine can check — which is what lets an **agent**, not just a human, compose it in -safely. Giving the shared unit its own short noun follows the tradition of a gem, a -crate, a package. - -"Module" replaced the earlier names "Hex" and "System" after both failed the -spoken-sentence rubric in [vocabulary-tests.md](vocabulary-tests.md) — Hex by -forcing ecosystem identity onto the unit noun, System by having a size floor -("install the cron system" is a category error; a system is operated, not -installed). "Module" has no size floor, sits naturally in the install and publish -frames, and its prior art (Nest, Angular, Terraform) means exactly what we mean. The -full reasoning is in -[ADR-0025](../../docs/design/90-decisions/ADR-0025-name-the-unit-of-composition-module.md). - -Vocabulary sits in three registers: a **package** is the artifact npm hosts (npm's -word, not ours); an **extension** slots into `prisma-composer.config.ts` and extends the -toolchain; a **Module** is what you plug together inside the app. One package may -register an extension and provide modules — "use the cron module from the composer-prisma-cloud -extension" is the model in one sentence. - -## How Modules are distributed - -Hosting and discovery are split: - -- **Hosting → npm.** Modules are ordinary TypeScript libraries. npm brings semver, - resolution, and tooling for free; the substrate stays boring and commodity. -- **Discovery → a registry.** A thin, named directory on top: search, ranking, - trust, and a one-command install. **Its name is deferred** until the registry is - built. Because the unit noun carries no identity burden, the registry name can be - chosen purely for the identity frames ("it's on ___," "publish to ___") — the - current shortlist holds **Prism** (Prisma-centric; tolerable adjacency at - registry frequency, disqualifying at unit frequency). - -This is the shape skills.sh proved — decentralized hosting, a named central -directory, one-command install — with one deliberate difference: **the registry's -install composes, it doesn't just copy.** skills.sh drops text files into an agent's -config; here the install wires a Module's typed contract into the app's topology. -That richer install is the point, and it depends on the typed-contract model being -sound. Because community Modules are arbitrary npm packages, the registry recognizes -them by convention — a `keywords` entry or a manifest field. - -## The framework: Prisma Composer - -"Composer" is the agent noun for the tool that composes — the compiler / bundler / -linter / formatter family. It's semantically exact where every synonym is not: -build, construct, and make describe fabricating from raw material; *compose* means -assembling finished parts into a whole, which is the product ("composability" is -already the term for the property it sells). The verb stays the verb and the tool -takes the agent noun, aligning the name with its role word in the family table and -completing the vocabulary chain: **you compose Modules with Prisma Composer into -your Prisma App** — the compiler / compile / compilation shape. It passes the -referential battery: "I'm working on this feature in Composer" refers instantly. - -The agent-noun form is also what buys distance from **Compute**. "Compose" and -"Compute" are two syllables a single vowel apart and collide in speech; "Composer" -adds a third syllable and a distinct stressed vowel, separable by ear as well as on -the page. That separation is why the framework is "Composer" and not the bare verb. - -Accepted trade-offs, eyes open: an external, same-category name-share with AWS -Infrastructure/Application Composer — always vendor-prefixed ("Prisma Composer"), -never shipped bare; the same class of shared token as Docker Compose and Jetpack -Compose, which is evidence the token carries a product name well. Plus an SEO fight -with docker-compose-with-Prisma content (winnable; see ADR-0026). - -## Names to avoid - -- **"Model" or "Contract" for the data layer** — each steals a word more useful one - level down (`model` is the PSL construct; a `Contract` is what models compile to), - and neither names the user's actual value: data access. -- **A registry name derived from the unit noun** — the registry deserves its own - proper noun (gem→RubyGems, crate→crates.io), not "Module registry" as a brand. - Deferred, not decided. -- **Branded unit nouns (Prism, Shard, Facet, Lens)** — identity is the registry's - job; a branded unit noun fails the gloss test by construction and taxes every - composition sentence (this is how we got "Hex"). Prism belongs on the registry - shortlist, nowhere else. -- **"System" for anything** — superseded vocabulary - ([ADR-0025](../../docs/design/90-decisions/ADR-0025-name-the-unit-of-composition-module.md)); - plain-English use in prose is fine, but no construct, term, or diagram label. -- **"Prisma App" for the tool** — it names the artifact only - ([ADR-0026](../../docs/design/90-decisions/ADR-0026-name-the-framework-prisma-compose.md)). - Docs say "built with Prisma Composer," never "built with Prisma App." The general - rule is hard: never name a tool after its output. diff --git a/agent-os/product/principles.md b/agent-os/product/principles.md deleted file mode 100644 index e92fd5ca9..000000000 --- a/agent-os/product/principles.md +++ /dev/null @@ -1,8 +0,0 @@ -# Moved: principles live in `docs/design/` - -This file is a stub kept for compatibility with Agent OS workflows. - -- Guiding principles: `docs/design/01-principles/guiding-principles.md` -- Architectural principles: `docs/design/01-principles/architectural-principles.md` -- Design docs root: `docs/design/README.md` - diff --git a/agent-os/product/prisma-data-rename.md b/agent-os/product/prisma-data-rename.md deleted file mode 100644 index 32fdc617e..000000000 --- a/agent-os/product/prisma-data-rename.md +++ /dev/null @@ -1,75 +0,0 @@ -# Renaming Prisma ORM → Prisma Data - -## The decision - -At GA, **Prisma ORM becomes Prisma Data** — not "Prisma 8", which was the original -plan. Prisma ORM keeps its name and continues to serve everyone already on it; Prisma -Data is the product we point new work at. - -## Why not Prisma 8 - -A version number is a promise of continuity — Prisma 7 → 8 tells people "same thing, -here's your upgrade path." Prisma Data can't honor that promise: - -- **It's a different product, not a drop-in upgrade.** It asks users to learn a new - mental model. Any compatibility we shim on top would be a lie that leaks — the - interface might match, but the behavior won't, and that mismatch surfaces as subtle, - hard-to-trace differences rather than honest errors. -- **A version number caps the ambition.** "Prisma 8" says "still the ORM." Prisma Data - is more than an ORM, and the name should fit what it's becoming, not what it evolved - from. - -Forcing Prisma ORM into a Prisma 8 shape is also a large, painful compatibility -project — one we take on only to ship a name that misrepresents the product. - -## Why "Data" - -- **It names the user's value, not the machinery.** People say "my data." For the - non-technical, agent-assisted audience we're going after, "data" is the one word - they already own — where "ORM", "schema", and "contract" are noise. -- **It's a superset of "ORM", which keeps our options open.** "Data" is broader than - "ORM", so the name gives us room to be more than a query layer. A version number - would have locked us into the ORM category. -- **It fits the Prisma App family.** Read in context, "Data" is simply the data - component of the stack — the way "Compute" and "Postgres" read as parts of an app. - -## It fits the company pivot - -Decoupling "Prisma" from "Prisma ORM" is a direction the company has already -committed to: the goal is to pivot toward **Prisma Apps** and capture the fast-growing -market of people — often non-technical — building apps with agents (Lovable, Bolt, v0, -and the like). That audience already dwarfs the number of humans hand-writing apps on -the ORM. This rename is one move in that larger shift, not a standalone branding -exercise. - -Because of that, this is **not** about continuity for existing ORM users. They lose -nothing — Prisma ORM stays. - -## Living alongside Prisma ORM - -We can't retire the ORM — seven major versions are in the wild and will be forever. -What we can do is steer new attention: update the docs and website, make Prisma Data -the default, and add deprecation notices. - -The routing between the two is **temporal, not functional** — they overlap in what -they do, so we don't pretend a capability boundary that isn't there: - -- **Already using Prisma ORM?** Keep going. You're supported. (No migration nudge — - we're not pushing anyone onto a different mental model.) -- **Starting today?** Use Prisma Data — it's how you query, migrate, and model your - data now. - -## How we talk about it - -Two audiences, two messages — kept deliberately separate: - -- **To the builder (adoption):** the value is the outcome — *define your data once, and - it's instantly a typed, queryable, wired-in part of your app.* Plainer still: "your - app's data, handled." Don't mention ORMs or categories; just offer the easy path. -- **To the technical evaluator (differentiation):** Prisma Data is not an ORM. The - proof is the Contract — a machine-checkable definition of your schema that the rest - of the app, and the agent building it, can build against. That's the mechanism, not - the headline: lead with the outcome, keep the Contract as the reason it holds. - -Keeping "not an ORM" visible to technical evaluators is what segments the market — -query builders compete on ORM terms, and Prisma Data isn't playing that game. diff --git a/agent-os/product/roadmap.md b/agent-os/product/roadmap.md deleted file mode 100644 index 91683caf3..000000000 --- a/agent-os/product/roadmap.md +++ /dev/null @@ -1,21 +0,0 @@ -# Product Roadmap - -> INPUT REQUIRED -> After you share your product idea + key features, I’ll replace this with a real, ordered roadmap. -> (No bootstrapping tasks; each item should be end-to-end and testable.) - -1. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[`EFFORT`]` -2. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[`EFFORT`]` -3. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[`EFFORT`]` -4. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[`EFFORT`]` -5. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[`EFFORT`]` -6. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[`EFFORT`]` -7. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[`EFFORT`]` -8. [ ] [FEATURE_NAME] — [1-2 SENTENCE DESCRIPTION OF COMPLETE, TESTABLE FEATURE] `[`EFFORT`]` - -> Effort scale -> - `XS`: 1 day -> - `S`: 2-3 days -> - `M`: 1 week -> - `L`: 2 weeks -> - `XL`: 3+ weeks diff --git a/agent-os/product/tech-stack.md b/agent-os/product/tech-stack.md deleted file mode 100644 index 055d62074..000000000 --- a/agent-os/product/tech-stack.md +++ /dev/null @@ -1,52 +0,0 @@ -# Product Tech Stack - -This is the tech stack for building the **Prisma Composer** (the TypeScript framework) and its supporting toolchain, plus the -platform primitives it targets on the Prisma Platform. - -## Framework & Runtime -- **Language:** TypeScript -- **Runtime (target):** Bun (Prisma Compute runs JS on Bun instances) -- **Runtime (dev/tooling):** Node.js `>=22.18` (the version that turns TypeScript type stripping on by default, which is how the CLI imports the user's `.ts` entry) + Bun where useful -- **Distribution:** npm packages (Prisma Composer as a library + companion packages) -- **Package Manager:** pnpm (match Prisma ORM; pnpm workspaces) -- **Monorepo tooling:** Turborepo (task runner + caching) -- **Module system:** ESM (`"type": "module"`) - -## Platform Primitives (Target Environment) -- **Database:** Prisma Postgres -- **Compute:** Prisma Compute (VM-based, Bun runtime) -- **File Storage:** Prisma File Storage (working title; not yet created) -- **Streaming:** Durable Streams (working title; not yet created) - -## Build & Tooling (match Prisma ORM) -- **Build system:** Turbo pipelines (`turbo run build`, `turbo watch build`) -- **Package builds:** tsdown (base config) and tsup (where needed) -- **Typechecking:** `tsc --noEmit` with TS project references -- **Versioning/Release:** Changesets -- **Git hooks:** Husky + lint-staged -- **Dependency rules:** dependency-cruiser (enforce package boundaries/architecture) - -## API / Architecture -- **App definition model:** TypeScript DSL to define executable units + dependencies (static analyzable) -- **Topology output:** Build a static service graph for provisioning (IaC inference) -- **Runtime wiring:** Dependency injection of service implementations (platform vs local vs test) -- **Artifacts + metadata:** Standard artifact structure plus a dependency/contract map JSON for upload to Foundry (working title) -- **Composition:** “Component” abstraction for bundling units with explicit ports and linkable dependencies - -## Integrations -- **ORM / Data Access:** Prisma ORM -- **Framework integration:** Prisma ORM (must integrate seamlessly) - -## Local Dev & Testing -- **Local runtime:** Local implementations/emulators of platform services (storage/streams/compute) swapped via DI -- **Test isolation:** Easy per-test/per-suite environment isolation by swapping implementations and/or provisioning isolated resources -- **Test Framework:** Vitest (match Prisma ORM) -- **Frontend/tooling (if needed):** Vite (match Prisma ORM toolchain usage) - -## Quality -- **Linting/Formatting:** Biome (match Prisma ORM) -- **ESLint:** Used selectively (e.g., custom lint rules packaged as an ESLint plugin), otherwise prefer Biome -- **Type Safety:** TypeScript strict mode (recommended; align with shared tsconfig) - -## CI/CD -- **CI:** GitHub Actions (recommended; TBD) diff --git a/agent-os/product/vocabulary-tests.md b/agent-os/product/vocabulary-tests.md deleted file mode 100644 index 2415eb351..000000000 --- a/agent-os/product/vocabulary-tests.md +++ /dev/null @@ -1,130 +0,0 @@ -# Vocabulary tests — spoken-sentence rubrics - -Two rubrics live here, both born from names that read well and failed in speech: -one for **units of composition** (produced by the "System" failure, resolved by -[ADR-0025](../../docs/design/90-decisions/ADR-0025-name-the-unit-of-composition-module.md)), -and one for **product names that must refer** (produced by the "Prisma App" -framework-name failure, resolved by -[ADR-0026](../../docs/design/90-decisions/ADR-0026-name-the-framework-prisma-compose.md)). -Every test in both is a sentence said aloud to a colleague. - -## Part 1 — naming a unit of composition - -The tests in [naming.md](naming.md) decide **product names** (Prisma Data): name -the value, not the machinery. They have no purchase on **vocabulary nouns** — the -words for constructs and units. To the user a container noun is *all* machinery; -the value lives in the capability (cron, auth), never in the box. So the box needs -its own rubric of speech. - -These tests exist because "System" (ADR-0014) failed them in practice the moment the -first small shared unit (cron) landed. The ADR's case rested on one sentence at one -grain — "the auth system" — and on the claim that nobody says "the auth module." -People say "the auth module" all the time. See the failure record at the bottom. - -## The register model - -Three registers, only one of which we name: - -- **Package** — the artifact npm hosts. npm owns this vocabulary; we don't rename it. -- **Extension** — what you slot into `prisma-composer.config.ts`: deploy targets, build - kinds, anything that extends the toolchain. -- **Unit of composition** — what you plug together inside the app. This is the noun - under test. - -One npm package may register an extension *and* provide composable units; the -registers name roles, not artifacts. - -## The tests - -1. **Grain sweep.** "The auth ___" *and* "a ___ that provides one cron job." The - noun must have no size floor and no size ceiling. Most units in a real ecosystem - are small — composition math guarantees it — so a noun that only fits hero-sized - units fails where most of the usage is. -2. **Repetition.** "This ___ uses the cron ___ and an image-resizer ___." The noun - appears three times in an ordinary sentence about composition; it must stay - invisible. -3. **Install.** "Install the cron ___ and use that." The noun must sit naturally in - the consuming frame, not just the authoring frame. -4. **Publish.** "I published a ___." It should not be absurd. (Ecosystem *identity* — - the "it's a gem" signal — is the registry's job, not this noun's. Forcing the - unit noun to carry identity is how we got "Hex.") -5. **Instance/artifact duality.** Does the same word work for "the ___ on npm" and - "the ___ in my app"? If not, split the registers deliberately (see above) rather - than letting one word fail both. -6. **Adjacency.** Distinguishable in speech from Service, App, and Extension. If the - glossary has to "hold the line" between two words, the name is already losing. -7. **The gloss test.** Introduce the noun without reaching for a different noun. If - the explanation contains a better word, the gloss wins — stop and use it. -8. **Prior-art check.** Any claim about how developers talk ("nobody says X") must - survive contact with real usage in adjacent ecosystems (Nest, Angular, Terraform, - Go, Rails). Test the claim at more than one grain and in more than one sentence - frame before it decides anything. - -## Failure record: "System" - -The case study that produced these tests: - -| Test | Result | -|---|---| -| Grain sweep | Fails small: "the cron system" for a scheduled job is absurd. English "system" has a size floor — a system is something you *operate*. | -| Repetition | Fails: "this system uses the cron system and an image-resizer system." | -| Install | Fails: systems are operated, not installed — category error in the consuming frame. | -| Publish | Fails: "I published a system" is generic to the point of meaningless. | -| Duality | Fails both sides; no register split was defined. | -| Adjacency | Flagged in ADR-0014 itself: Service/System "needs the glossary to hold the line." | -| Gloss | Fails: the README introduces it as "a component — a System —". The gloss contained the better register. | -| Prior art | The deciding claim ("nobody says 'my auth module'") was false — tested at one grain, in one sentence, against one example app whose units were all hero-sized. | - -**Outcome:** the rubric was applied and the unit renamed — the unit of composition -is a **Module**, authored with `module()` -([ADR-0025](../../docs/design/90-decisions/ADR-0025-name-the-unit-of-composition-module.md)). -"Module" passed every test; "component" fell to adjacency (a Module contains React -components — an intra-app collision); branded nouns (Prism, Shard, Facet, Lens) fell -to the gloss test and the identity rule, with Prism moving to the registry -shortlist. - -## Part 2 — product names must also refer - -A name has two jobs: to **describe** (what is this?) and to **refer** (which thing -do you mean?). The value tests in [naming.md](naming.md) measure description only. -A product that people talk *about* — above all a framework, which is a **milieu** -developers live inside, not a component inside their app — must also pass -referential frames: - -1. **The workbench frame.** "I'm working on this feature in ___." Said by a - contributor, understood by a stranger. The daily sentence; if it fails, the team - invents shorthand and the name is already dead internally. -2. **The artifact-collision rule** (hard rule, not a scored test). The name must not - be the word for what users produce with the tool. A tool named after its output - can never be referred to separately from the output — every mention of the tool - parses as a mention of the artifact, and no prefix can rescue it. -3. **The bare-token test.** People drop prefixes; does the name survive unprefixed? - ("Rails" yes; "App" no.) -4. **Identity frames.** "Intro to ___" as a talk title, "___ 2.0", "does ___ - support X?" — all sayable and unambiguous. - -The register distinction that decides *which* rubric applies: **components** -(Postgres, Compute, Data) are parts inside the user's app, mentioned occasionally -and happily prefix-qualified — the value rule names them well. A **framework** is a -milieu, named constantly in referential frames; every durable framework name -(Rails, Django, Next, Vite) is a distinctive token, and most name *mechanism* — -which is the proper register for a milieu, not a violation of the value rule. - -### Failure record: "Prisma App" (as the framework name) - -| Test | Result | -|---|---| -| Workbench frame | Fails: "I'm working on this feature in App" refers to nothing — the sentence the team needed all week and could not say. | -| Artifact collision | Fails the hard rule: the tool was named after its output. "App" is the user's artifact word. | -| Bare token | Fails: "app" is ambient noise in every conversation about software. | -| Identity frames | Fails: "App 2.0" and "Intro to Prisma App" cannot pick out the product. | - -**Outcome:** the framework is **Prisma Composer** -([ADR-0026](../../docs/design/90-decisions/ADR-0026-name-the-framework-prisma-compose.md)); -"Prisma App" is kept as the name of the *artifact*, where it was always right — you -compose Modules with Prisma Composer into your Prisma App. The verb *compose* beat -the field (Construct, Alloy, Assemble, Wire, Forge, Stack, Prism, and a nineteen-word -synonym sweep) as the only candidate with no failing grade and the semantically -correct one — build/construct/make mean fabricating from raw material, while *compose* -means assembling finished parts into a whole, which is what the framework does; the -framework itself takes the agent-noun form, **Composer**. diff --git a/agent-os/standards/backend/api.md b/agent-os/standards/backend/api.md deleted file mode 100644 index d8bd5c292..000000000 --- a/agent-os/standards/backend/api.md +++ /dev/null @@ -1,10 +0,0 @@ -## API endpoint standards and conventions - -- **RESTful Design**: Follow REST principles with clear resource-based URLs and appropriate HTTP methods (GET, POST, PUT, PATCH, DELETE) -- **Consistent Naming**: Use consistent, lowercase, hyphenated or underscored naming conventions for endpoints across the API -- **Versioning**: Implement API versioning strategy (URL path or headers) to manage breaking changes without disrupting existing clients -- **Plural Nouns**: Use plural nouns for resource endpoints (e.g., `/users`, `/products`) for consistency -- **Nested Resources**: Limit nesting depth to 2-3 levels maximum to keep URLs readable and maintainable -- **Query Parameters**: Use query parameters for filtering, sorting, pagination, and search rather than creating separate endpoints -- **HTTP Status Codes**: Return appropriate, consistent HTTP status codes that accurately reflect the response (200, 201, 400, 404, 500, etc.) -- **Rate Limiting Headers**: Include rate limit information in response headers to help clients manage their usage diff --git a/agent-os/standards/backend/migrations.md b/agent-os/standards/backend/migrations.md deleted file mode 100644 index 494a74c52..000000000 --- a/agent-os/standards/backend/migrations.md +++ /dev/null @@ -1,9 +0,0 @@ -## Database migration best practices - -- **Reversible Migrations**: Always implement rollback/down methods to enable safe migration reversals -- **Small, Focused Changes**: Keep each migration focused on a single logical change for clarity and easier troubleshooting -- **Zero-Downtime Deployments**: Consider deployment order and backwards compatibility for high-availability systems -- **Separate Schema and Data**: Keep schema changes separate from data migrations for better rollback safety -- **Index Management**: Create indexes on large tables carefully, using concurrent options when available to avoid locks -- **Naming Conventions**: Use clear, descriptive names that indicate what the migration does -- **Version Control**: Always commit migrations to version control and never modify existing migrations after deployment diff --git a/agent-os/standards/backend/models.md b/agent-os/standards/backend/models.md deleted file mode 100644 index 1a1e46d85..000000000 --- a/agent-os/standards/backend/models.md +++ /dev/null @@ -1,10 +0,0 @@ -## Database model best practices - -- **Clear Naming**: Use singular names for models and plural for tables following your framework's conventions -- **Timestamps**: Include created and updated timestamps on all tables for auditing and debugging -- **Data Integrity**: Use database constraints (NOT NULL, UNIQUE, foreign keys) to enforce data rules at the database level -- **Appropriate Data Types**: Choose data types that match the data's purpose and size requirements -- **Indexes on Foreign Keys**: Index foreign key columns and other frequently queried fields for performance -- **Validation at Multiple Layers**: Implement validation at both model and database levels for defense in depth -- **Relationship Clarity**: Define relationships clearly with appropriate cascade behaviors and naming conventions -- **Avoid Over-Normalization**: Balance normalization with practical query performance needs diff --git a/agent-os/standards/backend/queries.md b/agent-os/standards/backend/queries.md deleted file mode 100644 index 3edeb590f..000000000 --- a/agent-os/standards/backend/queries.md +++ /dev/null @@ -1,9 +0,0 @@ -## Database query best practices - -- **Prevent SQL Injection**: Always use parameterized queries or ORM methods; never interpolate user input into SQL strings -- **Avoid N+1 Queries**: Use eager loading or joins to fetch related data in a single query instead of multiple queries -- **Select Only Needed Data**: Request only the columns you need rather than using SELECT * for better performance -- **Index Strategic Columns**: Index columns used in WHERE, JOIN, and ORDER BY clauses for query optimization -- **Use Transactions for Related Changes**: Wrap related database operations in transactions to maintain data consistency -- **Set Query Timeouts**: Implement timeouts to prevent runaway queries from impacting system performance -- **Cache Expensive Queries**: Cache results of complex or frequently-run queries when appropriate diff --git a/agent-os/standards/frontend/accessibility.md b/agent-os/standards/frontend/accessibility.md deleted file mode 100644 index 4dc628ce8..000000000 --- a/agent-os/standards/frontend/accessibility.md +++ /dev/null @@ -1,10 +0,0 @@ -## UI accessibility best practices - -- **Semantic HTML**: Use appropriate HTML elements (nav, main, button, etc.) that convey meaning to assistive technologies -- **Keyboard Navigation**: Ensure all interactive elements are accessible via keyboard with visible focus indicators -- **Color Contrast**: Maintain sufficient contrast ratios (4.5:1 for normal text) and don't rely solely on color to convey information -- **Alternative Text**: Provide descriptive alt text for images and meaningful labels for all form inputs -- **Screen Reader Testing**: Test and verify that all views are accessible on screen reading devices. -- **ARIA When Needed**: Use ARIA attributes to enhance complex components when semantic HTML isn't sufficient -- **Logical Heading Structure**: Use heading levels (h1-h6) in proper order to create a clear document outline -- **Focus Management**: Manage focus appropriately in dynamic content, modals, and single-page applications diff --git a/agent-os/standards/frontend/components.md b/agent-os/standards/frontend/components.md deleted file mode 100644 index a851b2fcf..000000000 --- a/agent-os/standards/frontend/components.md +++ /dev/null @@ -1,11 +0,0 @@ -## UI component best practices - -- **Single Responsibility**: Each component should have one clear purpose and do it well -- **Reusability**: Design components to be reused across different contexts with configurable props -- **Composability**: Build complex UIs by combining smaller, simpler components rather than monolithic structures -- **Clear Interface**: Define explicit, well-documented props with sensible defaults for ease of use -- **Encapsulation**: Keep internal implementation details private and expose only necessary APIs -- **Consistent Naming**: Use clear, descriptive names that indicate the component's purpose and follow team conventions -- **State Management**: Keep state as local as possible; lift it up only when needed by multiple components -- **Minimal Props**: Keep the number of props manageable; if a component needs many props, consider composition or splitting it -- **Documentation**: Document component usage, props, and provide examples for easier adoption by team members diff --git a/agent-os/standards/frontend/css.md b/agent-os/standards/frontend/css.md deleted file mode 100644 index b60d22794..000000000 --- a/agent-os/standards/frontend/css.md +++ /dev/null @@ -1,7 +0,0 @@ -## CSS best practices - -- **Consistent Methodology**: Apply and stick to the project's consistent CSS methodology (Tailwind, BEM, utility classes, CSS modules, etc.) across the entire project -- **Avoid Overriding Framework Styles**: Work with your framework's patterns rather than fighting against them with excessive overrides -- **Maintain Design System**: Establish and document design tokens (colors, spacing, typography) for consistency -- **Minimize Custom CSS**: Leverage framework utilities and components to reduce custom CSS maintenance burden -- **Performance Considerations**: Optimize for production with CSS purging/tree-shaking to remove unused styles diff --git a/agent-os/standards/frontend/responsive.md b/agent-os/standards/frontend/responsive.md deleted file mode 100644 index 1b8fc1ad8..000000000 --- a/agent-os/standards/frontend/responsive.md +++ /dev/null @@ -1,11 +0,0 @@ -## Responsive design best practices - -- **Mobile-First Development**: Start with mobile layout and progressively enhance for larger screens -- **Standard Breakpoints**: Consistently use standard breakpoints across the application (e.g., mobile, tablet, desktop) -- **Fluid Layouts**: Use percentage-based widths and flexible containers that adapt to screen size -- **Relative Units**: Prefer rem/em units over fixed pixels for better scalability and accessibility -- **Test Across Devices**: Test and verify UI changes across multiple screen sizes from mobile to tablet to desktop screen sizes and ensure a balanced, user-friendly viewing and reading experience on all -- **Touch-Friendly Design**: Ensure tap targets are appropriately sized (minimum 44x44px) for mobile users -- **Performance on Mobile**: Optimize images and assets for mobile network conditions and smaller screens -- **Readable Typography**: Maintain readable font sizes across all breakpoints without requiring zoom -- **Content Priority**: Show the most important content first on smaller screens through thoughtful layout decisions diff --git a/agent-os/standards/global/coding-style.md b/agent-os/standards/global/coding-style.md deleted file mode 100644 index 493b2945d..000000000 --- a/agent-os/standards/global/coding-style.md +++ /dev/null @@ -1,10 +0,0 @@ -## Coding style best practices - -- **Consistent Naming Conventions**: Establish and follow naming conventions for variables, functions, classes, and files across the codebase -- **Automated Formatting**: Maintain consistent code style (indenting, line breaks, etc.) -- **Meaningful Names**: Choose descriptive names that reveal intent; avoid abbreviations and single-letter variables except in narrow contexts -- **Small, Focused Functions**: Keep functions small and focused on a single task for better readability and testability -- **Consistent Indentation**: Use consistent indentation (spaces or tabs) and configure your editor/linter to enforce it -- **Remove Dead Code**: Delete unused code, commented-out blocks, and imports rather than leaving them as clutter -- **Backward compatibility only when required:** Unless specifically instructed otherwise, assume you do not need to write additional code logic to handle backward compatibility. -- **DRY Principle**: Avoid duplication by extracting common logic into reusable functions or modules diff --git a/agent-os/standards/global/commenting.md b/agent-os/standards/global/commenting.md deleted file mode 100644 index 021745e33..000000000 --- a/agent-os/standards/global/commenting.md +++ /dev/null @@ -1,5 +0,0 @@ -## Code commenting best practices - -- **Self-Documenting Code**: Write code that explains itself through clear structure and naming -- **Minimal, helpful comments**: Add concise, minimal comments to explain large sections of code logic. -- **Don't comment changes or fixes**: Do not leave code comments that speak to recent or temporary changes or fixes. Comments should be evergreen informational texts that are relevant far into the future. diff --git a/agent-os/standards/global/conventions.md b/agent-os/standards/global/conventions.md deleted file mode 100644 index 5fdd6925f..000000000 --- a/agent-os/standards/global/conventions.md +++ /dev/null @@ -1,11 +0,0 @@ -## General development conventions - -- **Consistent Project Structure**: Organize files and directories in a predictable, logical structure that team members can navigate easily -- **Clear Documentation**: Maintain up-to-date README files with setup instructions, architecture overview, and contribution guidelines -- **Version Control Best Practices**: Use clear commit messages, feature branches, and meaningful pull/merge requests with descriptions -- **Environment Configuration**: Use environment variables for configuration; never commit secrets or API keys to version control -- **Dependency Management**: Keep dependencies up-to-date and minimal; document why major dependencies are used -- **Code Review Process**: Establish a consistent code review process with clear expectations for reviewers and authors -- **Testing Requirements**: Define what level of testing is required before merging (unit tests, integration tests, etc.) -- **Feature Flags**: Use feature flags for incomplete features rather than long-lived feature branches -- **Changelog Maintenance**: Keep a changelog or release notes to track significant changes and improvements diff --git a/agent-os/standards/global/error-handling.md b/agent-os/standards/global/error-handling.md deleted file mode 100644 index 4a4ab049a..000000000 --- a/agent-os/standards/global/error-handling.md +++ /dev/null @@ -1,9 +0,0 @@ -## Error handling best practices - -- **User-Friendly Messages**: Provide clear, actionable error messages to users without exposing technical details or security information -- **Fail Fast and Explicitly**: Validate input and check preconditions early; fail with clear error messages rather than allowing invalid state -- **Specific Exception Types**: Use specific exception/error types rather than generic ones to enable targeted handling -- **Centralized Error Handling**: Handle errors at appropriate boundaries (controllers, API layers) rather than scattering try-catch blocks everywhere -- **Graceful Degradation**: Design systems to degrade gracefully when non-critical services fail rather than breaking entirely -- **Retry Strategies**: Implement exponential backoff for transient failures in external service calls -- **Clean Up Resources**: Always clean up resources (file handles, connections) in finally blocks or equivalent mechanisms diff --git a/agent-os/standards/global/tech-stack.md b/agent-os/standards/global/tech-stack.md deleted file mode 100644 index adb309b24..000000000 --- a/agent-os/standards/global/tech-stack.md +++ /dev/null @@ -1,31 +0,0 @@ -## Tech stack - -Define your technical stack below. This serves as a reference for all team members and helps maintain consistency across the project. - -### Framework & Runtime -- **Application Framework:** [e.g., Rails, Django, Next.js, Express] -- **Language/Runtime:** [e.g., Ruby, Python, Node.js, Java] -- **Package Manager:** [e.g., bundler, pip, npm, yarn] - -### Frontend -- **JavaScript Framework:** [e.g., React, Vue, Svelte, Alpine, vanilla JS] -- **CSS Framework:** [e.g., Tailwind CSS, Bootstrap, custom] -- **UI Components:** [e.g., shadcn/ui, Material UI, custom library] - -### Database & Storage -- **Database:** [e.g., PostgreSQL, MySQL, MongoDB] -- **ORM/Query Builder:** [e.g., ActiveRecord, Prisma, Sequelize] -- **Caching:** [e.g., Redis, Memcached] - -### Testing & Quality -- **Test Framework:** [e.g., Jest, RSpec, pytest] -- **Linting/Formatting:** [e.g., ESLint, Prettier, RuboCop] - -### Deployment & Infrastructure -- **Hosting:** [e.g., Heroku, AWS, Vercel, Railway] -- **CI/CD:** [e.g., GitHub Actions, CircleCI] - -### Third-Party Services -- **Authentication:** [e.g., Auth0, Devise, NextAuth] -- **Email:** [e.g., SendGrid, Postmark] -- **Monitoring:** [e.g., Sentry, Datadog] diff --git a/agent-os/standards/global/validation.md b/agent-os/standards/global/validation.md deleted file mode 100644 index 4129e3f5d..000000000 --- a/agent-os/standards/global/validation.md +++ /dev/null @@ -1,11 +0,0 @@ -## Validation best practices - -- **Validate on Server Side**: Always validate on the server; never trust client-side validation alone for security or data integrity -- **Client-Side for UX**: Use client-side validation to provide immediate user feedback, but duplicate checks server-side -- **Fail Early**: Validate input as early as possible and reject invalid data before processing -- **Specific Error Messages**: Provide clear, field-specific error messages that help users correct their input -- **Allowlists Over Blocklists**: When possible, define what is allowed rather than trying to block everything that's not -- **Type and Format Validation**: Check data types, formats, ranges, and required fields systematically -- **Sanitize Input**: Sanitize user input to prevent injection attacks (SQL, XSS, command injection) -- **Business Rule Validation**: Validate business rules (e.g., sufficient balance, valid dates) at the appropriate application layer -- **Consistent Validation**: Apply validation consistently across all entry points (web forms, API endpoints, background jobs) diff --git a/agent-os/standards/testing/test-writing.md b/agent-os/standards/testing/test-writing.md deleted file mode 100644 index a57de5ed7..000000000 --- a/agent-os/standards/testing/test-writing.md +++ /dev/null @@ -1,9 +0,0 @@ -## Test coverage best practices - -- **Write Minimal Tests During Development**: Do NOT write tests for every change or intermediate step. Focus on completing the feature implementation first, then add strategic tests only at logical completion points -- **Test Only Core User Flows**: Write tests exclusively for critical paths and primary user workflows. Skip writing tests for non-critical utilities and secondary workflows until if/when you're instructed to do so. -- **Defer Edge Case Testing**: Do NOT test edge cases, error states, or validation logic unless they are business-critical. These can be addressed in dedicated testing phases, not during feature development. -- **Test Behavior, Not Implementation**: Focus tests on what the code does, not how it does it, to reduce brittleness -- **Clear Test Names**: Use descriptive names that explain what's being tested and the expected outcome -- **Mock External Dependencies**: Isolate units by mocking databases, APIs, file systems, and other external services -- **Fast Execution**: Keep unit tests fast (milliseconds) so developers run them frequently during development diff --git a/docs/design/90-decisions/ADR-0025-name-the-unit-of-composition-module.md b/docs/design/90-decisions/ADR-0025-name-the-unit-of-composition-module.md index 0d2360894..cdc3390c9 100644 --- a/docs/design/90-decisions/ADR-0025-name-the-unit-of-composition-module.md +++ b/docs/design/90-decisions/ADR-0025-name-the-unit-of-composition-module.md @@ -64,8 +64,7 @@ repository is ESM-only, so there's no CommonJS `module` global to shadow. The evaluation method behind this choice — and behind "System" failing before it — is simple: say the candidate noun out loud in the sentences developers actually use (install it, publish it, name a small one, name a big one, use it -next to "service") and check whether every sentence still sounds natural. The -full rubric is recorded in `agent-os/product/vocabulary-tests.md`. +next to "service") and check whether every sentence still sounds natural. Two rules fall out of this and are worth stating on their own. First, **identity is the registry's job, not the unit noun's** — "the registry" being @@ -106,8 +105,7 @@ where most of the actual usage is. failed once modules got small: "install the cron system" reads as a category error in English (a system is something you *operate*, not something you install), and "this system uses the cron system and an image-resizer system" - is barely sayable. The full failure record is in - `agent-os/product/vocabulary-tests.md`. + is barely sayable. - **Component.** A natural word for the unit, and it matches C4's architecture sense. Disqualified by a collision one level down: a Module *contains* React components in the flagship examples, so "the @@ -124,10 +122,6 @@ where most of the actual usage is. ## Related -- `agent-os/product/vocabulary-tests.md` — the spoken-sentence rubric and the - "System" failure record. -- `agent-os/product/naming.md` — product-name tests and the register model's - place in the family story. - [ADR-0014](ADR-0014-one-authoring-primitive.md) — the single-primitive model this renames; its shape stands unchanged, its framework, package, and CLI names are superseded by diff --git a/docs/design/90-decisions/ADR-0026-name-the-framework-prisma-compose.md b/docs/design/90-decisions/ADR-0026-name-the-framework-prisma-compose.md index d45e51dd8..1beaaa5a2 100644 --- a/docs/design/90-decisions/ADR-0026-name-the-framework-prisma-compose.md +++ b/docs/design/90-decisions/ADR-0026-name-the-framework-prisma-compose.md @@ -133,9 +133,6 @@ under Alternatives. ## Related -- `agent-os/product/vocabulary-tests.md` — the referential test battery and - the "Prisma App" failure record. -- `agent-os/product/naming.md` — the family table and register model. - [ADR-0014](ADR-0014-one-authoring-primitive.md) — the framework, package, and CLI names this ADR supersedes; its single-primitive decision stands. - [ADR-0025](ADR-0025-name-the-unit-of-composition-module.md) — the unit noun diff --git a/docs/design/90-decisions/ADR-0027-two-packages-compose-and-compose-prisma-cloud.md b/docs/design/90-decisions/ADR-0027-two-packages-compose-and-compose-prisma-cloud.md index 0ab65ac33..c74cd38ee 100644 --- a/docs/design/90-decisions/ADR-0027-two-packages-compose-and-compose-prisma-cloud.md +++ b/docs/design/90-decisions/ADR-0027-two-packages-compose-and-compose-prisma-cloud.md @@ -101,4 +101,3 @@ family's unpublish — two names is the entire republish burden. (package / extension / Module) this leans on. - [ADR-0014](ADR-0014-one-authoring-primitive.md) — the single-primitive model and the unified-CLI note. -- `agent-os/product/naming.md` — the family table and distribution model. diff --git a/docs/design/README.md b/docs/design/README.md index 46f8d3fa1..04739016a 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -3,7 +3,6 @@ This directory is the **source of truth** for Prisma Composer’s architecture and design. It is intended to be consumed by: - Humans reading the repository -- The Agent OS workflows in this repo (product planning stays in `agent-os/product/`, but design/architecture lives here) - Other teams integrating with Prisma Composer (platform/orchestration/tools) - Implementation contributors working in this codebase