Skip to content

refactor: consolidate and reorganize packages per ADR-0026/0027/0028#60

Merged
wmadden-electric merged 8 commits into
mainfrom
claude/package-consolidation
Jul 12, 2026
Merged

refactor: consolidate and reorganize packages per ADR-0026/0027/0028#60
wmadden-electric merged 8 commits into
mainfrom
claude/package-consolidation

Conversation

@wmadden-electric

Copy link
Copy Markdown
Contributor

Implements the package rename, consolidation, and reorganization designed in ADR-0026, ADR-0027, and ADR-0028. Carries #59's ADR-0027/0028 docs (cherry-picked; this PR subsumes #59).

The workspace shape (ADR-0028)

packages/
  0-framework/
    0-foundation/   @internal/foundation (casts, assertions), @internal/tsdown-config
    1-core/         @internal/core (module(), graph, config params, deploy)
    2-authoring/    @internal/rpc, @internal/node, @internal/nextjs
    3-tooling/      @internal/assemble, @internal/cli
  1-prisma-cloud/
    0-lowering/     @internal/lowering (the Alchemy provider, ex @prisma/alchemy)
    1-extensions/   @internal/prisma-cloud (compute(), postgres(), target descriptor)
    2-modules/      @internal/cron
  9-public/
    compose/              → @prisma/compose
    compose-prisma-cloud/ → @prisma/compose-prisma-cloud

