import { ProviderKind, type ThreadId } from "@t3tools/contracts";import { Effect, Layer, Option, Schema } from "effect"; import type { ProviderSessionRuntime } from "../../persistence/Services/ProviderSessionRuntime.ts";import { ProviderSessionRuntimeRepository } from "../../persistence/Services/ProviderSessionRuntime.ts";import { ProviderSessionDirectoryPersistenceError, ProviderValidationError } from "../Errors.ts";import { ProviderSessionDirectory, type ProviderRuntimeBinding, type ProviderRuntimeBindingWithMetadata, type ProviderSessionDirectoryShape,} from "../Services/ProviderSessionDirectory.ts"; function toPersistenceError(operation: string) { return (cause: unknown) => new ProviderSessionDirectoryPersistenceError({ operation, detail: `Failed to execute ${operation}.`, cause, });} function decodeProviderKind( providerName: string, operation: string,): Effect.Effect<ProviderKind, ProviderSessionDirectoryPersistenceError> { return Schema.decodeUnknownEffect(ProviderKind)(providerName).pipe( Effect.mapError( (cause) => new ProviderSessionDirectoryPersistenceError({ operation, detail: `Unknown persisted provider '${providerName}'.`, cause, }), ), );} function isRecord(value: unknown): value is Record<string, unknown> { return value !== null && typeof value === "object" && !Array.isArray(value);} function mergeRuntimePayload( existing: unknown | null, next: unknown | null | undefined,): unknown | null { if (next === undefined) { return existing ?? null; } if (isRecord(existing) && isRecord(next)) { return { ...existing, ...next }; } return next;} function toRuntimeBinding( runtime: ProviderSessionRuntime, operation: string,): Effect.Effect<ProviderRuntimeBindingWithMetadata, ProviderSessionDirectoryPersistenceError> { return decodeProviderKind(runtime.providerName, operation).pipe( Effect.map( (provider) => ({ threadId: runtime.threadId, provider, adapterKey: runtime.adapterKey, runtimeMode: runtime.runtimeMode, status: runtime.status, resumeCursor: runtime.resumeCursor, runtimePayload: runtime.runtimePayload, lastSeenAt: runtime.lastSeenAt, }) satisfies ProviderRuntimeBindingWithMetadata, ), );} const makeProviderSessionDirectory = Effect.gen(function* () { const repository = yield* ProviderSessionRuntimeRepository; const getBinding = (threadId: ThreadId) => repository.getByThreadId({ threadId }).pipe( Effect.mapError(toPersistenceError("ProviderSessionDirectory.getBinding:getByThreadId")), Effect.flatMap((runtime) => Option.match(runtime, { onNone: () => Effect.succeed(Option.none<ProviderRuntimeBinding>()), onSome: (value) => toRuntimeBinding(value, "ProviderSessionDirectory.getBinding").pipe( Effect.map((binding) => Option.some(binding)), ), }), ), ); const upsert: ProviderSessionDirectoryShape["upsert"] = Effect.fn(function* (binding) { const existing = yield* repository .getByThreadId({ threadId: binding.threadId }) .pipe(Effect.mapError(toPersistenceError("ProviderSessionDirectory.upsert:getByThreadId"))); const existingRuntime = Option.getOrUndefined(existing); const resolvedThreadId = binding.threadId ?? existingRuntime?.threadId; if (!resolvedThreadId) { return yield* new ProviderValidationError({ operation: "ProviderSessionDirectory.upsert", issue: "threadId must be a non-empty string.", }); } const now = new Date().toISOString(); const providerChanged = existingRuntime !== undefined && existingRuntime.providerName !== binding.provider; yield* repository .upsert({ threadId: resolvedThreadId, providerName: binding.provider, adapterKey: binding.adapterKey ?? (providerChanged ? binding.provider : (existingRuntime?.adapterKey ?? binding.provider)), runtimeMode: binding.runtimeMode ?? existingRuntime?.runtimeMode ?? "full-access", status: binding.status ?? existingRuntime?.status ?? "running", lastSeenAt: now, resumeCursor: binding.resumeCursor !== undefined ? binding.resumeCursor : (existingRuntime?.resumeCursor ?? null), runtimePayload: mergeRuntimePayload( existingRuntime?.runtimePayload ?? null, binding.runtimePayload, ), }) .pipe(Effect.mapError(toPersistenceError("ProviderSessionDirectory.upsert:upsert"))); }); const getProvider: ProviderSessionDirectoryShape["getProvider"] = (threadId) => getBinding(threadId).pipe( Effect.flatMap((binding) => Option.match(binding, { onSome: (value) => Effect.succeed(value.provider), onNone: () => Effect.fail( new ProviderSessionDirectoryPersistenceError({ operation: "ProviderSessionDirectory.getProvider", detail: `No persisted provider binding found for thread '${threadId}'.`, }), ), }), ), ); const listThreadIds: ProviderSessionDirectoryShape["listThreadIds"] = () => repository.list().pipe( Effect.mapError(toPersistenceError("ProviderSessionDirectory.listThreadIds:list")), Effect.map((rows) => rows.map((row) => row.threadId)), ); const listBindings: ProviderSessionDirectoryShape["listBindings"] = () => repository.list().pipe( Effect.mapError(toPersistenceError("ProviderSessionDirectory.listBindings:list")), Effect.flatMap((rows) => Effect.forEach( rows, (row) => toRuntimeBinding(row, "ProviderSessionDirectory.listBindings"), { concurrency: "unbounded" }, ), ), ); return { upsert, getProvider, getBinding, listThreadIds, listBindings, } satisfies ProviderSessionDirectoryShape;}); export const ProviderSessionDirectoryLive = Layer.effect( ProviderSessionDirectory, makeProviderSessionDirectory,); export function makeProviderSessionDirectoryLive() { return Layer.effect(ProviderSessionDirectory, makeProviderSessionDirectory);}
import { ProviderKind, type ThreadId } from "@t3tools/contracts";
import { Effect, Layer, Option, Schema } from "effect";
import type { ProviderSessionRuntime } from "../../persistence/Services/ProviderSessionRuntime.ts";
import { ProviderSessionRuntimeRepository } from "../../persistence/Services/ProviderSessionRuntime.ts";
import { ProviderSessionDirectoryPersistenceError, ProviderValidationError } from "../Errors.ts";
import {
ProviderSessionDirectory,
type ProviderRuntimeBinding,
type ProviderRuntimeBindingWithMetadata,
type ProviderSessionDirectoryShape,
} from "../Services/ProviderSessionDirectory.ts";
function toPersistenceError(operation: string) {
return (cause: unknown) =>
new ProviderSessionDirectoryPersistenceError({
operation,
detail: `Failed to execute ${operation}.`,
cause,
});
}
function decodeProviderKind(
providerName: string,
operation: string,
): Effect.Effect<ProviderKind, ProviderSessionDirectoryPersistenceError> {
return Schema.decodeUnknownEffect(ProviderKind)(providerName).pipe(
Effect.mapError(
(cause) =>
new ProviderSessionDirectoryPersistenceError({
operation,
detail: `Unknown persisted provider '${providerName}'.`,
cause,
}),
),
);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function mergeRuntimePayload(
existing: unknown | null,
next: unknown | null | undefined,
): unknown | null {
if (next === undefined) {
return existing ?? null;
}
if (isRecord(existing) && isRecord(next)) {
return { ...existing, ...next };
}
return next;
}
function toRuntimeBinding(
runtime: ProviderSessionRuntime,
operation: string,
): Effect.Effect<ProviderRuntimeBindingWithMetadata, ProviderSessionDirectoryPersistenceError> {
return decodeProviderKind(runtime.providerName, operation).pipe(
Effect.map(
(provider) =>
({
threadId: runtime.threadId,
provider,
adapterKey: runtime.adapterKey,
runtimeMode: runtime.runtimeMode,
status: runtime.status,
resumeCursor: runtime.resumeCursor,
runtimePayload: runtime.runtimePayload,
lastSeenAt: runtime.lastSeenAt,
}) satisfies ProviderRuntimeBindingWithMetadata,
),
);
}
const makeProviderSessionDirectory = Effect.gen(function* () {
const repository = yield* ProviderSessionRuntimeRepository;
const getBinding = (threadId: ThreadId) =>
repository.getByThreadId({ threadId }).pipe(
Effect.mapError(toPersistenceError("ProviderSessionDirectory.getBinding:getByThreadId")),
Effect.flatMap((runtime) =>
Option.match(runtime, {
onNone: () => Effect.succeed(Option.none<ProviderRuntimeBinding>()),
onSome: (value) =>
toRuntimeBinding(value, "ProviderSessionDirectory.getBinding").pipe(
Effect.map((binding) => Option.some(binding)),
),
}),
),
);
const upsert: ProviderSessionDirectoryShape["upsert"] = Effect.fn(function* (binding) {
const existing = yield* repository
.getByThreadId({ threadId: binding.threadId })
.pipe(Effect.mapError(toPersistenceError("ProviderSessionDirectory.upsert:getByThreadId")));
const existingRuntime = Option.getOrUndefined(existing);
const resolvedThreadId = binding.threadId ?? existingRuntime?.threadId;
if (!resolvedThreadId) {
return yield* new ProviderValidationError({
operation: "ProviderSessionDirectory.upsert",
issue: "threadId must be a non-empty string.",
});
}
const now = new Date().toISOString();
const providerChanged =
existingRuntime !== undefined && existingRuntime.providerName !== binding.provider;
yield* repository
.upsert({
threadId: resolvedThreadId,
providerName: binding.provider,
adapterKey:
binding.adapterKey ??
(providerChanged ? binding.provider : (existingRuntime?.adapterKey ?? binding.provider)),
runtimeMode: binding.runtimeMode ?? existingRuntime?.runtimeMode ?? "full-access",
status: binding.status ?? existingRuntime?.status ?? "running",
lastSeenAt: now,
resumeCursor:
binding.resumeCursor !== undefined
? binding.resumeCursor
: (existingRuntime?.resumeCursor ?? null),
runtimePayload: mergeRuntimePayload(
existingRuntime?.runtimePayload ?? null,
binding.runtimePayload,
),
})
.pipe(Effect.mapError(toPersistenceError("ProviderSessionDirectory.upsert:upsert")));
});
const getProvider: ProviderSessionDirectoryShape["getProvider"] = (threadId) =>
getBinding(threadId).pipe(
Effect.flatMap((binding) =>
Option.match(binding, {
onSome: (value) => Effect.succeed(value.provider),
onNone: () =>
Effect.fail(
new ProviderSessionDirectoryPersistenceError({
operation: "ProviderSessionDirectory.getProvider",
detail: `No persisted provider binding found for thread '${threadId}'.`,
}),
),
}),
),
);
const listThreadIds: ProviderSessionDirectoryShape["listThreadIds"] = () =>
repository.list().pipe(
Effect.mapError(toPersistenceError("ProviderSessionDirectory.listThreadIds:list")),
Effect.map((rows) => rows.map((row) => row.threadId)),
);
const listBindings: ProviderSessionDirectoryShape["listBindings"] = () =>
repository.list().pipe(
Effect.mapError(toPersistenceError("ProviderSessionDirectory.listBindings:list")),
Effect.flatMap((rows) =>
Effect.forEach(
rows,
(row) => toRuntimeBinding(row, "ProviderSessionDirectory.listBindings"),
{ concurrency: "unbounded" },
),
),
);
return {
upsert,
getProvider,
getBinding,
listThreadIds,
listBindings,
} satisfies ProviderSessionDirectoryShape;
});
export const ProviderSessionDirectoryLive = Layer.effect(
ProviderSessionDirectory,
makeProviderSessionDirectory,
);
export function makeProviderSessionDirectoryLive() {
return Layer.effect(ProviderSessionDirectory, makeProviderSessionDirectory);
}