{"version":3,"sources":["../src/runtime/threadWorkerEntry.ts","../src/runtime/clientProxy.ts"],"sourcesContent":["/**\n * Worker thread entry point for the shared ThreadPool.\n *\n * Each thread in the pool runs this script. It:\n * 1. Waits for job messages on parentPort\n * 2. Dynamically imports and caches the handler module per handlerModule path\n * 3. Creates a client proxy + job proxy per job\n * 4. Calls the handler, sends back the result\n *\n * Threads are generic — the handler module is specified per-job, not per-thread.\n * Handlers are cached by module path so each import happens only once per thread.\n */\nimport { parentPort } from 'node:worker_threads';\nimport type { JobResult as ApiJobResult } from '../gen/types.gen';\nimport { createClientProxy } from './clientProxy.ts';\n\n// Inline the JobActionReceipt constant to avoid importing the full SDK dependency chain\n// (jobWorker.ts → ../gen/CamundaClient → entire SDK) in the worker thread.\nconst JobActionReceipt = 'JOB_ACTION_RECEIPT' as const;\n\nif (!parentPort) {\n  throw new Error('threadWorkerEntry must run inside a worker_threads Worker');\n}\n\ninterface JobMessage {\n  type: 'job';\n  /** Unique ID for this job dispatch (correlates response) */\n  taskId: string;\n  /** Serialized job data (plain object, no methods) */\n  jobData: Record<string, unknown>;\n  /** Handler module path — resolved per job, cached per thread */\n  handlerModule: string;\n}\n\ninterface JobResult {\n  type: 'job-result';\n  taskId: string;\n  ok: boolean;\n  error?: string;\n  /** When the handler acknowledged the job via complete/fail/error,\n   *  the action is captured here for the main thread to execute. */\n  completionAction?: {\n    method: string;\n    args: unknown[];\n  };\n}\n\n/** Cache of loaded handlers keyed by module path. Each thread loads a handler once per unique path. */\nconst handlerCache = new Map<string, (job: any, client: any) => Promise<any>>();\n\nasync function loadHandler(\n  handlerModule: string\n): Promise<(job: any, client: any) => Promise<any>> {\n  const cached = handlerCache.get(handlerModule);\n  if (cached) return cached;\n\n  // Convert filesystem paths to file:// URLs for dynamic import().\n  // Handles relative (./ ../) paths, Unix absolute (/), and Windows absolute (C:\\).\n  const isPath =\n    handlerModule.startsWith('.') ||\n    handlerModule.startsWith('/') ||\n    /^[a-zA-Z]:[\\\\/]/.test(handlerModule);\n  const modulePath = isPath\n    ? new URL(handlerModule, `file://${process.cwd()}/`).href\n    : handlerModule;\n  const mod = await import(modulePath);\n  const handler = mod.default ?? mod.handler;\n  if (typeof handler !== 'function') {\n    throw new Error(\n      `Threaded handler module '${handlerModule}' must export a default function or named 'handler' export`\n    );\n  }\n  handlerCache.set(handlerModule, handler);\n  return handler;\n}\n\nparentPort.on('message', async (msg: JobMessage & { clientPort?: any }) => {\n  if (msg.type !== 'job') return;\n\n  const { taskId, jobData, handlerModule, clientPort } = msg as JobMessage & {\n    clientPort: import('node:worker_threads').MessagePort;\n  };\n\n  try {\n    const handlerFn = await loadHandler(handlerModule);\n\n    // Create a client proxy for this job's MessagePort\n    const clientProxy = createClientProxy(clientPort);\n\n    // Build a job-like object with proxied action methods\n    const job = createJobProxy(jobData, clientProxy);\n\n    await handlerFn(job, clientProxy);\n\n    // If the handler never acknowledged the job (no complete/fail/error/ignore),\n    // treat it as an error so the broker can retry rather than silently dropping it.\n    if (!job.acknowledged) {\n      const result: JobResult = {\n        type: 'job-result',\n        taskId,\n        ok: false,\n        error:\n          `Handler for '${handlerModule}' returned without acknowledging the job. ` +\n          'Call job.complete(), job.fail(), job.error(), or job.ignore().',\n      };\n      parentPort!.postMessage(result);\n      return;\n    }\n\n    const result: JobResult = {\n      type: 'job-result',\n      taskId,\n      ok: true,\n      completionAction: job._completionAction || undefined,\n    };\n    parentPort!.postMessage(result);\n  } catch (err: any) {\n    const result: JobResult = {\n      type: 'job-result',\n      taskId,\n      ok: false,\n      error: err?.message || String(err),\n    };\n    parentPort!.postMessage(result);\n  } finally {\n    // Close this job's client port\n    if (clientPort && typeof clientPort.close === 'function') {\n      clientPort.close();\n    }\n  }\n});\n\n/**\n * Build a job object with action methods that proxy through the client.\n * The job data is plain serialized data from the main thread.\n * Action methods (complete, fail, error, etc.) call back to the main thread client.\n */\nfunction createJobProxy(jobData: Record<string, unknown>, client: any): any {\n  const acknowledged = { value: false };\n  const ack = () => {\n    acknowledged.value = true;\n    job.acknowledged = true;\n  };\n\n  const job: any = { ...jobData };\n\n  /**\n   * Completion actions (complete/fail/error/cancelWorkflow) are stored as intent\n   * rather than proxied through the MessagePort. The thread returns immediately,\n   * and the main thread executes the API call asynchronously. This keeps threads\n   * free for CPU work instead of blocking on I/O round-trips.\n   */\n\n  job.complete = async (variables: Record<string, unknown> = {}, result?: ApiJobResult) => {\n    ack();\n    job._completionAction = {\n      method: 'completeJob',\n      args: [{ variables, jobKey: jobData.jobKey, ...(result !== undefined && { result }) }],\n    };\n    return JobActionReceipt;\n  };\n\n  job.fail = async (reason: any) => {\n    ack();\n    job._completionAction = {\n      method: 'failJob',\n      args: [{ ...reason, jobKey: jobData.jobKey }],\n    };\n    return JobActionReceipt;\n  };\n\n  job.error = async (error: any) => {\n    ack();\n    job._completionAction = {\n      method: 'throwJobError',\n      args: [{ ...error, jobKey: jobData.jobKey }],\n    };\n    return JobActionReceipt;\n  };\n\n  job.cancelWorkflow = async () => {\n    ack();\n    job._completionAction = {\n      method: 'cancelProcessInstance',\n      args: [{ processInstanceKey: jobData.processInstanceKey }],\n    };\n    return JobActionReceipt;\n  };\n\n  job.ignore = async () => {\n    ack();\n    return JobActionReceipt;\n  };\n\n  // Non-completion actions still proxy through the client (rare, need response)\n  job.modifyJobTimeout = ({ newTimeoutMs }: { newTimeoutMs: number }) =>\n    client.updateJob({ changeset: { timeout: newTimeoutMs }, jobKey: jobData.jobKey });\n\n  job.modifyRetries = ({ retries }: { retries: number }) =>\n    client.updateJob({ changeset: { retries }, jobKey: jobData.jobKey });\n\n  return job;\n}\n\n// Signal ready\nparentPort.postMessage({ type: 'ready' });\n","/**\n * Client proxy for worker_threads: allows threaded job handlers to call\n * CamundaClient methods transparently via MessagePort.\n *\n * Main thread side: installClientCallHandler(port, client)\n * Worker thread side: createClientProxy(port) → CamundaClient-shaped proxy\n */\n\nimport type { MessagePort } from 'node:worker_threads';\nimport type { CamundaClient } from '../gen/CamundaClient';\n\n// Wire protocol\nexport interface ClientCallMessage {\n  type: 'client-call';\n  callId: string;\n  method: string;\n  args: unknown[];\n}\n\nexport interface ClientCallResult {\n  type: 'client-call-result';\n  callId: string;\n  result?: unknown;\n  error?: string;\n}\n\n/** Fallback UUID generator for runtimes where globalThis.crypto.randomUUID is unavailable. */\nlet _counter = 0;\nfunction fallbackUUID(): string {\n  return `${Date.now().toString(36)}-${(++_counter).toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/**\n * Create a Proxy that looks like CamundaClient but forwards every method call\n * over a MessagePort to the main thread. Runs inside a worker thread.\n */\nexport function createClientProxy(port: MessagePort): CamundaClient {\n  return new Proxy({} as CamundaClient, {\n    get(_, method: string) {\n      // Ignore symbol properties and common non-method accesses\n      if (typeof method === 'symbol') return undefined;\n      if (method === 'then') return undefined; // Prevent Proxy from being treated as thenable\n      if (method === 'toJSON') return undefined;\n\n      return (...args: unknown[]) => {\n        const callId = globalThis.crypto?.randomUUID\n          ? globalThis.crypto.randomUUID()\n          : fallbackUUID();\n        const msg: ClientCallMessage = { type: 'client-call', callId, method, args };\n        port.postMessage(msg);\n        return new Promise<unknown>((resolve, reject) => {\n          const handler = (reply: ClientCallResult) => {\n            if (reply.type !== 'client-call-result' || reply.callId !== callId) return;\n            port.off('message', handler);\n            if (reply.error !== undefined) {\n              reject(new Error(reply.error));\n            } else {\n              resolve(reply.result);\n            }\n          };\n          port.on('message', handler);\n        });\n      };\n    },\n  });\n}\n\n/**\n * Install a message handler on a MessagePort (main thread side) that\n * receives client-call messages from a worker thread and executes them\n * on the real CamundaClient instance.\n */\nexport function installClientCallHandler(port: MessagePort, client: CamundaClient): void {\n  port.on('message', async (msg: ClientCallMessage) => {\n    if (msg.type !== 'client-call') return;\n    try {\n      const fn = (client as any)[msg.method];\n      if (typeof fn !== 'function') {\n        throw new Error(`CamundaClient has no method '${msg.method}'`);\n      }\n      const result = await fn.apply(client, msg.args);\n      const reply: ClientCallResult = { type: 'client-call-result', callId: msg.callId, result };\n      port.postMessage(reply);\n    } catch (err: any) {\n      const reply: ClientCallResult = {\n        type: 'client-call-result',\n        callId: msg.callId,\n        error: err?.message || String(err),\n      };\n      port.postMessage(reply);\n    }\n  });\n}\n"],"mappings":"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","names":["result"]}