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),
});
}