refactor: src/exports holds only re-exports; implementation is placed by plane#136
Conversation
Implementation moves out of src/exports/ to the package root, where the 0-foundation/** directory glob already classifies it as shared, and each exports file becomes a re-export of the same symbol set. The within-package secret test now imports the implementation module directly. The published subpath keys are unchanged (./assertions, ./casts, ./secret) and the generated exports map is byte-identical. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Implementation moves out of src/exports/: the shared `testing.ts` to the package root (covered by src/*.ts -> shared) and the control-plane `deploy.ts` and `app-config.ts` to a new src/control/ directory. Each exports file becomes a re-export of the same symbol set; exports/index.ts was already thin and is unchanged. Control-plane implementation cannot sit at the root: core collapsed its root to src/*.ts -> shared, and a per-file override would overlap that glob, which the cruiser resolves by putting the file in both planes. A subdirectory keeps the globs non-overlapping, so architecture.config.json gains core/src/control/** -> control. deploy and app-config are mutually dependent and stay siblings, so their ./ imports of each other are unchanged. Within-package tests now import the implementation modules rather than the exports shims. The published subpath keys are unchanged (., ./config, ./deploy, ./testing) and the generated exports map is byte-identical. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The `node()` build adapter moves to src/node.ts (shared) and the control-plane descriptor and assembler to src/control/build.ts, each exports file becoming a re-export of the same symbol set — including the default export the adapter ships. control/build.ts took the sibling type import with it, now reading the implementation at ../node.ts rather than the exports shim; its deploy-time esbuild build() call is untouched. architecture.config.json gains node/src/*.ts -> shared and node/src/control/** -> control; the two existing per-file exports globs are unchanged and nothing overlaps. The firewall test still walks from the authoring entry, so it now follows the shim into the implementation; its offending-import pattern is widened to catch the new src/control/ directory form as well as a /control entry. The published subpath keys are unchanged (., ./control) and the generated exports map is byte-identical. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Mirrors the node package: the `nextjs()` build adapter moves to src/nextjs.ts (shared) and the control-plane descriptor and assembler to src/control/build.ts, each exports file becoming a re-export of the same symbol set including the default export. control/build.ts reads its sibling type from ../nextjs.ts rather than the exports shim; its deploy-time esbuild build() call is untouched. architecture.config.json gains nextjs/src/*.ts -> shared and nextjs/src/control/** -> control, non-overlapping with the existing per-file exports globs. The firewall test's offending-import pattern is widened to catch the src/control/ directory form. The published subpath keys are unchanged (., ./control) and the generated exports map is byte-identical. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The deployment report renderer moves to src/render-deployment.ts and the exports file becomes a re-export. cli is classified by a single src/** -> control glob, which already covers both the package root and src/exports/, so architecture.config.json needs no change. The within-package test now imports the implementation module it exercises. The published subpath keys are unchanged (. and ./report) and the generated exports map is byte-identical. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The Providers collection and the providers() bundle move to src/providers.ts; exports/index.ts keeps only re-exports — the implementation, the client, container and credential helpers, and the sibling compute and postgres entrypoints. An exports file re-exporting another exports file is fine, both being public surface; providers.ts imports only internal modules, never src/exports/. lowering is classified by a single src/** -> control glob, which already covers the package root, so architecture.config.json needs no change. The published subpath keys are unchanged (., ./compute, ./postgres, ./state) and the generated exports map is byte-identical. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The control-plane extension entry moves to src/control/extension.ts — named for what it holds, the prismaCloud() ExtensionDescriptor factory — and the three shared surfaces (pg-connection, prisma-next, testing) move to the package root. Each exports file becomes a re-export; exports/index.ts was already thin and re-exports only non-moving root modules, so it is unchanged. This removes the inversion that motivated the rule: pg-warm-resource.ts and prisma-next-migrate.ts imported ./exports/pg-connection.ts and descriptors/prisma-next.ts imported ../exports/prisma-next.ts. All three now read the root implementation, so no implementation depends on the public surface. Tests may still import the surface, which is what they are for. target stays per-file in architecture.config.json — its root is mixed-plane, so src/*.ts would misclassify the control files there. It gains src/control/** -> control plus shared globs for the three new root modules; every existing glob is untouched and nothing overlaps. The invariants test's process-env inventory follows the implementation from exports/control.ts to control/extension.ts. The published subpath keys are unchanged (., ./connection, ./control, ./prisma-next, ./testing). Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The bare scheduler node and the scheduler boot program move to src/execution/, the plane their globs already named, and each exports file becomes a re-export. scheduler-service ships a default export, so its shim re-exports `default` explicitly rather than using `export *`, which would not carry it. scheduler-entrypoint exports nothing — it is a program — so its shim imports it for effect; the package declares no `sideEffects: false`, so that import is not dropped. src/execution/ is the same depth as src/exports/, so both moved files keep their ../scheduler.ts imports unchanged. architecture.config.json gains cron/src/execution/** -> execution; the existing globs are untouched. The exports map and every dist entry filename are byte-identical, which is what the runtime import.meta.url sibling resolution depends on. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The storage service node moves to the package root as shared-plane implementation and the boot program to src/execution/, matching the planes their globs already named. Each exports file becomes a re-export; exports/index.ts and exports/testing.ts were already thin and are unchanged. storage-service ships a default export, so its shim re-exports both the named factory and `default`; storage-entrypoint exports nothing and is imported for effect. This removes the inversion: storage-module.ts imported ./exports/storage-service.ts and now reads the root implementation, so no implementation depends on the public surface. storage stays per-file in architecture.config.json — its root is mixed-plane, so src/*.ts would misclassify the runtime engine there. It gains src/execution/** -> execution and src/storage-service.ts -> shared. The exports map and every dist entry filename are byte-identical. The internal content-hashed chunks changed name, which is expected and allowed: moving the implementation back to the package root restored the module ids those hashes derive from. Runtime resolution targets the entry files, which did not move. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The streams service node moves to the package root, where the collapsed src/*.ts -> shared glob already classifies it, and the boot program and the local stand-in move to src/execution/. testing.ts goes to src/execution/ rather than the root deliberately: it is execution-plane, and the root is collapsed to shared, which would misclassify it. Each exports file becomes a re-export. streams-service ships a default export, so its shim re-exports both the named factory and `default`; streams-entrypoint exports nothing and is imported for effect. This removes the inversion: streams-module.ts imported ./exports/streams-service.ts and now reads the root implementation. architecture.config.json gains streams/src/execution/** -> execution; the existing globs are untouched and nothing overlaps. The exports map and every dist entry filename are byte-identical; only the internal content-hashed chunks changed name, which is allowed. The entrypoint integration test, which boots the entry as a child process, still passes. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…placed by plane ADR-0035 recorded the opposite of what the workspace now does. It documented as an accepted characteristic that implementation may live in src/exports/ and that internal code may import "up" into an exports/ sibling, naming the stricter form an optional future refactor. The operator rejected that, and the workspace has been converted, so the ADR is corrected rather than left contradicting the code. It now records: an exports file holds nothing but re-exports; no implementation module may import from exports/, with tests exempt because a test importing the public surface is consuming it as a consumer would; and implementation is placed by plane — shared at the package root, control in src/control/**, execution in src/execution/**. That placement is forced rather than stylistic: packages whose root is collapsed to src/*.ts -> shared would misclassify a control or execution module placed there, and a per-file override would overlap the collapsed glob, which with no glob precedence puts the file in two plane groups. The ADR also now says plainly that this is stricter than the pattern it adapts, whose own adapter-postgres exports file carries implementation, and why we chose the stricter rule. The generated-map decision, the two hand-maintained exceptions, the entry-filename invariant, and the non-overlapping-globs constraint are unchanged. The rule gains the thin-surface requirement, the tests exemption, the plane placement, and the two shim traps this slice hit: `export *` does not carry a `default` export, and an entrypoint program with no exports is imported for effect. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Adds the project record for the slice that moved every package's public entrypoints into src/exports/ (merged as PR #130) and for the follow-up that made those files thin: the investigation, the slice specs, the dispatch briefs, and the learnings. Matches how the six existing project records are stored; reviews/ stays untracked per .gitignore. Two learnings entries are corrected because later work falsified them. The "internal code imports up into src/exports/" entry recorded an accepted characteristic that the follow-up slice reversed — it is kept, marked as reversed, with the lesson that an exports file which can hold implementation eventually does, and that being green under the current rules is not evidence the shape is right. The "D6 collapse dropped" entry now records that D9 fixed the glob normalizer and took the collapse, with the lesson that a separate concern blocking a cleanup is worth pricing before dropping the cleanup. A new entry records this slice's traps: `export *` silently drops a `default` export, entrypoint programs have nothing to re-export so their shim imports for effect, implementation placement is forced by the plane globs, and a test that identifies a file by path breaks silently when the file moves. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
commit: |
Brings the branch up to date with main (postgres Branch naming in create, the cast-reading rules doc, the extension-registry model refresh, and the SPI-inversion project close-out). No conflicts: main touched no file this branch changed. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Brings in main's bucket() resource (#132). The additive bucket files — new lowering providers (buckets/Bucket.ts, buckets/BucketKey.ts, exports/buckets.ts), the target descriptor and node module, the example app, and the CI job — merge cleanly. Two files conflicted because bucket added exports to what are, on this branch, thin re-export shims. Resolved on the slice's principle — main's content, our placement: - lowering/src/exports/index.ts: main folded the Bucket/BucketKey providers into providers() and re-exported ./buckets.ts. The providers() additions go to the implementation in src/providers.ts; the shim keeps only the new re-export. - target/src/exports/control.ts: main registered the bucket descriptor under the 's3' node kind inside prismaCloud(). That goes to the implementation in src/control/extension.ts; the shim stays `export * from '../control/extension.ts'`. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
|
Warning Review limit reached
Next review available in: 16 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (73)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
What
src/exports/*.tsnow holds nothing but re-exports, and no implementationmodule imports from
exports/. All implementation moved out — 21 files across10 packages.
This reverses a characteristic ADR-0035
had recorded as accepted: that an exports file could be the implementation home
and internal modules could import "up" into it. That shipped in #130 and is
rejected here. The ADR is amended in this PR — a permanent decision record
documenting the opposite of what the code does is worse than no record.
Why it matters
An exports file that can hold implementation eventually does, and then internal
modules import it — at which point the published surface becomes something the
package's own internals depend on. That had already happened:
pg-warm-resource.tsand
prisma-next-migrate.tsimported./exports/pg-connection.ts, anddescriptors/prisma-next.tsimported../exports/prisma-next.ts. Those are gone.Where implementation lives, and why the layout is plane-shaped
This is forced, not stylistic. The plane globs must not overlap (the cruiser has
no glob precedence), and
core,cronandstreamsclassify their roots withsrc/*.ts → shared. A control- or execution-plane module at the root would beclassified
shared, and a per-file override would overlap that glob and put thefile in two plane groups. Subdirectories are the only non-overlapping option.
targetandstoragekeep one glob per root file instead, because their rootsare genuinely mixed-plane.
The directories are named after the planes the cruiser already enforces
(ADR-0017/0028) rather than importing Prisma Next's
core/name — onevocabulary, not two.
This is stricter than the pattern it adapts: Prisma Next's own
adapter-postgres/src/exports/column-types.tscarries ~185 lines ofimplementation. The stricter rule was taken deliberately.
Proof
Two workspace sweeps, both run at the tip:
function/class/const/let/var/enum/interface/typedeclaration.exports/.Tests are the one exemption: a test or fixture importing the public surface is
consuming it as a consumer would, which is what
test-import-patterns.mdcprescribes — and core's
probe-core-authoring.tsexists precisely to bundle thepublic barrel and assert it stays lean.
Invariants held
Every published subpath key is unchanged, for every package. The multi-pass
packages (
cron/storage/streams) keepexports:false, their hand-maintainedmaps, and their multi-pass configs; their
package.json#exportsand dist ENTRYfilenames are byte-identical, which is what their runtime
import.meta.urlsibling resolution depends on. (Internal content-hashed chunks may rehash — the
accepted invariant from #130. Amusingly, storage's reverted to their pre-#130
hashes, since moving implementation back to the root restored the module IDs.)
Full suite green:
build28/28 ·typecheck58/58 ·lint·lint:deps(no violations, 366 modules / 642 deps; architecture-coverage: 167 files
classified, 213 specifiers — every edge visible) ·
test48/48 ·lint:rules:symlinksconsistent.Two traps worth knowing (now in the rule)
export *does not carry adefault. Three*-service.tsfiles have one —the runnable node
build.modulepoints at. A bareexport *would havesilently dropped it.
*-entrypoint.tsfiles areside-effect programs with zero exports; their shims
importfor effect, whichis safe only while no package declares
sideEffects: false.Both are documented in
.agents/rules/exports-entrypoints.mdc.Also here
The Drive records for this slice and #130 (
.drive/projects/exports-entrypoint/)—
.drive/projects/is tracked in this repo and six other project records are onmain; this one was missing. Its
learnings.mdis corrected where this slicefalsified it.
🤖 Generated with Claude Code