Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
65 lines
2.0 KiB
TypeScript
65 lines
2.0 KiB
TypeScript
export type ToolkitDirectoryEntry = {
|
|
slug: string;
|
|
name: string;
|
|
logo: string | null;
|
|
noAuth: boolean;
|
|
};
|
|
|
|
export type ToolkitCatalogEntry = ToolkitDirectoryEntry & { connected: boolean };
|
|
|
|
export const COMPOSIO_DIRECTORY_TTL_MS = 60 * 60 * 1000;
|
|
|
|
export function mergeCatalogWithConnected(
|
|
directory: ToolkitDirectoryEntry[],
|
|
connectedSlugs: Iterable<string>,
|
|
): ToolkitCatalogEntry[] {
|
|
const connected = new Set([...connectedSlugs].map((slug) => slug.trim().toLowerCase()));
|
|
return directory.map((item) => ({
|
|
...item,
|
|
connected: connected.has(item.slug.trim().toLowerCase()),
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Coalesces directory loads and serves stale entries during refreshes. Failed
|
|
* refreshes preserve stale entries for later retries; cold-load errors propagate.
|
|
*/
|
|
export function createToolkitDirectoryCache(opts?: { ttlMs?: number; now?: () => number }) {
|
|
const ttlMs = opts?.ttlMs ?? COMPOSIO_DIRECTORY_TTL_MS;
|
|
const now = opts?.now ?? Date.now;
|
|
let entry: { items: ToolkitDirectoryEntry[]; fetchedAt: number } | undefined;
|
|
let inflight: Promise<ToolkitDirectoryEntry[]> | undefined;
|
|
|
|
async function load(loader: () => Promise<ToolkitDirectoryEntry[]>) {
|
|
inflight ??= loader()
|
|
.then((items) => {
|
|
entry = { items, fetchedAt: now() };
|
|
return items;
|
|
})
|
|
.finally(() => {
|
|
inflight = undefined;
|
|
});
|
|
return inflight;
|
|
}
|
|
|
|
return {
|
|
peek(): ToolkitDirectoryEntry[] | undefined {
|
|
return entry?.items;
|
|
},
|
|
async get(loader: () => Promise<ToolkitDirectoryEntry[]>): Promise<ToolkitDirectoryEntry[]> {
|
|
if (!entry) return load(loader);
|
|
if (now() - entry.fetchedAt < ttlMs) return entry.items;
|
|
if (!inflight) {
|
|
// Keep stale items on failure; load clears inflight so a later read can retry.
|
|
void load(loader).catch(() => undefined);
|
|
}
|
|
return entry.items;
|
|
},
|
|
invalidate() {
|
|
entry = undefined;
|
|
},
|
|
};
|
|
}
|
|
|
|
export const composioToolkitDirectory = createToolkitDirectoryCache();
|