87 lines · 4.0 KB · TypeScript
RawDownload
/** * ProviderInstanceRegistry — the single Effect service in the new model. * * Owns a `Map<ProviderInstanceId, ProviderInstance>` produced by running * registered driver factories against `ServerSettings.providerInstances`. * The registry watches settings; when an instance's config changes (or * the entry disappears), the registry tears down the affected instance's * scope and rebuilds — that's the entire hot-reload story. * * What rest-of-server reads from here: *   - `getInstance(instanceId)` — for routing turn/session calls. *   - `listInstances` — for snapshot aggregation in `ProviderRegistry`. *   - `listUnavailable` — `ServerProvider` shadows for instances whose *     driver is not registered in this build (rollback / fork tolerance). *   - `streamChanges` — coalesced "registry mutated" pings so consumers *     can re-pull lists or re-broadcast. * * @module provider/Services/ProviderInstanceRegistry */import type { ProviderInstanceId, ServerProvider } from "@t3tools/contracts";import * as Context from "effect/Context";import type * as Effect from "effect/Effect";import type * as PubSub from "effect/PubSub";import type * as Scope from "effect/Scope";import type * as Stream from "effect/Stream"; import type { ProviderInstance } from "../ProviderDriver.ts"; export interface ProviderInstanceRegistryShape {  /**   * Look up one instance by id. Returns `undefined` (not Option) when the   * id is unknown — callers branch on falsy and emit   * `ProviderInstanceNotFoundError`.   */  readonly getInstance: (    instanceId: ProviderInstanceId,  ) => Effect.Effect<ProviderInstance | undefined>;  /**   * Every available (driver-registered, successfully created) instance,   * in stable settings-author order.   */  readonly listInstances: Effect.Effect<ReadonlyArray<ProviderInstance>>;  /**   * Wire-shape shadow snapshots for instances whose driver is unknown to   * this build (or whose config failed to decode). Suitable for merging   * directly into `ProviderRegistry` output.   */  readonly listUnavailable: Effect.Effect<ReadonlyArray<ServerProvider>>;  /**   * Push notification stream emitted whenever the registry's contents   * change — instance added, removed, or rebuilt. The payload is `void`   * because consumers always want to re-pull `listInstances` /   * `listUnavailable` together.   *   * NOTE: because `Stream.fromPubSub` defers `PubSub.subscribe` until the   * stream starts running, forking a consumer via   * `Stream.runForEach(...).pipe(Effect.forkScoped)` races the next   * publish — the forked fiber may not have subscribed yet when the   * publish lands. Hot-reload consumers that must not miss a publish   * should use `subscribeChanges` below instead, which acquires the   * subscription synchronously in the caller's fiber before the consumer   * loop is forked.   */  readonly streamChanges: Stream.Stream<void>;  /**   * Acquire a subscription to the registry's change channel synchronously   * in the caller's fiber. Returns a `PubSub.Subscription<void>` whose   * lifetime is scoped to the provided `Scope` (the subscription is   * released when the scope closes). Consumers typically `yield*` this   * in the same fiber that forks their consumer loop, then drain with   * `PubSub.take(subscription)` inside `Effect.forever`. Because the   * subscription is registered with the PubSub before this `yield*`   * returns, no subsequent publish can land in a gap.   *   * This exists because the `ProviderInstanceRegistry` publishes on a   * PubSub and `Stream.fromPubSub` defers subscription until the stream   * starts executing — a consumer that `forkScoped`s the stream   * consumption can miss a publish that lands in the narrow window   * between "fiber scheduled" and "fiber starts running".   */  readonly subscribeChanges: Effect.Effect<PubSub.Subscription<void>, never, Scope.Scope>;} export class ProviderInstanceRegistry extends Context.Service<  ProviderInstanceRegistry,  ProviderInstanceRegistryShape>()("t3/provider/Services/ProviderInstanceRegistry") {}