Skip to content

feat(dev): checkouts of one app get their own local databases and buckets, and prisma dev stops its Postgres servers on exit - #356

Merged
wmadden-electric merged 20 commits into
mainfrom
feat/emulator-data-instances
Oct 15, 2026
Merged

wmadden-electric merged 20 commits into
mainfrom
feat/emulator-data-instances

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Linked issue

n/a. Part of the local-dev work that adds prisma dev url, prisma dev stop and prisma emulator list (plan in .drive/projects/emulator-commands/).

Summary

Two checkouts of the same app now get separate local databases and buckets, and stopping prisma dev now stops its Postgres servers too:

~/work/shop   $ prisma dev module.ts   # writes a row to the catalog database
~/work/shop-2 $ prisma dev module.ts   # its catalog database is empty, and its uploads bucket has nothing from ~/work/shop
^C                                     # catalog's Postgres server stops, and its port closes
~/work/shop   $ prisma dev module.ts   # catalog is back on the same port, with the row

Decisions:

  1. The local Postgres and bucket emulators no longer know what an app, a database name or a directory is, as feat(dev): checkouts of one app get their own local services, and a service keeps its port #354 did for the emulator that runs services.
  2. A directory keeps local data for every app run there. Running a different app stops the previous one and keeps its data.
  3. When prisma dev ends, it stops the app's Postgres servers as well as its service processes. Data stays.

ADR-0052 is extended to cover all three emulators.

Background

prisma dev runs your app against local stand-ins for Prisma Cloud, called emulators. They are long-lived processes shared by everything on the machine:

  • the Compute emulator runs service processes;
  • the Postgres emulator hosts one local Postgres server per database;
  • the bucket emulator serves the S3 protocol over plain files.

#354 changed the Compute emulator to run instances under ids it generates, with Composer recording in each directory (.prisma-composer/dev/emulator-instances.json) which instance runs which service. The Postgres and bucket emulators were still keyed by app name, so two checkouts of one app shared a database and a bucket, and a --fresh in either wiped both.

