@microfox/ai-worker 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +18 -0
- package/dist/chunk-4WU5ZCHS.mjs +1053 -0
- package/dist/chunk-4WU5ZCHS.mjs.map +1 -0
- package/dist/chunk-BJPQY2NJ.mjs +186 -0
- package/dist/chunk-BJPQY2NJ.mjs.map +1 -0
- package/dist/client-D25XR0V8.d.mts +167 -0
- package/dist/client-D25XR0V8.d.ts +167 -0
- package/dist/client.d.mts +2 -64
- package/dist/client.d.ts +2 -64
- package/dist/client.js +107 -2
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +7 -3
- package/dist/handler.d.mts +83 -14
- package/dist/handler.d.ts +83 -14
- package/dist/handler.js +773 -5
- package/dist/handler.js.map +1 -1
- package/dist/handler.mjs +7 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1027 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +159 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
- package/dist/chunk-FQCZSXDI.mjs +0 -83
- package/dist/chunk-FQCZSXDI.mjs.map +0 -1
- package/dist/chunk-WVR4JVWK.mjs +0 -285
- package/dist/chunk-WVR4JVWK.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/handler.ts","../src/mongoJobStore.ts","../src/redisJobStore.ts","../src/queueJobStore.ts"],"sourcesContent":["/**\n * Generic Lambda handler wrapper for worker agents.\n * Handles SQS events, executes user handlers, and sends webhook callbacks.\n * Job store: MongoDB only. Never uses HTTP/origin URL for job updates.\n */\n\nimport type { SQSEvent, SQSRecord, Context as LambdaContext } from 'aws-lambda';\nimport type { ZodType } from 'zod';\nimport { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';\nimport {\n createMongoJobStore,\n upsertJob,\n isMongoJobStoreConfigured,\n getJobById as getMongoJobById,\n} from './mongoJobStore';\nimport {\n createRedisJobStore,\n upsertRedisJob,\n isRedisJobStoreConfigured,\n loadJob as loadRedisJob,\n} from './redisJobStore';\nimport {\n appendQueueJobStepInStore,\n updateQueueJobStepInStore,\n upsertInitialQueueJob,\n} from './queueJobStore';\n\nexport interface JobStoreUpdate {\n status?: 'queued' | 'running' | 'completed' | 'failed';\n metadata?: Record<string, any>;\n progress?: number;\n progressMessage?: string;\n output?: any;\n error?: {\n message: string;\n stack?: string;\n name?: string;\n };\n}\n\nexport interface JobRecord {\n jobId: string;\n workerId: string;\n status: 'queued' | 'running' | 'completed' | 'failed';\n input: any;\n output?: any;\n error?: { message: string; stack?: string };\n metadata?: Record<string, any>;\n internalJobs?: Array<{ jobId: string; workerId: string }>;\n createdAt: string;\n updatedAt: string;\n completedAt?: string;\n}\n\nexport interface JobStore {\n /**\n * Update job in job store.\n * @param update - Update object with status, metadata, progress, output, or error\n */\n update(update: JobStoreUpdate): Promise<void>;\n /**\n * Get current job record from job store.\n * @returns Job record or null if not found\n */\n get(): Promise<JobRecord | null>;\n /**\n * Append an internal (child) job to the current job's internalJobs list.\n * Used when this worker dispatches another worker (fire-and-forget or await).\n */\n appendInternalJob?(entry: { jobId: string; workerId: string }): Promise<void>;\n /**\n * Get any job by jobId (e.g. to poll child job status when await: true).\n * @returns Job record or null if not found\n */\n getJob?(jobId: string): Promise<JobRecord | null>;\n}\n\n/** Max SQS delay in seconds (AWS limit). */\nexport const SQS_MAX_DELAY_SECONDS = 900;\n\n/** Options for ctx.dispatchWorker (worker-to-worker). */\nexport interface DispatchWorkerOptions {\n webhookUrl?: string;\n metadata?: Record<string, any>;\n /** Optional job ID for the child job (default: generated). */\n jobId?: string;\n /** If true, poll job store until child completes or fails; otherwise fire-and-forget. */\n await?: boolean;\n pollIntervalMs?: number;\n pollTimeoutMs?: number;\n /**\n * Delay before the child is invoked (fire-and-forget only; ignored when await is true).\n * Uses SQS DelaySeconds (0–900). In local mode, waits this many seconds before sending the trigger request.\n */\n delaySeconds?: number;\n}\n\nexport interface WorkerHandlerParams<INPUT, OUTPUT> {\n input: INPUT;\n ctx: {\n jobId: string;\n workerId: string;\n requestId?: string;\n /**\n * Job store interface for updating and retrieving job state.\n * Uses MongoDB directly when configured; never HTTP/origin URL.\n */\n jobStore?: JobStore;\n /**\n * Dispatch another worker (fire-and-forget or await). Uses WORKER_QUEUE_URL_<SANITIZED_ID> env.\n * Always provided by the runtime (Lambda and local).\n */\n dispatchWorker: (\n workerId: string,\n input: unknown,\n options?: DispatchWorkerOptions\n ) => Promise<{ jobId: string; messageId?: string; output?: unknown }>;\n [key: string]: any;\n };\n}\n\nexport type WorkerHandler<INPUT, OUTPUT> = (\n params: WorkerHandlerParams<INPUT, OUTPUT>\n) => Promise<OUTPUT>;\n\n/** Result of getNextStep for queue chaining. */\nexport interface QueueNextStep {\n workerId: string;\n delaySeconds?: number;\n mapInputFromPrev?: string;\n}\n\n/** Runtime helpers for queue-aware wrappers (provided by generated registry). */\nexport interface QueueRuntime {\n getNextStep(queueId: string, stepIndex: number): QueueNextStep | undefined;\n invokeMapInput?(\n queueId: string,\n stepIndex: number,\n prevOutput: unknown,\n initialInput: unknown\n ): Promise<unknown> | unknown;\n}\n\nconst WORKER_QUEUE_KEY = '__workerQueue';\nasync function notifyQueueJobStep(\n queueJobId: string,\n action: 'start' | 'complete' | 'fail' | 'append',\n params: {\n stepIndex?: number;\n workerJobId: string;\n workerId?: string;\n output?: unknown;\n error?: { message: string };\n input?: unknown;\n }\n): Promise<void> {\n try {\n if (action === 'append') {\n if (!params.workerId || !params.workerJobId) return;\n await appendQueueJobStepInStore({\n queueJobId,\n workerId: params.workerId,\n workerJobId: params.workerJobId,\n });\n if (process.env.DEBUG_WORKER_QUEUES === '1') {\n console.log('[Worker] Queue job step appended', {\n queueJobId,\n workerId: params.workerId,\n workerJobId: params.workerJobId,\n });\n }\n return;\n }\n\n if (params.stepIndex === undefined) return;\n\n const status =\n action === 'start'\n ? 'running'\n : action === 'complete'\n ? 'completed'\n : action === 'fail'\n ? 'failed'\n : undefined;\n if (!status) return;\n\n await updateQueueJobStepInStore({\n queueJobId,\n stepIndex: params.stepIndex,\n workerId: params.workerId || '',\n workerJobId: params.workerJobId,\n status,\n input: params.input,\n output: params.output,\n error: params.error,\n });\n if (process.env.DEBUG_WORKER_QUEUES === '1') {\n console.log('[Worker] Queue job step updated', {\n queueJobId,\n action,\n stepIndex: params.stepIndex,\n status,\n });\n }\n } catch (err: any) {\n console.warn('[Worker] Queue job update error:', {\n queueJobId,\n action,\n error: err?.message ?? String(err),\n });\n }\n}\n\n/**\n * Wraps a user handler so that when the job has __workerQueue context (from\n * dispatchQueue or queue cron), it dispatches the next worker in the sequence\n * after the handler completes. Uses literal worker IDs so the CLI env injection\n * picks up WORKER_QUEUE_URL_* for next-step workers.\n */\nexport function wrapHandlerForQueue<INPUT, OUTPUT>(\n handler: WorkerHandler<INPUT, OUTPUT>,\n queueRuntime: QueueRuntime\n): WorkerHandler<INPUT & { __workerQueue?: { id: string; stepIndex: number; initialInput: unknown } }, OUTPUT> {\n return async (params) => {\n const queueContext = (params.input as any)?.[WORKER_QUEUE_KEY];\n const output = await handler(params);\n\n if (!queueContext || typeof queueContext !== 'object' || !queueContext.id) {\n return output;\n }\n\n const { id: queueId, stepIndex, initialInput, queueJobId } = queueContext;\n const next = queueRuntime.getNextStep(queueId, stepIndex);\n if (!next) {\n return output;\n }\n\n const childJobId = `job-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n if (queueJobId) {\n await notifyQueueJobStep(queueJobId, 'append', {\n workerJobId: childJobId,\n workerId: next.workerId,\n });\n }\n\n let nextInput: unknown = output;\n if (next.mapInputFromPrev && typeof queueRuntime.invokeMapInput === 'function') {\n nextInput = await queueRuntime.invokeMapInput(queueId, stepIndex + 1, output, initialInput);\n }\n\n const nextInputWithQueue = {\n ...(nextInput !== null && typeof nextInput === 'object' ? (nextInput as Record<string, unknown>) : { value: nextInput }),\n [WORKER_QUEUE_KEY]: {\n id: queueId,\n stepIndex: stepIndex + 1,\n initialInput,\n queueJobId,\n },\n };\n\n const debug = process.env.AI_WORKER_QUEUES_DEBUG === '1';\n if (debug) {\n console.log('[Worker] Queue chain dispatching next:', {\n queueId,\n fromStep: stepIndex,\n nextWorkerId: next.workerId,\n delaySeconds: next.delaySeconds,\n });\n }\n\n await params.ctx.dispatchWorker(next.workerId, nextInputWithQueue, {\n await: false,\n delaySeconds: next.delaySeconds,\n jobId: childJobId,\n });\n\n return output;\n };\n}\n\nexport interface SQSMessageBody {\n workerId: string;\n jobId: string;\n input: any;\n context: Record<string, any>;\n webhookUrl?: string;\n /** @deprecated Never use. Job updates use MongoDB only. */\n jobStoreUrl?: string;\n metadata?: Record<string, any>;\n timestamp: string;\n}\n\nexport interface WebhookPayload {\n jobId: string;\n workerId: string;\n status: 'success' | 'error';\n output?: any;\n error?: {\n message: string;\n stack?: string;\n name?: string;\n };\n metadata?: Record<string, any>;\n}\n\nconst DEFAULT_POLL_INTERVAL_MS = 2000;\nconst DEFAULT_POLL_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes\n\nfunction sanitizeWorkerIdForEnv(workerId: string): string {\n return workerId.replace(/-/g, '_').toUpperCase();\n}\n\nfunction getQueueUrlForWorker(calleeWorkerId: string): string | undefined {\n const key = `WORKER_QUEUE_URL_${sanitizeWorkerIdForEnv(calleeWorkerId)}`;\n return process.env[key]?.trim() || undefined;\n}\n\n/**\n * Create dispatchWorker for use in handler context (Lambda).\n * Sends message to SQS, appends to parent internalJobs, optionally polls until child completes.\n */\nfunction createDispatchWorker(\n parentJobId: string,\n parentWorkerId: string,\n parentContext: Record<string, any>,\n jobStore: JobStore | undefined\n): (\n workerId: string,\n input: unknown,\n options?: DispatchWorkerOptions\n) => Promise<{ jobId: string; messageId?: string; output?: unknown }> {\n return async (\n calleeWorkerId: string,\n input: unknown,\n options?: DispatchWorkerOptions\n ): Promise<{ jobId: string; messageId?: string; output?: unknown }> => {\n const childJobId =\n options?.jobId ||\n `job-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n const metadata = options?.metadata ?? {};\n const serializedContext: Record<string, any> = {};\n if (parentContext.requestId) serializedContext.requestId = parentContext.requestId;\n\n const messageBody: SQSMessageBody = {\n workerId: calleeWorkerId,\n jobId: childJobId,\n input: input ?? {},\n context: serializedContext,\n webhookUrl: options?.webhookUrl,\n metadata,\n timestamp: new Date().toISOString(),\n };\n\n const queueUrl = getQueueUrlForWorker(calleeWorkerId);\n\n if (queueUrl) {\n const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'us-east-1';\n const sqs = new SQSClient({ region });\n // SQS message timer (per-message DelaySeconds): message stays invisible for N seconds.\n // Calling worker returns immediately; no computation during delay. See:\n // https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-delay-queues.html\n const delaySeconds =\n options?.await !== true && options?.delaySeconds != null\n ? Math.min(SQS_MAX_DELAY_SECONDS, Math.max(0, Math.floor(options.delaySeconds)))\n : undefined;\n const sendResult = await sqs.send(\n new SendMessageCommand({\n QueueUrl: queueUrl,\n MessageBody: JSON.stringify(messageBody),\n ...(delaySeconds !== undefined && delaySeconds > 0 ? { DelaySeconds: delaySeconds } : {}),\n })\n );\n const messageId = sendResult.MessageId ?? undefined;\n\n if (jobStore?.appendInternalJob) {\n await jobStore.appendInternalJob({ jobId: childJobId, workerId: calleeWorkerId });\n }\n\n if (options?.await && jobStore?.getJob) {\n const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const pollTimeoutMs = options.pollTimeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;\n const deadline = Date.now() + pollTimeoutMs;\n while (Date.now() < deadline) {\n const child = await jobStore.getJob(childJobId);\n if (!child) {\n await new Promise((r) => setTimeout(r, pollIntervalMs));\n continue;\n }\n if (child.status === 'completed') {\n return { jobId: childJobId, messageId, output: child.output };\n }\n if (child.status === 'failed') {\n const err = child.error;\n throw new Error(\n err?.message ?? `Child worker ${calleeWorkerId} failed`\n );\n }\n await new Promise((r) => setTimeout(r, pollIntervalMs));\n }\n throw new Error(\n `Child worker ${calleeWorkerId} (${childJobId}) did not complete within ${pollTimeoutMs}ms`\n );\n }\n\n return { jobId: childJobId, messageId };\n }\n\n // Fallback: no queue URL (e.g. local dev). Caller (index.ts) should provide in-process dispatch.\n throw new Error(\n `WORKER_QUEUE_URL_${sanitizeWorkerIdForEnv(calleeWorkerId)} is not set. ` +\n 'Configure queue URL for worker-to-worker dispatch, or run in local mode.'\n );\n };\n}\n\n/**\n * Sends a webhook callback to the specified URL.\n */\nasync function sendWebhook(\n webhookUrl: string,\n payload: WebhookPayload\n): Promise<void> {\n try {\n const response = await fetch(webhookUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': 'ai-router-worker/1.0',\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => '');\n console.error('[Worker] Webhook callback failed:', {\n url: webhookUrl,\n status: response.status,\n statusText: response.statusText,\n errorText,\n });\n // Don't throw - webhook failures shouldn't fail the Lambda\n } else {\n console.log('[Worker] Webhook callback successful:', {\n url: webhookUrl,\n status: response.status,\n });\n }\n } catch (error: any) {\n console.error('[Worker] Webhook callback error:', {\n url: webhookUrl,\n error: error?.message || String(error),\n stack: error?.stack,\n });\n // Don't throw - webhook failures shouldn't fail the Lambda\n }\n}\n\n/**\n * Creates a Lambda handler function that processes SQS events for workers.\n * Job store: MongoDB only. Never uses HTTP/origin URL for job updates.\n *\n * @param handler - The user's worker handler function\n * @param outputSchema - Optional Zod schema for output validation\n * @returns A Lambda handler function\n */\nexport function createLambdaHandler<INPUT, OUTPUT>(\n handler: WorkerHandler<INPUT, OUTPUT>,\n outputSchema?: ZodType<OUTPUT>\n): (event: SQSEvent, context: LambdaContext) => Promise<void> {\n return async (event: SQSEvent, lambdaContext: LambdaContext) => {\n const promises = event.Records.map(async (record: SQSRecord) => {\n let messageBody: SQSMessageBody | null = null;\n try {\n messageBody = JSON.parse(record.body) as SQSMessageBody;\n\n const { workerId, jobId, input, context, webhookUrl, metadata = {} } =\n messageBody;\n\n // Idempotency: skip if this job was already completed or failed (e.g. SQS redelivery or duplicate trigger).\n // Only the Lambda that processes a message creates/updates that job's key; parent workers only append to internalJobs and poll – they never write child job documents.\n const raw = (process.env.WORKER_DATABASE_TYPE || 'upstash-redis').toLowerCase();\n const jobStoreType: 'mongodb' | 'upstash-redis' =\n raw === 'mongodb' ? 'mongodb' : 'upstash-redis';\n if (jobStoreType === 'upstash-redis' && isRedisJobStoreConfigured()) {\n const existing = await loadRedisJob(jobId);\n if (existing && (existing.status === 'completed' || existing.status === 'failed')) {\n console.log('[Worker] Skipping already terminal job (idempotent):', {\n jobId,\n workerId,\n status: existing.status,\n });\n return;\n }\n } else if (jobStoreType === 'mongodb' || isMongoJobStoreConfigured()) {\n const existing = await getMongoJobById(jobId);\n if (existing && (existing.status === 'completed' || existing.status === 'failed')) {\n console.log('[Worker] Skipping already terminal job (idempotent):', {\n jobId,\n workerId,\n status: existing.status,\n });\n return;\n }\n }\n\n // Select job store and upsert this message's job only (never write child job documents from parent).\n let jobStore: JobStore | undefined;\n if (\n jobStoreType === 'upstash-redis' &&\n isRedisJobStoreConfigured()\n ) {\n await upsertRedisJob(jobId, workerId, input, metadata);\n jobStore = createRedisJobStore(workerId, jobId, input, metadata);\n } else if (\n jobStoreType === 'mongodb' ||\n isMongoJobStoreConfigured()\n ) {\n await upsertJob(jobId, workerId, input, metadata);\n jobStore = createMongoJobStore(workerId, jobId, input, metadata);\n }\n\n const baseContext = {\n jobId,\n workerId,\n requestId: context.requestId || lambdaContext.awsRequestId,\n ...context,\n };\n const handlerContext = {\n ...baseContext,\n ...(jobStore ? { jobStore } : {}),\n dispatchWorker: createDispatchWorker(\n jobId,\n workerId,\n baseContext,\n jobStore\n ),\n };\n\n if (jobStore) {\n try {\n await jobStore.update({ status: 'running' });\n console.log('[Worker] Job status updated to running:', {\n jobId,\n workerId,\n });\n } catch (error: any) {\n console.warn('[Worker] Failed to update status to running:', {\n jobId,\n workerId,\n error: error?.message || String(error),\n });\n }\n }\n\n const queueCtx = (input as any)?.__workerQueue ?? metadata?.__workerQueue;\n if (queueCtx?.queueJobId && typeof queueCtx.stepIndex === 'number') {\n // Ensure initial queue job exists (mainly for cron/queue-starter paths)\n if (queueCtx.stepIndex === 0) {\n try {\n await upsertInitialQueueJob({\n queueJobId: queueCtx.queueJobId,\n queueId: queueCtx.id,\n firstWorkerId: workerId,\n firstWorkerJobId: jobId,\n metadata,\n });\n } catch (e: any) {\n console.warn('[Worker] Failed to upsert initial queue job:', {\n queueJobId: queueCtx.queueJobId,\n queueId: queueCtx.id,\n error: e?.message ?? String(e),\n });\n }\n }\n await notifyQueueJobStep(queueCtx.queueJobId, 'start', {\n stepIndex: queueCtx.stepIndex,\n workerJobId: jobId,\n workerId,\n input,\n });\n }\n\n let output: OUTPUT;\n try {\n output = await handler({\n input: input as INPUT,\n ctx: handlerContext,\n });\n\n if (outputSchema) {\n output = outputSchema.parse(output);\n }\n } catch (error: any) {\n const errorPayload: WebhookPayload = {\n jobId,\n workerId,\n status: 'error',\n error: {\n message: error.message || 'Unknown error',\n stack: error.stack,\n name: error.name || 'Error',\n },\n metadata,\n };\n\n if (jobStore) {\n try {\n await jobStore.update({\n status: 'failed',\n error: errorPayload.error,\n });\n console.log('[Worker] Job status updated to failed:', {\n jobId,\n workerId,\n });\n } catch (updateError: any) {\n console.warn('[Worker] Failed to update job store on error:', {\n jobId,\n workerId,\n error: updateError?.message || String(updateError),\n });\n }\n }\n\n const queueCtxFail = (input as any)?.__workerQueue ?? metadata?.__workerQueue;\n if (queueCtxFail?.queueJobId && typeof queueCtxFail.stepIndex === 'number') {\n await notifyQueueJobStep(queueCtxFail.queueJobId, 'fail', {\n stepIndex: queueCtxFail.stepIndex,\n workerJobId: jobId,\n workerId,\n error: errorPayload.error,\n });\n }\n\n if (webhookUrl) {\n await sendWebhook(webhookUrl, errorPayload);\n }\n throw error;\n }\n\n if (jobStore) {\n try {\n await jobStore.update({\n status: 'completed',\n output,\n });\n console.log('[Worker] Job status updated to completed:', {\n jobId,\n workerId,\n });\n } catch (updateError: any) {\n console.warn('[Worker] Failed to update job store on success:', {\n jobId,\n workerId,\n error: updateError?.message || String(updateError),\n });\n }\n }\n\n const queueCtxSuccess = (input as any)?.__workerQueue ?? metadata?.__workerQueue;\n if (queueCtxSuccess?.queueJobId && typeof queueCtxSuccess.stepIndex === 'number') {\n await notifyQueueJobStep(queueCtxSuccess.queueJobId, 'complete', {\n stepIndex: queueCtxSuccess.stepIndex,\n workerJobId: jobId,\n workerId,\n output,\n });\n }\n\n console.log('[Worker] Job completed:', {\n jobId,\n workerId,\n output,\n });\n\n const successPayload: WebhookPayload = {\n jobId,\n workerId,\n status: 'success',\n output,\n metadata,\n };\n\n if (webhookUrl) {\n await sendWebhook(webhookUrl, successPayload);\n }\n } catch (error: any) {\n console.error('[Worker] Error processing SQS record:', {\n jobId: messageBody?.jobId ?? '(parse failed)',\n workerId: messageBody?.workerId ?? '(parse failed)',\n error: error?.message || String(error),\n stack: error?.stack,\n });\n throw error;\n }\n });\n\n await Promise.all(promises);\n };\n}\n","/**\n * MongoDB-backed job store for Lambda workers.\n * Updates jobs directly in MongoDB; never uses HTTP/origin URL.\n *\n * Env: MONGODB_WORKER_URI (or MONGODB_URI), MONGODB_WORKER_DB (or MONGODB_DB),\n * MONGODB_WORKER_JOBS_COLLECTION (default: worker_jobs).\n */\n\nimport { MongoClient, type Collection } from 'mongodb';\nimport type { JobStore, JobStoreUpdate } from './handler';\n\nconst uri = process.env.MONGODB_WORKER_URI || process.env.DATABASE_MONGODB_URI || process.env.MONGODB_URI;\nconst dbName =\n process.env.MONGODB_WORKER_DB ||\n process.env.MONGODB_DB ||\n 'worker';\nconst collectionName =\n process.env.MONGODB_WORKER_JOBS_COLLECTION || 'worker_jobs';\n\ntype InternalJobEntry = { jobId: string; workerId: string };\n\ntype Doc = {\n _id: string;\n jobId: string;\n workerId: string;\n status: 'queued' | 'running' | 'completed' | 'failed';\n input: any;\n output?: any;\n error?: { message: string; stack?: string; name?: string };\n metadata?: Record<string, any>;\n internalJobs?: InternalJobEntry[];\n createdAt: string;\n updatedAt: string;\n completedAt?: string;\n};\n\nlet clientPromise: Promise<MongoClient> | null = null;\n\nfunction getClient(): Promise<MongoClient> {\n if (!uri) {\n throw new Error(\n 'MongoDB URI required for job store. Set DATABASE_MONGODB_URI or MONGODB_URI.'\n );\n }\n if (!clientPromise) {\n clientPromise = new MongoClient(uri, {\n maxPoolSize: 10,\n minPoolSize: 0,\n serverSelectionTimeoutMS: 10_000,\n }).connect();\n }\n return clientPromise;\n}\n\nasync function getCollection(): Promise<Collection<Doc>> {\n const client = await getClient();\n return client.db(dbName).collection<Doc>(collectionName);\n}\n\n/**\n * Load a job by id (read-only). Used for idempotency check before processing.\n */\nexport async function getJobById(jobId: string): Promise<{\n jobId: string;\n workerId: string;\n status: 'queued' | 'running' | 'completed' | 'failed';\n input: any;\n output?: any;\n error?: { message: string; stack?: string };\n metadata?: Record<string, any>;\n internalJobs?: Array<{ jobId: string; workerId: string }>;\n createdAt: string;\n updatedAt: string;\n completedAt?: string;\n} | null> {\n try {\n const coll = await getCollection();\n const doc = await coll.findOne({ _id: jobId });\n if (!doc) return null;\n const { _id, ...r } = doc;\n return r as any;\n } catch (e: any) {\n console.error('[Worker] MongoDB getJobById failed:', {\n jobId,\n error: e?.message ?? String(e),\n });\n return null;\n }\n}\n\n/**\n * Create a JobStore that reads/writes directly to MongoDB.\n * Caller must ensure the job exists (upsert on first use).\n */\nexport function createMongoJobStore(\n workerId: string,\n jobId: string,\n input: any,\n metadata: Record<string, any>\n): JobStore {\n return {\n update: async (update: JobStoreUpdate): Promise<void> => {\n try {\n const coll = await getCollection();\n const now = new Date().toISOString();\n const existing = await coll.findOne({ _id: jobId });\n\n let metadataUpdate: Record<string, any> = { ...(existing?.metadata ?? {}) };\n if (update.metadata) {\n Object.assign(metadataUpdate, update.metadata);\n }\n if (update.progress !== undefined || update.progressMessage !== undefined) {\n metadataUpdate.progress = update.progress;\n metadataUpdate.progressMessage = update.progressMessage;\n }\n\n const set: Partial<Doc> = {\n updatedAt: now,\n metadata: metadataUpdate,\n };\n if (update.status !== undefined) {\n set.status = update.status;\n if (['completed', 'failed'].includes(update.status) && !existing?.completedAt) {\n set.completedAt = now;\n }\n }\n if (update.output !== undefined) set.output = update.output;\n if (update.error !== undefined) set.error = update.error;\n\n if (existing) {\n await coll.updateOne({ _id: jobId }, { $set: set });\n } else {\n const doc: Doc = {\n _id: jobId,\n jobId,\n workerId,\n status: (update.status as Doc['status']) ?? 'queued',\n input: input ?? {},\n output: update.output,\n error: update.error,\n metadata: metadataUpdate,\n createdAt: now,\n updatedAt: now,\n completedAt: set.completedAt,\n };\n if (doc.status === 'completed' || doc.status === 'failed') {\n doc.completedAt = doc.completedAt ?? now;\n }\n await coll.updateOne({ _id: jobId }, { $set: doc }, { upsert: true });\n }\n } catch (e: any) {\n console.error('[Worker] MongoDB job store update failed:', {\n jobId,\n workerId,\n error: e?.message ?? String(e),\n });\n }\n },\n get: async () => {\n try {\n const coll = await getCollection();\n const doc = await coll.findOne({ _id: jobId });\n if (!doc) return null;\n const { _id, ...r } = doc;\n return r as any;\n } catch (e: any) {\n console.error('[Worker] MongoDB job store get failed:', {\n jobId,\n workerId,\n error: e?.message ?? String(e),\n });\n return null;\n }\n },\n appendInternalJob: async (entry: { jobId: string; workerId: string }): Promise<void> => {\n try {\n const coll = await getCollection();\n await coll.updateOne(\n { _id: jobId },\n { $push: { internalJobs: entry } }\n );\n } catch (e: any) {\n console.error('[Worker] MongoDB job store appendInternalJob failed:', {\n jobId,\n workerId,\n error: e?.message ?? String(e),\n });\n }\n },\n getJob: async (otherJobId: string): Promise<{\n jobId: string;\n workerId: string;\n status: 'queued' | 'running' | 'completed' | 'failed';\n input: any;\n output?: any;\n error?: { message: string; stack?: string };\n metadata?: Record<string, any>;\n internalJobs?: Array<{ jobId: string; workerId: string }>;\n createdAt: string;\n updatedAt: string;\n completedAt?: string;\n } | null> => {\n try {\n const coll = await getCollection();\n const doc = await coll.findOne({ _id: otherJobId });\n if (!doc) return null;\n const { _id, ...r } = doc;\n return r as any;\n } catch (e: any) {\n console.error('[Worker] MongoDB job store getJob failed:', {\n otherJobId,\n error: e?.message ?? String(e),\n });\n return null;\n }\n },\n };\n}\n\n/**\n * Upsert initial job record in MongoDB (queued).\n * Call this when the Lambda starts processing a message.\n */\nexport async function upsertJob(\n jobId: string,\n workerId: string,\n input: any,\n metadata: Record<string, any>\n): Promise<void> {\n const coll = await getCollection();\n const now = new Date().toISOString();\n await coll.updateOne(\n { _id: jobId },\n {\n $set: {\n _id: jobId,\n jobId,\n workerId,\n status: 'queued',\n input: input ?? {},\n metadata: metadata ?? {},\n createdAt: now,\n updatedAt: now,\n },\n },\n { upsert: true }\n );\n}\n\nexport function isMongoJobStoreConfigured(): boolean {\n return Boolean(uri?.trim());\n}\n","import { Redis } from '@upstash/redis';\nimport type { JobStore, JobStoreUpdate, JobRecord } from './handler';\n\n// Canonical: WORKER_* first, then UPSTASH_* / REDIS_* / WORKFLOW_* fallbacks\nconst redisUrl =\n process.env.WORKER_UPSTASH_REDIS_REST_URL ||\n process.env.UPSTASH_REDIS_REST_URL ||\n process.env.UPSTASH_REDIS_URL;\nconst redisToken =\n process.env.WORKER_UPSTASH_REDIS_REST_TOKEN ||\n process.env.UPSTASH_REDIS_REST_TOKEN ||\n process.env.UPSTASH_REDIS_TOKEN;\nconst jobKeyPrefix =\n process.env.WORKER_UPSTASH_REDIS_JOBS_PREFIX ||\n process.env.UPSTASH_REDIS_KEY_PREFIX ||\n process.env.REDIS_WORKER_JOB_PREFIX ||\n 'worker:jobs:';\nconst defaultTtlSeconds = 60 * 60 * 24 * 7; // 7 days\nconst jobTtlSeconds =\n typeof process.env.WORKER_JOBS_TTL_SECONDS === 'string'\n ? parseInt(process.env.WORKER_JOBS_TTL_SECONDS, 10) || defaultTtlSeconds\n : typeof process.env.REDIS_WORKER_JOB_TTL_SECONDS === 'string'\n ? parseInt(process.env.REDIS_WORKER_JOB_TTL_SECONDS, 10) || defaultTtlSeconds\n : typeof process.env.WORKFLOW_JOBS_TTL_SECONDS === 'string'\n ? parseInt(process.env.WORKFLOW_JOBS_TTL_SECONDS, 10) || defaultTtlSeconds\n : defaultTtlSeconds;\n\nlet redisClient: Redis | null = null;\n\nfunction getRedis(): Redis {\n if (!redisUrl || !redisToken) {\n throw new Error(\n 'Upstash Redis configuration missing. Set WORKER_UPSTASH_REDIS_REST_URL and WORKER_UPSTASH_REDIS_REST_TOKEN (or UPSTASH_REDIS_REST_URL/UPSTASH_REDIS_REST_TOKEN).'\n );\n }\n if (!redisClient) {\n redisClient = new Redis({\n url: redisUrl,\n token: redisToken,\n });\n }\n return redisClient;\n}\n\nfunction jobKey(jobId: string): string {\n return `${jobKeyPrefix}${jobId}`;\n}\n\n/** Separate LIST key for internal job refs; each RPUSH is atomic so no race when appending multiple. */\nfunction internalListKey(jobId: string): string {\n return `${jobKeyPrefix}${jobId}:internal`;\n}\n\nexport function isRedisJobStoreConfigured(): boolean {\n return Boolean((redisUrl || '').trim() && (redisToken || '').trim());\n}\n\n/** Load a job by id (read-only). Used for idempotency check before processing. */\nexport async function loadJob(jobId: string): Promise<JobRecord | null> {\n const redis = getRedis();\n const key = jobKey(jobId);\n const data = await redis.hgetall<Record<string, string>>(key);\n if (!data || Object.keys(data).length === 0) return null;\n const parseJson = <T>(val?: string | null): T | undefined => {\n if (!val) return undefined;\n try {\n return JSON.parse(val) as T;\n } catch {\n return undefined;\n }\n };\n // Prefer atomic list key for internal jobs; fallback to hash field for old records\n const listKey = internalListKey(jobId);\n const listItems = await redis.lrange<string>(listKey, 0, -1);\n let internalJobs: Array<{ jobId: string; workerId: string }> | undefined;\n if (listItems && listItems.length > 0) {\n internalJobs = listItems.map((s) => {\n try {\n return JSON.parse(s) as { jobId: string; workerId: string };\n } catch {\n return null;\n }\n }).filter(Boolean) as Array<{ jobId: string; workerId: string }>;\n } else {\n internalJobs = parseJson<Array<{ jobId: string; workerId: string }>>(data.internalJobs);\n }\n const record: JobRecord = {\n jobId: data.jobId,\n workerId: data.workerId,\n status: (data.status as JobRecord['status']) || 'queued',\n input: parseJson<any>(data.input) ?? {},\n output: parseJson<any>(data.output),\n error: parseJson<any>(data.error),\n metadata: parseJson<Record<string, any>>(data.metadata) ?? {},\n internalJobs,\n createdAt: data.createdAt,\n updatedAt: data.updatedAt,\n completedAt: data.completedAt,\n };\n return record;\n}\n\nexport function createRedisJobStore(\n workerId: string,\n jobId: string,\n input: any,\n metadata: Record<string, any>\n): JobStore {\n return {\n update: async (update: JobStoreUpdate): Promise<void> => {\n const redis = getRedis();\n const key = jobKey(jobId);\n const now = new Date().toISOString();\n\n // Load existing to merge metadata/progress if needed\n const existing = await loadJob(jobId);\n const next: Partial<JobRecord> = {};\n\n // Start from existing metadata\n const mergedMeta: Record<string, any> = { ...(existing?.metadata ?? {}) };\n if (update.metadata) {\n Object.assign(mergedMeta, update.metadata);\n }\n if (update.progress !== undefined || update.progressMessage !== undefined) {\n mergedMeta.progress = update.progress;\n mergedMeta.progressMessage = update.progressMessage;\n }\n\n next.metadata = mergedMeta;\n if (update.status !== undefined) {\n next.status = update.error ? 'failed' : update.status;\n if ((update.status === 'completed' || update.status === 'failed') && !existing?.completedAt) {\n next.completedAt = now;\n }\n }\n if (update.output !== undefined) next.output = update.output;\n if (update.error !== undefined) next.error = update.error;\n\n const toSet: Record<string, string> = {};\n if (next.status) toSet['status'] = String(next.status);\n if (next.output !== undefined) toSet['output'] = JSON.stringify(next.output);\n if (next.error !== undefined) toSet['error'] = JSON.stringify(next.error);\n if (next.metadata !== undefined) toSet['metadata'] = JSON.stringify(next.metadata);\n if (next.completedAt) {\n toSet['completedAt'] = next.completedAt;\n }\n toSet['updatedAt'] = now;\n\n await redis.hset(key, toSet);\n if (jobTtlSeconds > 0) {\n await redis.expire(key, jobTtlSeconds);\n }\n },\n get: async () => {\n return loadJob(jobId);\n },\n appendInternalJob: async (entry) => {\n const redis = getRedis();\n const listKey = internalListKey(jobId);\n await redis.rpush(listKey, JSON.stringify(entry));\n const mainKey = jobKey(jobId);\n await redis.hset(mainKey, { updatedAt: new Date().toISOString() });\n if (jobTtlSeconds > 0) {\n await redis.expire(listKey, jobTtlSeconds);\n await redis.expire(mainKey, jobTtlSeconds);\n }\n },\n getJob: async (otherJobId: string) => {\n return loadJob(otherJobId);\n },\n };\n}\n\nexport async function upsertRedisJob(\n jobId: string,\n workerId: string,\n input: any,\n metadata: Record<string, any>\n): Promise<void> {\n const redis = getRedis();\n const key = jobKey(jobId);\n const now = new Date().toISOString();\n const doc: Partial<JobRecord> = {\n jobId,\n workerId,\n status: 'queued',\n input,\n metadata,\n createdAt: now,\n updatedAt: now,\n };\n const toSet: Record<string, string> = {\n jobId: jobId,\n workerId: workerId,\n status: doc.status!,\n input: JSON.stringify(doc.input ?? {}),\n metadata: JSON.stringify(doc.metadata ?? {}),\n createdAt: now,\n updatedAt: now,\n };\n await redis.hset(key, toSet);\n if (jobTtlSeconds > 0) {\n await redis.expire(key, jobTtlSeconds);\n }\n}\n\n","/**\n * Queue job store for worker queues (MongoDB or Upstash Redis).\n *\n * Mirrors the worker_jobs pattern but optimized for queues:\n * - MongoDB: collection `queue_jobs` (configurable via MONGODB_QUEUE_JOBS_COLLECTION)\n * - Upstash Redis: JSON blob per queue job with compact step entries\n *\n * This module is runtime-only (used by Lambda workers). Next.js APIs can read\n * the same collections/keys to show queue progress.\n */\n\nimport type { Redis } from '@upstash/redis';\nimport { Redis as UpstashRedis } from '@upstash/redis';\nimport { MongoClient, type Collection } from 'mongodb';\n\ntype QueueJobStep = {\n workerId: string;\n workerJobId: string;\n status: 'queued' | 'running' | 'completed' | 'failed';\n input?: unknown;\n output?: unknown;\n error?: { message: string };\n startedAt?: string;\n completedAt?: string;\n};\n\ntype QueueJobDoc = {\n _id: string; // queueJobId\n id: string;\n queueId: string;\n status: 'running' | 'completed' | 'failed' | 'partial';\n steps: QueueJobStep[];\n metadata?: Record<string, unknown>;\n createdAt: string;\n updatedAt: string;\n completedAt?: string;\n};\n\n// === Mongo backend (shares connection pattern with mongoJobStore) ===\n\nconst mongoUri = process.env.DATABASE_MONGODB_URI || process.env.MONGODB_URI;\nconst mongoDbName =\n process.env.DATABASE_MONGODB_DB ||\n process.env.MONGODB_DB ||\n 'mediamake';\nconst mongoQueueCollectionName =\n process.env.MONGODB_QUEUE_JOBS_COLLECTION || 'queue_jobs';\n\nlet mongoClientPromise: Promise<MongoClient> | null = null;\n\nasync function getMongoClient(): Promise<MongoClient> {\n if (!mongoUri) {\n throw new Error(\n 'MongoDB URI required for queue job store. Set DATABASE_MONGODB_URI or MONGODB_URI.'\n );\n }\n if (!mongoClientPromise) {\n mongoClientPromise = new MongoClient(mongoUri, {\n maxPoolSize: 10,\n minPoolSize: 0,\n serverSelectionTimeoutMS: 10_000,\n }).connect();\n }\n return mongoClientPromise;\n}\n\nasync function getMongoQueueCollection(): Promise<Collection<QueueJobDoc>> {\n const client = await getMongoClient();\n return client.db(mongoDbName).collection<QueueJobDoc>(mongoQueueCollectionName);\n}\n\n// === Redis backend (Upstash) ===\n\nconst redisUrl =\n process.env.WORKER_UPSTASH_REDIS_REST_URL ||\n process.env.UPSTASH_REDIS_REST_URL ||\n process.env.UPSTASH_REDIS_URL;\nconst redisToken =\n process.env.WORKER_UPSTASH_REDIS_REST_TOKEN ||\n process.env.UPSTASH_REDIS_REST_TOKEN ||\n process.env.UPSTASH_REDIS_TOKEN;\nconst queueKeyPrefix =\n process.env.WORKER_UPSTASH_REDIS_QUEUE_PREFIX ||\n process.env.UPSTASH_REDIS_QUEUE_PREFIX ||\n 'worker:queue-jobs:';\n\nconst defaultTtlSeconds = 60 * 60 * 24 * 7; // 7 days\nconst queueJobTtlSeconds =\n typeof process.env.WORKER_QUEUE_JOBS_TTL_SECONDS === 'string'\n ? parseInt(process.env.WORKER_QUEUE_JOBS_TTL_SECONDS, 10) || defaultTtlSeconds\n : typeof process.env.WORKER_JOBS_TTL_SECONDS === 'string'\n ? parseInt(process.env.WORKER_JOBS_TTL_SECONDS, 10) || defaultTtlSeconds\n : defaultTtlSeconds;\n\nlet redisClient: Redis | null = null;\n\nfunction getRedis(): Redis {\n if (!redisUrl || !redisToken) {\n throw new Error(\n 'Upstash Redis configuration missing for queue job store. Set WORKER_UPSTASH_REDIS_REST_URL and WORKER_UPSTASH_REDIS_REST_TOKEN (or UPSTASH_REDIS_REST_URL/UPSTASH_REDIS_REST_TOKEN).'\n );\n }\n if (!redisClient) {\n redisClient = new UpstashRedis({\n url: redisUrl,\n token: redisToken,\n });\n }\n return redisClient;\n}\n\nfunction queueKey(id: string): string {\n return `${queueKeyPrefix}${id}`;\n}\n\ntype QueueJobRecord = Omit<QueueJobDoc, '_id'>;\n\n/** Hash values from Upstash hgetall may be auto-parsed (array/object) or raw strings. */\nfunction stepsFromHash(val: unknown): QueueJobStep[] {\n if (Array.isArray(val)) return val as QueueJobStep[];\n if (typeof val === 'string') {\n try {\n const parsed = JSON.parse(val) as QueueJobStep[];\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n }\n return [];\n}\n\nfunction metadataFromHash(val: unknown): Record<string, unknown> {\n if (val && typeof val === 'object' && !Array.isArray(val)) return val as Record<string, unknown>;\n if (typeof val === 'string') {\n try {\n const parsed = JSON.parse(val) as Record<string, unknown>;\n return parsed && typeof parsed === 'object' ? parsed : {};\n } catch {\n return {};\n }\n }\n return {};\n}\n\nasync function loadQueueJobRedis(queueJobId: string): Promise<QueueJobRecord | null> {\n const redis = getRedis();\n const key = queueKey(queueJobId);\n const data = await redis.hgetall(key);\n if (!data || typeof data !== 'object' || Object.keys(data).length === 0) return null;\n const d = data as Record<string, unknown>;\n const record: QueueJobRecord = {\n id: (d.id === undefined ? queueJobId : String(d.id)) as string,\n queueId: String(d.queueId ?? ''),\n status: (String(d.status ?? 'running') as QueueJobRecord['status']),\n steps: stepsFromHash(d.steps),\n metadata: metadataFromHash(d.metadata),\n createdAt: String(d.createdAt ?? new Date().toISOString()),\n updatedAt: String(d.updatedAt ?? new Date().toISOString()),\n completedAt: d.completedAt != null ? String(d.completedAt) : undefined,\n };\n return record;\n}\n\nasync function saveQueueJobRedis(record: QueueJobRecord): Promise<void> {\n const redis = getRedis();\n const key = queueKey(record.id);\n const now = new Date().toISOString();\n const toSet: Record<string, string> = {\n id: record.id,\n queueId: record.queueId,\n status: record.status,\n steps: JSON.stringify(record.steps || []),\n metadata: JSON.stringify(record.metadata || {}),\n createdAt: record.createdAt || now,\n updatedAt: record.updatedAt || now,\n };\n if (record.completedAt) {\n toSet.completedAt = record.completedAt;\n }\n await redis.hset(key, toSet);\n if (queueJobTtlSeconds > 0) {\n await redis.expire(key, queueJobTtlSeconds);\n }\n}\n\n// === Backend selection ===\n\nfunction getStoreType(): 'mongodb' | 'upstash-redis' {\n const t = (process.env.WORKER_DATABASE_TYPE || 'upstash-redis').toLowerCase();\n return t === 'mongodb' ? 'mongodb' : 'upstash-redis';\n}\n\nfunction preferMongo(): boolean {\n return getStoreType() === 'mongodb' && Boolean(mongoUri?.trim());\n}\n\nfunction preferRedis(): boolean {\n return getStoreType() !== 'mongodb' && Boolean((redisUrl || '').trim() && (redisToken || '').trim());\n}\n\n// === Public API used from handler.ts ===\n\nexport async function upsertInitialQueueJob(options: {\n queueJobId: string;\n queueId: string;\n firstWorkerId: string;\n firstWorkerJobId: string;\n metadata?: Record<string, any>;\n}): Promise<void> {\n const { queueJobId, queueId, firstWorkerId, firstWorkerJobId, metadata } = options;\n const now = new Date().toISOString();\n\n if (preferMongo()) {\n const coll = await getMongoQueueCollection();\n const existing = await coll.findOne({ _id: queueJobId });\n if (existing) {\n const steps = existing.steps ?? [];\n if (steps.length === 0) {\n steps.push({\n workerId: firstWorkerId,\n workerJobId: firstWorkerJobId,\n status: 'queued',\n });\n }\n await coll.updateOne(\n { _id: queueJobId },\n {\n $set: {\n steps,\n updatedAt: now,\n },\n }\n );\n } else {\n const doc: QueueJobDoc = {\n _id: queueJobId,\n id: queueJobId,\n queueId,\n status: 'running',\n steps: [\n {\n workerId: firstWorkerId,\n workerJobId: firstWorkerJobId,\n status: 'queued',\n },\n ],\n metadata: metadata ?? {},\n createdAt: now,\n updatedAt: now,\n };\n await coll.updateOne(\n { _id: queueJobId },\n { $set: doc },\n { upsert: true }\n );\n }\n return;\n }\n\n if (preferRedis()) {\n const existing = await loadQueueJobRedis(queueJobId);\n if (existing) {\n // Ensure we have at least one step\n if (!existing.steps || existing.steps.length === 0) {\n existing.steps = [\n {\n workerId: firstWorkerId,\n workerJobId: firstWorkerJobId,\n status: 'queued',\n },\n ];\n }\n existing.updatedAt = now;\n await saveQueueJobRedis(existing);\n } else {\n const record: QueueJobRecord = {\n id: queueJobId,\n queueId,\n status: 'running',\n steps: [\n {\n workerId: firstWorkerId,\n workerJobId: firstWorkerJobId,\n status: 'queued',\n },\n ],\n metadata: metadata ?? {},\n createdAt: now,\n updatedAt: now,\n };\n await saveQueueJobRedis(record);\n }\n }\n}\n\nexport async function updateQueueJobStepInStore(options: {\n queueJobId: string;\n queueId?: string;\n stepIndex: number;\n workerId: string;\n workerJobId: string;\n status: 'running' | 'completed' | 'failed';\n input?: unknown;\n output?: unknown;\n error?: { message: string };\n}): Promise<void> {\n const { queueJobId, stepIndex, status, input, output, error } = options;\n const now = new Date().toISOString();\n\n if (preferMongo()) {\n const coll = await getMongoQueueCollection();\n const existing = await coll.findOne({ _id: queueJobId });\n if (!existing) return;\n const step = existing.steps[stepIndex];\n if (!step) return;\n\n const mergedStep: QueueJobStep = {\n ...step,\n status,\n ...(input !== undefined && { input }),\n ...(output !== undefined && { output }),\n ...(error !== undefined && { error }),\n startedAt: step.startedAt ?? (status === 'running' ? now : step.startedAt),\n completedAt:\n step.completedAt ??\n (status === 'completed' || status === 'failed' ? now : step.completedAt),\n };\n\n const setDoc: Partial<QueueJobDoc> & { steps: QueueJobStep[] } = {\n steps: existing.steps,\n updatedAt: now,\n };\n setDoc.steps[stepIndex] = mergedStep;\n if (status === 'failed') {\n setDoc.status = 'failed';\n if (!existing.completedAt) setDoc.completedAt = now;\n } else if (status === 'completed' && stepIndex === existing.steps.length - 1) {\n setDoc.status = 'completed';\n if (!existing.completedAt) setDoc.completedAt = now;\n }\n\n await coll.updateOne(\n { _id: queueJobId },\n {\n $set: setDoc,\n }\n );\n return;\n }\n\n if (preferRedis()) {\n const existing = await loadQueueJobRedis(queueJobId);\n if (!existing) {\n // No queue job; nothing to update\n return;\n }\n const steps = existing.steps || [];\n const step = steps[stepIndex];\n if (!step) {\n return;\n }\n step.status = status;\n if (input !== undefined) step.input = input;\n if (output !== undefined) step.output = output;\n if (error !== undefined) step.error = error;\n if (status === 'running') {\n step.startedAt = step.startedAt ?? now;\n }\n if (status === 'completed' || status === 'failed') {\n step.completedAt = step.completedAt ?? now;\n }\n\n existing.steps = steps;\n existing.updatedAt = now;\n if (status === 'failed') {\n existing.status = 'failed';\n existing.completedAt = existing.completedAt ?? now;\n } else if (status === 'completed' && stepIndex === steps.length - 1) {\n existing.status = 'completed';\n existing.completedAt = existing.completedAt ?? now;\n }\n await saveQueueJobRedis(existing);\n }\n}\n\nexport async function appendQueueJobStepInStore(options: {\n queueJobId: string;\n queueId?: string;\n workerId: string;\n workerJobId: string;\n}): Promise<void> {\n const { queueJobId, workerId, workerJobId } = options;\n const now = new Date().toISOString();\n\n if (preferMongo()) {\n const coll = await getMongoQueueCollection();\n await coll.updateOne(\n { _id: queueJobId },\n {\n $push: {\n steps: {\n workerId,\n workerJobId,\n status: 'queued',\n } as QueueJobStep,\n },\n $set: { updatedAt: now },\n }\n );\n return;\n }\n\n if (preferRedis()) {\n const existing = await loadQueueJobRedis(queueJobId);\n if (!existing) return;\n const steps = existing.steps || [];\n steps.push({\n workerId,\n workerJobId,\n status: 'queued',\n });\n existing.steps = steps;\n existing.updatedAt = now;\n await saveQueueJobRedis(existing);\n }\n}\n\n"],"mappings":";AAQA,SAAS,WAAW,0BAA0B;;;ACA9C,SAAS,mBAAoC;AAG7C,IAAM,MAAM,QAAQ,IAAI,sBAAsB,QAAQ,IAAI,wBAAwB,QAAQ,IAAI;AAC9F,IAAM,SACJ,QAAQ,IAAI,qBACZ,QAAQ,IAAI,cACZ;AACF,IAAM,iBACJ,QAAQ,IAAI,kCAAkC;AAmBhD,IAAI,gBAA6C;AAEjD,SAAS,YAAkC;AACzC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,eAAe;AAClB,oBAAgB,IAAI,YAAY,KAAK;AAAA,MACnC,aAAa;AAAA,MACb,aAAa;AAAA,MACb,0BAA0B;AAAA,IAC5B,CAAC,EAAE,QAAQ;AAAA,EACb;AACA,SAAO;AACT;AAEA,eAAe,gBAA0C;AACvD,QAAM,SAAS,MAAM,UAAU;AAC/B,SAAO,OAAO,GAAG,MAAM,EAAE,WAAgB,cAAc;AACzD;AAKA,eAAsB,WAAW,OAYvB;AACR,MAAI;AACF,UAAM,OAAO,MAAM,cAAc;AACjC,UAAM,MAAM,MAAM,KAAK,QAAQ,EAAE,KAAK,MAAM,CAAC;AAC7C,QAAI,CAAC,IAAK,QAAO;AACjB,UAAM,EAAE,KAAK,GAAG,EAAE,IAAI;AACtB,WAAO;AAAA,EACT,SAAS,GAAQ;AACf,YAAQ,MAAM,uCAAuC;AAAA,MACnD;AAAA,MACA,OAAO,GAAG,WAAW,OAAO,CAAC;AAAA,IAC/B,CAAC;AACD,WAAO;AAAA,EACT;AACF;AAMO,SAAS,oBACd,UACA,OACA,OACA,UACU;AACV,SAAO;AAAA,IACL,QAAQ,OAAO,WAA0C;AACvD,UAAI;AACF,cAAM,OAAO,MAAM,cAAc;AACjC,cAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,cAAM,WAAW,MAAM,KAAK,QAAQ,EAAE,KAAK,MAAM,CAAC;AAElD,YAAI,iBAAsC,EAAE,GAAI,UAAU,YAAY,CAAC,EAAG;AAC1E,YAAI,OAAO,UAAU;AACnB,iBAAO,OAAO,gBAAgB,OAAO,QAAQ;AAAA,QAC/C;AACA,YAAI,OAAO,aAAa,UAAa,OAAO,oBAAoB,QAAW;AACzE,yBAAe,WAAW,OAAO;AACjC,yBAAe,kBAAkB,OAAO;AAAA,QAC1C;AAEA,cAAM,MAAoB;AAAA,UACxB,WAAW;AAAA,UACX,UAAU;AAAA,QACZ;AACA,YAAI,OAAO,WAAW,QAAW;AAC/B,cAAI,SAAS,OAAO;AACpB,cAAI,CAAC,aAAa,QAAQ,EAAE,SAAS,OAAO,MAAM,KAAK,CAAC,UAAU,aAAa;AAC7E,gBAAI,cAAc;AAAA,UACpB;AAAA,QACF;AACA,YAAI,OAAO,WAAW,OAAW,KAAI,SAAS,OAAO;AACrD,YAAI,OAAO,UAAU,OAAW,KAAI,QAAQ,OAAO;AAEnD,YAAI,UAAU;AACZ,gBAAM,KAAK,UAAU,EAAE,KAAK,MAAM,GAAG,EAAE,MAAM,IAAI,CAAC;AAAA,QACpD,OAAO;AACL,gBAAM,MAAW;AAAA,YACf,KAAK;AAAA,YACL;AAAA,YACA;AAAA,YACA,QAAS,OAAO,UAA4B;AAAA,YAC5C,OAAO,SAAS,CAAC;AAAA,YACjB,QAAQ,OAAO;AAAA,YACf,OAAO,OAAO;AAAA,YACd,UAAU;AAAA,YACV,WAAW;AAAA,YACX,WAAW;AAAA,YACX,aAAa,IAAI;AAAA,UACnB;AACA,cAAI,IAAI,WAAW,eAAe,IAAI,WAAW,UAAU;AACzD,gBAAI,cAAc,IAAI,eAAe;AAAA,UACvC;AACA,gBAAM,KAAK,UAAU,EAAE,KAAK,MAAM,GAAG,EAAE,MAAM,IAAI,GAAG,EAAE,QAAQ,KAAK,CAAC;AAAA,QACtE;AAAA,MACF,SAAS,GAAQ;AACf,gBAAQ,MAAM,6CAA6C;AAAA,UACzD;AAAA,UACA;AAAA,UACA,OAAO,GAAG,WAAW,OAAO,CAAC;AAAA,QAC/B,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IACA,KAAK,YAAY;AACf,UAAI;AACF,cAAM,OAAO,MAAM,cAAc;AACjC,cAAM,MAAM,MAAM,KAAK,QAAQ,EAAE,KAAK,MAAM,CAAC;AAC7C,YAAI,CAAC,IAAK,QAAO;AACjB,cAAM,EAAE,KAAK,GAAG,EAAE,IAAI;AACtB,eAAO;AAAA,MACT,SAAS,GAAQ;AACf,gBAAQ,MAAM,0CAA0C;AAAA,UACtD;AAAA,UACA;AAAA,UACA,OAAO,GAAG,WAAW,OAAO,CAAC;AAAA,QAC/B,CAAC;AACD,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,mBAAmB,OAAO,UAA8D;AACtF,UAAI;AACF,cAAM,OAAO,MAAM,cAAc;AACjC,cAAM,KAAK;AAAA,UACT,EAAE,KAAK,MAAM;AAAA,UACb,EAAE,OAAO,EAAE,cAAc,MAAM,EAAE;AAAA,QACnC;AAAA,MACF,SAAS,GAAQ;AACf,gBAAQ,MAAM,wDAAwD;AAAA,UACpE;AAAA,UACA;AAAA,UACA,OAAO,GAAG,WAAW,OAAO,CAAC;AAAA,QAC/B,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IACA,QAAQ,OAAO,eAYF;AACX,UAAI;AACF,cAAM,OAAO,MAAM,cAAc;AACjC,cAAM,MAAM,MAAM,KAAK,QAAQ,EAAE,KAAK,WAAW,CAAC;AAClD,YAAI,CAAC,IAAK,QAAO;AACjB,cAAM,EAAE,KAAK,GAAG,EAAE,IAAI;AACtB,eAAO;AAAA,MACT,SAAS,GAAQ;AACf,gBAAQ,MAAM,6CAA6C;AAAA,UACzD;AAAA,UACA,OAAO,GAAG,WAAW,OAAO,CAAC;AAAA,QAC/B,CAAC;AACD,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;AAMA,eAAsB,UACpB,OACA,UACA,OACA,UACe;AACf,QAAM,OAAO,MAAM,cAAc;AACjC,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAM,KAAK;AAAA,IACT,EAAE,KAAK,MAAM;AAAA,IACb;AAAA,MACE,MAAM;AAAA,QACJ,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,QACR,OAAO,SAAS,CAAC;AAAA,QACjB,UAAU,YAAY,CAAC;AAAA,QACvB,WAAW;AAAA,QACX,WAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA,EAAE,QAAQ,KAAK;AAAA,EACjB;AACF;AAEO,SAAS,4BAAqC;AACnD,SAAO,QAAQ,KAAK,KAAK,CAAC;AAC5B;;;AC3PA,SAAS,aAAa;AAItB,IAAM,WACJ,QAAQ,IAAI,iCACZ,QAAQ,IAAI,0BACZ,QAAQ,IAAI;AACd,IAAM,aACJ,QAAQ,IAAI,mCACZ,QAAQ,IAAI,4BACZ,QAAQ,IAAI;AACd,IAAM,eACJ,QAAQ,IAAI,oCACZ,QAAQ,IAAI,4BACZ,QAAQ,IAAI,2BACZ;AACF,IAAM,oBAAoB,KAAK,KAAK,KAAK;AACzC,IAAM,gBACJ,OAAO,QAAQ,IAAI,4BAA4B,WAC3C,SAAS,QAAQ,IAAI,yBAAyB,EAAE,KAAK,oBACrD,OAAO,QAAQ,IAAI,iCAAiC,WAClD,SAAS,QAAQ,IAAI,8BAA8B,EAAE,KAAK,oBAC1D,OAAO,QAAQ,IAAI,8BAA8B,WAC/C,SAAS,QAAQ,IAAI,2BAA2B,EAAE,KAAK,oBACvD;AAEV,IAAI,cAA4B;AAEhC,SAAS,WAAkB;AACzB,MAAI,CAAC,YAAY,CAAC,YAAY;AAC5B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,aAAa;AAChB,kBAAc,IAAI,MAAM;AAAA,MACtB,KAAK;AAAA,MACL,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,SAAS,OAAO,OAAuB;AACrC,SAAO,GAAG,YAAY,GAAG,KAAK;AAChC;AAGA,SAAS,gBAAgB,OAAuB;AAC9C,SAAO,GAAG,YAAY,GAAG,KAAK;AAChC;AAEO,SAAS,4BAAqC;AACnD,SAAO,SAAS,YAAY,IAAI,KAAK,MAAM,cAAc,IAAI,KAAK,CAAC;AACrE;AAGA,eAAsB,QAAQ,OAA0C;AACtE,QAAM,QAAQ,SAAS;AACvB,QAAM,MAAM,OAAO,KAAK;AACxB,QAAM,OAAO,MAAM,MAAM,QAAgC,GAAG;AAC5D,MAAI,CAAC,QAAQ,OAAO,KAAK,IAAI,EAAE,WAAW,EAAG,QAAO;AACpD,QAAM,YAAY,CAAI,QAAuC;AAC3D,QAAI,CAAC,IAAK,QAAO;AACjB,QAAI;AACF,aAAO,KAAK,MAAM,GAAG;AAAA,IACvB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,UAAU,gBAAgB,KAAK;AACrC,QAAM,YAAY,MAAM,MAAM,OAAe,SAAS,GAAG,EAAE;AAC3D,MAAI;AACJ,MAAI,aAAa,UAAU,SAAS,GAAG;AACrC,mBAAe,UAAU,IAAI,CAAC,MAAM;AAClC,UAAI;AACF,eAAO,KAAK,MAAM,CAAC;AAAA,MACrB,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF,CAAC,EAAE,OAAO,OAAO;AAAA,EACnB,OAAO;AACL,mBAAe,UAAsD,KAAK,YAAY;AAAA,EACxF;AACA,QAAM,SAAoB;AAAA,IACxB,OAAO,KAAK;AAAA,IACZ,UAAU,KAAK;AAAA,IACf,QAAS,KAAK,UAAkC;AAAA,IAChD,OAAO,UAAe,KAAK,KAAK,KAAK,CAAC;AAAA,IACtC,QAAQ,UAAe,KAAK,MAAM;AAAA,IAClC,OAAO,UAAe,KAAK,KAAK;AAAA,IAChC,UAAU,UAA+B,KAAK,QAAQ,KAAK,CAAC;AAAA,IAC5D;AAAA,IACA,WAAW,KAAK;AAAA,IAChB,WAAW,KAAK;AAAA,IAChB,aAAa,KAAK;AAAA,EACpB;AACA,SAAO;AACT;AAEO,SAAS,oBACd,UACA,OACA,OACA,UACU;AACV,SAAO;AAAA,IACL,QAAQ,OAAO,WAA0C;AACvD,YAAM,QAAQ,SAAS;AACvB,YAAM,MAAM,OAAO,KAAK;AACxB,YAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAGnC,YAAM,WAAW,MAAM,QAAQ,KAAK;AACpC,YAAM,OAA2B,CAAC;AAGlC,YAAM,aAAkC,EAAE,GAAI,UAAU,YAAY,CAAC,EAAG;AACxE,UAAI,OAAO,UAAU;AACnB,eAAO,OAAO,YAAY,OAAO,QAAQ;AAAA,MAC3C;AACA,UAAI,OAAO,aAAa,UAAa,OAAO,oBAAoB,QAAW;AACzE,mBAAW,WAAW,OAAO;AAC7B,mBAAW,kBAAkB,OAAO;AAAA,MACtC;AAEA,WAAK,WAAW;AAChB,UAAI,OAAO,WAAW,QAAW;AAC/B,aAAK,SAAS,OAAO,QAAQ,WAAW,OAAO;AAC/C,aAAK,OAAO,WAAW,eAAe,OAAO,WAAW,aAAa,CAAC,UAAU,aAAa;AAC3F,eAAK,cAAc;AAAA,QACrB;AAAA,MACF;AACA,UAAI,OAAO,WAAW,OAAW,MAAK,SAAS,OAAO;AACtD,UAAI,OAAO,UAAU,OAAW,MAAK,QAAQ,OAAO;AAEpD,YAAM,QAAgC,CAAC;AACvC,UAAI,KAAK,OAAQ,OAAM,QAAQ,IAAI,OAAO,KAAK,MAAM;AACrD,UAAI,KAAK,WAAW,OAAW,OAAM,QAAQ,IAAI,KAAK,UAAU,KAAK,MAAM;AAC3E,UAAI,KAAK,UAAU,OAAW,OAAM,OAAO,IAAI,KAAK,UAAU,KAAK,KAAK;AACxE,UAAI,KAAK,aAAa,OAAW,OAAM,UAAU,IAAI,KAAK,UAAU,KAAK,QAAQ;AACjF,UAAI,KAAK,aAAa;AACpB,cAAM,aAAa,IAAI,KAAK;AAAA,MAC9B;AACA,YAAM,WAAW,IAAI;AAErB,YAAM,MAAM,KAAK,KAAK,KAAK;AAC3B,UAAI,gBAAgB,GAAG;AACrB,cAAM,MAAM,OAAO,KAAK,aAAa;AAAA,MACvC;AAAA,IACF;AAAA,IACA,KAAK,YAAY;AACf,aAAO,QAAQ,KAAK;AAAA,IACtB;AAAA,IACA,mBAAmB,OAAO,UAAU;AAClC,YAAM,QAAQ,SAAS;AACvB,YAAM,UAAU,gBAAgB,KAAK;AACrC,YAAM,MAAM,MAAM,SAAS,KAAK,UAAU,KAAK,CAAC;AAChD,YAAM,UAAU,OAAO,KAAK;AAC5B,YAAM,MAAM,KAAK,SAAS,EAAE,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC;AACjE,UAAI,gBAAgB,GAAG;AACrB,cAAM,MAAM,OAAO,SAAS,aAAa;AACzC,cAAM,MAAM,OAAO,SAAS,aAAa;AAAA,MAC3C;AAAA,IACF;AAAA,IACA,QAAQ,OAAO,eAAuB;AACpC,aAAO,QAAQ,UAAU;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,eAAsB,eACpB,OACA,UACA,OACA,UACe;AACf,QAAM,QAAQ,SAAS;AACvB,QAAM,MAAM,OAAO,KAAK;AACxB,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAM,MAA0B;AAAA,IAC9B;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,WAAW;AAAA,EACb;AACA,QAAM,QAAgC;AAAA,IACpC;AAAA,IACA;AAAA,IACA,QAAQ,IAAI;AAAA,IACZ,OAAO,KAAK,UAAU,IAAI,SAAS,CAAC,CAAC;AAAA,IACrC,UAAU,KAAK,UAAU,IAAI,YAAY,CAAC,CAAC;AAAA,IAC3C,WAAW;AAAA,IACX,WAAW;AAAA,EACb;AACA,QAAM,MAAM,KAAK,KAAK,KAAK;AAC3B,MAAI,gBAAgB,GAAG;AACrB,UAAM,MAAM,OAAO,KAAK,aAAa;AAAA,EACvC;AACF;;;AChMA,SAAS,SAAS,oBAAoB;AACtC,SAAS,eAAAA,oBAAoC;AA2B7C,IAAM,WAAW,QAAQ,IAAI,wBAAwB,QAAQ,IAAI;AACjE,IAAM,cACJ,QAAQ,IAAI,uBACZ,QAAQ,IAAI,cACZ;AACF,IAAM,2BACJ,QAAQ,IAAI,iCAAiC;AAE/C,IAAI,qBAAkD;AAEtD,eAAe,iBAAuC;AACpD,MAAI,CAAC,UAAU;AACb,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,oBAAoB;AACvB,yBAAqB,IAAIA,aAAY,UAAU;AAAA,MAC7C,aAAa;AAAA,MACb,aAAa;AAAA,MACb,0BAA0B;AAAA,IAC5B,CAAC,EAAE,QAAQ;AAAA,EACb;AACA,SAAO;AACT;AAEA,eAAe,0BAA4D;AACzE,QAAM,SAAS,MAAM,eAAe;AACpC,SAAO,OAAO,GAAG,WAAW,EAAE,WAAwB,wBAAwB;AAChF;AAIA,IAAMC,YACJ,QAAQ,IAAI,iCACZ,QAAQ,IAAI,0BACZ,QAAQ,IAAI;AACd,IAAMC,cACJ,QAAQ,IAAI,mCACZ,QAAQ,IAAI,4BACZ,QAAQ,IAAI;AACd,IAAM,iBACJ,QAAQ,IAAI,qCACZ,QAAQ,IAAI,8BACZ;AAEF,IAAMC,qBAAoB,KAAK,KAAK,KAAK;AACzC,IAAM,qBACJ,OAAO,QAAQ,IAAI,kCAAkC,WACjD,SAAS,QAAQ,IAAI,+BAA+B,EAAE,KAAKA,qBAC3D,OAAO,QAAQ,IAAI,4BAA4B,WAC7C,SAAS,QAAQ,IAAI,yBAAyB,EAAE,KAAKA,qBACrDA;AAER,IAAIC,eAA4B;AAEhC,SAASC,YAAkB;AACzB,MAAI,CAACJ,aAAY,CAACC,aAAY;AAC5B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAACE,cAAa;AAChB,IAAAA,eAAc,IAAI,aAAa;AAAA,MAC7B,KAAKH;AAAA,MACL,OAAOC;AAAA,IACT,CAAC;AAAA,EACH;AACA,SAAOE;AACT;AAEA,SAAS,SAAS,IAAoB;AACpC,SAAO,GAAG,cAAc,GAAG,EAAE;AAC/B;AAKA,SAAS,cAAc,KAA8B;AACnD,MAAI,MAAM,QAAQ,GAAG,EAAG,QAAO;AAC/B,MAAI,OAAO,QAAQ,UAAU;AAC3B,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,aAAO,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC;AAAA,IAC3C,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AACA,SAAO,CAAC;AACV;AAEA,SAAS,iBAAiB,KAAuC;AAC/D,MAAI,OAAO,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,GAAG,EAAG,QAAO;AAClE,MAAI,OAAO,QAAQ,UAAU;AAC3B,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,aAAO,UAAU,OAAO,WAAW,WAAW,SAAS,CAAC;AAAA,IAC1D,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AACA,SAAO,CAAC;AACV;AAEA,eAAe,kBAAkB,YAAoD;AACnF,QAAM,QAAQC,UAAS;AACvB,QAAM,MAAM,SAAS,UAAU;AAC/B,QAAM,OAAO,MAAM,MAAM,QAAQ,GAAG;AACpC,MAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,OAAO,KAAK,IAAI,EAAE,WAAW,EAAG,QAAO;AAChF,QAAM,IAAI;AACV,QAAM,SAAyB;AAAA,IAC7B,IAAK,EAAE,OAAO,SAAY,aAAa,OAAO,EAAE,EAAE;AAAA,IAClD,SAAS,OAAO,EAAE,WAAW,EAAE;AAAA,IAC/B,QAAS,OAAO,EAAE,UAAU,SAAS;AAAA,IACrC,OAAO,cAAc,EAAE,KAAK;AAAA,IAC5B,UAAU,iBAAiB,EAAE,QAAQ;AAAA,IACrC,WAAW,OAAO,EAAE,cAAa,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,IACzD,WAAW,OAAO,EAAE,cAAa,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,IACzD,aAAa,EAAE,eAAe,OAAO,OAAO,EAAE,WAAW,IAAI;AAAA,EAC/D;AACA,SAAO;AACT;AAEA,eAAe,kBAAkB,QAAuC;AACtE,QAAM,QAAQA,UAAS;AACvB,QAAM,MAAM,SAAS,OAAO,EAAE;AAC9B,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAM,QAAgC;AAAA,IACpC,IAAI,OAAO;AAAA,IACX,SAAS,OAAO;AAAA,IAChB,QAAQ,OAAO;AAAA,IACf,OAAO,KAAK,UAAU,OAAO,SAAS,CAAC,CAAC;AAAA,IACxC,UAAU,KAAK,UAAU,OAAO,YAAY,CAAC,CAAC;AAAA,IAC9C,WAAW,OAAO,aAAa;AAAA,IAC/B,WAAW,OAAO,aAAa;AAAA,EACjC;AACA,MAAI,OAAO,aAAa;AACtB,UAAM,cAAc,OAAO;AAAA,EAC7B;AACA,QAAM,MAAM,KAAK,KAAK,KAAK;AAC3B,MAAI,qBAAqB,GAAG;AAC1B,UAAM,MAAM,OAAO,KAAK,kBAAkB;AAAA,EAC5C;AACF;AAIA,SAAS,eAA4C;AACnD,QAAM,KAAK,QAAQ,IAAI,wBAAwB,iBAAiB,YAAY;AAC5E,SAAO,MAAM,YAAY,YAAY;AACvC;AAEA,SAAS,cAAuB;AAC9B,SAAO,aAAa,MAAM,aAAa,QAAQ,UAAU,KAAK,CAAC;AACjE;AAEA,SAAS,cAAuB;AAC9B,SAAO,aAAa,MAAM,aAAa,SAASJ,aAAY,IAAI,KAAK,MAAMC,eAAc,IAAI,KAAK,CAAC;AACrG;AAIA,eAAsB,sBAAsB,SAM1B;AAChB,QAAM,EAAE,YAAY,SAAS,eAAe,kBAAkB,SAAS,IAAI;AAC3E,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,MAAI,YAAY,GAAG;AACjB,UAAM,OAAO,MAAM,wBAAwB;AAC3C,UAAM,WAAW,MAAM,KAAK,QAAQ,EAAE,KAAK,WAAW,CAAC;AACvD,QAAI,UAAU;AACZ,YAAM,QAAQ,SAAS,SAAS,CAAC;AACjC,UAAI,MAAM,WAAW,GAAG;AACtB,cAAM,KAAK;AAAA,UACT,UAAU;AAAA,UACV,aAAa;AAAA,UACb,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AACA,YAAM,KAAK;AAAA,QACT,EAAE,KAAK,WAAW;AAAA,QAClB;AAAA,UACE,MAAM;AAAA,YACJ;AAAA,YACA,WAAW;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,YAAM,MAAmB;AAAA,QACvB,KAAK;AAAA,QACL,IAAI;AAAA,QACJ;AAAA,QACA,QAAQ;AAAA,QACR,OAAO;AAAA,UACL;AAAA,YACE,UAAU;AAAA,YACV,aAAa;AAAA,YACb,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,QACA,UAAU,YAAY,CAAC;AAAA,QACvB,WAAW;AAAA,QACX,WAAW;AAAA,MACb;AACA,YAAM,KAAK;AAAA,QACT,EAAE,KAAK,WAAW;AAAA,QAClB,EAAE,MAAM,IAAI;AAAA,QACZ,EAAE,QAAQ,KAAK;AAAA,MACjB;AAAA,IACF;AACA;AAAA,EACF;AAEA,MAAI,YAAY,GAAG;AACjB,UAAM,WAAW,MAAM,kBAAkB,UAAU;AACnD,QAAI,UAAU;AAEZ,UAAI,CAAC,SAAS,SAAS,SAAS,MAAM,WAAW,GAAG;AAClD,iBAAS,QAAQ;AAAA,UACf;AAAA,YACE,UAAU;AAAA,YACV,aAAa;AAAA,YACb,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AACA,eAAS,YAAY;AACrB,YAAM,kBAAkB,QAAQ;AAAA,IAClC,OAAO;AACL,YAAM,SAAyB;AAAA,QAC7B,IAAI;AAAA,QACJ;AAAA,QACA,QAAQ;AAAA,QACR,OAAO;AAAA,UACL;AAAA,YACE,UAAU;AAAA,YACV,aAAa;AAAA,YACb,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,QACA,UAAU,YAAY,CAAC;AAAA,QACvB,WAAW;AAAA,QACX,WAAW;AAAA,MACb;AACA,YAAM,kBAAkB,MAAM;AAAA,IAChC;AAAA,EACF;AACF;AAEA,eAAsB,0BAA0B,SAU9B;AAChB,QAAM,EAAE,YAAY,WAAW,QAAQ,OAAO,QAAQ,MAAM,IAAI;AAChE,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,MAAI,YAAY,GAAG;AACjB,UAAM,OAAO,MAAM,wBAAwB;AAC3C,UAAM,WAAW,MAAM,KAAK,QAAQ,EAAE,KAAK,WAAW,CAAC;AACvD,QAAI,CAAC,SAAU;AACf,UAAM,OAAO,SAAS,MAAM,SAAS;AACrC,QAAI,CAAC,KAAM;AAEX,UAAM,aAA2B;AAAA,MAC/B,GAAG;AAAA,MACH;AAAA,MACA,GAAI,UAAU,UAAa,EAAE,MAAM;AAAA,MACnC,GAAI,WAAW,UAAa,EAAE,OAAO;AAAA,MACrC,GAAI,UAAU,UAAa,EAAE,MAAM;AAAA,MACnC,WAAW,KAAK,cAAc,WAAW,YAAY,MAAM,KAAK;AAAA,MAChE,aACE,KAAK,gBACJ,WAAW,eAAe,WAAW,WAAW,MAAM,KAAK;AAAA,IAChE;AAEA,UAAM,SAA2D;AAAA,MAC/D,OAAO,SAAS;AAAA,MAChB,WAAW;AAAA,IACb;AACA,WAAO,MAAM,SAAS,IAAI;AAC1B,QAAI,WAAW,UAAU;AACvB,aAAO,SAAS;AAChB,UAAI,CAAC,SAAS,YAAa,QAAO,cAAc;AAAA,IAClD,WAAW,WAAW,eAAe,cAAc,SAAS,MAAM,SAAS,GAAG;AAC5E,aAAO,SAAS;AAChB,UAAI,CAAC,SAAS,YAAa,QAAO,cAAc;AAAA,IAClD;AAEA,UAAM,KAAK;AAAA,MACT,EAAE,KAAK,WAAW;AAAA,MAClB;AAAA,QACE,MAAM;AAAA,MACR;AAAA,IACF;AACA;AAAA,EACF;AAEA,MAAI,YAAY,GAAG;AACjB,UAAM,WAAW,MAAM,kBAAkB,UAAU;AACnD,QAAI,CAAC,UAAU;AAEb;AAAA,IACF;AACA,UAAM,QAAQ,SAAS,SAAS,CAAC;AACjC,UAAM,OAAO,MAAM,SAAS;AAC5B,QAAI,CAAC,MAAM;AACT;AAAA,IACF;AACA,SAAK,SAAS;AACd,QAAI,UAAU,OAAW,MAAK,QAAQ;AACtC,QAAI,WAAW,OAAW,MAAK,SAAS;AACxC,QAAI,UAAU,OAAW,MAAK,QAAQ;AACtC,QAAI,WAAW,WAAW;AACxB,WAAK,YAAY,KAAK,aAAa;AAAA,IACrC;AACA,QAAI,WAAW,eAAe,WAAW,UAAU;AACjD,WAAK,cAAc,KAAK,eAAe;AAAA,IACzC;AAEA,aAAS,QAAQ;AACjB,aAAS,YAAY;AACrB,QAAI,WAAW,UAAU;AACvB,eAAS,SAAS;AAClB,eAAS,cAAc,SAAS,eAAe;AAAA,IACjD,WAAW,WAAW,eAAe,cAAc,MAAM,SAAS,GAAG;AACnE,eAAS,SAAS;AAClB,eAAS,cAAc,SAAS,eAAe;AAAA,IACjD;AACA,UAAM,kBAAkB,QAAQ;AAAA,EAClC;AACF;AAEA,eAAsB,0BAA0B,SAK9B;AAChB,QAAM,EAAE,YAAY,UAAU,YAAY,IAAI;AAC9C,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,MAAI,YAAY,GAAG;AACjB,UAAM,OAAO,MAAM,wBAAwB;AAC3C,UAAM,KAAK;AAAA,MACT,EAAE,KAAK,WAAW;AAAA,MAClB;AAAA,QACE,OAAO;AAAA,UACL,OAAO;AAAA,YACL;AAAA,YACA;AAAA,YACA,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,QACA,MAAM,EAAE,WAAW,IAAI;AAAA,MACzB;AAAA,IACF;AACA;AAAA,EACF;AAEA,MAAI,YAAY,GAAG;AACjB,UAAM,WAAW,MAAM,kBAAkB,UAAU;AACnD,QAAI,CAAC,SAAU;AACf,UAAM,QAAQ,SAAS,SAAS,CAAC;AACjC,UAAM,KAAK;AAAA,MACT;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,IACV,CAAC;AACD,aAAS,QAAQ;AACjB,aAAS,YAAY;AACrB,UAAM,kBAAkB,QAAQ;AAAA,EAClC;AACF;;;AH3VO,IAAM,wBAAwB;AAiErC,IAAM,mBAAmB;AACzB,eAAe,mBACb,YACA,QACA,QAQe;AACf,MAAI;AACF,QAAI,WAAW,UAAU;AACvB,UAAI,CAAC,OAAO,YAAY,CAAC,OAAO,YAAa;AAC/C,YAAM,0BAA0B;AAAA,QAC9B;AAAA,QACA,UAAU,OAAO;AAAA,QACjB,aAAa,OAAO;AAAA,MACtB,CAAC;AACD,UAAI,QAAQ,IAAI,wBAAwB,KAAK;AAC3C,gBAAQ,IAAI,oCAAoC;AAAA,UAC9C;AAAA,UACA,UAAU,OAAO;AAAA,UACjB,aAAa,OAAO;AAAA,QACtB,CAAC;AAAA,MACH;AACE;AAAA,IACF;AAEA,QAAI,OAAO,cAAc,OAAW;AAEpC,UAAM,SACJ,WAAW,UACP,YACA,WAAW,aACT,cACA,WAAW,SACT,WACA;AACV,QAAI,CAAC,OAAQ;AAEb,UAAM,0BAA0B;AAAA,MAC9B;AAAA,MACA,WAAW,OAAO;AAAA,MAClB,UAAU,OAAO,YAAY;AAAA,MAC7B,aAAa,OAAO;AAAA,MACpB;AAAA,MACA,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO;AAAA,IAChB,CAAC;AACD,QAAI,QAAQ,IAAI,wBAAwB,KAAK;AAC3C,cAAQ,IAAI,mCAAmC;AAAA,QAC7C;AAAA,QACA;AAAA,QACA,WAAW,OAAO;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAU;AACjB,YAAQ,KAAK,oCAAoC;AAAA,MAC/C;AAAA,MACA;AAAA,MACA,OAAO,KAAK,WAAW,OAAO,GAAG;AAAA,IACnC,CAAC;AAAA,EACH;AACF;AAQO,SAAS,oBACd,SACA,cAC6G;AAC7G,SAAO,OAAO,WAAW;AACvB,UAAM,eAAgB,OAAO,QAAgB,gBAAgB;AAC7D,UAAM,SAAS,MAAM,QAAQ,MAAM;AAEnC,QAAI,CAAC,gBAAgB,OAAO,iBAAiB,YAAY,CAAC,aAAa,IAAI;AACzE,aAAO;AAAA,IACT;AAEA,UAAM,EAAE,IAAI,SAAS,WAAW,cAAc,WAAW,IAAI;AAC7D,UAAM,OAAO,aAAa,YAAY,SAAS,SAAS;AACxD,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAC/E,QAAI,YAAY;AACd,YAAM,mBAAmB,YAAY,UAAU;AAAA,QAC7C,aAAa;AAAA,QACb,UAAU,KAAK;AAAA,MACjB,CAAC;AAAA,IACH;AAEA,QAAI,YAAqB;AACzB,QAAI,KAAK,oBAAoB,OAAO,aAAa,mBAAmB,YAAY;AAC9E,kBAAY,MAAM,aAAa,eAAe,SAAS,YAAY,GAAG,QAAQ,YAAY;AAAA,IAC5F;AAEA,UAAM,qBAAqB;AAAA,MACzB,GAAI,cAAc,QAAQ,OAAO,cAAc,WAAY,YAAwC,EAAE,OAAO,UAAU;AAAA,MACtH,CAAC,gBAAgB,GAAG;AAAA,QAClB,IAAI;AAAA,QACJ,WAAW,YAAY;AAAA,QACvB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,QAAQ,QAAQ,IAAI,2BAA2B;AACrD,QAAI,OAAO;AACT,cAAQ,IAAI,0CAA0C;AAAA,QACpD;AAAA,QACA,UAAU;AAAA,QACV,cAAc,KAAK;AAAA,QACnB,cAAc,KAAK;AAAA,MACrB,CAAC;AAAA,IACH;AAEA,UAAM,OAAO,IAAI,eAAe,KAAK,UAAU,oBAAoB;AAAA,MACjE,OAAO;AAAA,MACP,cAAc,KAAK;AAAA,MACnB,OAAO;AAAA,IACT,CAAC;AAED,WAAO;AAAA,EACT;AACF;AA2BA,IAAM,2BAA2B;AACjC,IAAM,0BAA0B,KAAK,KAAK;AAE1C,SAAS,uBAAuB,UAA0B;AACxD,SAAO,SAAS,QAAQ,MAAM,GAAG,EAAE,YAAY;AACjD;AAEA,SAAS,qBAAqB,gBAA4C;AACxE,QAAM,MAAM,oBAAoB,uBAAuB,cAAc,CAAC;AACtE,SAAO,QAAQ,IAAI,GAAG,GAAG,KAAK,KAAK;AACrC;AAMA,SAAS,qBACP,aACA,gBACA,eACA,UAKoE;AACpE,SAAO,OACL,gBACA,OACA,YACqE;AACrE,UAAM,aACJ,SAAS,SACT,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAC9D,UAAM,WAAW,SAAS,YAAY,CAAC;AACvC,UAAM,oBAAyC,CAAC;AAChD,QAAI,cAAc,UAAW,mBAAkB,YAAY,cAAc;AAEzE,UAAM,cAA8B;AAAA,MAClC,UAAU;AAAA,MACV,OAAO;AAAA,MACP,OAAO,SAAS,CAAC;AAAA,MACjB,SAAS;AAAA,MACT,YAAY,SAAS;AAAA,MACrB;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAEA,UAAM,WAAW,qBAAqB,cAAc;AAEpD,QAAI,UAAU;AACZ,YAAM,SAAS,QAAQ,IAAI,cAAc,QAAQ,IAAI,sBAAsB;AAC3E,YAAM,MAAM,IAAI,UAAU,EAAE,OAAO,CAAC;AAIpC,YAAM,eACJ,SAAS,UAAU,QAAQ,SAAS,gBAAgB,OAChD,KAAK,IAAI,uBAAuB,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,YAAY,CAAC,CAAC,IAC7E;AACN,YAAM,aAAa,MAAM,IAAI;AAAA,QAC3B,IAAI,mBAAmB;AAAA,UACrB,UAAU;AAAA,UACV,aAAa,KAAK,UAAU,WAAW;AAAA,UACvC,GAAI,iBAAiB,UAAa,eAAe,IAAI,EAAE,cAAc,aAAa,IAAI,CAAC;AAAA,QACzF,CAAC;AAAA,MACH;AACA,YAAM,YAAY,WAAW,aAAa;AAE1C,UAAI,UAAU,mBAAmB;AAC/B,cAAM,SAAS,kBAAkB,EAAE,OAAO,YAAY,UAAU,eAAe,CAAC;AAAA,MAClF;AAEA,UAAI,SAAS,SAAS,UAAU,QAAQ;AACtC,cAAM,iBAAiB,QAAQ,kBAAkB;AACjD,cAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,cAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,eAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,gBAAM,QAAQ,MAAM,SAAS,OAAO,UAAU;AAC9C,cAAI,CAAC,OAAO;AACV,kBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,cAAc,CAAC;AACtD;AAAA,UACF;AACA,cAAI,MAAM,WAAW,aAAa;AAChC,mBAAO,EAAE,OAAO,YAAY,WAAW,QAAQ,MAAM,OAAO;AAAA,UAC9D;AACA,cAAI,MAAM,WAAW,UAAU;AAC7B,kBAAM,MAAM,MAAM;AAClB,kBAAM,IAAI;AAAA,cACR,KAAK,WAAW,gBAAgB,cAAc;AAAA,YAChD;AAAA,UACF;AACA,gBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,cAAc,CAAC;AAAA,QACxD;AACA,cAAM,IAAI;AAAA,UACR,gBAAgB,cAAc,KAAK,UAAU,6BAA6B,aAAa;AAAA,QACzF;AAAA,MACF;AAEA,aAAO,EAAE,OAAO,YAAY,UAAU;AAAA,IACxC;AAGA,UAAM,IAAI;AAAA,MACR,oBAAoB,uBAAuB,cAAc,CAAC;AAAA,IAE5D;AAAA,EACF;AACF;AAKA,eAAe,YACb,YACA,SACe;AACf,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,YAAY;AAAA,MACvC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAChB;AAAA,MACA,MAAM,KAAK,UAAU,OAAO;AAAA,IAC9B,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACtD,cAAQ,MAAM,qCAAqC;AAAA,QACjD,KAAK;AAAA,QACL,QAAQ,SAAS;AAAA,QACjB,YAAY,SAAS;AAAA,QACrB;AAAA,MACF,CAAC;AAAA,IAEH,OAAO;AACL,cAAQ,IAAI,yCAAyC;AAAA,QACnD,KAAK;AAAA,QACL,QAAQ,SAAS;AAAA,MACnB,CAAC;AAAA,IACH;AAAA,EACF,SAAS,OAAY;AACnB,YAAQ,MAAM,oCAAoC;AAAA,MAChD,KAAK;AAAA,MACL,OAAO,OAAO,WAAW,OAAO,KAAK;AAAA,MACrC,OAAO,OAAO;AAAA,IAChB,CAAC;AAAA,EAEH;AACF;AAUO,SAAS,oBACd,SACA,cAC4D;AAC5D,SAAO,OAAO,OAAiB,kBAAiC;AAC9D,UAAM,WAAW,MAAM,QAAQ,IAAI,OAAO,WAAsB;AAC9D,UAAI,cAAqC;AACzC,UAAI;AACF,sBAAc,KAAK,MAAM,OAAO,IAAI;AAEpC,cAAM,EAAE,UAAU,OAAO,OAAO,SAAS,YAAY,WAAW,CAAC,EAAE,IACjE;AAIF,cAAM,OAAO,QAAQ,IAAI,wBAAwB,iBAAiB,YAAY;AAC9E,cAAM,eACJ,QAAQ,YAAY,YAAY;AAClC,YAAI,iBAAiB,mBAAmB,0BAA0B,GAAG;AACnE,gBAAM,WAAW,MAAM,QAAa,KAAK;AACzC,cAAI,aAAa,SAAS,WAAW,eAAe,SAAS,WAAW,WAAW;AACjF,oBAAQ,IAAI,wDAAwD;AAAA,cAClE;AAAA,cACA;AAAA,cACA,QAAQ,SAAS;AAAA,YACnB,CAAC;AACD;AAAA,UACF;AAAA,QACF,WAAW,iBAAiB,aAAa,0BAA0B,GAAG;AACpE,gBAAM,WAAW,MAAM,WAAgB,KAAK;AAC5C,cAAI,aAAa,SAAS,WAAW,eAAe,SAAS,WAAW,WAAW;AACjF,oBAAQ,IAAI,wDAAwD;AAAA,cAClE;AAAA,cACA;AAAA,cACA,QAAQ,SAAS;AAAA,YACnB,CAAC;AACD;AAAA,UACF;AAAA,QACF;AAGA,YAAI;AACJ,YACE,iBAAiB,mBACjB,0BAA0B,GAC1B;AACA,gBAAM,eAAe,OAAO,UAAU,OAAO,QAAQ;AACrD,qBAAW,oBAAoB,UAAU,OAAO,OAAO,QAAQ;AAAA,QACjE,WACE,iBAAiB,aACjB,0BAA0B,GAC1B;AACA,gBAAM,UAAU,OAAO,UAAU,OAAO,QAAQ;AAChD,qBAAW,oBAAoB,UAAU,OAAO,OAAO,QAAQ;AAAA,QACjE;AAEA,cAAM,cAAc;AAAA,UAClB;AAAA,UACA;AAAA,UACA,WAAW,QAAQ,aAAa,cAAc;AAAA,UAC9C,GAAG;AAAA,QACL;AACA,cAAM,iBAAiB;AAAA,UACrB,GAAG;AAAA,UACH,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,UAC/B,gBAAgB;AAAA,YACd;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAEA,YAAI,UAAU;AACZ,cAAI;AACF,kBAAM,SAAS,OAAO,EAAE,QAAQ,UAAU,CAAC;AAC3C,oBAAQ,IAAI,2CAA2C;AAAA,cACrD;AAAA,cACA;AAAA,YACF,CAAC;AAAA,UACH,SAAS,OAAY;AACnB,oBAAQ,KAAK,gDAAgD;AAAA,cAC3D;AAAA,cACA;AAAA,cACA,OAAO,OAAO,WAAW,OAAO,KAAK;AAAA,YACvC,CAAC;AAAA,UACH;AAAA,QACF;AAEA,cAAM,WAAY,OAAe,iBAAiB,UAAU;AAC5D,YAAI,UAAU,cAAc,OAAO,SAAS,cAAc,UAAU;AAElE,cAAI,SAAS,cAAc,GAAG;AAC5B,gBAAI;AACF,oBAAM,sBAAsB;AAAA,gBAC1B,YAAY,SAAS;AAAA,gBACrB,SAAS,SAAS;AAAA,gBAClB,eAAe;AAAA,gBACf,kBAAkB;AAAA,gBAClB;AAAA,cACF,CAAC;AAAA,YACH,SAAS,GAAQ;AACf,sBAAQ,KAAK,gDAAgD;AAAA,gBAC3D,YAAY,SAAS;AAAA,gBACrB,SAAS,SAAS;AAAA,gBAClB,OAAO,GAAG,WAAW,OAAO,CAAC;AAAA,cAC/B,CAAC;AAAA,YACH;AAAA,UACF;AACA,gBAAM,mBAAmB,SAAS,YAAY,SAAS;AAAA,YACrD,WAAW,SAAS;AAAA,YACpB,aAAa;AAAA,YACb;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH;AAEA,YAAI;AACJ,YAAI;AACF,mBAAS,MAAM,QAAQ;AAAA,YACrB;AAAA,YACA,KAAK;AAAA,UACP,CAAC;AAED,cAAI,cAAc;AAChB,qBAAS,aAAa,MAAM,MAAM;AAAA,UACpC;AAAA,QACF,SAAS,OAAY;AACnB,gBAAM,eAA+B;AAAA,YACnC;AAAA,YACA;AAAA,YACA,QAAQ;AAAA,YACR,OAAO;AAAA,cACL,SAAS,MAAM,WAAW;AAAA,cAC1B,OAAO,MAAM;AAAA,cACb,MAAM,MAAM,QAAQ;AAAA,YACtB;AAAA,YACA;AAAA,UACF;AAEA,cAAI,UAAU;AACZ,gBAAI;AACF,oBAAM,SAAS,OAAO;AAAA,gBACpB,QAAQ;AAAA,gBACR,OAAO,aAAa;AAAA,cACtB,CAAC;AACD,sBAAQ,IAAI,0CAA0C;AAAA,gBACpD;AAAA,gBACA;AAAA,cACF,CAAC;AAAA,YACH,SAAS,aAAkB;AACzB,sBAAQ,KAAK,iDAAiD;AAAA,gBAC5D;AAAA,gBACA;AAAA,gBACA,OAAO,aAAa,WAAW,OAAO,WAAW;AAAA,cACnD,CAAC;AAAA,YACH;AAAA,UACF;AAEA,gBAAM,eAAgB,OAAe,iBAAiB,UAAU;AAChE,cAAI,cAAc,cAAc,OAAO,aAAa,cAAc,UAAU;AAC1E,kBAAM,mBAAmB,aAAa,YAAY,QAAQ;AAAA,cACxD,WAAW,aAAa;AAAA,cACxB,aAAa;AAAA,cACb;AAAA,cACA,OAAO,aAAa;AAAA,YACtB,CAAC;AAAA,UACH;AAEA,cAAI,YAAY;AACd,kBAAM,YAAY,YAAY,YAAY;AAAA,UAC5C;AACA,gBAAM;AAAA,QACR;AAEA,YAAI,UAAU;AACZ,cAAI;AACF,kBAAM,SAAS,OAAO;AAAA,cACpB,QAAQ;AAAA,cACR;AAAA,YACF,CAAC;AACD,oBAAQ,IAAI,6CAA6C;AAAA,cACvD;AAAA,cACA;AAAA,YACF,CAAC;AAAA,UACH,SAAS,aAAkB;AACzB,oBAAQ,KAAK,mDAAmD;AAAA,cAC9D;AAAA,cACA;AAAA,cACA,OAAO,aAAa,WAAW,OAAO,WAAW;AAAA,YACnD,CAAC;AAAA,UACH;AAAA,QACF;AAEA,cAAM,kBAAmB,OAAe,iBAAiB,UAAU;AACnE,YAAI,iBAAiB,cAAc,OAAO,gBAAgB,cAAc,UAAU;AAChF,gBAAM,mBAAmB,gBAAgB,YAAY,YAAY;AAAA,YAC/D,WAAW,gBAAgB;AAAA,YAC3B,aAAa;AAAA,YACb;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH;AAEA,gBAAQ,IAAI,2BAA2B;AAAA,UACrC;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,cAAM,iBAAiC;AAAA,UACrC;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAEA,YAAI,YAAY;AACd,gBAAM,YAAY,YAAY,cAAc;AAAA,QAC9C;AAAA,MACF,SAAS,OAAY;AACnB,gBAAQ,MAAM,yCAAyC;AAAA,UACrD,OAAO,aAAa,SAAS;AAAA,UAC7B,UAAU,aAAa,YAAY;AAAA,UACnC,OAAO,OAAO,WAAW,OAAO,KAAK;AAAA,UACrC,OAAO,OAAO;AAAA,QAChB,CAAC;AACD,cAAM;AAAA,MACR;AAAA,IACF,CAAC;AAED,UAAM,QAAQ,IAAI,QAAQ;AAAA,EAC5B;AACF;","names":["MongoClient","redisUrl","redisToken","defaultTtlSeconds","redisClient","getRedis"]}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
function getWorkersTriggerUrl() {
|
|
3
|
+
const raw = process.env.WORKER_BASE_URL || process.env.NEXT_PUBLIC_WORKER_BASE_URL || process.env.WORKERS_TRIGGER_API_URL || process.env.NEXT_PUBLIC_WORKERS_TRIGGER_API_URL || process.env.WORKERS_CONFIG_API_URL || process.env.NEXT_PUBLIC_WORKERS_CONFIG_API_URL;
|
|
4
|
+
if (!raw) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
"WORKER_BASE_URL (preferred) or NEXT_PUBLIC_WORKER_BASE_URL is required for background workers"
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
const url = new URL(raw);
|
|
10
|
+
url.search = "";
|
|
11
|
+
url.hash = "";
|
|
12
|
+
const path = url.pathname || "";
|
|
13
|
+
url.pathname = path.replace(/\/?workers\/(trigger|config)\/?$/, "");
|
|
14
|
+
const basePath = url.pathname.replace(/\/+$/, "");
|
|
15
|
+
url.pathname = `${basePath}/workers/trigger`.replace(/\/+$/, "");
|
|
16
|
+
return url.toString();
|
|
17
|
+
}
|
|
18
|
+
function serializeContext(ctx) {
|
|
19
|
+
const serialized = {};
|
|
20
|
+
if (ctx.requestId) {
|
|
21
|
+
serialized.requestId = ctx.requestId;
|
|
22
|
+
}
|
|
23
|
+
if (ctx.metadata && typeof ctx.metadata === "object") {
|
|
24
|
+
Object.assign(serialized, ctx.metadata);
|
|
25
|
+
}
|
|
26
|
+
if (ctx._serializeContext && typeof ctx._serializeContext === "function") {
|
|
27
|
+
const custom = ctx._serializeContext();
|
|
28
|
+
Object.assign(serialized, custom);
|
|
29
|
+
}
|
|
30
|
+
return serialized;
|
|
31
|
+
}
|
|
32
|
+
async function dispatch(workerId, input, inputSchema, options, ctx) {
|
|
33
|
+
const validatedInput = inputSchema.parse(input);
|
|
34
|
+
const jobId = options.jobId || `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
35
|
+
const triggerUrl = getWorkersTriggerUrl();
|
|
36
|
+
const serializedContext = ctx ? serializeContext(ctx) : {};
|
|
37
|
+
const messageBody = {
|
|
38
|
+
workerId,
|
|
39
|
+
jobId,
|
|
40
|
+
input: validatedInput,
|
|
41
|
+
context: serializedContext,
|
|
42
|
+
webhookUrl: options.webhookUrl,
|
|
43
|
+
metadata: options.metadata || {},
|
|
44
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
45
|
+
};
|
|
46
|
+
const headers = {
|
|
47
|
+
"Content-Type": "application/json"
|
|
48
|
+
};
|
|
49
|
+
const triggerKey = process.env.WORKERS_TRIGGER_API_KEY;
|
|
50
|
+
if (triggerKey) {
|
|
51
|
+
headers["x-workers-trigger-key"] = triggerKey;
|
|
52
|
+
}
|
|
53
|
+
const response = await fetch(triggerUrl, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers,
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
workerId,
|
|
58
|
+
body: messageBody
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
const text = await response.text().catch(() => "");
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Failed to trigger worker "${workerId}": ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const data = await response.json().catch(() => ({}));
|
|
68
|
+
const messageId = data?.messageId ? String(data.messageId) : `trigger-${jobId}`;
|
|
69
|
+
return {
|
|
70
|
+
messageId,
|
|
71
|
+
status: "queued",
|
|
72
|
+
jobId
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async function dispatchLocal(handler, input, ctx) {
|
|
76
|
+
return handler({ input, ctx: ctx || {} });
|
|
77
|
+
}
|
|
78
|
+
async function dispatchQueue(queueId, initialInput, options = {}, ctx) {
|
|
79
|
+
const registry = options.registry;
|
|
80
|
+
if (!registry?.getQueueById) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
"dispatchQueue requires options.registry with getQueueById. Use getQueueRegistry() from your workflows registry (e.g. app/api/workflows/registry/workers) and pass { registry: await getQueueRegistry() }."
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
const { getQueueById, invokeMapInput } = registry;
|
|
86
|
+
const queue = getQueueById(queueId);
|
|
87
|
+
if (!queue) {
|
|
88
|
+
throw new Error(`Worker queue "${queueId}" not found in registry`);
|
|
89
|
+
}
|
|
90
|
+
if (!queue.steps || queue.steps.length === 0) {
|
|
91
|
+
throw new Error(`Worker queue "${queueId}" has no steps defined`);
|
|
92
|
+
}
|
|
93
|
+
const stepIndex = 0;
|
|
94
|
+
const firstStep = queue.steps[stepIndex];
|
|
95
|
+
const firstWorkerId = firstStep.workerId;
|
|
96
|
+
if (!firstWorkerId) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Worker queue "${queueId}" has an invalid first step (missing workerId)`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
let firstInput = initialInput;
|
|
102
|
+
if (firstStep.mapInputFromPrev && typeof invokeMapInput === "function") {
|
|
103
|
+
firstInput = await invokeMapInput(
|
|
104
|
+
queueId,
|
|
105
|
+
stepIndex,
|
|
106
|
+
void 0,
|
|
107
|
+
initialInput
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const jobId = options.jobId || `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
111
|
+
const queueContext = {
|
|
112
|
+
id: queueId,
|
|
113
|
+
stepIndex,
|
|
114
|
+
initialInput,
|
|
115
|
+
queueJobId: jobId
|
|
116
|
+
};
|
|
117
|
+
if (options.onCreateQueueJob) {
|
|
118
|
+
try {
|
|
119
|
+
await options.onCreateQueueJob({
|
|
120
|
+
queueJobId: jobId,
|
|
121
|
+
queueId,
|
|
122
|
+
firstStep: { workerId: firstWorkerId, workerJobId: jobId },
|
|
123
|
+
metadata: options.metadata
|
|
124
|
+
});
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.warn("[dispatchQueue] onCreateQueueJob failed:", err?.message ?? err);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const normalizedFirstInput = firstInput !== null && typeof firstInput === "object" ? firstInput : { value: firstInput };
|
|
130
|
+
const inputWithQueue = {
|
|
131
|
+
...normalizedFirstInput,
|
|
132
|
+
__workerQueue: queueContext
|
|
133
|
+
};
|
|
134
|
+
const metadataWithQueue = {
|
|
135
|
+
...options.metadata || {},
|
|
136
|
+
__workerQueue: queueContext
|
|
137
|
+
};
|
|
138
|
+
const triggerUrl = getWorkersTriggerUrl();
|
|
139
|
+
const serializedContext = ctx ? serializeContext(ctx) : {};
|
|
140
|
+
const messageBody = {
|
|
141
|
+
workerId: firstWorkerId,
|
|
142
|
+
jobId,
|
|
143
|
+
input: inputWithQueue,
|
|
144
|
+
context: serializedContext,
|
|
145
|
+
webhookUrl: options.webhookUrl,
|
|
146
|
+
metadata: metadataWithQueue,
|
|
147
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
148
|
+
};
|
|
149
|
+
const headers = {
|
|
150
|
+
"Content-Type": "application/json"
|
|
151
|
+
};
|
|
152
|
+
const triggerKey = process.env.WORKERS_TRIGGER_API_KEY;
|
|
153
|
+
if (triggerKey) {
|
|
154
|
+
headers["x-workers-trigger-key"] = triggerKey;
|
|
155
|
+
}
|
|
156
|
+
const response = await fetch(triggerUrl, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers,
|
|
159
|
+
body: JSON.stringify({
|
|
160
|
+
workerId: firstWorkerId,
|
|
161
|
+
body: messageBody
|
|
162
|
+
})
|
|
163
|
+
});
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
const text = await response.text().catch(() => "");
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Failed to trigger queue "${queueId}" (worker "${firstWorkerId}"): ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
const data = await response.json().catch(() => ({}));
|
|
171
|
+
const messageId = data?.messageId ? String(data.messageId) : `trigger-${jobId}`;
|
|
172
|
+
return {
|
|
173
|
+
queueId,
|
|
174
|
+
messageId,
|
|
175
|
+
status: "queued",
|
|
176
|
+
jobId
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export {
|
|
181
|
+
getWorkersTriggerUrl,
|
|
182
|
+
dispatch,
|
|
183
|
+
dispatchLocal,
|
|
184
|
+
dispatchQueue
|
|
185
|
+
};
|
|
186
|
+
//# sourceMappingURL=chunk-BJPQY2NJ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Client for dispatching background worker jobs.\n *\n * In production, dispatching happens via the workers HTTP API:\n * POST /workers/trigger -> enqueues message to SQS on the workers service side\n *\n * This avoids requiring AWS credentials in your Next.js app.\n */\n\nimport type { ZodType, z } from 'zod';\nimport type { WorkerQueueConfig, WorkerQueueContext } from './queue.js';\n\nexport interface WorkerQueueRegistry {\n getQueueById(queueId: string): WorkerQueueConfig | undefined;\n invokeMapInput?: (\n queueId: string,\n stepIndex: number,\n prevOutput: unknown,\n initialInput: unknown\n ) => Promise<unknown> | unknown;\n}\n\nexport interface DispatchOptions {\n /**\n * Optional webhook callback URL to notify when the job finishes.\n * Only called when provided. Default: no webhook (use job store / MongoDB only).\n */\n webhookUrl?: string;\n /**\n * Controls how dispatch executes.\n * - \"auto\" (default): local inline execution in development unless WORKERS_LOCAL_MODE=false.\n * - \"local\": force inline execution (no SQS).\n * - \"remote\": force SQS/Lambda dispatch even in development.\n */\n mode?: 'auto' | 'local' | 'remote';\n jobId?: string;\n metadata?: Record<string, any>;\n /**\n * In-memory queue registry for dispatchQueue. Required when using dispatchQueue.\n * Pass a registry that imports from your .queue.ts definitions (works on Vercel/serverless).\n */\n registry?: WorkerQueueRegistry;\n /**\n * Optional callback to create a queue job record before dispatching.\n * Called with queueJobId (= first worker's jobId), queueId, and firstStep.\n */\n onCreateQueueJob?: (params: {\n queueJobId: string;\n queueId: string;\n firstStep: { workerId: string; workerJobId: string };\n metadata?: Record<string, unknown>;\n }) => Promise<void>;\n}\n\nexport interface DispatchResult {\n messageId: string;\n status: 'queued';\n jobId: string;\n}\n\nexport interface DispatchQueueResult extends DispatchResult {\n queueId: string;\n}\n\nexport interface SerializedContext {\n requestId?: string;\n userId?: string;\n traceId?: string;\n [key: string]: any;\n}\n\n/**\n * Derives the full /workers/trigger URL from env.\n * Exported for use by local dispatchWorker (worker-to-worker in dev).\n *\n * Preferred env vars:\n * - WORKER_BASE_URL: base URL of the workers service (e.g. https://.../prod)\n * - NEXT_PUBLIC_WORKER_BASE_URL: same, but exposed to the browser\n *\n * Legacy env vars (still supported for backwards compatibility):\n * - WORKERS_TRIGGER_API_URL / NEXT_PUBLIC_WORKERS_TRIGGER_API_URL\n * - WORKERS_CONFIG_API_URL / NEXT_PUBLIC_WORKERS_CONFIG_API_URL\n */\nexport function getWorkersTriggerUrl(): string {\n const raw =\n process.env.WORKER_BASE_URL ||\n process.env.NEXT_PUBLIC_WORKER_BASE_URL ||\n process.env.WORKERS_TRIGGER_API_URL ||\n process.env.NEXT_PUBLIC_WORKERS_TRIGGER_API_URL ||\n process.env.WORKERS_CONFIG_API_URL ||\n process.env.NEXT_PUBLIC_WORKERS_CONFIG_API_URL;\n\n if (!raw) {\n throw new Error(\n 'WORKER_BASE_URL (preferred) or NEXT_PUBLIC_WORKER_BASE_URL is required for background workers'\n );\n }\n\n const url = new URL(raw);\n url.search = '';\n url.hash = '';\n\n const path = url.pathname || '';\n\n // If the user pointed at a specific endpoint, normalize back to the service root.\n url.pathname = path.replace(/\\/?workers\\/(trigger|config)\\/?$/, '');\n\n const basePath = url.pathname.replace(/\\/+$/, '');\n url.pathname = `${basePath}/workers/trigger`.replace(/\\/+$/, '');\n\n return url.toString();\n}\n\n/**\n * Serializes context data for transmission to Lambda.\n * Only serializes safe, JSON-compatible properties.\n */\nfunction serializeContext(ctx: any): SerializedContext {\n const serialized: SerializedContext = {};\n\n if (ctx.requestId) {\n serialized.requestId = ctx.requestId;\n }\n\n // Extract any additional serializable metadata\n if (ctx.metadata && typeof ctx.metadata === 'object') {\n Object.assign(serialized, ctx.metadata);\n }\n\n // Allow custom context serialization via a helper property\n if (ctx._serializeContext && typeof ctx._serializeContext === 'function') {\n const custom = ctx._serializeContext();\n Object.assign(serialized, custom);\n }\n\n return serialized;\n}\n\n\n/**\n * Dispatches a background worker job to SQS.\n *\n * @param workerId - The ID of the worker to dispatch\n * @param input - The input data for the worker (will be validated against inputSchema)\n * @param inputSchema - Zod schema for input validation\n * @param options - Dispatch options including webhook URL\n * @param ctx - Optional context object (only serializable parts will be sent)\n * @returns Promise resolving to dispatch result with messageId and jobId\n */\nexport async function dispatch<INPUT_SCHEMA extends ZodType<any>>(\n workerId: string,\n input: z.input<INPUT_SCHEMA>,\n inputSchema: INPUT_SCHEMA,\n options: DispatchOptions,\n ctx?: any\n): Promise<DispatchResult> {\n // Validate input against schema\n const validatedInput = inputSchema.parse(input);\n\n // Generate job ID if not provided\n const jobId =\n options.jobId || `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n\n // Resolve /workers/trigger endpoint URL\n const triggerUrl = getWorkersTriggerUrl();\n\n // Serialize context (only safe, JSON-compatible parts)\n const serializedContext = ctx ? serializeContext(ctx) : {};\n\n // Job updates use MongoDB only; never pass jobStoreUrl/origin URL.\n const messageBody = {\n workerId,\n jobId,\n input: validatedInput,\n context: serializedContext,\n webhookUrl: options.webhookUrl,\n metadata: options.metadata || {},\n timestamp: new Date().toISOString(),\n };\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n const triggerKey = process.env.WORKERS_TRIGGER_API_KEY;\n if (triggerKey) {\n headers['x-workers-trigger-key'] = triggerKey;\n }\n\n const response = await fetch(triggerUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n workerId,\n body: messageBody,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(\n `Failed to trigger worker \"${workerId}\": ${response.status} ${response.statusText}${text ? ` - ${text}` : ''}`\n );\n }\n\n const data = (await response.json().catch(() => ({}))) as any;\n const messageId = data?.messageId ? String(data.messageId) : `trigger-${jobId}`;\n\n return {\n messageId,\n status: 'queued',\n jobId,\n };\n}\n\n/**\n * Local development mode: runs the handler immediately in the same process.\n * This bypasses SQS and Lambda for faster iteration during development.\n *\n * @param handler - The worker handler function\n * @param input - The input data\n * @param ctx - The context object\n * @returns The handler result\n */\nexport async function dispatchLocal<INPUT, OUTPUT>(\n handler: (params: { input: INPUT; ctx: any }) => Promise<OUTPUT>,\n input: INPUT,\n ctx?: any\n): Promise<OUTPUT> {\n return handler({ input, ctx: ctx || {} });\n}\n\n/**\n * Dispatches a queue by ID, using a generated registry of .queue.ts\n * definitions to determine the first worker and initial input mapping.\n *\n * This API intentionally mirrors the ergonomics of dispatching a single\n * worker, but under the hood it embeds queue context into the job input\n * so queue-aware wrappers can chain subsequent steps.\n */\nexport async function dispatchQueue<InitialInput = any>(\n queueId: string,\n initialInput: InitialInput,\n options: DispatchOptions = {},\n ctx?: any\n): Promise<DispatchQueueResult> {\n const registry = options.registry;\n if (!registry?.getQueueById) {\n throw new Error(\n 'dispatchQueue requires options.registry with getQueueById. ' +\n 'Use getQueueRegistry() from your workflows registry (e.g. app/api/workflows/registry/workers) and pass { registry: await getQueueRegistry() }.'\n );\n }\n const { getQueueById, invokeMapInput } = registry;\n const queue = getQueueById(queueId);\n\n if (!queue) {\n throw new Error(`Worker queue \"${queueId}\" not found in registry`);\n }\n\n if (!queue.steps || queue.steps.length === 0) {\n throw new Error(`Worker queue \"${queueId}\" has no steps defined`);\n }\n\n const stepIndex = 0;\n const firstStep = queue.steps[stepIndex];\n const firstWorkerId = firstStep.workerId;\n\n if (!firstWorkerId) {\n throw new Error(\n `Worker queue \"${queueId}\" has an invalid first step (missing workerId)`\n );\n }\n\n // Compute the first step's input:\n // - If a mapping function is configured and the registry exposes invokeMapInput,\n // use it (prevOutput is undefined for the first step).\n // - Otherwise, default to the initial input.\n let firstInput: unknown = initialInput;\n if (firstStep.mapInputFromPrev && typeof invokeMapInput === 'function') {\n firstInput = await invokeMapInput(\n queueId,\n stepIndex,\n undefined,\n initialInput\n );\n }\n\n const jobId =\n options.jobId ||\n `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n\n const queueContext: WorkerQueueContext<InitialInput> = {\n id: queueId,\n stepIndex,\n initialInput,\n queueJobId: jobId,\n };\n\n // Create queue job record if callback provided (for progress tracking)\n if (options.onCreateQueueJob) {\n try {\n await options.onCreateQueueJob({\n queueJobId: jobId,\n queueId,\n firstStep: { workerId: firstWorkerId, workerJobId: jobId },\n metadata: options.metadata as Record<string, unknown> | undefined,\n });\n } catch (err: any) {\n console.warn('[dispatchQueue] onCreateQueueJob failed:', err?.message ?? err);\n }\n }\n\n // Embed queue context into the worker input under a reserved key.\n const normalizedFirstInput =\n firstInput !== null && typeof firstInput === 'object'\n ? (firstInput as Record<string, any>)\n : { value: firstInput };\n\n const inputWithQueue = {\n ...normalizedFirstInput,\n __workerQueue: queueContext,\n };\n\n const metadataWithQueue = {\n ...(options.metadata || {}),\n __workerQueue: queueContext,\n };\n\n const triggerUrl = getWorkersTriggerUrl();\n const serializedContext = ctx ? serializeContext(ctx) : {};\n\n const messageBody = {\n workerId: firstWorkerId,\n jobId,\n input: inputWithQueue,\n context: serializedContext,\n webhookUrl: options.webhookUrl,\n metadata: metadataWithQueue,\n timestamp: new Date().toISOString(),\n };\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n const triggerKey = process.env.WORKERS_TRIGGER_API_KEY;\n if (triggerKey) {\n headers['x-workers-trigger-key'] = triggerKey;\n }\n\n const response = await fetch(triggerUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n workerId: firstWorkerId,\n body: messageBody,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(\n `Failed to trigger queue \"${queueId}\" (worker \"${firstWorkerId}\"): ` +\n `${response.status} ${response.statusText}${text ? ` - ${text}` : ''}`\n );\n }\n\n const data = (await response.json().catch(() => ({}))) as any;\n const messageId = data?.messageId ? String(data.messageId) : `trigger-${jobId}`;\n\n return {\n queueId,\n messageId,\n status: 'queued',\n jobId,\n };\n}\n\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { ZodType, z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Queue definition and context types for worker queues.
|
|
5
|
+
*
|
|
6
|
+
* These types are used at code-time by .queue.ts files and at runtime
|
|
7
|
+
* by the client and generated registry/queue wrappers.
|
|
8
|
+
*/
|
|
9
|
+
interface WorkerQueueStep {
|
|
10
|
+
/** Worker ID for this step. Must match an existing worker id. */
|
|
11
|
+
workerId: string;
|
|
12
|
+
/**
|
|
13
|
+
* Optional delay (in seconds) before this step is executed.
|
|
14
|
+
* Implemented via SQS DelaySeconds (0–900).
|
|
15
|
+
*/
|
|
16
|
+
delaySeconds?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Optional name of a mapping function exported from the .queue.ts file
|
|
19
|
+
* that derives this step's input from the previous step's output and
|
|
20
|
+
* the original initial input.
|
|
21
|
+
*/
|
|
22
|
+
mapInputFromPrev?: string;
|
|
23
|
+
}
|
|
24
|
+
interface WorkerQueueConfig<InitialInput = any, StepOutput = any> {
|
|
25
|
+
/** Stable queue identifier, e.g. "cost-usage". */
|
|
26
|
+
id: string;
|
|
27
|
+
/** Ordered list of workers forming the queue. */
|
|
28
|
+
steps: WorkerQueueStep[];
|
|
29
|
+
/**
|
|
30
|
+
* Optional schedule for the queue (cron or rate).
|
|
31
|
+
* When set, the CLI generates a queue-starter Lambda triggered by this schedule.
|
|
32
|
+
* Example: 'cron(0 3 * * ? *)' for daily at 03:00 UTC.
|
|
33
|
+
*/
|
|
34
|
+
schedule?: string | {
|
|
35
|
+
rate: string;
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
input?: Record<string, any>;
|
|
38
|
+
};
|
|
39
|
+
_initialInputType?: InitialInput;
|
|
40
|
+
_stepOutputType?: StepOutput;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Queue execution context that is embedded into job input/metadata so
|
|
44
|
+
* queue-aware wrappers can determine where they are in the queue.
|
|
45
|
+
*/
|
|
46
|
+
interface WorkerQueueContext<InitialInput = any> {
|
|
47
|
+
id: string;
|
|
48
|
+
stepIndex: number;
|
|
49
|
+
initialInput: InitialInput;
|
|
50
|
+
/** Queue job ID (same as first worker's jobId) for tracking progress. */
|
|
51
|
+
queueJobId?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Identity helper for defining worker queues in .queue.ts files.
|
|
55
|
+
* This is primarily for type safety and CLI discovery.
|
|
56
|
+
*/
|
|
57
|
+
declare function defineWorkerQueue<T extends WorkerQueueConfig>(config: T): T;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Client for dispatching background worker jobs.
|
|
61
|
+
*
|
|
62
|
+
* In production, dispatching happens via the workers HTTP API:
|
|
63
|
+
* POST /workers/trigger -> enqueues message to SQS on the workers service side
|
|
64
|
+
*
|
|
65
|
+
* This avoids requiring AWS credentials in your Next.js app.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
interface WorkerQueueRegistry {
|
|
69
|
+
getQueueById(queueId: string): WorkerQueueConfig | undefined;
|
|
70
|
+
invokeMapInput?: (queueId: string, stepIndex: number, prevOutput: unknown, initialInput: unknown) => Promise<unknown> | unknown;
|
|
71
|
+
}
|
|
72
|
+
interface DispatchOptions {
|
|
73
|
+
/**
|
|
74
|
+
* Optional webhook callback URL to notify when the job finishes.
|
|
75
|
+
* Only called when provided. Default: no webhook (use job store / MongoDB only).
|
|
76
|
+
*/
|
|
77
|
+
webhookUrl?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Controls how dispatch executes.
|
|
80
|
+
* - "auto" (default): local inline execution in development unless WORKERS_LOCAL_MODE=false.
|
|
81
|
+
* - "local": force inline execution (no SQS).
|
|
82
|
+
* - "remote": force SQS/Lambda dispatch even in development.
|
|
83
|
+
*/
|
|
84
|
+
mode?: 'auto' | 'local' | 'remote';
|
|
85
|
+
jobId?: string;
|
|
86
|
+
metadata?: Record<string, any>;
|
|
87
|
+
/**
|
|
88
|
+
* In-memory queue registry for dispatchQueue. Required when using dispatchQueue.
|
|
89
|
+
* Pass a registry that imports from your .queue.ts definitions (works on Vercel/serverless).
|
|
90
|
+
*/
|
|
91
|
+
registry?: WorkerQueueRegistry;
|
|
92
|
+
/**
|
|
93
|
+
* Optional callback to create a queue job record before dispatching.
|
|
94
|
+
* Called with queueJobId (= first worker's jobId), queueId, and firstStep.
|
|
95
|
+
*/
|
|
96
|
+
onCreateQueueJob?: (params: {
|
|
97
|
+
queueJobId: string;
|
|
98
|
+
queueId: string;
|
|
99
|
+
firstStep: {
|
|
100
|
+
workerId: string;
|
|
101
|
+
workerJobId: string;
|
|
102
|
+
};
|
|
103
|
+
metadata?: Record<string, unknown>;
|
|
104
|
+
}) => Promise<void>;
|
|
105
|
+
}
|
|
106
|
+
interface DispatchResult {
|
|
107
|
+
messageId: string;
|
|
108
|
+
status: 'queued';
|
|
109
|
+
jobId: string;
|
|
110
|
+
}
|
|
111
|
+
interface DispatchQueueResult extends DispatchResult {
|
|
112
|
+
queueId: string;
|
|
113
|
+
}
|
|
114
|
+
interface SerializedContext {
|
|
115
|
+
requestId?: string;
|
|
116
|
+
userId?: string;
|
|
117
|
+
traceId?: string;
|
|
118
|
+
[key: string]: any;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Derives the full /workers/trigger URL from env.
|
|
122
|
+
* Exported for use by local dispatchWorker (worker-to-worker in dev).
|
|
123
|
+
*
|
|
124
|
+
* Preferred env vars:
|
|
125
|
+
* - WORKER_BASE_URL: base URL of the workers service (e.g. https://.../prod)
|
|
126
|
+
* - NEXT_PUBLIC_WORKER_BASE_URL: same, but exposed to the browser
|
|
127
|
+
*
|
|
128
|
+
* Legacy env vars (still supported for backwards compatibility):
|
|
129
|
+
* - WORKERS_TRIGGER_API_URL / NEXT_PUBLIC_WORKERS_TRIGGER_API_URL
|
|
130
|
+
* - WORKERS_CONFIG_API_URL / NEXT_PUBLIC_WORKERS_CONFIG_API_URL
|
|
131
|
+
*/
|
|
132
|
+
declare function getWorkersTriggerUrl(): string;
|
|
133
|
+
/**
|
|
134
|
+
* Dispatches a background worker job to SQS.
|
|
135
|
+
*
|
|
136
|
+
* @param workerId - The ID of the worker to dispatch
|
|
137
|
+
* @param input - The input data for the worker (will be validated against inputSchema)
|
|
138
|
+
* @param inputSchema - Zod schema for input validation
|
|
139
|
+
* @param options - Dispatch options including webhook URL
|
|
140
|
+
* @param ctx - Optional context object (only serializable parts will be sent)
|
|
141
|
+
* @returns Promise resolving to dispatch result with messageId and jobId
|
|
142
|
+
*/
|
|
143
|
+
declare function dispatch<INPUT_SCHEMA extends ZodType<any>>(workerId: string, input: z.input<INPUT_SCHEMA>, inputSchema: INPUT_SCHEMA, options: DispatchOptions, ctx?: any): Promise<DispatchResult>;
|
|
144
|
+
/**
|
|
145
|
+
* Local development mode: runs the handler immediately in the same process.
|
|
146
|
+
* This bypasses SQS and Lambda for faster iteration during development.
|
|
147
|
+
*
|
|
148
|
+
* @param handler - The worker handler function
|
|
149
|
+
* @param input - The input data
|
|
150
|
+
* @param ctx - The context object
|
|
151
|
+
* @returns The handler result
|
|
152
|
+
*/
|
|
153
|
+
declare function dispatchLocal<INPUT, OUTPUT>(handler: (params: {
|
|
154
|
+
input: INPUT;
|
|
155
|
+
ctx: any;
|
|
156
|
+
}) => Promise<OUTPUT>, input: INPUT, ctx?: any): Promise<OUTPUT>;
|
|
157
|
+
/**
|
|
158
|
+
* Dispatches a queue by ID, using a generated registry of .queue.ts
|
|
159
|
+
* definitions to determine the first worker and initial input mapping.
|
|
160
|
+
*
|
|
161
|
+
* This API intentionally mirrors the ergonomics of dispatching a single
|
|
162
|
+
* worker, but under the hood it embeds queue context into the job input
|
|
163
|
+
* so queue-aware wrappers can chain subsequent steps.
|
|
164
|
+
*/
|
|
165
|
+
declare function dispatchQueue<InitialInput = any>(queueId: string, initialInput: InitialInput, options?: DispatchOptions, ctx?: any): Promise<DispatchQueueResult>;
|
|
166
|
+
|
|
167
|
+
export { type DispatchOptions as D, type SerializedContext as S, type WorkerQueueRegistry as W, type DispatchResult as a, type DispatchQueueResult as b, dispatchLocal as c, dispatch as d, dispatchQueue as e, type WorkerQueueStep as f, getWorkersTriggerUrl as g, type WorkerQueueConfig as h, type WorkerQueueContext as i, defineWorkerQueue as j };
|