feat: add module contracts and discovery
CI / verify (push) Has been cancelled
CI / infrastructure-smoke (push) Has been cancelled

This commit is contained in:
ekklesia
2026-07-12 22:14:01 -05:00
parent d9489384c1
commit 57552a21a4
43 changed files with 2277 additions and 21 deletions
+16
View File
@@ -0,0 +1,16 @@
# Ekklesia module template
This is the smallest supported backend-only Ekklesia module. It proves the manifest, package,
Module SDK, discovery, capability, permission, and logical storage-bucket contracts without
pretending that the later runtime, API, or Drizzle registration layers already exist.
To create a module inside this repository:
1. Copy this directory to `modules/<module-id>`.
2. Rename the package in `package.json` and remove `private` when it is ready for publication.
3. Update the ID, name, version, namespaces, compatibility ranges, and bucket requests in
`ekklesia.module.json`.
4. Replace the example server entrypoint with backend behavior only.
5. Run `pnpm install`, `pnpm modules:validate`, and `pnpm verify` from the repository root.
Do not add UI components, styles, renderers, navigation, or layouts. Those belong to surfaces.
+24
View File
@@ -0,0 +1,24 @@
{
"manifestVersion": 1,
"id": "example",
"name": "Example Module",
"description": "A minimal backend-only Ekklesia module template.",
"version": "0.1.0",
"compatibility": {
"platform": "^0.1.0",
"moduleSdk": "^0.1.0"
},
"capabilities": ["example.records"],
"permissions": ["example.records.read"],
"storage": {
"buckets": [
{
"id": "records",
"description": "Private objects owned by this example module."
}
]
},
"entrypoints": {
"server": "./src/server/index.ts"
}
}
+41
View File
@@ -0,0 +1,41 @@
{
"name": "@ekklesia/module-template",
"version": "0.1.0",
"private": true,
"type": "module",
"files": [
"dist",
"src",
"ekklesia.module.json"
],
"exports": {
".": {
"types": "./dist/server/index.d.ts",
"import": "./dist/server/index.js"
},
"./server": {
"types": "./dist/server/index.d.ts",
"import": "./dist/server/index.js"
}
},
"scripts": {
"build": "tsc --project tsconfig.build.json",
"lint": "biome lint .",
"prepack": "pnpm --workspace-root exec turbo run build --filter=@ekklesia/module-template",
"test": "vitest run",
"typecheck": "tsc --noEmit --project tsconfig.json"
},
"dependencies": {
"@ekklesia/module-sdk": "workspace:*"
},
"devDependencies": {
"@biomejs/biome": "2.5.3",
"@types/node": "24.13.3",
"@ekklesia/typescript-config": "workspace:*",
"typescript": "7.0.2",
"vitest": "4.1.10"
},
"ekklesia": {
"layer": "module"
}
}
+7
View File
@@ -0,0 +1,7 @@
export interface ExampleModuleServer {
readonly id: "example";
}
export const exampleModuleServer: ExampleModuleServer = {
id: "example",
};
+14
View File
@@ -0,0 +1,14 @@
import { readFile } from "node:fs/promises";
import { defineModule, type ModuleManifestInput } from "@ekklesia/module-sdk";
import { describe, expect, it } from "vitest";
describe("module template", () => {
it("ships a valid backend-only manifest", async () => {
const manifestUrl = new URL("../ekklesia.module.json", import.meta.url);
const manifestInput = JSON.parse(await readFile(manifestUrl, "utf8")) as ModuleManifestInput;
const manifest = defineModule(manifestInput);
expect(manifest.id).toBe("example");
expect(manifest.storage.buckets.map((bucket) => bucket.id)).toEqual(["records"]);
});
});
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"noEmit": false,
"outDir": "dist",
"rootDir": "src",
"sourceMap": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.test.ts", "tests"]
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "@ekklesia/typescript-config/node.json",
"compilerOptions": {
"noEmit": true,
"types": ["node", "vitest/globals"]
},
"include": ["src/**/*.ts", "tests/**/*.ts"]
}