Files
Ekklesia/templates/module

Ekklesia module template

This is the smallest supported backend-only Ekklesia module. It proves the static manifest, compiled runtime and schema entrypoints, capability registration, a module-owned Drizzle table, a generated module-owned migration, logical storage-bucket declarations, and one permissioned HTTP route (GET /records, declared in register) that reads that table through context.services.database — the platform-provided ModuleDatabase facade, never a module-constructed Drizzle instance or a raw pg client. Migration execution and storage provisioning are intentionally not included.

It also ships an optional React UI (src/ui/), compiled to the entrypoints.ui artifact its manifest declares by pnpm build (ADR 0023). UI is a choice, not a requirement: to keep a module backend-only, delete src/ui/, tsconfig.ui.json, tsconfig.ui-test.json, tests/module-ui.test.ts, the ui entrypoint from the manifest, and the React and module-UI dependencies. Everything else keeps working, and ekklesia-build-module-ui simply reports that there is nothing to build.

No host mounts a module UI yet — the Dashboard shell does not exist. tests/module-ui.test.ts builds the artifact from source, validates it with parseModuleUiExport, mounts it into a real DOM, and unmounts it, which is what proves the pipeline today. See Declaring a UI entrypoint.

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. Keep the manifest capability versions and dependency ranges aligned with the contracts the 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. Declare HTTP routes only in register, against a permission the manifest already declares; start, stop, and every route handler — never register — receive context.services.database, a typed ModuleDatabase view over the platform's one PostgreSQL pool.
  6. Replace the pure Drizzle declarations in src/schema/tables.ts and keep its table prefix aligned with the module ID. Keep the defineModuleSchema ownership wrapper in src/schema/index.ts aligned too, and never import another module's schema.
  7. Update drizzle.config.ts, generate forward migrations with pnpm migration:generate --name=lowercase_name from the module directory, and review the SQL. Keep each generated tag in drizzle/meta/_ekklesia.json, classify it as none or approval-required, and change that file's moduleId when renaming the template.
  8. Run pnpm install, pnpm build, pnpm modules:validate, pnpm migrations:validate, and pnpm verify from the repository root. The declared production entrypoint is the compiled dist/server/index.js, not source 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, and release them in stop. Never construct a Drizzle instance, read DATABASE_URL, or reach for a raw pg client — context.services.database is the only supported way to query this module's own tables, and it is never available during register.

The migration directory uses the pinned stable Drizzle Kit 0.31.10 PostgreSQL journal and snapshot format. Migrations are forward-only and may modify only this module's objects. Offline validation treats SQL as opaque bytes, calculates SHA-256 from the exact contents, and never opens a database or executes SQL. Destructive declarations are review metadata, not automatic SQL classification or approval.