Introduce ModuleDatabase (a typed, restricted Drizzle query surface) and the frozen ModuleRuntimeServices boundary in @ekklesia/contracts, and thread the same services object through Core's module start/stop lifecycle hooks and apps/api's authorized module HTTP handler context. apps/api builds the facade via a new core/src/modules/database.ts view (createModuleDatabaseView) over the one existing shared PostgreSQL pool, alongside the identity and authorization views, and wires it into startApi's fixed startup order ahead of Identity/Authorization construction. Registration stays database-free; a module reaches its database only through an authorized start, stop, or HTTP handler invocation.
42 lines
1.9 KiB
TypeScript
42 lines
1.9 KiB
TypeScript
import type { ModuleDatabase } from "@ekklesia/contracts";
|
|
import {
|
|
type AuthorizationDrizzleDatabase,
|
|
createAuthorizationDrizzleView,
|
|
} from "@ekklesia/core/authorization";
|
|
import { createValidatedPostgresPool, type PostgresPoolOptions } from "@ekklesia/core/database";
|
|
import { createIdentityDrizzleView, type IdentityDrizzleDatabase } from "@ekklesia/core/identity";
|
|
import { createModuleDatabaseView } from "@ekklesia/core/modules";
|
|
|
|
export interface ApiDatabaseHandle {
|
|
readonly authorization: AuthorizationDrizzleDatabase;
|
|
readonly identity: IdentityDrizzleDatabase;
|
|
readonly module: ModuleDatabase;
|
|
/** Closes the one shared pool exactly once, regardless of how many typed views were created
|
|
* over it. Safe to call from both a startup-failure cleanup path and normal shutdown. */
|
|
readonly close: () => Promise<void>;
|
|
}
|
|
|
|
/**
|
|
* Composes the one physical PostgreSQL pool this process uses for Core's identity and
|
|
* authorization schemas plus the module database facade: one validated, timeout-bounded
|
|
* `pg.Pool` (`createValidatedPostgresPool`, `@ekklesia/core/database`), wrapped by three
|
|
* independent typed Drizzle views (`createIdentityDrizzleView`, `createAuthorizationDrizzleView`,
|
|
* `createModuleDatabaseView`). No domain owns or closes the pool in this composition — `close()`
|
|
* here is the only thing that ever calls `pool.end()`. `createIdentityDrizzleDatabase`/
|
|
* `createAuthorizationDrizzleDatabase` remain available, unaffected, and independently testable
|
|
* for callers that only need one domain.
|
|
*/
|
|
export function createApiDatabase(
|
|
databaseUrl: string,
|
|
options: PostgresPoolOptions = {},
|
|
): ApiDatabaseHandle {
|
|
const { close, pool } = createValidatedPostgresPool(databaseUrl, options);
|
|
|
|
return Object.freeze({
|
|
authorization: createAuthorizationDrizzleView(pool),
|
|
close,
|
|
identity: createIdentityDrizzleView(pool),
|
|
module: createModuleDatabaseView(pool),
|
|
});
|
|
}
|