Databases and buckets become instances

  • A database gets an id (db_<hex>) and its own Postgres server named after that id. New operations: stop closes the server and keeps its data and port; start brings it back on the same port with the same rows; delete removes the server and its data. A stopped database keeps its port for the same reason a stopped service does: its connection URL is in other services' environments, and a warm start doesn't redeploy.
  • A bucket gets an id (bkt-<hex>; S3 bucket names can't contain _), and that id is its name on the S3 wire. Access keys are registered per bucket. Objects stay as plain files in the project, now under .prisma-composer/dev/buckets/<app>/<bucket>/.

As with services, Composer's names reach these emulators only as labels they store but never read.

The record holds every app run from a directory

{ current: "shop",
  apps: { shop: { nodes: { quotes:  { kind: "service",  id: "svc_…", port: 3000 },
                           catalog: { kind: "database", id: "db_…",  port: 51300 },
                           uploads: { kind: "bucket",   id: "bkt-…" } } } } }

Each entry under nodes is keyed by its address, the node's dotted name in the app.

#354 deleted the previous app's services when a directory ran a different app. With databases involved, that would delete data whenever someone renamed the root module or passed a different --name. So now:

  • Switching apps stops the previous app's services and databases and keeps them.
  • Switching back resumes them, with their data and ports.
  • --fresh deletes only the current app's instances.

A record written by #354 is read as a single app.

Starting and stopping

  • Starting: before deploying, prisma dev makes sure every node of the app has its instance. It reuses recorded instances, re-creates missing ones on their remembered ports, and starts stopped databases. That last part fixes an existing gap. After the Postgres emulator restarted, for example after a reboot, a warm prisma dev never started its databases again, because the deploy engine saw nothing to change.
  • Re-created databases are migrated. A re-created database comes back empty on the same URL, so the deploy engine would skip its migration. The local migration step now checks the database's migration marker on every start, at the cost of one connection per database.
  • Stopping: when prisma dev ends, or a start fails partway, the app's databases are stopped along with its services.

Old state

Data from before the upgrade is discarded, as with every local-state change:

  • The Postgres emulator deletes the old app-named servers. It skips any server another live process is still serving, and tries again later.
  • The bucket emulator drops its old state file.

Testing performed

Against real Compute, Postgres and bucket emulators in temporary emulators directories (skipped on Windows):

  • Two directories running one app each see only their own rows and their own bucket objects.
  • When the app stops, its database server stops and its port closes. The next start has the data on the same port.
  • Switching apps A, then B, then A brings A's data and ports back.
  • A lost database is re-created on its port and migrated. --fresh removes only the current app.
  • The Postgres emulator:
    • Stop and start keep data. Delete frees the port.
    • Delete still works after the Postgres library copy used at creation is removed.
    • Old-data cleanup leaves a server another process is serving alone.
  • By hand, on examples/orm-demo: write a row, Ctrl-C, warm start with the row intact; delete the database through the emulator, then start, and it is re-created and migrated.

Also pnpm build, typecheck, lint, lint:deps, lint:casts, the docs checks, and the local-dev integration scripts, each run on its own.

Checklist

  • All commits are signed off (git commit -s) per the DCO. The DCO status check will block merge if any commit is missing a Signed-off-by: trailer.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • The PR title is a conventional commit (feat, fix, chore, docs, refactor, test, build) — PR titles drive the auto-generated release notes.
  • Tests are updated (or n/a if the change is doc-only / refactor with no behavioural delta).

Notes for the reviewer

Alternatives considered

Agent: attila-51

🤖 Generated with Claude Code

wmadden-electric and others added 10 commits October 13, 2026 17:27
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ates, with stop and start

The Postgres emulator no longer knows apps. A client creates a database
instance with free-form labels, an optional requested port and the path
it resolved `@prisma/dev` from; the emulator generates the id, starts a
`@prisma/dev` server named after it and stores the labels verbatim. Every
operation is by id, and GET /instances lists them all.

Stop shuts a server down and keeps its data and its port; start brings
it back on the same port with the same data. A database keeps its port
until it is deleted, because its URL is in dependents' environments.
Delete removes the server's data.

`@prisma/dev` keeps a server's data in its own data directory, outside
the emulators directory, under the server's name. State from the
app-keyed version is removed on load, and its servers' data is deleted
on the first request that brings a `@prisma/dev` module, unless another
live process still serves one. A failed create removes the directory its
start leaked.

Temporary adaptation until the directory's record covers databases: the
local Database provider finds its instance by the labels it wrote, and
teardown deletes the instances labelled with the directory and app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…per-bucket credentials

The bucket emulator no longer knows apps. A client creates a bucket
instance with free-form labels and the data root its objects live in;
the emulator generates the id, which is also the bucket's name on the S3
wire (`bkt-<16 hex digits>`, since an S3 bucket name allows no
underscore), and stores the labels and data root verbatim. Credentials
are registered per bucket, and a request is verified only against its
target bucket's own keys. GET /_pcdev/instances lists every bucket.
Deleting a bucket forgets it and its credentials; its objects stay in
its data root. State from the app-keyed version is discarded on load.

The path-segment rules and the app-keyed instance names are gone with
the routes that used them.

Temporary adaptation until the directory's record covers buckets: the
local Bucket provider finds its instance by the labels it wrote, and
teardown deletes the instances labelled with the directory and app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…s per directory

The directory's emulator-instances.json now holds every app run from the
directory: `{ current, apps: { <app>: { nodes: { <address>:
{ kind, id, port? } } } } }`, where a node is a Compute instance, a
database or a bucket. A single-app record from the previous version
reads as that one app and is rewritten on the next write.

The emulators hook starts the emulators the topology uses, makes the app
the directory's current app, and ensures every node's instance in graph
order: a recorded instance is reused, a stopped database is started, and
a missing one is created again on its remembered port. Starting recorded
databases before converge closes the warm-start gap where Alchemy finds
nothing to change and never calls the Database provider.

Switching apps now stops the previous app's services and databases and
keeps them recorded, so switching back resumes them with their data and
ports; the [dev] line says so. --fresh deletes the current app's
instances of every kind and its entry, and its part of the dev
directory, leaving other apps' entries and bucket objects alone. Bucket
objects therefore live under `.prisma-composer/dev/buckets/<app>/`. When
`prisma dev` ends, the attachment's stopServices stops the app's
databases as well as its service processes.

The Database, Connection, Bucket and BucketAccessKey providers reach
their instances through the record; the label lookups are gone. A
Database or Bucket whose recorded instance changed asks for an update,
and the local OrmMigration provider asks for one when the database is
not at its target by its marker, so a database created again empty is
migrated even though its URL is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
… the multi-app record

ADR-0052 now covers all three emulators: databases and buckets are
instances keyed by generated ids with labels never read, the record
holds every app run from a directory with `current` naming the last one,
switching apps stops the previous app's instances and keeps them, and
`--fresh` deletes only the current app's. It records why `prisma dev`
stops its database servers when it ends and why the hook starts them
before converge, and why a database created again empty is migrated
although its URL is unchanged.

local-dev.md describes the Postgres and bucket instances, the
multi-app record and how each part of dev uses it, stopping on exit,
and where a database's and a bucket's data live. The "still keyed by
the app" caveats are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…stops Postgres on exit

The running-locally guide and the core-concepts skill now say that two
checkouts of one app have separate databases and buckets as well as
services, that ending `prisma dev` stops the app's Postgres servers and
keeps their data and ports, that a re-created database replays its
migrations, and that running a different app from a directory stops the
previous one's instances and keeps them. The note that the emulators
directory does not isolate Postgres is replaced by where a database's
data lives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…what they are

- The core attachment seam `stopServices` is now `stopApp`: it stops
  everything the target runs for the app, databases included, and no
  longer mirrors `startServices`, which still starts only services.
- The record's node kinds are `service`, `database` and `bucket`, all
  node kinds, so nothing translates `compute` into "service" for users.
  A single-app record from S1a, which called services `compute`, reads
  them as `service`.
- The Compute client's types are `ServiceInstanceInfo`,
  `CreateServiceRequest` and `ServiceInstanceStatus`, beside the
  database and bucket ones.
- One helper, `resourceName`, gives a database's and a bucket's resource
  name; the lowering and the emulators hook both use it, and the local
  providers refuse a resource without a name instead of recording it
  under its Alchemy resource id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…atabases with the caller's @prisma/dev

- Deleting a database loads `@prisma/dev` from the path the caller
  sends, so `--fresh` still deletes the data after an upgrade removed
  the copy the database was created with. DELETE /instances/<id>
  accepts an optional `{ prismaDevModulePath }`.
- A failure while the emulators hook ensures the app's instances stops
  what that run started, best effort, then reports the failure.
- Stopping the app attempts both the service and the database stop and
  reports the first failure after both, so a failed service stop never
  leaves the database servers running.
- The Postgres emulator writes its state before acknowledging a create
  or a delete.

Tests: the local OrmMigration diff against a real Postgres (no marker
asks for an update, at the target asks for nothing, unreachable asks for
an update); the attachment's stopApp stops the app's database servers
and keeps their ports; --fresh's file removal on a temporary directory;
the legacy cleanup leaving a server another live process serves alone
until it exits; a delete after the creating module copy is gone; the
module path under the marker directory in the state test; the hook's
failure path. The integration script asserts the database is stopped and
its port closed after the first Ctrl-C. The extension's module mocks now
apply only while their own file's tests run, since bun's mock.module is
global to the run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…and fix the ADR-0041 summary

- The running-locally guide and the core-concepts skill show the
  port-change line for a database as well as a service, and say that a
  re-created Prisma ORM database replays its migrations while a
  rawPostgres() one stays empty.
- ADR-0052 and local-dev.md show the record's `service` kind, and
  local-dev.md says how S1a's `compute` nodes are read and that a
  database's and bucket's key comes from one helper.
- The ADR index's ADR-0041 summary says the Postgres emulator hosts one
  `@prisma/dev` server per database instance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@prisma-gizmo

prisma-gizmo Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

✅ Gizmo reviewed 9ea57c1 — posted 0 inline comment(s) this pass.

Open findings: none

Change walkthrough

prisma dev now gives each project directory its own local Postgres databases and S3 buckets, keeps every app run from that directory recorded with stable ids and ports, and shuts the app's Postgres servers down when the dev session ends (Ctrl-C, SIGTERM, or failure) — data and ports survive restarts and app switches.

Emulator instances (dev-emulators). The Postgres emulator gains stop/start/delete: stopping closes a @prisma/dev server but keeps its data directory and reserved port, so a warm redeploy brings it back on the same URL. The bucket emulator switches to per-bucket ids (bkt-…, no _ for S3-name legality), per-bucket credential checks, and app-scoped object paths under .prisma-composer/dev/buckets/<app>/<bucket>/ so two apps in one directory can't share or wipe each other's files. Old state files are discarded, with legacy Postgres servers cleaned up only when no other live process is serving them.

Dev record and hook (local-target). The per-directory record now keys every node of every app (service/database/bucket kinds with instance ids and ports); a #354-shaped record is read with compute mapped to service. The emulators hook ensures each node in dependency order before Alchemy runs — reusing instances, re-creating missing ones on remembered ports, and starting stopped databases, which fixes the pre-existing gap where a restarted emulator left warm-start databases down. --fresh deletes only the current app's instances, and a partially failed hook stops what it already started. stopServices is renamed stopApp on the target-agnostic local-target interface.

Migration re-run. Because a re-created database is empty on an unchanged URL (Alchemy sees nothing to diff), the local OrmMigration provider reads the migration marker on each start and requests an update when it isn't at target, costing one connection per postgres() database per start.

Compute emulator concurrency. A start whose child process has already exited is no longer joined by a concurrent resume or identical PUT; those start the service again instead of waiting on a doomed start. The poll interval is exposed as --ready-poll-ms (tests only; the CLI never passes it), with readiness tests moved onto a file-private port range.

Docs and skills. ADR-0052 now covers all three emulators, the local-dev design doc records the new concurrency rule, and the skill documents platform logicalId addressing (branchless deploys omit it; local providers pass null) along with the 0.30.0 / alchemy beta.83 bumps.

@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 3005caa2-8ffe-4e95-b8ad-24d985998b64
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@prisma/composer@356
npm i https://pkg.pr.new/@prisma/composer-cli@356
npm i https://pkg.pr.new/@prisma/composer-prisma-cloud@356

commit: b68f53a

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New findings: 🟡 2 minor · trace

Comment thread .drive/projects/emulator-commands/slices/s1b-data-instances/spec.md
Comment thread packages/1-prisma-cloud/1-extensions/target/src/local-target/emulators.ts Outdated

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No critical or major Gizmo finding is open and the head commit has been reviewed. Approving.

…art stops the whole app

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New findings: none · trace

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No critical or major Gizmo finding is open and the head commit has been reviewed. Approving.

wmadden-electric and others added 3 commits October 14, 2026 08:36
Brings in the `listens: false` deployment property. The one conflict, in the
core-concepts skill, keeps this branch's description of per-directory
databases and buckets and adds the sentence about services declared with
`compute({ ..., listens: false })`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Bucket objects moved from `.prisma-composer/dev/buckets/<bucket>/` to
`.prisma-composer/dev/buckets/<app>/<bucket>/`, so the bucket round-trip
proof waited for a file that never appeared there. It now reads the app's
name from the directory's `emulator-instances.json`, which records the
current app, and checks and drops files under that app's bucket directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New findings: none · trace

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No critical or major Gizmo finding is open and the head commit has been reviewed. Approving.

wmadden-electric and others added 4 commits October 15, 2026 17:10
Brings in the fix that a start does not wait for one whose process has
exited, and main. Conflicts: the local-target manifest keeps this
branch's dev dependencies at main's version, 0.30.0; the stage database
keeps this branch's resourceName(id) and gains main's logical id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Brings in the readiness tests' own port range. The one conflict keeps
this branch's ServiceInstanceInfo type and adds the get-port import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Brings in the --ready-poll-ms tests for a start that must not join a dead in-flight start. No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Brings in the removal of a repeated test assertion.

Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New findings: none · trace

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No Gizmo finding is open and the head commit has been reviewed. Approving.

Brings in #353's own port range for the readiness tests; the test file keeps the instance-keyed version. No other changes.

Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No Gizmo finding is open and the head commit has been reviewed. Approving.

@wmadden-electric wmadden-electric changed the title feat(dev): each directory gets its own local databases and buckets, and prisma dev stops its Postgres servers on exit feat(dev): checkouts of one app get their own local databases and buckets, and prisma dev stops its Postgres servers on exit Oct 15, 2026
Base automatically changed from feat/emulator-instances to main October 15, 2026 19:01
Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>

@prisma-gizmo prisma-gizmo Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No Gizmo finding is open and the head commit has been reviewed. Approving.

@wmadden-electric
wmadden-electric merged commit 765c83d into main Oct 15, 2026
22 checks passed
@wmadden-electric
wmadden-electric deleted the feat/emulator-data-instances branch October 15, 2026 19:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant