feat: standalone module-build readiness for the public @ekklesia packages (ADR 0053)
CI / verify (pull_request) Successful in 1m2s
CI / infrastructure-smoke (pull_request) Successful in 28s
CI / migration-smoke (pull_request) Successful in 1m52s
CI / standalone-module (pull_request) Successful in 26s
CI / image-build (pull_request) Successful in 30s
CI / deploy-smoke (same-origin) (pull_request) Successful in 50s
CI / deploy-smoke (split) (pull_request) Successful in 1m42s

Makes the public module-development package set usable from a module
repository outside the monorepo. Nothing is published.

- Curated @ekklesia/module-sdk: an explicit author API, no contracts
  barrel, no identity/authorization internals, no Drizzle and no legacy
  ModuleDatabase type. defineModuleRuntime returns the SDK's own named
  interfaces so a module's emitted declarations never reach into
  @ekklesia/contracts. defineModuleSchema is gone; createModuleTable and
  defineModuleSchemaMetadata live in @ekklesia/module-database.
- Drizzle: no longer a dependency of module-sdk or contracts. Core keeps
  the legacy Drizzle schema validator (moved to core/src/modules) and
  its legacy runtime support; drizzle-orm is an optional peer only.
- React is a peer (plus devDependency) of @ekklesia/ui; the boundary
  checker allows exactly that consumer-selected peer.
- @ekklesia/typescript-config is publishable; module-ui-host is private.
  Every public package carries "ekklesia.distribution":
  "module-development" and no monorepo-root prepack.
- module-pack reads the Node.js range from the module's own engines.node
  and recognizes Ekklesia packages by name plus that marker, so
  registry-installed packages are incorporated, SDK-compatibility
  checked, and never credited as third-party.
- scripts/pack-public-packages.ts (pnpm packages:pack) rebuilds, packs
  and inspects the nine public tarballs; scripts/test-standalone-module.sh
  (pnpm test:standalone-module, new CI job) installs them into a fixture
  module outside the repository and proves it typechecks, builds,
  packages, and verifies.
- Public-package boundary tests and module-development docs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ekklesia
2026-10-08 07:28:54 -05:00
co-authored by Claude Opus 5.5
parent 9426c83109
commit 89d5d0f2da
63 changed files with 2320 additions and 237 deletions
@@ -1,18 +1,69 @@
import {
type LegacyModuleRuntimeDefinition,
type Awaitable,
type CapabilityRegistrar,
type CapabilityResolver,
type ModuleRegistrationContext as ContractModuleRegistrationContext,
type ModuleRuntimeContext as ContractModuleRuntimeContext,
type ModuleRuntimeServices as ContractModuleRuntimeServices,
type ModuleConfiguration,
type ModuleDatabaseAbiRuntimeDefinition,
type ModuleRuntimeDefinition,
type ModuleDatabaseExecutor,
type ModuleId,
parseModuleRuntimeDefinition,
} from "@ekklesia/contracts";
export function defineModuleRuntime<const Definition extends ModuleDatabaseAbiRuntimeDefinition>(
definition: Definition,
): Readonly<Definition>;
export function defineModuleRuntime<const Definition extends LegacyModuleRuntimeDefinition>(
definition: Definition,
): Readonly<Definition>;
export function defineModuleRuntime<const Definition extends ModuleRuntimeDefinition>(
definition: Definition,
): Readonly<Definition> {
return parseModuleRuntimeDefinition(definition) as Readonly<Definition>;
/**
* What a module's `register` hook receives. `Database` is what `services.database` is for the
* module: the Module Database ABI v1 executor when it declares `database: { abi: 1 }`, and `never`
* when it declares no database, so code cannot reach a database it never declared.
*/
export type ModuleRegistrationContext<
Database extends ModuleDatabaseExecutor = ModuleDatabaseExecutor,
> = ContractModuleRegistrationContext<Database>;
/** What a module's `start` and `stop` hooks receive. */
export type ModuleRuntimeContext<Database extends ModuleDatabaseExecutor = ModuleDatabaseExecutor> =
ContractModuleRuntimeContext<Database>;
/** The platform services every hook and handler receives. `database` is the ABI v1 executor for a
* module that declares one, and `never` otherwise. */
export type ModuleRuntimeServices<
Database extends ModuleDatabaseExecutor = ModuleDatabaseExecutor,
> = ContractModuleRuntimeServices<Database>;
/** A module that declares the Module Database ABI v1 (ADR 0050). Its hooks and route handlers see
* `services.database` as the ABI v1 executor. */
export interface ModuleRuntimeDefinitionWithDatabase extends ModuleDatabaseAbiRuntimeDefinition {}
/** A module that uses no database. Its hooks see `services.database` as `never`. */
export interface ModuleRuntimeDefinitionWithoutDatabase {
readonly id: ModuleId;
readonly version: string;
readonly database?: never;
readonly register?: (context: ModuleRegistrationContext<never>) => Awaitable<void>;
readonly start?: (context: ModuleRuntimeContext<never>) => Awaitable<void>;
readonly stop?: (context: ModuleRuntimeContext<never>) => Awaitable<void>;
}
/** Every runtime definition the Module SDK supports. The legacy Drizzle database facade is not one
* of them: a module that uses a database declares `database: { abi: 1 }` (ADR 0053). */
export type ModuleRuntimeDefinition =
| ModuleRuntimeDefinitionWithDatabase
| ModuleRuntimeDefinitionWithoutDatabase;
/**
* Validates and freezes a module's server runtime. The result is typed as one of this package's own
* named interfaces, so a module that emits declarations can always name its runtime's type through
* `@ekklesia/module-sdk`, without depending on the packages behind it.
*/
export function defineModuleRuntime(
definition: ModuleRuntimeDefinitionWithDatabase,
): ModuleRuntimeDefinitionWithDatabase;
export function defineModuleRuntime(
definition: ModuleRuntimeDefinitionWithoutDatabase,
): ModuleRuntimeDefinitionWithoutDatabase;
export function defineModuleRuntime(definition: ModuleRuntimeDefinition): ModuleRuntimeDefinition {
return parseModuleRuntimeDefinition(definition) as ModuleRuntimeDefinition;
}
export type { Awaitable, CapabilityRegistrar, CapabilityResolver, ModuleConfiguration };