You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
122 lines
4.8 KiB
122 lines
4.8 KiB
/**
|
|
* 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.<name>`. 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<CoreServices, TCoreDeps[number]>;
|
|
readonly services: Partial<Record<TServiceDeps[number], unknown>>;
|
|
}): 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<string, AppServiceFactory<string, any, any, unknown>>;
|
|
|
|
/**
|
|
* Resolves the instance shape from a schema. Used by `ActiveApp<TSchema>`
|
|
* 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<TSchema extends AppServiceSchema> = {
|
|
readonly [K in keyof TSchema]: TSchema[K] extends AppServiceFactory<
|
|
string,
|
|
readonly CoreServiceKey[],
|
|
readonly string[],
|
|
infer I
|
|
>
|
|
? I
|
|
: never;
|
|
};
|