feat: add module schema discovery and validation
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
# Ekklesia module template
|
||||
|
||||
This is the smallest supported backend-only Ekklesia module. It proves the static manifest,
|
||||
compiled runtime entrypoint, capability registration, and logical storage-bucket declarations
|
||||
without pretending that API, Drizzle, or storage provisioning already exist.
|
||||
compiled runtime and schema entrypoints, capability registration, a module-owned Drizzle table, and
|
||||
logical storage-bucket declarations without pretending that migrations, API, or storage
|
||||
provisioning already exist.
|
||||
|
||||
To create a module inside this repository:
|
||||
|
||||
@@ -14,9 +15,11 @@ To create a module inside this repository:
|
||||
module actually implements and consumes.
|
||||
5. Replace `src/server/index.ts` with backend behavior only. Its `defineModuleRuntime` ID and version
|
||||
must match the manifest; do not duplicate the rest of the manifest in TypeScript.
|
||||
6. Run `pnpm install`, `pnpm build`, `pnpm modules:validate`, and `pnpm verify` from the repository
|
||||
6. Replace `src/schema/index.ts` with declaration-only Drizzle tables. Keep `createModuleTable` and
|
||||
`defineModuleSchema` aligned with the module ID, and never import another module's schema.
|
||||
7. Run `pnpm install`, `pnpm build`, `pnpm modules:validate`, and `pnpm verify` from the repository
|
||||
root. The declared production entrypoint is the compiled `dist/server/index.js`, not source
|
||||
TypeScript.
|
||||
TypeScript; the schema entrypoint is likewise compiled to `dist/schema/index.js`.
|
||||
|
||||
Do not add UI components, styles, renderers, navigation, or layouts. Those belong to surfaces.
|
||||
Keep imports side-effect-free, register capabilities in `register`, acquire resources in `start`,
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
]
|
||||
},
|
||||
"entrypoints": {
|
||||
"server": "./dist/server/index.js"
|
||||
"server": "./dist/server/index.js",
|
||||
"schema": "./dist/schema/index.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +25,13 @@
|
||||
"typecheck": "tsc --noEmit --project tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ekklesia/module-sdk": "workspace:*"
|
||||
"@ekklesia/module-sdk": "workspace:*",
|
||||
"drizzle-orm": "0.45.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.5.3",
|
||||
"@types/node": "24.13.3",
|
||||
"@ekklesia/typescript-config": "workspace:*",
|
||||
"@types/node": "24.13.3",
|
||||
"typescript": "7.0.2",
|
||||
"vitest": "4.1.10"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { createModuleTable, defineModuleSchema } from "@ekklesia/module-sdk";
|
||||
import { text, uuid } from "drizzle-orm/pg-core";
|
||||
|
||||
const moduleTable = createModuleTable("example");
|
||||
|
||||
export const records = moduleTable("records", {
|
||||
id: uuid("id").primaryKey(),
|
||||
value: text("value").notNull(),
|
||||
});
|
||||
|
||||
export default defineModuleSchema({
|
||||
moduleId: "example",
|
||||
tables: { records },
|
||||
});
|
||||
@@ -4,7 +4,9 @@ import {
|
||||
parseModuleManifest,
|
||||
type RegisteredCapability,
|
||||
} from "@ekklesia/module-sdk";
|
||||
import { getTableConfig } from "drizzle-orm/pg-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import moduleSchema, { records } from "../src/schema/index.js";
|
||||
import moduleRuntime, { recordsCapability } from "../src/server/index.js";
|
||||
|
||||
describe("module template", () => {
|
||||
@@ -16,6 +18,7 @@ describe("module template", () => {
|
||||
expect(manifest.id).toBe("example");
|
||||
expect(manifest.capabilities).toEqual([{ id: "example.records", version: "1.0.0" }]);
|
||||
expect(manifest.storage.buckets.map((bucket) => bucket.id)).toEqual(["records"]);
|
||||
expect(manifest.entrypoints.schema).toBe("./dist/schema/index.js");
|
||||
});
|
||||
|
||||
it("exports the typed runtime definition used by the compiled server entrypoint", async () => {
|
||||
@@ -35,4 +38,10 @@ describe("module template", () => {
|
||||
expect(moduleRuntime).toMatchObject({ id: "example", version: "0.1.0" });
|
||||
expect(provided.get("example.records")).toBe(recordsCapability);
|
||||
});
|
||||
|
||||
it("exports a namespaced module-owned Drizzle schema", () => {
|
||||
expect(moduleSchema.moduleId).toBe("example");
|
||||
expect(moduleSchema.tables.records).toBe(records);
|
||||
expect(getTableConfig(records)).toMatchObject({ name: "example_records", schema: undefined });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user