import { EnvironmentId, type EnvironmentId as EnvironmentIdType } from "@t3tools/contracts"; import * as Cause from "effect/Cause"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Option from "effect/Option"; import * as Stream from "effect/Stream"; import * as SubscriptionRef from "effect/SubscriptionRef"; import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; import type { ConnectionAttemptError } from "../connection/model.ts"; import { EnvironmentNotRegisteredError, EnvironmentRegistry } from "../connection/registry.ts"; import { type EnvironmentRpcInput, type EnvironmentRpcStreamFailure, type EnvironmentRpcStreamValue, type EnvironmentSubscriptionRpcTag, type EnvironmentUnaryRpcTag, EnvironmentRpcUnavailableError, request, subscribe, } from "../rpc/client.ts"; import { EnvironmentSupervisor } from "../connection/supervisor.ts"; interface EnvironmentAtomOptions { readonly label: string; readonly execute: (input: Input) => Effect.Effect; readonly scheduler?: AtomCommandScheduler; readonly concurrency?: AtomCommandConcurrency<{ readonly environmentId: EnvironmentIdType; readonly input: Input; }>; } interface EnvironmentCommandAtomOptions extends Omit< EnvironmentAtomOptions, "execute" > { readonly execute: ( input: Input, registry: AtomRegistry.AtomRegistry, environmentId: EnvironmentIdType, ) => Effect.Effect; } interface EnvironmentQueryAtomOptions extends EnvironmentAtomOptions< Input, A, E, R > { readonly staleTimeMs?: number; readonly idleTtlMs?: number; readonly refreshIntervalMs?: number; readonly refreshTrigger?: (target: { readonly environmentId: EnvironmentIdType; readonly input: Input; }) => Atom.Atom | undefined; } interface EnvironmentSubscriptionAtomOptions { readonly label: string; readonly subscribe: (input: Input) => Stream.Stream; readonly idleTtlMs?: number; } export type SettledAsyncResult = AsyncResult.Success | AsyncResult.Failure; export type AtomCommandResult = SettledAsyncResult; export type AtomCommandSuccess = R extends AtomCommandResult ? A : never; export type AtomCommandFailure = R extends AtomCommandResult ? E : never; export interface AtomCommandOptions { readonly label?: string; readonly reportFailure?: boolean; readonly reportDefect?: boolean; } export interface AtomCommandReporter { readonly warn: (message: string, cause: Cause.Cause) => void; readonly error: (message: string, cause: Cause.Cause) => void; } export interface AtomCommand { readonly label: string; readonly run: (registry: AtomRegistry.AtomRegistry, input: W) => Promise>; } export type AtomCommandConcurrency = /** Every invocation runs independently. */ | { readonly mode: "parallel" } | { /** * `serial` preserves every invocation in FIFO order, `singleFlight` shares an active * invocation, and `latest` coalesces queued invocations to the newest input. */ readonly mode: "serial" | "singleFlight" | "latest"; readonly key: (input: W) => string; }; interface AtomCommandSchedulerState { readonly serial: Map>; readonly singleFlight: Map>; readonly latest: Map; } interface AtomCommandLatestBatch { execute: () => Promise>; readonly resolve: Array<(result: AtomCommandResult) => void>; } interface AtomCommandLatestLane { running: boolean; pending: AtomCommandLatestBatch | undefined; } export interface AtomCommandScheduler { readonly schedule: ( registry: AtomRegistry.AtomRegistry, concurrency: AtomCommandConcurrency, input: W, execute: () => Promise>, ) => Promise>; } async function settleAtomCommandResult( execute: () => Promise>, ): Promise> { try { return await execute(); } catch (defect) { return AsyncResult.failure(Cause.die(defect)); } } export function createAtomCommandScheduler(): AtomCommandScheduler { const registryStates = new WeakMap(); const stateFor = (registry: AtomRegistry.AtomRegistry): AtomCommandSchedulerState => { const existing = registryStates.get(registry); if (existing !== undefined) { return existing; } const state: AtomCommandSchedulerState = { serial: new Map(), singleFlight: new Map(), latest: new Map(), }; registryStates.set(registry, state); return state; }; return { schedule: ( registry: AtomRegistry.AtomRegistry, concurrency: AtomCommandConcurrency, input: W, execute: () => Promise>, ): Promise> => { if (concurrency.mode === "parallel") { return execute(); } const key = concurrency.key(input); const state = stateFor(registry); if (concurrency.mode === "singleFlight") { const existing = state.singleFlight.get(key) as | Promise> | undefined; if (existing !== undefined) { return existing; } const current = execute(); state.singleFlight.set(key, current); void current.then( () => { if (state.singleFlight.get(key) === current) { state.singleFlight.delete(key); } }, () => { if (state.singleFlight.get(key) === current) { state.singleFlight.delete(key); } }, ); return current; } if (concurrency.mode === "serial") { const previous = state.serial.get(key); const current = previous === undefined ? execute() : previous.then(execute, execute); state.serial.set(key, current); void current.then( () => { if (state.serial.get(key) === current) { state.serial.delete(key); } }, () => { if (state.serial.get(key) === current) { state.serial.delete(key); } }, ); return current; } let lane = state.latest.get(key); if (lane === undefined) { lane = { running: false, pending: undefined }; state.latest.set(key, lane); } const activeLane = lane; const result = new Promise>((resolve) => { if (activeLane.pending === undefined) { activeLane.pending = { execute: execute as () => Promise>, resolve: [resolve as (result: AtomCommandResult) => void], }; return; } activeLane.pending.execute = execute as () => Promise>; activeLane.pending.resolve.push( resolve as (result: AtomCommandResult) => void, ); }); if (!activeLane.running) { activeLane.running = true; void (async () => { while (activeLane.pending !== undefined) { const batch = activeLane.pending; activeLane.pending = undefined; let batchResult: AtomCommandResult; try { batchResult = await batch.execute(); } catch (defect) { batchResult = AsyncResult.failure(Cause.die(defect)); } for (const resolve of batch.resolve) { resolve(batchResult); } } activeLane.running = false; if (state.latest.get(key) === activeLane) { state.latest.delete(key); } })(); } return result; }, }; } /** Runs one effect inside an existing command scheduler lane. */ export function scheduleAtomCommandEffect( registry: AtomRegistry.AtomRegistry, scheduler: AtomCommandScheduler, concurrency: AtomCommandConcurrency, input: W, effect: Effect.Effect, ): Effect.Effect { return Effect.gen(function* () { const context = yield* Effect.context(); const result = yield* Effect.promise((signal) => scheduler.schedule(registry, concurrency, input, async () => { const exit = await Effect.runPromiseExitWith(context)(effect, { signal }); return Exit.isSuccess(exit) ? AsyncResult.success(exit.value) : AsyncResult.failure(exit.cause); }), ); return result._tag === "Success" ? result.value : yield* Effect.failCause(result.cause); }); } export async function runAtomCommand( registry: AtomRegistry.AtomRegistry, command: AtomCommand, input: W, options: AtomCommandOptions = {}, reporter: AtomCommandReporter = console, ): Promise> { const result = await settleAtomCommandResult(() => command.run(registry, input)); reportAtomCommandResult(result, { ...options, label: options.label ?? command.label }, reporter); return result; } export function mapAtomCommandResult( result: AtomCommandResult, map: (value: A) => B, ): AtomCommandResult { return result._tag === "Success" ? AsyncResult.success(map(result.value)) : AsyncResult.failure(result.cause); } export function isAtomCommandInterrupted(result: AtomCommandResult): boolean { return result._tag === "Failure" && Cause.hasInterruptsOnly(result.cause); } export function squashAtomCommandFailure(result: { readonly cause: Cause.Cause; }): unknown { return Cause.squash(result.cause); } export async function settleAsyncResult( execute: () => Promise>, ): Promise> { try { return AsyncResult.fromExit(await execute()); } catch (defect) { return AsyncResult.failure(Cause.die(defect)); } } export async function executeAtomCommand( execute: () => Promise>, options: AtomCommandOptions = {}, reporter: AtomCommandReporter = console, ): Promise> { const result = await settleAsyncResult(execute); reportAtomCommandResult(result, options, reporter); return result; } export interface AtomQueryOptions extends AtomCommandOptions { /** * Force a fresh execution instead of accepting a value the atom already * holds (e.g. an SWR-cached result within its stale window). Used by * verification flows where a cached failure must not satisfy a retry. */ readonly refresh?: boolean; /** Interrupt the query wait when its caller no longer wants the result. */ readonly signal?: AbortSignal; } export async function executeAtomQuery( registry: AtomRegistry.AtomRegistry, atom: Atom.Atom>, options: AtomQueryOptions = {}, reporter: AtomCommandReporter = console, ): Promise> { const query = Effect.scoped( Effect.gen(function* () { yield* AtomRegistry.mount(registry, atom); if (options.refresh) { yield* Effect.sync(() => { // Only a settled value can be a leftover from an earlier read; a // computation that mounting just started is already fresh. const current = registry.get(atom); if (current._tag !== "Initial" && !current.waiting) { registry.refresh(atom); } }); } return yield* AtomRegistry.getResult(registry, atom, { suspendOnWaiting: true, }); }), ); return executeAtomCommand( () => Effect.runPromiseExit(query, { signal: options.signal }), options, reporter, ); } export function createRuntimeCommand( runtime: Atom.AtomRuntime, options: { readonly label: string; readonly execute: (input: W, registry: AtomRegistry.AtomRegistry) => Effect.Effect; readonly scheduler?: AtomCommandScheduler; readonly concurrency?: AtomCommandConcurrency; }, ): AtomCommand { const scheduler = options.scheduler ?? createAtomCommandScheduler(); const concurrency = options.concurrency ?? { mode: "parallel" as const }; return { label: options.label, run: (registry, input) => settleAtomCommandResult(() => scheduler.schedule(registry, concurrency, input, () => { const atom = runtime .atom(options.execute(input, registry)) .pipe(Atom.withLabel(options.label)); return executeAtomQuery(registry, atom, { reportDefect: false, reportFailure: false }); }), ), }; } export function reportAtomCommandResult( result: AtomCommandResult, options: AtomCommandOptions = {}, reporter: AtomCommandReporter = console, ): void { if (AsyncResult.isSuccess(result) || Cause.hasInterruptsOnly(result.cause)) { return; } const label = options.label ?? "atom command"; if (Cause.hasDies(result.cause)) { if (options.reportDefect ?? true) { reporter.error(`[atom-command] ${label} defected`, result.cause); } } else if (options.reportFailure ?? true) { reporter.warn(`[atom-command] ${label} failed`, result.cause); } } export async function settlePromise( execute: () => Promise, ): Promise> { try { return AsyncResult.success(await execute()); } catch (defect) { return AsyncResult.failure(Cause.die(defect)); } } export function environmentRpcKey(target: { readonly environmentId: EnvironmentIdType; readonly input: Input; }): string { return JSON.stringify([target.environmentId, target.input]); } function parseEnvironmentRpcKey(key: string): { readonly environmentId: EnvironmentIdType; readonly input: Input; } { const decoded = JSON.parse(key) as [EnvironmentIdType, Input]; return { environmentId: EnvironmentId.make(decoded[0]), input: decoded[1], }; } function runInEnvironment( environmentId: EnvironmentIdType, effect: Effect.Effect, ): Effect.Effect< A, E | EnvironmentNotRegisteredError, EnvironmentRegistry | Exclude > { return EnvironmentRegistry.pipe( Effect.flatMap((registry) => registry.run(environmentId, effect)), ); } export function runStreamInEnvironment( environmentId: EnvironmentIdType, stream: Stream.Stream, ): Stream.Stream< A, E | EnvironmentNotRegisteredError, EnvironmentRegistry | Exclude > { return Stream.unwrap( EnvironmentRegistry.pipe(Effect.map((registry) => registry.runStream(environmentId, stream))), ); } export function followStreamInEnvironment( environmentId: EnvironmentIdType, stream: Stream.Stream, ): Stream.Stream> { return Stream.unwrap( EnvironmentRegistry.pipe( Effect.map((registry) => registry.followStream(environmentId, stream)), ), ); } export function createEnvironmentQueryAtomFamily( runtime: Atom.AtomRuntime, options: EnvironmentQueryAtomOptions< Input, A, E, EnvironmentSupervisor | EnvironmentRegistry | AtomRegistry.AtomRegistry | R >, ): (target: { readonly environmentId: EnvironmentIdType; readonly input: Input; }) => Atom.Atom> { const connectionAtom = Atom.family((environmentId: EnvironmentIdType) => runtime.atom( followStreamInEnvironment( environmentId, Stream.unwrap( EnvironmentSupervisor.pipe( Effect.map((supervisor) => SubscriptionRef.changes(supervisor.state).pipe( Stream.zipLatest(SubscriptionRef.changes(supervisor.session)), ), ), ), ), ), { initialValue: null }, ), ); const family = Atom.family((key: string) => { const target = parseEnvironmentRpcKey(key); const idleTtlMs = options.idleTtlMs ?? 5 * 60_000; const queryAtom = runtime .atom< A, E | ConnectionAttemptError | EnvironmentNotRegisteredError | EnvironmentRpcUnavailableError >((get) => { const connection = Option.getOrNull( AsyncResult.value(get(connectionAtom(target.environmentId))), ); if (connection === null) { return Effect.never; } const [connectionState, session] = connection; switch (connectionState.phase) { case "connected": return Option.isSome(session) ? runInEnvironment(target.environmentId, options.execute(target.input)) : Effect.never; case "connecting": case "backoff": return Effect.never; case "available": case "offline": case "blocked": if (connectionState.lastFailure !== null) { return Effect.fail(connectionState.lastFailure); } return Effect.fail( new EnvironmentRpcUnavailableError({ environmentId: target.environmentId, message: `Environment ${target.environmentId} is ${ connectionState.phase === "available" ? "not connected" : connectionState.phase }.`, }), ); } }) .pipe( Atom.swr({ staleTime: options.staleTimeMs ?? 30_000, revalidateOnMount: true, }), Atom.setIdleTTL(idleTtlMs), ); const intervalQuery = options.refreshIntervalMs === undefined ? queryAtom : queryAtom.pipe(Atom.withRefresh(options.refreshIntervalMs)); const refreshTrigger = options.refreshTrigger?.(target); return ( refreshTrigger === undefined ? intervalQuery : intervalQuery.pipe(Atom.makeRefreshOnSignal(refreshTrigger)) ).pipe(Atom.setIdleTTL(idleTtlMs), Atom.withLabel(`${options.label}:${key}`)); }); return (target) => family(environmentRpcKey(target)); } export function createEnvironmentSubscriptionAtomFamily( runtime: Atom.AtomRuntime, options: EnvironmentSubscriptionAtomOptions, ) { const family = Atom.family((key: string) => { const target = parseEnvironmentRpcKey(key); return runtime .atom(followStreamInEnvironment(target.environmentId, options.subscribe(target.input))) .pipe( Atom.setIdleTTL(options.idleTtlMs ?? 5 * 60_000), Atom.withLabel(`${options.label}:${key}`), ); }); return (target: { readonly environmentId: EnvironmentIdType; readonly input: Input }) => family(environmentRpcKey(target)); } export function createEnvironmentCommand( runtime: Atom.AtomRuntime, options: EnvironmentCommandAtomOptions, ) { return createRuntimeCommand(runtime, { label: options.label, ...(options.scheduler === undefined ? {} : { scheduler: options.scheduler }), ...(options.concurrency === undefined ? {} : { concurrency: options.concurrency }), execute: (target, registry) => runInEnvironment( target.environmentId, options.execute(target.input, registry, target.environmentId), ), }); } export function createEnvironmentRpcQueryAtomFamily( runtime: Atom.AtomRuntime, options: { readonly label: string; readonly tag: TTag; readonly staleTimeMs?: number; readonly idleTtlMs?: number; readonly refreshIntervalMs?: number; readonly refreshTrigger?: (target: { readonly environmentId: EnvironmentIdType; readonly input: EnvironmentRpcInput; }) => Atom.Atom | undefined; }, ) { return createEnvironmentQueryAtomFamily(runtime, { label: options.label, ...(options.staleTimeMs === undefined ? {} : { staleTimeMs: options.staleTimeMs }), ...(options.idleTtlMs === undefined ? {} : { idleTtlMs: options.idleTtlMs }), ...(options.refreshIntervalMs === undefined ? {} : { refreshIntervalMs: options.refreshIntervalMs }), ...(options.refreshTrigger === undefined ? {} : { refreshTrigger: options.refreshTrigger }), execute: (input: EnvironmentRpcInput) => request(options.tag, input), }); } export function createEnvironmentRpcSubscriptionAtomFamily< R, ER, TTag extends EnvironmentSubscriptionRpcTag, B = EnvironmentRpcStreamValue, >( runtime: Atom.AtomRuntime, options: { readonly label: string; readonly tag: TTag; readonly idleTtlMs?: number; readonly transform?: ( stream: Stream.Stream< EnvironmentRpcStreamValue, EnvironmentRpcStreamFailure, EnvironmentSupervisor | R >, ) => Stream.Stream, EnvironmentSupervisor | R>; }, ) { return createEnvironmentSubscriptionAtomFamily(runtime, { label: options.label, ...(options.idleTtlMs === undefined ? {} : { idleTtlMs: options.idleTtlMs }), subscribe: (input: EnvironmentRpcInput) => { const stream = subscribe(options.tag, input); return options.transform === undefined ? (stream as Stream.Stream, EnvironmentSupervisor | R>) : options.transform(stream); }, }); } export function createEnvironmentRpcCommand( runtime: Atom.AtomRuntime, options: { readonly label: string; readonly tag: TTag; readonly scheduler?: AtomCommandScheduler; readonly concurrency?: AtomCommandConcurrency<{ readonly environmentId: EnvironmentIdType; readonly input: EnvironmentRpcInput; }>; readonly onSuccess?: ( target: { readonly environmentId: EnvironmentIdType; readonly input: EnvironmentRpcInput; }, registry: AtomRegistry.AtomRegistry, ) => Effect.Effect; readonly onSettled?: ( target: { readonly environmentId: EnvironmentIdType; readonly input: EnvironmentRpcInput; }, registry: AtomRegistry.AtomRegistry, ) => Effect.Effect; }, ) { return createEnvironmentCommand(runtime, { label: options.label, ...(options.scheduler === undefined ? {} : { scheduler: options.scheduler }), ...(options.concurrency === undefined ? {} : { concurrency: options.concurrency }), execute: (input: EnvironmentRpcInput, registry, environmentId) => { const target = { environmentId, input, }; return request(options.tag, input).pipe( Effect.tap(() => options.onSuccess?.(target, registry) ?? Effect.void), Effect.ensuring(options.onSettled?.(target, registry) ?? Effect.void), ); }, }); }