feat: deliver module database ABI by manifest
CI / verify (pull_request) Successful in 1m0s
CI / infrastructure-smoke (pull_request) Successful in 36s
CI / migration-smoke (pull_request) Successful in 1m53s
CI / image-build (pull_request) Successful in 23s
CI / deploy-smoke (same-origin) (pull_request) Successful in 50s
CI / deploy-smoke (split) (pull_request) Successful in 1m30s
CI / verify (pull_request) Successful in 1m0s
CI / infrastructure-smoke (pull_request) Successful in 36s
CI / migration-smoke (pull_request) Successful in 1m53s
CI / image-build (pull_request) Successful in 23s
CI / deploy-smoke (same-origin) (pull_request) Successful in 50s
CI / deploy-smoke (split) (pull_request) Successful in 1m30s
This commit is contained in:
@@ -198,7 +198,7 @@ without executing module code:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifestVersion": 7,
|
||||
"manifestVersion": 8,
|
||||
"id": "calendar",
|
||||
"name": "Calendar",
|
||||
"version": "0.1.0",
|
||||
@@ -275,10 +275,10 @@ has its own exact semantic version because its public contract can evolve indepe
|
||||
module release. Capability dependencies use semantic-version ranges. Optional means the provider
|
||||
may be absent; if it is installed, its capability version must be compatible.
|
||||
|
||||
`manifestVersion: 7` (`packages/contracts/src/module-manifest.ts`) is a required, exact literal — a
|
||||
version 6 manifest, and any other version, fails validation outright rather than being silently
|
||||
`manifestVersion: 8` (`packages/contracts/src/module-manifest.ts`) is a required, exact literal — a
|
||||
version 7 manifest, and any other version, fails validation outright rather than being silently
|
||||
reinterpreted; there is no compatibility adapter or automatic migration between manifest versions.
|
||||
Version 7 adds the optional `contributions` array
|
||||
Already-built version 7 packages must be rebuilt. Version 7 added the optional `contributions` array
|
||||
([ADR 0041](../architecture/decisions/0041-module-browser-contributions.md)). A version 6 module that
|
||||
contributes nothing changes only the version literal; every browser artifact remains independently
|
||||
optional ([ADR 0033](../architecture/decisions/0033-display-kiosk-host-contract.md)).
|
||||
@@ -1491,13 +1491,14 @@ destructive behavior. Review those properties explicitly. Core is the only migra
|
||||
follows Drizzle's exact `--> statement-breakpoint` delimiter and never naively splits semicolons or
|
||||
falls back to non-transactional execution.
|
||||
|
||||
## Module Database ABI v1 adapters (not yet delivered at runtime)
|
||||
## Module Database ABI v1
|
||||
|
||||
The stable database boundary is Module Database ABI v1: SQL text, ordered bound values, an explicit
|
||||
row mode, low-level PostgreSQL result values, and callback-scoped transactions. ORM objects never
|
||||
cross it. `@ekklesia/module-database` is module-side code for consuming that boundary once a later
|
||||
manifest/runtime slice delivers a `ModuleDatabaseExecutor`; it is **not usable by deployed modules
|
||||
yet**.
|
||||
cross it. A manifest version 8 module opts in with `"database": { "abi": 1 }`; omitting `database`
|
||||
selects the temporary legacy facade. “Legacy” here describes that facade, never a version 7
|
||||
manifest. Core selects the service from the validated manifest and gives each ABI module its own
|
||||
executor, reused by that module's lifecycle hooks and route handlers.
|
||||
|
||||
A Drizzle module imports the optional adapter subpath and declares its own exact compatible
|
||||
`drizzle-orm` dependency. The adapter's peer range (`>=0.45.2 <0.46.0`) describes adapter API
|
||||
@@ -1505,13 +1506,25 @@ compatibility; it is not a Core or platform compatibility range:
|
||||
|
||||
```ts
|
||||
import { moduleDrizzle } from "@ekklesia/module-database/drizzle";
|
||||
import { defineModuleRuntime } from "@ekklesia/module-sdk";
|
||||
import * as schema from "./schema/tables.js";
|
||||
|
||||
// `executor` will be supplied by later ABI service delivery work.
|
||||
const db = moduleDrizzle(executor, { schema });
|
||||
const events = await db.select().from(schema.events);
|
||||
export default defineModuleRuntime({
|
||||
database: { abi: 1 },
|
||||
id: "calendar",
|
||||
version: "0.1.0",
|
||||
async start({ services }) {
|
||||
const db = moduleDrizzle(services.database, { schema });
|
||||
const events = await db.select().from(schema.events);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The repeated runtime discriminator is what gives lifecycle and route-handler code the exact
|
||||
`ModuleDatabaseExecutor` type instead of a legacy-or-ABI union. Core still chooses from the static
|
||||
manifest and refuses startup when the runtime discriminator disagrees with it; neither contract can
|
||||
silently receive the other service.
|
||||
|
||||
Drizzle compiles SQL and parameters before calling the ABI. The adapter always requests positional
|
||||
(`rowMode: "array"`) results, gives those rows back to Drizzle in column order, and Drizzle applies
|
||||
the module's own column decoders for bigint, numeric, dates/timestamps, JSON, byte arrays, nulls, and
|
||||
@@ -1560,9 +1573,28 @@ const result = await executor.query({ ...statement, rowMode: "object" });
|
||||
The tag does not stringify values. Unsupported values pass through unchanged and the ABI executor
|
||||
rejects them; strings containing quotes or SQL syntax always remain parameters.
|
||||
|
||||
## Database access
|
||||
ABI modules also export plain schema metadata rather than Drizzle objects. Drizzle users can derive
|
||||
it on the module side; raw-SQL modules can write the same inert shape directly:
|
||||
|
||||
`context.services.database` — available in `start`, `stop`, and every authorized route handler,
|
||||
```ts
|
||||
import { moduleDrizzleSchemaMetadata } from "@ekklesia/module-database/drizzle";
|
||||
import * as tables from "./tables.js";
|
||||
|
||||
export default moduleDrizzleSchemaMetadata("calendar", tables);
|
||||
```
|
||||
|
||||
The result is `{ moduleId, tables: [{ name, foreignKeys: [{ table }] }] }`. Core validates table
|
||||
prefixes, duplicates, and same-module foreign-key targets from those strings and never inspects an
|
||||
ABI module's ORM objects.
|
||||
|
||||
The canonical module template has moved to manifest version 8 but intentionally omits `database`,
|
||||
so it remains on the legacy facade for one more slice. The packager still emits ADR 0049's
|
||||
`transitional.databaseRuntime`; the next package-transition change will remove that fact for ABI
|
||||
packages and migrate the template at the same time.
|
||||
|
||||
## Legacy database access
|
||||
|
||||
For a version 8 manifest that omits `database`, `context.services.database` — available in `start`, `stop`, and every authorized route handler,
|
||||
never in `register` — is a `ModuleDatabase`: a typed Drizzle query surface over exactly `select`,
|
||||
`selectDistinct`, `selectDistinctOn`, `insert`, `update`, `delete`, `execute`, and `transaction`,
|
||||
preserving Drizzle's own table-aware argument and result inference. It is the platform's only
|
||||
|
||||
Reference in New Issue
Block a user