import { Effect } from "effect"; import type { AppDescriptor } from "../pipeline/descriptor"; import type { AppsStore } from "../plugin/store"; import { BindingError, type ClientResolver, type ConnectionCandidate } from "../plugin/bindings"; import { ArtifactStoreError, asSnapshotId, type ArtifactStore, type FileSet, type ScopeArtifactStore, type SnapshotId, } from "../seams/artifact-store"; import { scopeAddressStorageKey } from "../seams/scope-address"; export * from "./daily-brief"; export const makeInMemoryAppsStore = (): AppsStore & { readonly descriptors: Map; } => { const descriptors = new Map(); const publishedAt = new Map(); const keyFor = (tenant: string, key: string): string => `${tenant}:${key}`; return { descriptors, putDescriptor: (tenant, _owner, descriptor) => Effect.sync(() => { const key = keyFor(tenant, descriptor.scope); descriptors.set(key, descriptor); publishedAt.set(key, Date.now()); }), getDescriptor: (tenant, scope) => Effect.sync(() => descriptors.get(keyFor(tenant, scope)) ?? null), removeDescriptor: (tenant, scope) => Effect.sync(() => { const key = keyFor(tenant, scope); descriptors.delete(key); publishedAt.delete(key); }), listDescriptors: (tenant) => Effect.sync(() => [...descriptors.entries()] .filter(([key]) => key.startsWith(`${tenant}:`)) .map(([key, descriptor]) => ({ descriptor, publishedAt: publishedAt.get(key) ?? 0, })), ), }; }; export const makeInMemoryArtifactStore = (): ArtifactStore => { const scopes = new Map>(); const order = new Map(); let counter = 0; const forScope = (key: string): ScopeArtifactStore => { const snaps = scopes.get(key) ?? new Map(); scopes.set(key, snaps); const seq = order.get(key) ?? []; order.set(key, seq); return { commit: (files, message) => Effect.sync(() => { const id = `mem${(++counter).toString(16).padStart(40, "0")}`; snaps.set(id, new Map(files)); seq.push(id); return { id: asSnapshotId(id), message, committedAt: Date.now() }; }), read: (id) => snaps.has(id) ? Effect.succeed(snaps.get(id)!) : Effect.fail(new ArtifactStoreError({ message: `no snapshot ${id}` })), readFile: (id, path) => Effect.succeed(snaps.get(id)?.get(path) ?? null), list: (id) => Effect.succeed([...(snaps.get(id)?.keys() ?? [])]), latest: () => Effect.sync(() => { const last = seq.at(-1); if (!last) return null; return { id: asSnapshotId(last), message: "latest", committedAt: Date.now() }; }), log: (limit) => Effect.sync(() => [...seq] .reverse() .slice(0, limit ?? seq.length) .map((id) => ({ id: asSnapshotId(id), message: "", committedAt: Date.now() })), ), }; }; return { forScope: (address) => Effect.succeed(forScope(scopeAddressStorageKey(address))), removeScope: (address) => Effect.sync(() => { const key = scopeAddressStorageKey(address); scopes.delete(key); order.delete(key); }), }; }; export type { SnapshotId }; export const makeTestResolver = ( handlers: Record unknown>>, connections?: readonly ConnectionCandidate[], ): ClientResolver & { readonly calls: { integration: string; connection: string; method: string }[]; } => { const calls: { integration: string; connection: string; method: string }[] = []; const knownConnections = connections ?? Object.keys(handlers).map((integration) => ({ address: `tools.${integration}.user.${integration}`, integration, name: integration, owner: "user", })); return { calls, listConnections: ({ integration }) => Effect.succeed( knownConnections.filter((connection) => connection.integration === integration), ), resolveConnection: ({ connection }) => Effect.succeed( knownConnections.find( (candidate) => candidate.address === connection || candidate.name === connection, ) ?? null, ), call: ({ integration, connection, path, args }) => { const method = path.join("."); calls.push({ integration, connection, method }); const handler = handlers[integration]?.[method]; if (!handler) { return Effect.fail( new BindingError({ message: `no test handler for ${integration}.${method}`, role: integration, integration, }), ); } return Effect.sync(() => handler(args)); }, }; };