72 lines · 2.9 KB · TypeScript
RawDownload
import type { ServerProvider } from "@t3tools/contracts";import { Duration, Effect, PubSub, Ref, Scope, Stream } from "effect";import * as Semaphore from "effect/Semaphore"; import type { ServerProviderShape } from "./Services/ServerProvider";import { ServerSettingsError } from "../serverSettings"; export function makeManagedServerProvider<Settings>(input: {  readonly getSettings: Effect.Effect<Settings>;  readonly streamSettings: Stream.Stream<Settings>;  readonly haveSettingsChanged: (previous: Settings, next: Settings) => boolean;  readonly checkProvider: Effect.Effect<ServerProvider, ServerSettingsError>;  readonly refreshInterval?: Duration.Input;}): Effect.Effect<ServerProviderShape, ServerSettingsError, Scope.Scope> {  return Effect.gen(function* () {    const refreshSemaphore = yield* Semaphore.make(1);    const changesPubSub = yield* Effect.acquireRelease(      PubSub.unbounded<ServerProvider>(),      PubSub.shutdown,    );    const initialSettings = yield* input.getSettings;    const initialSnapshot = yield* input.checkProvider;    const snapshotRef = yield* Ref.make(initialSnapshot);    const settingsRef = yield* Ref.make(initialSettings);     const applySnapshot = (nextSettings: Settings, options?: { readonly forceRefresh?: boolean }) =>      refreshSemaphore.withPermits(1)(        Effect.gen(function* () {          const forceRefresh = options?.forceRefresh === true;          const previousSettings = yield* Ref.get(settingsRef);          if (!forceRefresh && !input.haveSettingsChanged(previousSettings, nextSettings)) {            yield* Ref.set(settingsRef, nextSettings);            return yield* Ref.get(snapshotRef);          }           const nextSnapshot = yield* input.checkProvider;          yield* Ref.set(settingsRef, nextSettings);          yield* Ref.set(snapshotRef, nextSnapshot);          yield* PubSub.publish(changesPubSub, nextSnapshot);          return nextSnapshot;        }),      );     const refreshSnapshot = Effect.gen(function* () {      const nextSettings = yield* input.getSettings;      return yield* applySnapshot(nextSettings, { forceRefresh: true });    });     yield* Stream.runForEach(input.streamSettings, (nextSettings) =>      Effect.asVoid(applySnapshot(nextSettings)),    ).pipe(Effect.forkScoped);     yield* Effect.forever(      Effect.sleep(input.refreshInterval ?? "60 seconds").pipe(        Effect.flatMap(() => refreshSnapshot),        Effect.ignoreCause({ log: true }),      ),    ).pipe(Effect.forkScoped);     return {      getSnapshot: input.getSettings.pipe(        Effect.flatMap(applySnapshot),        Effect.tapError(Effect.logError),        Effect.orDie,      ),      refresh: refreshSnapshot.pipe(Effect.tapError(Effect.logError), Effect.orDie),      get streamChanges() {        return Stream.fromPubSub(changesPubSub);      },    } satisfies ServerProviderShape;  });}