feat: define module database access

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.
This commit is contained in:
ekklesia
2026-07-25 20:57:32 -05:00
parent 9c9ede0418
commit 0e308eb9ad
17 changed files with 702 additions and 51 deletions
+12 -7
View File
@@ -1,26 +1,30 @@
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 both Core's identity and
* authorization schemas: one validated, timeout-bounded `pg.Pool` (`createValidatedPostgresPool`,
* `@ekklesia/core/database`), wrapped by two independent typed Drizzle views
* (`createIdentityDrizzleView`, `createAuthorizationDrizzleView`). Neither Core 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.
* 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,
@@ -32,5 +36,6 @@ export function createApiDatabase(
authorization: createAuthorizationDrizzleView(pool),
close,
identity: createIdentityDrizzleView(pool),
module: createModuleDatabaseView(pool),
});
}