import type { LureHandlerOptions, LureHandler } from "./types.js"; import type { StandardSchemaV1 } from "@standard-schema/spec"; import { LureCache } from "./loader.js"; import { watchLures } from "./watcher.js"; import { createQueue } from "./queue.js"; import type { FSWatcher } from "chokidar"; export async function createLureHandler( options: LureHandlerOptions, ): Promise { const { basePath: rawBasePath, luresDir, callback, configSchema, maxAttempts = 1, allowUnverified = true, watch = false, } = options; const basePath = "/" + rawBasePath.replace(/^\/+/, "").replace(/\/+$/, ""); const cache = new LureCache( luresDir, allowUnverified, configSchema as StandardSchemaV1 | undefined, ); await cache.load(); let watcher: FSWatcher | undefined; if (watch) { watcher = watchLures(luresDir, cache); } const queue = createQueue({ cache, callback: callback as (prompt: string, config: unknown) => Promise, maxAttempts, }); return { handle(req) { if (basePath === "/") { // root basePath: every path matches; lurePath is the full path } else if (req.path !== basePath && !req.path.startsWith(basePath + "/")) { return false; } const lurePath = basePath === "/" ? req.path : req.path.slice(basePath.length) || "/"; const lure = cache.get(lurePath); if (lure === undefined) { return false; } queue.enqueue(lurePath, req); return true; }, async destroy() { if (watcher !== undefined) { await watcher.close(); } await queue.drain(); }, }; }