fix: keep Drizzle out of every published declaration (ADR 0053)
CI / verify (pull_request) Successful in 1m2s
CI / infrastructure-smoke (pull_request) Successful in 37s
CI / migration-smoke (pull_request) Successful in 1m52s
CI / standalone-module (pull_request) Successful in 26s
CI / image-build (pull_request) Successful in 28s
CI / deploy-smoke (same-origin) (pull_request) Successful in 43s
CI / deploy-smoke (split) (pull_request) Successful in 1m30s

@ekklesia/contracts still emitted a type-only import of
drizzle-orm/node-postgres for the legacy ModuleDatabase facade, so a
standalone module type-checked only because skipLibCheck hid it.

- ModuleDatabase, LegacyModuleRuntimeServices and
  AnyModuleRuntimeServices move to core/src/modules/database.ts, beside
  the facade Core builds; the facade itself is unchanged.
- contracts' runtime, route, and services generics no longer name an
  ORM: the database type is a plain parameter defaulting to the ABI v1
  executor, and the legacy definition's database is opaque until Core
  specializes it. Core and apps/api name the legacy types from Core.
- contracts drops its drizzle-orm optional peer and devDependency;
  Drizzle stays public only through @ekklesia/module-database/drizzle.
- The facade's type-level test moves from contracts to Core.
- Regression proof: the standalone fixture type-checks with
  skipLibCheck: false against the packed tarballs, with no drizzle-orm
  installed (the smoke asserts both). Against the previous contracts
  tarball the same check fails with TS2307 on drizzle-orm/node-postgres.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ekklesia
2026-10-08 07:40:43 -05:00
co-authored by Claude Opus 5.5
parent 89d5d0f2da
commit aaa7ebfd84
31 changed files with 190 additions and 178 deletions
+6 -2
View File
@@ -1,7 +1,11 @@
{
"extends": "@ekklesia/typescript-config/node.json",
"compilerOptions": {
"noEmit": true
"noEmit": true,
"skipLibCheck": false
},
"include": ["src/server/**/*.ts", "src/schema/**/*.ts"]
"include": [
"src/server/**/*.ts",
"src/schema/**/*.ts"
]
}
+5 -2
View File
@@ -2,7 +2,10 @@
"extends": "@ekklesia/typescript-config/browser-react.json",
"compilerOptions": {
"noEmit": true,
"types": []
"types": [],
"skipLibCheck": false
},
"include": ["src/ui/**/*.tsx"]
"include": [
"src/ui/**/*.tsx"
]
}
+22 -4
View File
@@ -28,6 +28,7 @@ type Manifest = Record<string, unknown> & {
peerDependencies?: Record<string, string>;
peerDependenciesMeta?: Record<string, { optional?: boolean }>;
optionalDependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
ekklesia?: Record<string, unknown>;
files?: string[];
};
@@ -163,14 +164,21 @@ describe("the curated Module SDK", () => {
}
const contractsSource = path.join(repositoryRoot, "packages/contracts/src");
for (const file of await readdir(contractsSource)) {
// Only types: Core alone validates the legacy Drizzle schema definition, with its own Drizzle.
// Not even a type: the legacy facade's Drizzle type and schema validator are Core's own, so no
// declaration a consumer type-checks names Drizzle.
const source = await readFile(path.join(contractsSource, file), "utf8");
for (const line of source.split("\n").filter((text) => /from\s+"drizzle-orm/u.test(text))) {
expect(line, file).toMatch(/^import type /u);
}
expect(source, file).not.toMatch(/from\s+"drizzle-orm|import\(\s*"drizzle-orm/u);
}
const contracts = await readJson("packages/contracts/package.json");
expect(Object.keys(contracts.exports as object)).toEqual([".", "./module-package"]);
for (const field of [
"dependencies",
"peerDependencies",
"optionalDependencies",
"devDependencies",
] as const) {
expect(contracts[field]?.["drizzle-orm"], field).toBeUndefined();
}
});
});
@@ -190,6 +198,16 @@ describe("module authors' dependencies", () => {
expect(guide).toContain("`@ekklesia/contracts` is not a direct dependency.");
});
it("type-check every published declaration in the standalone fixture", async () => {
// skipLibCheck would hide a declaration naming a package the module never installed.
for (const project of ["tsconfig.json", "tsconfig.ui.json"]) {
const config = await readJson<{ compilerOptions: { skipLibCheck?: boolean } }>(
`tests/fixtures/standalone-module/${project}`,
);
expect(config.compilerOptions.skipLibCheck, project).toBe(false);
}
});
it("pin module-pack exactly and declare engines.node in the standalone fixture", async () => {
const fixture = await readJson("tests/fixtures/standalone-module/package.json");
const devDependencies = fixture.devDependencies as Record<string, string>;