| 1 | /** |
| 2 | * ProviderInstanceRegistryMutator — internal handle used by the hydration |
| 3 | * layer to reconcile the live registry with a fresh |
| 4 | * `ProviderInstanceConfigMap`. |
| 5 | * |
| 6 | * Kept separate from the public `ProviderInstanceRegistry` service tag so |
| 7 | * downstream consumers (drivers, reactors, `ProviderService`) can only read |
| 8 | * from the registry. Only the hydration layer — which watches |
| 9 | * `ServerSettingsService.streamChanges` and applies diffs — imports this |
| 10 | * tag. |
| 11 | * |
| 12 | * The mutator exposes a single entry point, `reconcile(configMap)`, which: |
| 13 | * |
| 14 | * 1. Diffs the incoming map against the live one keyed by instance id. |
| 15 | * 2. Closes the per-instance `Scope` of every removed or replaced entry |
| 16 | * (tearing down adapter processes, refresh fibres, temp files) BEFORE |
| 17 | * creating the replacement — `reconcile` guarantees "at most one live |
| 18 | * instance per id" at all times. |
| 19 | * 3. Opens a fresh child `Scope` for every added or replaced entry, runs |
| 20 | * the driver's `create`, and stores the resulting `ProviderInstance` |
| 21 | * plus its scope. |
| 22 | * 4. Publishes one `void` tick on the registry's `streamChanges` PubSub at |
| 23 | * the end of the batch — consumers re-pull `listInstances` / |
| 24 | * `listUnavailable`. |
| 25 | * |
| 26 | * `reconcile` is idempotent: calling it with an unchanged config map is a |
| 27 | * no-op (no scope churn, no pubsub emission). |
| 28 | * |
| 29 | * @module provider/ProviderInstanceRegistryMutator |
| 30 | */ |
| 31 | import type { ProviderInstanceConfigMap } from "@t3tools/contracts"; |
| 32 | import * as Context from "effect/Context"; |
| 33 | import type * as Effect from "effect/Effect"; |
| 34 | |
| 35 | export interface ProviderInstanceRegistryMutatorShape { |
| 36 | /** |
| 37 | * Bring the live registry in line with the supplied config map. See |
| 38 | * module docs for the add / remove / replace semantics. |
| 39 | * |
| 40 | * The effect never fails: individual driver `create` failures are |
| 41 | * captured as "unavailable" shadow snapshots inside the registry, the |
| 42 | * same way boot-time failures are handled by |
| 43 | * `makeProviderInstanceRegistry`. This keeps settings-watcher loops from |
| 44 | * erroring out on a single bad entry. |
| 45 | */ |
| 46 | readonly reconcile: (configMap: ProviderInstanceConfigMap) => Effect.Effect<void>; |
| 47 | } |
| 48 | |
| 49 | export class ProviderInstanceRegistryMutator extends Context.Service< |
| 50 | ProviderInstanceRegistryMutator, |
| 51 | ProviderInstanceRegistryMutatorShape |
| 52 | >()("t3/provider/ProviderInstanceRegistryMutator") {} |