/** * Service-schema contract for `arts/active-app`. * * `aapp` is built on top of two layers: * * - **Core** — fixed runtime infrastructure that always exists. Today * that is `logger`, `bus`, `timers` and `orca`. Configurable via the * options object root, never declared as a service. * * - **Services** — opt-in runtime pieces that the application declares * in `services: { … }`. If a service is not declared, it does not * exist on the App, and TypeScript reports an error when the * consumer tries to access it. * * Each service is built from an `AppServiceFactory` produced by a * `defineActive*` / `defineEngine*` helper that lives in * `arts/active-app/service-factories/`. Arts themselves stay pure — they * do not know they are wired into a service. */ import type { EngineBus } from '$bus'; import type { EngineLogger } from '$logger'; import type { EngineOrca } from '$orca'; import type { ActiveTimers } from '$timer'; // ── Core ──────────────────────────────────────────────────────────────── /** * The four pieces of the core. Always built before any service. A factory * may declare a subset of these as `coreDependencies`; the builder * supplies only the declared keys to `create()`. */ export interface CoreServices { readonly logger: EngineLogger; readonly bus: EngineBus; readonly timers: ActiveTimers; readonly orca: EngineOrca; } export type CoreServiceKey = keyof CoreServices; // ── Service lifecycle ─────────────────────────────────────────────────── /** * Construction policy for a service. * * `lazy` (default) — built on first access via `App.`. Suitable for * services that may never be used in some flows. * * `immediate` — built during `createActiveApp()` after the core is up. * Suitable for services with construction-time side effects (subscribing * to BroadcastChannel, hydrating from storage on boot, etc.). */ export type ServiceInitMode = 'immediate' | 'lazy'; /** * Observable state of a service. The builder exposes a snapshot of * `{ [name]: ServiceStatus }` via `App.services`. Useful for devtools * and tests; the application itself rarely reads this. */ export type ServiceStatus = 'absent' | 'present' | 'failed'; // ── Factory ───────────────────────────────────────────────────────────── /** * Factory contract for a service. Each art that participates in App is * adapted to this contract by a `defineActive*` / `defineEngine*` helper * in `arts/active-app/service-factories/`. * * Generics: * - `TName` — string literal name, must match the schema key. * - `TCoreDeps` — subset of `CoreServiceKey` the service consumes. * - `TServiceDeps` — keys of OTHER services the service depends on. * Resolved against the schema; if a declared dependency is not in the * schema, the slot is `undefined` at `create()` time. The factory * decides whether to error or degrade. * - `TInstance` — type of the constructed instance. */ export interface AppServiceFactory< TName extends string = string, TCoreDeps extends readonly CoreServiceKey[] = readonly CoreServiceKey[], TServiceDeps extends readonly string[] = readonly string[], TInstance = unknown > { readonly name: TName; readonly coreDependencies: TCoreDeps; readonly serviceDependencies?: TServiceDeps; readonly initMode?: ServiceInitMode; create(deps: { readonly core: Pick; readonly services: Partial>; }): TInstance; dispose?(instance: TInstance): void; } // ── Schema ────────────────────────────────────────────────────────────── /** * A schema is a record `{ [name]: AppServiceFactory }`. The key MUST equal * `factory.name`; the builder validates this at construction time. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export type AppServiceSchema = Record>; /** * Resolves the instance shape from a schema. Used by `ActiveApp` * so `App.cache` is typed as `ActiveCache` when `services.cache` is * declared, and `never` (i.e. compile error on access) when it is not. */ export type ResolveServiceInstances = { readonly [K in keyof TSchema]: TSchema[K] extends AppServiceFactory< string, readonly CoreServiceKey[], readonly string[], infer I > ? I : never; };