161 lines · 5.9 KB · TypeScript
RawDownload
import type { ServerProvider } from "@t3tools/contracts";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Equal from "effect/Equal";import * as Fiber from "effect/Fiber";import * as PubSub from "effect/PubSub";import * as Ref from "effect/Ref";import * as Scope from "effect/Scope";import * as Stream from "effect/Stream";import * as Semaphore from "effect/Semaphore"; import type { ServerProviderShape } from "./Services/ServerProvider.ts";import { ServerSettingsError } from "@t3tools/contracts"; interface ProviderSnapshotState {  readonly snapshot: ServerProvider;  readonly enrichmentGeneration: number;} export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")(function* <  Settings,>(input: {  readonly maintenanceCapabilities: ServerProviderShape["maintenanceCapabilities"];  readonly getSettings: Effect.Effect<Settings, ServerSettingsError>;  readonly streamSettings: Stream.Stream<Settings>;  readonly haveSettingsChanged: (previous: Settings, next: Settings) => boolean;  readonly initialSnapshot: (settings: Settings) => Effect.Effect<ServerProvider>;  readonly checkProvider: Effect.Effect<ServerProvider, ServerSettingsError>;  readonly enrichSnapshot?: (input: {    readonly settings: Settings;    readonly snapshot: ServerProvider;    readonly getSnapshot: Effect.Effect<ServerProvider>;    readonly publishSnapshot: (snapshot: ServerProvider) => Effect.Effect<void>;  }) => Effect.Effect<void>;  readonly refreshInterval?: Duration.Input;}): Effect.fn.Return<ServerProviderShape, ServerSettingsError, Scope.Scope> {  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.initialSnapshot(initialSettings);  const snapshotStateRef = yield* Ref.make<ProviderSnapshotState>({    snapshot: initialSnapshot,    enrichmentGeneration: 0,  });  const settingsRef = yield* Ref.make(initialSettings);  const enrichmentFiberRef = yield* Ref.make<Fiber.Fiber<void, unknown> | null>(null);  const scope = yield* Effect.scope;   const publishEnrichedSnapshot = Effect.fn("publishEnrichedSnapshot")(function* (    generation: number,    nextSnapshot: ServerProvider,  ) {    const snapshotToPublish = yield* Ref.modify(snapshotStateRef, (state) => {      if (state.enrichmentGeneration !== generation || Equal.equals(state.snapshot, nextSnapshot)) {        return [null, state] as const;      }      return [        nextSnapshot,        {          ...state,          snapshot: nextSnapshot,        },      ] as const;    });    if (snapshotToPublish === null) {      return;    }    yield* PubSub.publish(changesPubSub, snapshotToPublish);  });   const restartSnapshotEnrichment = Effect.fn("restartSnapshotEnrichment")(function* (    settings: Settings,    snapshot: ServerProvider,    generation: number,  ) {    const previousFiber = yield* Ref.getAndSet(enrichmentFiberRef, null);    if (previousFiber) {      yield* Fiber.interrupt(previousFiber).pipe(Effect.ignore);    }     if (!input.enrichSnapshot) {      return;    }     const fiber = yield* input      .enrichSnapshot({        settings,        snapshot,        getSnapshot: Ref.get(snapshotStateRef).pipe(Effect.map((state) => state.snapshot)),        publishSnapshot: (nextSnapshot) => publishEnrichedSnapshot(generation, nextSnapshot),      })      .pipe(Effect.ignoreCause({ log: true }), Effect.forkIn(scope));     yield* Ref.set(enrichmentFiberRef, fiber);  });   const applySnapshotBase = Effect.fn("applySnapshot")(function* (    nextSettings: Settings,    options?: { readonly forceRefresh?: boolean },  ) {    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(snapshotStateRef).pipe(Effect.map((state) => state.snapshot));    }     const nextSnapshot = yield* input.checkProvider;    const nextGeneration = yield* Ref.modify(snapshotStateRef, (state) => {      const generation = input.enrichSnapshot        ? state.enrichmentGeneration + 1        : state.enrichmentGeneration;      return [        generation,        {          snapshot: nextSnapshot,          enrichmentGeneration: generation,        },      ] as const;    });    yield* Ref.set(settingsRef, nextSettings);    yield* PubSub.publish(changesPubSub, nextSnapshot);    yield* restartSnapshotEnrichment(nextSettings, nextSnapshot, nextGeneration);    return nextSnapshot;  });  const applySnapshot = (nextSettings: Settings, options?: { readonly forceRefresh?: boolean }) =>    refreshSemaphore.withPermits(1)(applySnapshotBase(nextSettings, options));   const refreshSnapshot = Effect.fn("refreshSnapshot")(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);   yield* applySnapshot(initialSettings, { forceRefresh: true }).pipe(    Effect.ignoreCause({ log: true }),    Effect.forkScoped,  );   return {    maintenanceCapabilities: input.maintenanceCapabilities,    getSnapshot: Ref.get(snapshotStateRef).pipe(Effect.map((state) => state.snapshot)),    refresh: refreshSnapshot().pipe(Effect.tapError(Effect.logError), Effect.orDie),    get streamChanges() {      return Stream.fromPubSub(changesPubSub);    },  } satisfies ServerProviderShape;});