All internals are @internal/* + "private": true. Exactly two publishable packages, verified by the publish gate (check:publish-deps finds exactly 2) and npm pack --dry-run on each (no @internal/* in any published dependency field or dist import).

Public surface (ADR-0027)

  • @prisma/compose: prisma-compose bin, core at root, subpaths ./config ./deploy ./testing ./casts ./assertions ./rpc ./node(/control) ./nextjs(/control). Internals inlined at build (tsdown noExternal on the @internal scope).
  • @prisma/compose-prisma-cloud: target at root + ./control ./prisma-next ./testing ./cron(/scheduler-entrypoint). Cloud internals inlined; framework imports rewritten to @prisma/compose subpaths and left external (resolve hook + post-build dts rewrite) so one framework copy exists at runtime and type identity holds — core's node brand is a unique symbol; two bundled declaration copies would not unify (found empirically: the storefront example failed to typecheck against a bundled-framework cloud package).
  • No unscoped wrapper — packages/prisma-compose deleted.
  • Extension routing ids follow the public identities: @prisma/compose/node, @prisma/compose/nextjs, @prisma/compose-prisma-cloud.

Enforcement (ADR-0028)

architecture.config.json (globs → {domain, layer, plane}) + dependency-cruiser.config.mjs copied from prisma-next's shape. Downward-only across domains and layers; lateral allowed; 9-public is a sink; examples + integration tests import only 9-public; control/execution planes never import each other (ADR-0017), mapped per-entrypoint. Wired as pnpm lint:deps, in CI, and in lint-staged. Currently: ✔ no dependency violations (180 modules, 308 dependencies).

Judgment calls

  • CLI stays in 0-framework/3-tooling with one declared cross-domain exception (cli → @internal/lowering): the pre-stack container-ensure step (Project/Branch resolution) is Prisma Cloud-specific today. Known debt, documented in the config's exception entry, rather than moving the CLI into the cloud domain (ADR-0027 puts the bin in @prisma/compose).
  • serializer.ts classified shared, not control — the cruiser corrected the initial guess: the authoring surface (index/compute/testing) imports it.
  • Internals keep their own dist builds; publics bundle from prebuilt dist + .d.mts. Building publics straight from cross-package TS sources broke rolldown-plugin-dts three different ways (anonymous default re-exports, Effect-heavy files failing isolated-declaration emit, eager segfaulting).
  • @prisma/compose public exports keep ./config ./deploy ./testing ./casts ./assertions beyond ADR-0027's enumerated ./rpc ./node ./nextjs — examples and user configs (prisma-compose.config.ts imports defineConfig from @prisma/compose/config) need them.
  • Core's exports-map invariant updated (casts/assertions now live in @internal/foundation); cloud's updated for cron's move.
  • Artifact-sense "Prisma App" keeps (ADR-0023 and friends) — unchanged from refactor: rename to Prisma Compose across packages, CLI, and docs per ADR-0026 #57; both acceptance greps return only ADR-0023's filename, ADR-0026/0027's rename record, and the repo URL. grep "Prisma App Framework" → zero.
  • bun.lock regenerated — bun doesn't resolve the nested workspace globs, so it now tracks the root only; nothing in CI uses it.
  • Versions restart at 0.1.0 lockstep (old names unpublished 2026-07-12; new names never published). Not published — that's the release process's job.

Acceptance

  • pnpm build (17) / typecheck (36) / test (29) / test:types (20) / lint / lint:depsall green.
  • Examples (incl. cron) build and test importing only @prisma/compose / @prisma/compose-prisma-cloud (+ subpaths) — enforced by the cruiser from now on.

🤖 Generated with Claude Code

wmadden-electric and others added 7 commits July 12, 2026 18:39
…sma-cloud

Boundary at the user's one real choice (where does it run): core absorbs
CLI, assemble, and the target-agnostic authoring surfaces (/rpc /node
/nextjs as subpaths); compose-prisma-cloud carries the target plus
first-party modules as entrypoints (/cron) — module is a role, not an
artifact, per the ADR-0025 register model, and cron's scheduler is a
Prisma Cloud compute service anyway. No unscoped bin wrapper. Naming
conventions: compose-* in-scope, prisma-compose-* outside, targets carry
their full name (the compose-prisma-cloud stutter is deliberate).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Private workspace packages are unconstrained — internal seams are free;
names users install are not.

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

Adopt Prisma Next's Domains/Layers/Planes organization with a 9-public
twist: 0-framework (imports nothing), 1-prisma-cloud (imports
framework), 9-public (the only publishable packages, a sink; examples
import only these). The numbering is the ruleset. Planes adopted now as
entrypoint-mapped globs — they name ADR-0017's existing control/
execution split, not a directory axis. Internals are @internal/*
(unscoped internal/x verified fatal at Node ESM runtime). Public
packages bundle internal deps at build. Enforced by dependency-cruiser
from architecture.config.json via pnpm lint:deps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…R-0027/0028)

packages/ is reorganized into numbered domains and layers:

  0-framework/{0-foundation,1-core,2-authoring,3-tooling}
  1-prisma-cloud/{0-lowering,1-extensions,2-modules}
  9-public/{compose,compose-prisma-cloud}

Every internal package is @internal/<name> with "private": true (foundation,
tsdown-config, core, rpc, node, nextjs, assemble, cli, lowering, prisma-cloud,
cron). casts/assertions split out of core into @internal/foundation; cron split
out of the cloud extensions into @internal/cron; the unscoped prisma-compose
wrapper package is deleted (ADR-0027: no unscoped wrapper — npx @prisma/compose
runs the bin).

Exactly two publishable packages, both in 9-public:
- @prisma/compose — the prisma-compose bin, core at the root, and subpaths
  ./config ./deploy ./testing ./casts ./assertions ./rpc ./node(/control)
  ./nextjs(/control). Bundles its @internal deps at build time (tsdown
  noExternal on the @internal scope); the tarball is self-contained.
- @prisma/compose-prisma-cloud — the target at the root (+ ./control,
  ./prisma-next, ./testing) and ./cron(/scheduler-entrypoint). Cloud-domain
  internals are inlined; framework imports are rewritten to @prisma/compose
  subpaths and left external (a resolve hook + a post-build dts rewrite), so
  one framework copy exists at runtime and type identity holds (the node brand
  is a unique symbol — two bundled declaration copies would not unify).

Examples and the integration test import only the two public packages (+ their
subpaths); the extension routing ids follow the public identities
(@prisma/compose/node, @prisma/compose/nextjs, @prisma/compose-prisma-cloud).
Versions restart at 0.1.0 lockstep — the old names were unpublished 2026-07-12
and the new names have never been published. bun.lock regenerated (bun does not
resolve the nested workspace globs; it tracks the root only and no CI uses it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…er (ADR-0028)

architecture.config.json maps directory globs to {domain, layer, plane};
dependency-cruiser.config.mjs derives the forbidden rules from it — copied
from Prisma Next's implementation (Package-Layering + 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.

Rules: lower numbers importable by higher, never the reverse (domains and
layers); lateral same-layer imports allowed; control and execution planes
never import each other, both may import shared (ADR-0017), mapped
per-entrypoint. One declared cross-domain exception: the CLI's pre-stack
container-ensure step imports @internal/lowering — known, documented debt.

Package specifiers alias to workspace sources via tsconfig.depcruise.json
paths (exports maps point at dist, which is excluded from the cruise). Wired
as pnpm lint:deps, in CI after the Biome check, and in lint-staged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The retired @prisma/compose-* per-capability names become their ADR-0027
homes across README, docs/design, skills, SECURITY, and the integration-test
README: compose-cloud → @prisma/compose-prisma-cloud; compose-{node,nextjs,
rpc} → @prisma/compose subpaths; compose-{assemble,cli,alchemy,tsdown} →
their @internal/* names. versioning.md now records the two-package publish
surface. ADR-0026/0027 keep the old names as the rename record; ADR-0027/0028
cross-links retargeted to ADR-0014-one-authoring-primitive.md. .drive process
history untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The generic .config. exclude was silently skipping the examples'
prisma-compose.config.ts files — exactly the user-facing imports that pull
/control extensions into a deploy (ADR-0017) — and the /testing/fake and
example-test excludes skipped more user-visible import surface. Build-tool
configs are now excluded by name (tsdown/vitest/next), package-internal tests
stay excluded (they legitimately cross plane boundaries), and everything under
examples/ and test/ is cruised. 180→191 modules, 308→346 edges, still zero
violations; a planted @internal import in an example config fails the
examples-import-public-only rule.

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

pkg-pr-new Bot commented Jul 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/prisma/app/@prisma/compose@60
npm i https://pkg.pr.new/prisma/app/@prisma/compose-prisma-cloud@60

commit: 27f79f0

- packages/1-prisma-cloud/1-extensions/prisma-cloud → …/1-extensions/target —
  the leaf said the domain name twice; the package IS the Prisma Cloud target
  extension. Package name stays @internal/prisma-cloud.
- packages/1-prisma-cloud/2-modules → 2-shared-modules — "modules" alone was
  underqualified for the layer holding first-party modules realized on this
  target; ADR-0028 tree sketch updated to match.
- scripts/lint-publishable-location.mjs: every workspace package outside
  packages/9-public/ must be "private": true, and everything inside 9-public
  must be publishable — ADR-0027 made mechanical. Wired into pnpm lint:deps
  (so also lint-staged and CI). Verified both directions: flipping
  @internal/cron non-private fails the gate; 20 manifests checked clean.
- Internal repository.directory fields updated to the real paths (were stale
  from the manifest restore).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric wmadden-electric merged commit e7337df into main Jul 12, 2026
11 checks passed
@wmadden-electric wmadden-electric deleted the claude/package-consolidation branch July 12, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants