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

This commit is contained in:
ekklesia
2026-10-07 17:29:32 -05:00
parent a1cdcb3ad1
commit 49b4d4165d
60 changed files with 948 additions and 237 deletions
+63 -2
View File
@@ -38,10 +38,14 @@ async function createModulesDirectory(): Promise<string> {
function manifestFor(
id: string,
options: { dependencies?: Record<string, unknown>; schema?: string | false } = {},
options: {
database?: { abi: 1 };
dependencies?: Record<string, unknown>;
schema?: string | false;
} = {},
): Record<string, unknown> {
return {
manifestVersion: 7,
manifestVersion: 8,
id,
name: `${id} module`,
version: "0.1.0",
@@ -49,6 +53,7 @@ function manifestFor(
...(options.dependencies ? { dependencies: options.dependencies } : {}),
capabilities: [],
permissions: [],
...(options.database ? { database: options.database } : {}),
entrypoints: {
server: "./dist/server/index.js",
...(options.schema === false ? {} : { schema: options.schema ?? "./dist/schema/index.js" }),
@@ -126,6 +131,62 @@ function issueCodes(error: ModuleSchemaDiscoveryError): ModuleSchemaDiscoveryIss
}
describe("module schema discovery", () => {
it("loads ABI-v1 schema entrypoints as plain metadata without importing an ORM", async () => {
const modulesDirectory = await createModulesDirectory();
await createSchemaModule(modulesDirectory, "calendar", {
manifest: manifestFor("calendar", { database: { abi: 1 } }),
schemaSource: `
export default {
moduleId: "calendar",
tables: [
{ name: "calendar_events", foreignKeys: [] },
{ name: "calendar_attendees", foreignKeys: [{ table: "calendar_events" }] }
]
};
`,
});
const aggregate = await discoverModuleSchemas(discoveryOptions(modulesDirectory));
expect(aggregate.tables.map((table) => table.name)).toEqual([
"calendar_attendees",
"calendar_events",
]);
expect(aggregate.tables.every((table) => table.table === undefined)).toBe(true);
expect(aggregate.drizzleSchema).toEqual({});
});
it("aggregates ABI-v1 prefix, duplicate, cross-module FK, missing target, and malformed errors", async () => {
const modulesDirectory = await createModulesDirectory();
await createSchemaModule(modulesDirectory, "alpha", {
manifest: manifestFor("alpha", { database: { abi: 1 } }),
schemaSource: `export default {
moduleId: "alpha",
tables: [
{ name: "wrong_records", foreignKeys: [] },
{ name: "alpha_records", foreignKeys: [{ table: "beta_records" }] },
{ name: "alpha_records", foreignKeys: [{ table: "alpha_missing" }] }
]
};`,
});
await createSchemaModule(modulesDirectory, "malformed", {
manifest: manifestFor("malformed", { database: { abi: 1 } }),
schemaSource: `export default { moduleId: "malformed", tables: [{ name: "malformed_rows", unknown: true }] };`,
});
const error = await captureSchemaError(modulesDirectory);
expect(issueCodes(error)).toEqual(
expect.arrayContaining([
"MODULE_SCHEMA_CROSS_MODULE_FOREIGN_KEY",
"MODULE_SCHEMA_EXPORT_INVALID",
"MODULE_SCHEMA_FOREIGN_KEY_TARGET_MISSING",
"MODULE_SCHEMA_TABLE_DUPLICATE",
"MODULE_SCHEMA_TABLE_NAMESPACE_INVALID",
]),
);
});
it("returns a frozen empty aggregate without importing module servers", async () => {
const modulesDirectory = await createModulesDirectory();
const recorder = createGlobalEventRecorder();