/** * ProviderRegistryLive - Aggregates provider-specific snapshot services. * * @module ProviderRegistryLive */import type { ProviderKind, ServerProvider } from "@t3tools/contracts";import { Effect, Equal, Layer, PubSub, Ref, Stream } from "effect"; import { ClaudeProviderLive } from "./ClaudeProvider";import { CodexProviderLive } from "./CodexProvider";import type { ClaudeProviderShape } from "../Services/ClaudeProvider";import { ClaudeProvider } from "../Services/ClaudeProvider";import type { CodexProviderShape } from "../Services/CodexProvider";import { CodexProvider } from "../Services/CodexProvider";import { ProviderRegistry, type ProviderRegistryShape } from "../Services/ProviderRegistry"; const loadProviders = ( codexProvider: CodexProviderShape, claudeProvider: ClaudeProviderShape,): Effect.Effect<readonly [ServerProvider, ServerProvider]> => Effect.all([codexProvider.getSnapshot, claudeProvider.getSnapshot], { concurrency: "unbounded", }); export const haveProvidersChanged = ( previousProviders: ReadonlyArray<ServerProvider>, nextProviders: ReadonlyArray<ServerProvider>,): boolean => !Equal.equals(previousProviders, nextProviders); export const ProviderRegistryLive = Layer.effect( ProviderRegistry, Effect.gen(function* () { const codexProvider = yield* CodexProvider; const claudeProvider = yield* ClaudeProvider; const changesPubSub = yield* Effect.acquireRelease( PubSub.unbounded<ReadonlyArray<ServerProvider>>(), PubSub.shutdown, ); const providersRef = yield* Ref.make<ReadonlyArray<ServerProvider>>( yield* loadProviders(codexProvider, claudeProvider), ); const syncProviders = (options?: { readonly publish?: boolean }) => Effect.gen(function* () { const previousProviders = yield* Ref.get(providersRef); const providers = yield* loadProviders(codexProvider, claudeProvider); yield* Ref.set(providersRef, providers); if (options?.publish !== false && haveProvidersChanged(previousProviders, providers)) { yield* PubSub.publish(changesPubSub, providers); } return providers; }); yield* Stream.runForEach(codexProvider.streamChanges, () => syncProviders()).pipe( Effect.forkScoped, ); yield* Stream.runForEach(claudeProvider.streamChanges, () => syncProviders()).pipe( Effect.forkScoped, ); return { getProviders: syncProviders({ publish: false }).pipe( Effect.tapError(Effect.logError), Effect.orElseSucceed(() => []), ), refresh: (provider?: ProviderKind) => Effect.gen(function* () { switch (provider) { case "codex": yield* codexProvider.refresh; break; case "claudeAgent": yield* claudeProvider.refresh; break; default: yield* Effect.all([codexProvider.refresh, claudeProvider.refresh], { concurrency: "unbounded", }); break; } return yield* syncProviders(); }).pipe( Effect.tapError(Effect.logError), Effect.orElseSucceed(() => []), ), get streamChanges() { return Stream.fromPubSub(changesPubSub); }, } satisfies ProviderRegistryShape; }),).pipe(Layer.provideMerge(CodexProviderLive), Layer.provideMerge(ClaudeProviderLive));
/**
* ProviderRegistryLive - Aggregates provider-specific snapshot services.
*
* @module ProviderRegistryLive
*/
import type { ProviderKind, ServerProvider } from "@t3tools/contracts";
import { Effect, Equal, Layer, PubSub, Ref, Stream } from "effect";
import { ClaudeProviderLive } from "./ClaudeProvider";
import { CodexProviderLive } from "./CodexProvider";
import type { ClaudeProviderShape } from "../Services/ClaudeProvider";
import { ClaudeProvider } from "../Services/ClaudeProvider";
import type { CodexProviderShape } from "../Services/CodexProvider";
import { CodexProvider } from "../Services/CodexProvider";
import { ProviderRegistry, type ProviderRegistryShape } from "../Services/ProviderRegistry";
const loadProviders = (
codexProvider: CodexProviderShape,
claudeProvider: ClaudeProviderShape,
): Effect.Effect<readonly [ServerProvider, ServerProvider]> =>
Effect.all([codexProvider.getSnapshot, claudeProvider.getSnapshot], {
concurrency: "unbounded",
});
export const haveProvidersChanged = (
previousProviders: ReadonlyArray<ServerProvider>,
nextProviders: ReadonlyArray<ServerProvider>,
): boolean => !Equal.equals(previousProviders, nextProviders);
export const ProviderRegistryLive = Layer.effect(
ProviderRegistry,
Effect.gen(function* () {
const codexProvider = yield* CodexProvider;
const claudeProvider = yield* ClaudeProvider;
const changesPubSub = yield* Effect.acquireRelease(
PubSub.unbounded<ReadonlyArray<ServerProvider>>(),
PubSub.shutdown,
);
const providersRef = yield* Ref.make<ReadonlyArray<ServerProvider>>(
yield* loadProviders(codexProvider, claudeProvider),
);
const syncProviders = (options?: { readonly publish?: boolean }) =>
Effect.gen(function* () {
const previousProviders = yield* Ref.get(providersRef);
const providers = yield* loadProviders(codexProvider, claudeProvider);
yield* Ref.set(providersRef, providers);
if (options?.publish !== false && haveProvidersChanged(previousProviders, providers)) {
yield* PubSub.publish(changesPubSub, providers);
}
return providers;
});
yield* Stream.runForEach(codexProvider.streamChanges, () => syncProviders()).pipe(
Effect.forkScoped,
);
yield* Stream.runForEach(claudeProvider.streamChanges, () => syncProviders()).pipe(
Effect.forkScoped,
);
return {
getProviders: syncProviders({ publish: false }).pipe(
Effect.tapError(Effect.logError),
Effect.orElseSucceed(() => []),
),
refresh: (provider?: ProviderKind) =>
Effect.gen(function* () {
switch (provider) {
case "codex":
yield* codexProvider.refresh;
break;
case "claudeAgent":
yield* claudeProvider.refresh;
break;
default:
yield* Effect.all([codexProvider.refresh, claudeProvider.refresh], {
concurrency: "unbounded",
});
break;
}
return yield* syncProviders();
}).pipe(
Effect.tapError(Effect.logError),
Effect.orElseSucceed(() => []),
),
get streamChanges() {
return Stream.fromPubSub(changesPubSub);
},
} satisfies ProviderRegistryShape;
}),
).pipe(Layer.provideMerge(CodexProviderLive), Layer.provideMerge(ClaudeProviderLive));