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 Result from "effect/Result"; import * as Stream from "effect/Stream"; import * as SubscriptionRef from "effect/SubscriptionRef"; import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; import { EnvironmentNotRegisteredError, EnvironmentRegistry } from "../connection/registry.ts"; import { type EnvironmentRpcInput, type EnvironmentRpcStreamFailure, type EnvironmentRpcStreamValue, type EnvironmentStreamCommandRpcTag, type EnvironmentSubscriptionRpcTag, type EnvironmentUnaryRpcTag, request, runStream, 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 EnvironmentQueryAtomOptions extends EnvironmentAtomOptions< Input, A, E, R > { readonly staleTimeMs?: number; readonly idleTtlMs?: number; readonly refreshIntervalMs?: number; } 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; }, }; } 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 async function executeAtomQuery( registry: AtomRegistry.AtomRegistry, atom: Atom.Atom>, options: AtomCommandOptions = {}, reporter: AtomCommandReporter = console, ): Promise> { const query = Effect.scoped( Effect.gen(function* () { yield* AtomRegistry.mount(registry, atom); return yield* AtomRegistry.getResult(registry, atom, { suspendOnWaiting: true, }); }), ); return executeAtomCommand(() => Effect.runPromiseExit(query), 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 createRuntimeStreamCommand( runtime: Atom.AtomRuntime, options: { readonly label: string; readonly execute: (input: W, registry: AtomRegistry.AtomRegistry) => Stream.Stream; 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], }; } export 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)), ), ); } function createEnvironmentQueryAtomFamily( runtime: Atom.AtomRuntime, options: EnvironmentQueryAtomOptions, ): (target: { readonly environmentId: EnvironmentIdType; readonly input: Input; }) => Atom.Atom> { const rpcGenerationAtom = Atom.family((environmentId: EnvironmentIdType) => runtime.atom( followStreamInEnvironment( environmentId, Stream.unwrap( EnvironmentSupervisor.pipe( Effect.map((supervisor) => SubscriptionRef.changes(supervisor.state).pipe( Stream.filterMap((state) => state.phase === "connected" ? Result.succeed(state.generation) : Result.failVoid, ), Stream.changes, Stream.map((generation) => generation), ), ), ), ), ), { initialValue: null }, ), ); const family = Atom.family((key: string) => { const target = parseEnvironmentRpcKey(key); const idleTtlMs = options.idleTtlMs ?? 5 * 60_000; const queryAtom = runtime .atom((get) => { const generation = Option.getOrNull( AsyncResult.value(get(rpcGenerationAtom(target.environmentId))), ); if (generation === null) { return Effect.never; } return runInEnvironment(target.environmentId, options.execute(target.input)); }) .pipe( Atom.swr({ staleTime: options.staleTimeMs ?? 30_000, revalidateOnMount: true, }), Atom.setIdleTTL(idleTtlMs), ); return ( options.refreshIntervalMs === undefined ? queryAtom : queryAtom.pipe(Atom.withRefresh(options.refreshIntervalMs)) ).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: EnvironmentAtomOptions, ) { return createRuntimeCommand(runtime, { label: options.label, ...(options.scheduler === undefined ? {} : { scheduler: options.scheduler }), ...(options.concurrency === undefined ? {} : { concurrency: options.concurrency }), execute: (target) => runInEnvironment(target.environmentId, options.execute(target.input)), }); } function createEnvironmentStreamCommand( runtime: Atom.AtomRuntime, options: { readonly label: string; readonly execute: (input: Input) => Stream.Stream; readonly scheduler?: AtomCommandScheduler; readonly concurrency?: AtomCommandConcurrency<{ readonly environmentId: EnvironmentIdType; readonly input: Input; }>; }, ) { return createRuntimeStreamCommand(runtime, { label: options.label, ...(options.scheduler === undefined ? {} : { scheduler: options.scheduler }), ...(options.concurrency === undefined ? {} : { concurrency: options.concurrency }), execute: (target) => runStreamInEnvironment(target.environmentId, options.execute(target.input)).pipe( Stream.withSpan(options.label), ), }); } export function createEnvironmentRpcQueryAtomFamily( runtime: Atom.AtomRuntime, options: { readonly label: string; readonly tag: TTag; readonly staleTimeMs?: number; readonly idleTtlMs?: number; readonly refreshIntervalMs?: number; }, ) { return createEnvironmentQueryAtomFamily(runtime, { label: options.label, ...(options.staleTimeMs === undefined ? {} : { staleTimeMs: options.staleTimeMs }), ...(options.idleTtlMs === undefined ? {} : { idleTtlMs: options.idleTtlMs }), ...(options.refreshIntervalMs === undefined ? {} : { refreshIntervalMs: options.refreshIntervalMs }), 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; }>; }, ) { return createEnvironmentCommand(runtime, { label: options.label, ...(options.scheduler === undefined ? {} : { scheduler: options.scheduler }), ...(options.concurrency === undefined ? {} : { concurrency: options.concurrency }), execute: (input: EnvironmentRpcInput) => request(options.tag, input), }); } export function createEnvironmentRpcStreamCommand< R, ER, TTag extends EnvironmentStreamCommandRpcTag, >( runtime: Atom.AtomRuntime, options: { readonly label: string; readonly tag: TTag; readonly scheduler?: AtomCommandScheduler; readonly concurrency?: AtomCommandConcurrency<{ readonly environmentId: EnvironmentIdType; readonly input: EnvironmentRpcInput; }>; }, ) { return createEnvironmentStreamCommand(runtime, { label: options.label, ...(options.scheduler === undefined ? {} : { scheduler: options.scheduler }), ...(options.concurrency === undefined ? {} : { concurrency: options.concurrency }), execute: (input: EnvironmentRpcInput) => runStream(options.tag, input), }); }