@sonamu-kit/tasks 0.1.3 → 0.2.0

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.
Files changed (46) hide show
  1. package/dist/backend.d.ts +4 -0
  2. package/dist/backend.d.ts.map +1 -1
  3. package/dist/backend.js.map +1 -1
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +3 -1
  6. package/dist/client.js.map +1 -1
  7. package/dist/core/retry.d.ts +35 -19
  8. package/dist/core/retry.d.ts.map +1 -1
  9. package/dist/core/retry.js +50 -14
  10. package/dist/core/retry.js.map +1 -1
  11. package/dist/core/retry.test.js +172 -11
  12. package/dist/core/retry.test.js.map +1 -1
  13. package/dist/database/backend.d.ts.map +1 -1
  14. package/dist/database/backend.js +42 -10
  15. package/dist/database/backend.js.map +1 -1
  16. package/dist/database/backend.testsuite.d.ts.map +1 -1
  17. package/dist/database/backend.testsuite.js +106 -0
  18. package/dist/database/backend.testsuite.js.map +1 -1
  19. package/dist/execution.d.ts +2 -0
  20. package/dist/execution.d.ts.map +1 -1
  21. package/dist/execution.js +17 -3
  22. package/dist/execution.js.map +1 -1
  23. package/dist/execution.test.js +104 -0
  24. package/dist/execution.test.js.map +1 -1
  25. package/dist/internal.d.ts +2 -1
  26. package/dist/internal.d.ts.map +1 -1
  27. package/dist/internal.js +1 -1
  28. package/dist/internal.js.map +1 -1
  29. package/dist/worker.d.ts.map +1 -1
  30. package/dist/worker.js +2 -1
  31. package/dist/worker.js.map +1 -1
  32. package/dist/workflow.d.ts +3 -0
  33. package/dist/workflow.d.ts.map +1 -1
  34. package/dist/workflow.js.map +1 -1
  35. package/package.json +3 -3
  36. package/src/backend.ts +4 -0
  37. package/src/client.ts +2 -0
  38. package/src/core/retry.test.ts +180 -11
  39. package/src/core/retry.ts +95 -19
  40. package/src/database/backend.testsuite.ts +119 -0
  41. package/src/database/backend.ts +65 -11
  42. package/src/execution.test.ts +115 -0
  43. package/src/execution.ts +18 -2
  44. package/src/internal.ts +21 -1
  45. package/src/worker.ts +1 -0
  46. package/src/workflow.ts +3 -0
@@ -2,7 +2,7 @@ import { getLogger } from "@logtape/logtape";
2
2
  import { camelize } from "inflection";
3
3
  import knex from "knex";
4
4
  import { DEFAULT_NAMESPACE_ID } from "../backend.js";
5
- import { DEFAULT_RETRY_POLICY } from "../core/retry.js";
5
+ import { mergeRetryPolicy } from "../core/retry.js";
6
6
  import { DEFAULT_SCHEMA, migrate } from "./base.js";
7
7
  import { PostgresPubSub } from "./pubsub.js";
8
8
  export const DEFAULT_LISTEN_CHANNEL = "new_tasks";
@@ -116,6 +116,11 @@ const queryLogger = getLogger([
116
116
  workflowName: params.workflowName,
117
117
  version: params.version
118
118
  });
119
+ // config에 retryPolicy를 포함시킵니다.
120
+ const configWithRetryPolicy = {
121
+ ...typeof params.config === "object" && params.config !== null ? params.config : {},
122
+ retryPolicy: params.retryPolicy ?? undefined
123
+ };
119
124
  const qb = this.knex.withSchema(DEFAULT_SCHEMA).table("workflow_runs").insert({
120
125
  namespace_id: this.namespaceId,
121
126
  id: crypto.randomUUID(),
@@ -123,7 +128,7 @@ const queryLogger = getLogger([
123
128
  version: params.version,
124
129
  status: "pending",
125
130
  idempotency_key: params.idempotencyKey,
126
- config: params.config,
131
+ config: JSON.stringify(configWithRetryPolicy),
127
132
  context: params.context,
128
133
  input: params.input,
129
134
  attempts: 0,
@@ -300,21 +305,48 @@ const queryLogger = getLogger([
300
305
  if (!this.initialized) {
301
306
  throw new Error("Backend not initialized");
302
307
  }
303
- const { workflowRunId, error } = params;
304
- const { initialIntervalMs, backoffCoefficient, maximumIntervalMs } = DEFAULT_RETRY_POLICY;
308
+ const { workflowRunId, error, forceComplete, customDelayMs } = params;
309
+ logger.info("Failing workflow run: {workflowRunId}, {workerId}, {error}", {
310
+ workflowRunId: params.workflowRunId,
311
+ workerId: params.workerId,
312
+ error: params.error
313
+ });
314
+ const workflowRun = await this.knex.withSchema(DEFAULT_SCHEMA).table("workflow_runs").where("namespace_id", this.namespaceId).where("id", workflowRunId).first();
315
+ if (!workflowRun) {
316
+ throw new Error("Workflow run not found");
317
+ }
318
+ const config = typeof workflowRun.config === "string" ? JSON.parse(workflowRun.config) : workflowRun.config;
319
+ const savedRetryPolicy = config?.retryPolicy;
320
+ const retryPolicy = mergeRetryPolicy(savedRetryPolicy);
321
+ const { initialIntervalMs, backoffCoefficient, maximumIntervalMs, maxAttempts } = retryPolicy;
322
+ const currentAttempts = workflowRun.attempts ?? 0;
323
+ const shouldForceComplete = forceComplete || currentAttempts >= maxAttempts;
324
+ if (shouldForceComplete) {
325
+ const [updated] = await this.knex.withSchema(DEFAULT_SCHEMA).table("workflow_runs").where("namespace_id", this.namespaceId).where("id", workflowRunId).where("status", "running").where("worker_id", params.workerId).update({
326
+ status: "failed",
327
+ available_at: null,
328
+ finished_at: this.knex.fn.now(),
329
+ error: JSON.stringify(error),
330
+ worker_id: null,
331
+ started_at: null,
332
+ updated_at: this.knex.fn.now()
333
+ }).returning("*");
334
+ if (!updated) {
335
+ logger.error("Failed to mark workflow run failed: {params}", {
336
+ params
337
+ });
338
+ throw new Error("Failed to mark workflow run failed");
339
+ }
340
+ return updated;
341
+ }
305
342
  // this beefy query updates a workflow's status, available_at, and
306
343
  // finished_at based on the workflow's deadline and retry policy
307
344
  //
308
345
  // if the next retry would exceed the deadline, the run is marked as
309
346
  // 'failed' and finalized, otherwise, the run is rescheduled with an updated
310
347
  // 'available_at' timestamp for the next retry
311
- const retryIntervalExpr = `LEAST(${initialIntervalMs} * POWER(${backoffCoefficient}, "attempts" - 1), ${maximumIntervalMs}) * INTERVAL '1 millisecond'`;
348
+ const retryIntervalExpr = customDelayMs ? `${customDelayMs} * INTERVAL '1 millisecond'` : `LEAST(${initialIntervalMs} * POWER(${backoffCoefficient}, "attempts" - 1), ${maximumIntervalMs}) * INTERVAL '1 millisecond'`;
312
349
  const deadlineExceededCondition = `"deadline_at" IS NOT NULL AND NOW() + (${retryIntervalExpr}) >= "deadline_at"`;
313
- logger.info("Failing workflow run: {workflowRunId}, {workerId}, {error}", {
314
- workflowRunId: params.workflowRunId,
315
- workerId: params.workerId,
316
- error: params.error
317
- });
318
350
  const [updated] = await this.knex.withSchema(DEFAULT_SCHEMA).table("workflow_runs").where("namespace_id", this.namespaceId).where("id", workflowRunId).where("status", "running").where("worker_id", params.workerId).update({
319
351
  status: this.knex.raw(`CASE WHEN ${deadlineExceededCondition} THEN 'failed' ELSE 'pending' END`),
320
352
  available_at: this.knex.raw(`CASE WHEN ${deadlineExceededCondition} THEN NULL ELSE NOW() + (${retryIntervalExpr}) END`),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/database/backend.ts"],"sourcesContent":["import { getLogger } from \"@logtape/logtape\";\nimport { camelize } from \"inflection\";\nimport knex, { type Knex } from \"knex\";\nimport {\n type Backend,\n type CancelWorkflowRunParams,\n type ClaimWorkflowRunParams,\n type CompleteStepAttemptParams,\n type CompleteWorkflowRunParams,\n type CreateStepAttemptParams,\n type CreateWorkflowRunParams,\n DEFAULT_NAMESPACE_ID,\n type ExtendWorkflowRunLeaseParams,\n type FailStepAttemptParams,\n type FailWorkflowRunParams,\n type GetStepAttemptParams,\n type GetWorkflowRunParams,\n type ListStepAttemptsParams,\n type ListWorkflowRunsParams,\n type PaginatedResponse,\n type SleepWorkflowRunParams,\n} from \"../backend\";\nimport { DEFAULT_RETRY_POLICY } from \"../core/retry\";\nimport type { StepAttempt } from \"../core/step\";\nimport type { WorkflowRun } from \"../core/workflow\";\nimport { DEFAULT_SCHEMA, migrate } from \"./base\";\nimport { type OnSubscribed, PostgresPubSub } from \"./pubsub\";\n\nexport const DEFAULT_LISTEN_CHANNEL = \"new_tasks\" as const;\nconst DEFAULT_PAGINATION_PAGE_SIZE = 100 as const;\n\ninterface BackendPostgresOptions {\n namespaceId?: string;\n runMigrations?: boolean;\n\n // default: true\n usePubSub?: boolean;\n}\n\nconst logger = getLogger([\"sonamu\", \"internal\", \"tasks\"]);\nconst queryLogger = getLogger([\"sonamu\", \"internal\", \"tasks\", \"query\"]);\n\n/**\n * Manages a connection to a Postgres database for workflow operations.\n */\nexport class BackendPostgres implements Backend {\n private config: Knex.Config;\n private namespaceId: string;\n private usePubSub: boolean;\n private pubsub: PostgresPubSub | null = null;\n private initialized: boolean = false;\n private runMigrations: boolean;\n\n private _knex: Knex | null = null;\n private get knex(): Knex {\n if (!this._knex) {\n this._knex = knex(this.config);\n this._knex.on(\"query\", (query) => {\n queryLogger.debug(\"SQL: {query}, Values: {bindings}\", {\n query: query.sql,\n bindings: query.bindings,\n });\n });\n }\n\n return this._knex;\n }\n\n constructor(config: Knex.Config, options?: BackendPostgresOptions) {\n this.config = {\n ...config,\n postProcessResponse: (result, _queryContext) => {\n if (result === null || result === undefined) {\n return result;\n }\n\n if (config?.postProcessResponse) {\n result = config.postProcessResponse(result, _queryContext);\n }\n\n const camelizeRow = (row: Record<string, unknown>) =>\n Object.fromEntries(\n Object.entries(row).map(([key, value]) => [camelize(key, true), value]),\n );\n\n if (Array.isArray(result)) {\n return result.map(camelizeRow);\n }\n\n return camelizeRow(result);\n },\n };\n\n const { namespaceId, usePubSub, runMigrations } = {\n namespaceId: DEFAULT_NAMESPACE_ID,\n usePubSub: true,\n runMigrations: true,\n ...options,\n };\n\n this.namespaceId = namespaceId;\n this.usePubSub = usePubSub;\n this.runMigrations = runMigrations;\n }\n\n async initialize() {\n if (this.initialized) {\n return;\n }\n\n if (this.runMigrations) {\n await migrate(this.config, DEFAULT_SCHEMA);\n }\n\n this.initialized = true;\n }\n\n async subscribe(callback: OnSubscribed) {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n if (!this.usePubSub) {\n return;\n }\n\n if (!this.pubsub) {\n this.pubsub = await PostgresPubSub.create(this.knex);\n }\n\n this.pubsub.listenEvent(DEFAULT_LISTEN_CHANNEL, callback);\n }\n\n async publish(payload?: string): Promise<void> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n if (!this.usePubSub) {\n return;\n }\n\n await this.knex.raw(\n payload\n ? `NOTIFY ${DEFAULT_LISTEN_CHANNEL}, '${payload}'`\n : `NOTIFY ${DEFAULT_LISTEN_CHANNEL}`,\n );\n }\n\n async stop(): Promise<void> {\n if (!this.initialized) {\n return;\n }\n\n await this.pubsub?.destroy();\n this.pubsub = null;\n await this.knex.destroy();\n }\n\n async createWorkflowRun(params: CreateWorkflowRunParams): Promise<WorkflowRun> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Creating workflow run: {workflowName}:{version}\", {\n workflowName: params.workflowName,\n version: params.version,\n });\n\n const qb = this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .insert({\n namespace_id: this.namespaceId,\n id: crypto.randomUUID(),\n workflow_name: params.workflowName,\n version: params.version,\n status: \"pending\",\n idempotency_key: params.idempotencyKey,\n config: params.config,\n context: params.context,\n input: params.input,\n attempts: 0,\n available_at: params.availableAt ?? this.knex.fn.now(),\n deadline_at: params.deadlineAt,\n created_at: this.knex.fn.now(),\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n const workflowRun = await qb;\n if (!workflowRun[0]) {\n logger.error(\"Failed to create workflow run: {params}\", { params });\n throw new Error(\"Failed to create workflow run\");\n }\n\n return workflowRun[0];\n }\n\n async getWorkflowRun(params: GetWorkflowRunParams): Promise<WorkflowRun | null> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Getting workflow run: {workflowRunId}\", { workflowRunId: params.workflowRunId });\n const workflowRun = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", params.workflowRunId)\n .select(\n \"namespace_id\",\n \"id\",\n \"workflow_name\",\n \"version\",\n \"status\",\n \"idempotency_key\",\n \"config\",\n \"context\",\n \"input\",\n \"output\",\n \"error\",\n \"attempts\",\n \"parent_step_attempt_namespace_id\",\n \"parent_step_attempt_id\",\n \"worker_id\",\n \"available_at\",\n \"deadline_at\",\n \"started_at\",\n \"finished_at\",\n \"created_at\",\n \"updated_at\",\n )\n .first();\n\n return workflowRun ?? null;\n }\n\n async listWorkflowRuns(params: ListWorkflowRunsParams): Promise<PaginatedResponse<WorkflowRun>> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Listing workflow runs: {after}, {before}\", {\n after: params.after,\n before: params.before,\n });\n const limit = params.limit ?? DEFAULT_PAGINATION_PAGE_SIZE;\n const { after, before } = params;\n\n let cursor: Cursor | null = null;\n if (after) {\n cursor = decodeCursor(after);\n } else if (before) {\n cursor = decodeCursor(before);\n }\n\n const qb = this.buildListWorkflowRunsWhere(params, cursor);\n const rows = await qb\n .orderBy(\"created_at\", before ? \"desc\" : \"asc\")\n .orderBy(\"id\", before ? \"desc\" : \"asc\")\n .limit(limit + 1);\n\n return this.processPaginationResults(\n rows,\n limit,\n typeof after === \"string\",\n typeof before === \"string\",\n );\n }\n\n private buildListWorkflowRunsWhere(params: ListWorkflowRunsParams, cursor: Cursor | null) {\n const { after } = params;\n const qb = this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId);\n\n if (cursor) {\n const operator = after ? \">\" : \"<\";\n return qb.whereRaw(`(\"created_at\", \"id\") ${operator} (?, ?)`, [\n cursor.createdAt.toISOString(),\n cursor.id,\n ]);\n }\n\n return qb;\n }\n\n async claimWorkflowRun(params: ClaimWorkflowRunParams): Promise<WorkflowRun | null> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Claiming workflow run: {workerId}, {leaseDurationMs}\", {\n workerId: params.workerId,\n leaseDurationMs: params.leaseDurationMs,\n });\n const claimed = await this.knex\n .with(\"expired\", (qb) =>\n qb\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .update({\n status: \"failed\",\n error: JSON.stringify({ message: \"Workflow run deadline exceeded\" }),\n worker_id: null,\n available_at: null,\n finished_at: this.knex.raw(\"NOW()\"),\n updated_at: this.knex.raw(\"NOW()\"),\n })\n .where(\"namespace_id\", this.namespaceId)\n .whereIn(\"status\", [\"pending\", \"running\", \"sleeping\"])\n .whereNotNull(\"deadline_at\")\n .where(\"deadline_at\", \"<=\", this.knex.raw(\"NOW()\"))\n .returning(\"id\"),\n )\n .with(\"candidate\", (qb) =>\n qb\n .withSchema(DEFAULT_SCHEMA)\n .select(\"id\")\n .from(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .whereIn(\"status\", [\"pending\", \"running\", \"sleeping\"])\n .where(\"available_at\", \"<=\", this.knex.raw(\"NOW()\"))\n .where((qb2) => {\n qb2.whereNull(\"deadline_at\").orWhere(\"deadline_at\", \">\", this.knex.raw(\"NOW()\"));\n })\n .orderByRaw(\"CASE WHEN status = 'pending' THEN 0 ELSE 1 END\")\n .orderBy(\"available_at\", \"asc\")\n .orderBy(\"created_at\", \"asc\")\n .limit(1)\n .forUpdate()\n .skipLocked(),\n )\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs as wr\")\n .where(\"wr.namespace_id\", this.namespaceId)\n .where(\"wr.id\", this.knex.ref(\"candidate.id\"))\n .update({\n status: \"running\",\n attempts: this.knex.raw(\"wr.attempts + 1\"),\n worker_id: params.workerId,\n available_at: this.knex.raw(`NOW() + ${params.leaseDurationMs} * INTERVAL '1 millisecond'`),\n started_at: this.knex.raw(\"COALESCE(wr.started_at, NOW())\"),\n updated_at: this.knex.raw(\"NOW()\"),\n })\n .updateFrom(\"candidate\")\n .returning(\"wr.*\");\n\n return claimed[0] ?? null;\n }\n\n async extendWorkflowRunLease(params: ExtendWorkflowRunLeaseParams): Promise<WorkflowRun> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Extending workflow run lease: {workflowRunId}, {workerId}, {leaseDurationMs}\", {\n workflowRunId: params.workflowRunId,\n workerId: params.workerId,\n leaseDurationMs: params.leaseDurationMs,\n });\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", params.workflowRunId)\n .where(\"status\", \"running\")\n .where(\"worker_id\", params.workerId)\n .update({\n available_at: this.knex.raw(`NOW() + ${params.leaseDurationMs} * INTERVAL '1 millisecond'`),\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n if (!updated) {\n logger.error(\"Failed to extend lease for workflow run: {params}\", { params });\n throw new Error(\"Failed to extend lease for workflow run\");\n }\n\n return updated;\n }\n\n async sleepWorkflowRun(params: SleepWorkflowRunParams): Promise<WorkflowRun> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Sleeping workflow run: {workflowRunId}, {workerId}, {availableAt}\", {\n workflowRunId: params.workflowRunId,\n workerId: params.workerId,\n availableAt: params.availableAt,\n });\n\n // 'succeeded' status is deprecated\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", params.workflowRunId)\n .whereNotIn(\"status\", [\"succeeded\", \"completed\", \"failed\", \"canceled\"])\n .where(\"worker_id\", params.workerId)\n .update({\n status: \"sleeping\",\n available_at: params.availableAt,\n worker_id: null,\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n if (!updated) {\n logger.error(\"Failed to sleep workflow run: {params}\", { params });\n throw new Error(\"Failed to sleep workflow run\");\n }\n\n return updated;\n }\n\n async completeWorkflowRun(params: CompleteWorkflowRunParams): Promise<WorkflowRun> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Completing workflow run: {workflowRunId}, {workerId}, {output}\", {\n workflowRunId: params.workflowRunId,\n workerId: params.workerId,\n output: params.output,\n });\n\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", params.workflowRunId)\n .where(\"status\", \"running\")\n .where(\"worker_id\", params.workerId)\n .update({\n status: \"completed\",\n output: JSON.stringify(params.output),\n error: null,\n worker_id: params.workerId,\n available_at: null,\n finished_at: this.knex.fn.now(),\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n if (!updated) {\n logger.error(\"Failed to complete workflow run: {params}\", { params });\n throw new Error(\"Failed to complete workflow run\");\n }\n\n return updated;\n }\n\n async failWorkflowRun(params: FailWorkflowRunParams): Promise<WorkflowRun> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n const { workflowRunId, error } = params;\n const { initialIntervalMs, backoffCoefficient, maximumIntervalMs } = DEFAULT_RETRY_POLICY;\n\n // this beefy query updates a workflow's status, available_at, and\n // finished_at based on the workflow's deadline and retry policy\n //\n // if the next retry would exceed the deadline, the run is marked as\n // 'failed' and finalized, otherwise, the run is rescheduled with an updated\n // 'available_at' timestamp for the next retry\n const retryIntervalExpr = `LEAST(${initialIntervalMs} * POWER(${backoffCoefficient}, \"attempts\" - 1), ${maximumIntervalMs}) * INTERVAL '1 millisecond'`;\n const deadlineExceededCondition = `\"deadline_at\" IS NOT NULL AND NOW() + (${retryIntervalExpr}) >= \"deadline_at\"`;\n\n logger.info(\"Failing workflow run: {workflowRunId}, {workerId}, {error}\", {\n workflowRunId: params.workflowRunId,\n workerId: params.workerId,\n error: params.error,\n });\n\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", workflowRunId)\n .where(\"status\", \"running\")\n .where(\"worker_id\", params.workerId)\n .update({\n status: this.knex.raw(\n `CASE WHEN ${deadlineExceededCondition} THEN 'failed' ELSE 'pending' END`,\n ),\n available_at: this.knex.raw(\n `CASE WHEN ${deadlineExceededCondition} THEN NULL ELSE NOW() + (${retryIntervalExpr}) END`,\n ),\n finished_at: this.knex.raw(\n `CASE WHEN ${deadlineExceededCondition} THEN NOW() ELSE NULL END`,\n ),\n error: JSON.stringify(error),\n worker_id: null,\n started_at: null,\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n if (!updated) {\n logger.error(\"Failed to mark workflow run failed: {params}\", { params });\n throw new Error(\"Failed to mark workflow run failed\");\n }\n\n return updated;\n }\n\n async cancelWorkflowRun(params: CancelWorkflowRunParams): Promise<WorkflowRun> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Canceling workflow run: {workflowRunId}\", { workflowRunId: params.workflowRunId });\n\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", params.workflowRunId)\n .whereIn(\"status\", [\"pending\", \"running\", \"sleeping\"])\n .update({\n status: \"canceled\",\n worker_id: null,\n available_at: null,\n finished_at: this.knex.fn.now(),\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n if (!updated) {\n // workflow may already be in a terminal state\n const existing = await this.getWorkflowRun({\n workflowRunId: params.workflowRunId,\n });\n if (!existing) {\n throw new Error(`Workflow run ${params.workflowRunId} does not exist`);\n }\n\n // if already canceled, just return it\n if (existing.status === \"canceled\") {\n return existing;\n }\n\n // throw error for completed/failed workflows\n // 'succeeded' status is deprecated\n if ([\"succeeded\", \"completed\", \"failed\"].includes(existing.status)) {\n logger.error(\"Cannot cancel workflow run: {params} with status {status}\", {\n params,\n status: existing.status,\n });\n throw new Error(\n `Cannot cancel workflow run ${params.workflowRunId} with status ${existing.status}`,\n );\n }\n\n logger.error(\"Failed to cancel workflow run: {params}\", { params });\n throw new Error(\"Failed to cancel workflow run\");\n }\n\n return updated;\n }\n\n async createStepAttempt(params: CreateStepAttemptParams): Promise<StepAttempt> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Creating step attempt: {workflowRunId}, {stepName}, {kind}\", {\n workflowRunId: params.workflowRunId,\n stepName: params.stepName,\n kind: params.kind,\n });\n\n const [stepAttempt] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"step_attempts\")\n .insert({\n namespace_id: this.namespaceId,\n id: crypto.randomUUID(),\n workflow_run_id: params.workflowRunId,\n step_name: params.stepName,\n kind: params.kind,\n status: \"running\",\n config: JSON.stringify(params.config),\n context: JSON.stringify(params.context),\n started_at: this.knex.fn.now(),\n created_at: this.knex.raw(\"date_trunc('milliseconds', NOW())\"),\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n if (!stepAttempt) {\n logger.error(\"Failed to create step attempt: {params}\", { params });\n throw new Error(\"Failed to create step attempt\");\n }\n\n return stepAttempt;\n }\n\n async getStepAttempt(params: GetStepAttemptParams): Promise<StepAttempt | null> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Getting step attempt: {stepAttemptId}\", { stepAttemptId: params.stepAttemptId });\n\n const stepAttempt = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"step_attempts\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", params.stepAttemptId)\n .first();\n\n return stepAttempt ?? null;\n }\n\n async listStepAttempts(params: ListStepAttemptsParams): Promise<PaginatedResponse<StepAttempt>> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Listing step attempts: {workflowRunId}, {after}, {before}\", {\n workflowRunId: params.workflowRunId,\n after: params.after,\n before: params.before,\n });\n\n const limit = params.limit ?? DEFAULT_PAGINATION_PAGE_SIZE;\n const { after, before } = params;\n\n let cursor: Cursor | null = null;\n if (after) {\n cursor = decodeCursor(after);\n } else if (before) {\n cursor = decodeCursor(before);\n }\n\n const qb = this.buildListStepAttemptsWhere(params, cursor);\n const rows = await qb\n .orderBy(\"created_at\", before ? \"desc\" : \"asc\")\n .orderBy(\"id\", before ? \"desc\" : \"asc\")\n .limit(limit + 1);\n\n return this.processPaginationResults(\n rows,\n limit,\n typeof after === \"string\",\n typeof before === \"string\",\n );\n }\n\n private buildListStepAttemptsWhere(params: ListStepAttemptsParams, cursor: Cursor | null) {\n const { after } = params;\n const qb = this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"step_attempts\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"workflow_run_id\", params.workflowRunId);\n\n if (cursor) {\n const operator = after ? \">\" : \"<\";\n return qb.whereRaw(`(\"created_at\", \"id\") ${operator} (?, ?)`, [\n cursor.createdAt.toISOString(),\n cursor.id,\n ]);\n }\n\n return qb;\n }\n\n private processPaginationResults<T extends Cursor>(\n rows: T[],\n limit: number,\n hasAfter: boolean,\n hasBefore: boolean,\n ): PaginatedResponse<T> {\n const data = rows;\n let hasNext = false;\n let hasPrev = false;\n\n if (hasBefore) {\n data.reverse();\n if (data.length > limit) {\n hasPrev = true;\n data.shift();\n }\n hasNext = true;\n } else {\n if (data.length > limit) {\n hasNext = true;\n data.pop();\n }\n if (hasAfter) {\n hasPrev = true;\n }\n }\n\n const lastItem = data.at(-1);\n const nextCursor = hasNext && lastItem ? encodeCursor(lastItem) : null;\n const firstItem = data[0];\n const prevCursor = hasPrev && firstItem ? encodeCursor(firstItem) : null;\n\n return {\n data,\n pagination: {\n next: nextCursor,\n prev: prevCursor,\n },\n };\n }\n\n // NOTE: 실제 서비스에서 이게 안 되는 것 같은데, 쿼리 등을 체크할 필요가 있음.\n async completeStepAttempt(params: CompleteStepAttemptParams): Promise<StepAttempt> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Marking step attempt as completed: {workflowRunId}, {stepAttemptId}, {workerId}\", {\n workflowRunId: params.workflowRunId,\n stepAttemptId: params.stepAttemptId,\n workerId: params.workerId,\n });\n\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"step_attempts as sa\")\n .update({\n status: \"completed\",\n output: JSON.stringify(params.output),\n error: null,\n finished_at: this.knex.fn.now(),\n updated_at: this.knex.fn.now(),\n })\n .updateFrom(`${DEFAULT_SCHEMA}.workflow_runs as wr`)\n .where(\"sa.namespace_id\", this.namespaceId)\n .where(\"sa.workflow_run_id\", params.workflowRunId)\n .where(\"sa.id\", params.stepAttemptId)\n .where(\"sa.status\", \"running\")\n .where(\"wr.namespace_id\", this.knex.ref(\"sa.namespace_id\"))\n .where(\"wr.id\", this.knex.ref(\"sa.workflow_run_id\"))\n .where(\"wr.status\", \"running\")\n .where(\"wr.worker_id\", params.workerId)\n .returning(\"sa.*\");\n\n if (!updated) {\n logger.error(\"Failed to mark step attempt completed: {params}\", { params });\n throw new Error(\"Failed to mark step attempt completed\");\n }\n\n return updated;\n }\n\n async failStepAttempt(params: FailStepAttemptParams): Promise<StepAttempt> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Marking step attempt as failed: {workflowRunId}, {stepAttemptId}, {workerId}\", {\n workflowRunId: params.workflowRunId,\n stepAttemptId: params.stepAttemptId,\n workerId: params.workerId,\n });\n logger.info(\"Error: {error.message}\", { error: params.error.message });\n\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"step_attempts as sa\")\n .update({\n status: \"failed\",\n output: null,\n error: JSON.stringify(params.error),\n finished_at: this.knex.fn.now(),\n updated_at: this.knex.fn.now(),\n })\n .updateFrom(`${DEFAULT_SCHEMA}.workflow_runs as wr`)\n .where(\"sa.namespace_id\", this.namespaceId)\n .where(\"sa.workflow_run_id\", params.workflowRunId)\n .where(\"sa.id\", params.stepAttemptId)\n .where(\"sa.status\", \"running\")\n .where(\"wr.namespace_id\", this.knex.ref(\"sa.namespace_id\"))\n .where(\"wr.id\", this.knex.ref(\"sa.workflow_run_id\"))\n .where(\"wr.status\", \"running\")\n .where(\"wr.worker_id\", params.workerId)\n .returning(\"sa.*\");\n\n if (!updated) {\n logger.error(\"Failed to mark step attempt failed: {params}\", { params });\n throw new Error(\"Failed to mark step attempt failed\");\n }\n\n return updated;\n }\n}\n\n/**\n * Cursor used for pagination. Requires created_at and id fields. Because JS\n * Date does not natively support microsecond precision dates, created_at should\n * be stored with millisecond precision in paginated tables to avoid issues with\n * cursor comparisons.\n */\ninterface Cursor {\n createdAt: Date;\n id: string;\n}\n\nfunction encodeCursor(item: Cursor): string {\n const encoded = Buffer.from(\n JSON.stringify({ createdAt: item.createdAt.toISOString(), id: item.id }),\n ).toString(\"base64\");\n return encoded;\n}\n\nexport function decodeCursor(cursor: string): Cursor {\n const decoded = Buffer.from(cursor, \"base64\").toString(\"utf8\");\n const parsed = JSON.parse(decoded) as { createdAt: string; id: string };\n return {\n createdAt: new Date(parsed.createdAt),\n id: parsed.id,\n };\n}\n"],"names":["getLogger","camelize","knex","DEFAULT_NAMESPACE_ID","DEFAULT_RETRY_POLICY","DEFAULT_SCHEMA","migrate","PostgresPubSub","DEFAULT_LISTEN_CHANNEL","DEFAULT_PAGINATION_PAGE_SIZE","logger","queryLogger","BackendPostgres","config","namespaceId","usePubSub","pubsub","initialized","runMigrations","_knex","on","query","debug","sql","bindings","options","postProcessResponse","result","_queryContext","undefined","camelizeRow","row","Object","fromEntries","entries","map","key","value","Array","isArray","initialize","subscribe","callback","Error","create","listenEvent","publish","payload","raw","stop","destroy","createWorkflowRun","params","info","workflowName","version","qb","withSchema","table","insert","namespace_id","id","crypto","randomUUID","workflow_name","status","idempotency_key","idempotencyKey","context","input","attempts","available_at","availableAt","fn","now","deadline_at","deadlineAt","created_at","updated_at","returning","workflowRun","error","getWorkflowRun","workflowRunId","where","select","first","listWorkflowRuns","after","before","limit","cursor","decodeCursor","buildListWorkflowRunsWhere","rows","orderBy","processPaginationResults","operator","whereRaw","createdAt","toISOString","claimWorkflowRun","workerId","leaseDurationMs","claimed","with","update","JSON","stringify","message","worker_id","finished_at","whereIn","whereNotNull","from","qb2","whereNull","orWhere","orderByRaw","forUpdate","skipLocked","ref","started_at","updateFrom","extendWorkflowRunLease","updated","sleepWorkflowRun","whereNotIn","completeWorkflowRun","output","failWorkflowRun","initialIntervalMs","backoffCoefficient","maximumIntervalMs","retryIntervalExpr","deadlineExceededCondition","cancelWorkflowRun","existing","includes","createStepAttempt","stepName","kind","stepAttempt","workflow_run_id","step_name","getStepAttempt","stepAttemptId","listStepAttempts","buildListStepAttemptsWhere","hasAfter","hasBefore","data","hasNext","hasPrev","reverse","length","shift","pop","lastItem","at","nextCursor","encodeCursor","firstItem","prevCursor","pagination","next","prev","completeStepAttempt","failStepAttempt","item","encoded","Buffer","toString","decoded","parsed","parse","Date"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/database/backend.ts"],"sourcesContent":["import { getLogger } from \"@logtape/logtape\";\nimport { camelize } from \"inflection\";\nimport knex, { type Knex } from \"knex\";\nimport {\n type Backend,\n type CancelWorkflowRunParams,\n type ClaimWorkflowRunParams,\n type CompleteStepAttemptParams,\n type CompleteWorkflowRunParams,\n type CreateStepAttemptParams,\n type CreateWorkflowRunParams,\n DEFAULT_NAMESPACE_ID,\n type ExtendWorkflowRunLeaseParams,\n type FailStepAttemptParams,\n type FailWorkflowRunParams,\n type GetStepAttemptParams,\n type GetWorkflowRunParams,\n type ListStepAttemptsParams,\n type ListWorkflowRunsParams,\n type PaginatedResponse,\n type SleepWorkflowRunParams,\n} from \"../backend\";\nimport { mergeRetryPolicy, type SerializableRetryPolicy } from \"../core/retry\";\nimport type { StepAttempt } from \"../core/step\";\nimport type { WorkflowRun } from \"../core/workflow\";\nimport { DEFAULT_SCHEMA, migrate } from \"./base\";\nimport { type OnSubscribed, PostgresPubSub } from \"./pubsub\";\n\nexport const DEFAULT_LISTEN_CHANNEL = \"new_tasks\" as const;\nconst DEFAULT_PAGINATION_PAGE_SIZE = 100 as const;\n\ninterface BackendPostgresOptions {\n namespaceId?: string;\n runMigrations?: boolean;\n\n // default: true\n usePubSub?: boolean;\n}\n\nconst logger = getLogger([\"sonamu\", \"internal\", \"tasks\"]);\nconst queryLogger = getLogger([\"sonamu\", \"internal\", \"tasks\", \"query\"]);\n\n/**\n * Manages a connection to a Postgres database for workflow operations.\n */\nexport class BackendPostgres implements Backend {\n private config: Knex.Config;\n private namespaceId: string;\n private usePubSub: boolean;\n private pubsub: PostgresPubSub | null = null;\n private initialized: boolean = false;\n private runMigrations: boolean;\n\n private _knex: Knex | null = null;\n private get knex(): Knex {\n if (!this._knex) {\n this._knex = knex(this.config);\n this._knex.on(\"query\", (query) => {\n queryLogger.debug(\"SQL: {query}, Values: {bindings}\", {\n query: query.sql,\n bindings: query.bindings,\n });\n });\n }\n\n return this._knex;\n }\n\n constructor(config: Knex.Config, options?: BackendPostgresOptions) {\n this.config = {\n ...config,\n postProcessResponse: (result, _queryContext) => {\n if (result === null || result === undefined) {\n return result;\n }\n\n if (config?.postProcessResponse) {\n result = config.postProcessResponse(result, _queryContext);\n }\n\n const camelizeRow = (row: Record<string, unknown>) =>\n Object.fromEntries(\n Object.entries(row).map(([key, value]) => [camelize(key, true), value]),\n );\n\n if (Array.isArray(result)) {\n return result.map(camelizeRow);\n }\n\n return camelizeRow(result);\n },\n };\n\n const { namespaceId, usePubSub, runMigrations } = {\n namespaceId: DEFAULT_NAMESPACE_ID,\n usePubSub: true,\n runMigrations: true,\n ...options,\n };\n\n this.namespaceId = namespaceId;\n this.usePubSub = usePubSub;\n this.runMigrations = runMigrations;\n }\n\n async initialize() {\n if (this.initialized) {\n return;\n }\n\n if (this.runMigrations) {\n await migrate(this.config, DEFAULT_SCHEMA);\n }\n\n this.initialized = true;\n }\n\n async subscribe(callback: OnSubscribed) {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n if (!this.usePubSub) {\n return;\n }\n\n if (!this.pubsub) {\n this.pubsub = await PostgresPubSub.create(this.knex);\n }\n\n this.pubsub.listenEvent(DEFAULT_LISTEN_CHANNEL, callback);\n }\n\n async publish(payload?: string): Promise<void> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n if (!this.usePubSub) {\n return;\n }\n\n await this.knex.raw(\n payload\n ? `NOTIFY ${DEFAULT_LISTEN_CHANNEL}, '${payload}'`\n : `NOTIFY ${DEFAULT_LISTEN_CHANNEL}`,\n );\n }\n\n async stop(): Promise<void> {\n if (!this.initialized) {\n return;\n }\n\n await this.pubsub?.destroy();\n this.pubsub = null;\n await this.knex.destroy();\n }\n\n async createWorkflowRun(params: CreateWorkflowRunParams): Promise<WorkflowRun> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Creating workflow run: {workflowName}:{version}\", {\n workflowName: params.workflowName,\n version: params.version,\n });\n\n // config에 retryPolicy를 포함시킵니다.\n const configWithRetryPolicy = {\n ...(typeof params.config === \"object\" && params.config !== null ? params.config : {}),\n retryPolicy: params.retryPolicy ?? undefined,\n };\n\n const qb = this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .insert({\n namespace_id: this.namespaceId,\n id: crypto.randomUUID(),\n workflow_name: params.workflowName,\n version: params.version,\n status: \"pending\",\n idempotency_key: params.idempotencyKey,\n config: JSON.stringify(configWithRetryPolicy),\n context: params.context,\n input: params.input,\n attempts: 0,\n available_at: params.availableAt ?? this.knex.fn.now(),\n deadline_at: params.deadlineAt,\n created_at: this.knex.fn.now(),\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n const workflowRun = await qb;\n if (!workflowRun[0]) {\n logger.error(\"Failed to create workflow run: {params}\", { params });\n throw new Error(\"Failed to create workflow run\");\n }\n\n return workflowRun[0];\n }\n\n async getWorkflowRun(params: GetWorkflowRunParams): Promise<WorkflowRun | null> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Getting workflow run: {workflowRunId}\", { workflowRunId: params.workflowRunId });\n const workflowRun = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", params.workflowRunId)\n .select(\n \"namespace_id\",\n \"id\",\n \"workflow_name\",\n \"version\",\n \"status\",\n \"idempotency_key\",\n \"config\",\n \"context\",\n \"input\",\n \"output\",\n \"error\",\n \"attempts\",\n \"parent_step_attempt_namespace_id\",\n \"parent_step_attempt_id\",\n \"worker_id\",\n \"available_at\",\n \"deadline_at\",\n \"started_at\",\n \"finished_at\",\n \"created_at\",\n \"updated_at\",\n )\n .first();\n\n return workflowRun ?? null;\n }\n\n async listWorkflowRuns(params: ListWorkflowRunsParams): Promise<PaginatedResponse<WorkflowRun>> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Listing workflow runs: {after}, {before}\", {\n after: params.after,\n before: params.before,\n });\n const limit = params.limit ?? DEFAULT_PAGINATION_PAGE_SIZE;\n const { after, before } = params;\n\n let cursor: Cursor | null = null;\n if (after) {\n cursor = decodeCursor(after);\n } else if (before) {\n cursor = decodeCursor(before);\n }\n\n const qb = this.buildListWorkflowRunsWhere(params, cursor);\n const rows = await qb\n .orderBy(\"created_at\", before ? \"desc\" : \"asc\")\n .orderBy(\"id\", before ? \"desc\" : \"asc\")\n .limit(limit + 1);\n\n return this.processPaginationResults(\n rows,\n limit,\n typeof after === \"string\",\n typeof before === \"string\",\n );\n }\n\n private buildListWorkflowRunsWhere(params: ListWorkflowRunsParams, cursor: Cursor | null) {\n const { after } = params;\n const qb = this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId);\n\n if (cursor) {\n const operator = after ? \">\" : \"<\";\n return qb.whereRaw(`(\"created_at\", \"id\") ${operator} (?, ?)`, [\n cursor.createdAt.toISOString(),\n cursor.id,\n ]);\n }\n\n return qb;\n }\n\n async claimWorkflowRun(params: ClaimWorkflowRunParams): Promise<WorkflowRun | null> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Claiming workflow run: {workerId}, {leaseDurationMs}\", {\n workerId: params.workerId,\n leaseDurationMs: params.leaseDurationMs,\n });\n const claimed = await this.knex\n .with(\"expired\", (qb) =>\n qb\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .update({\n status: \"failed\",\n error: JSON.stringify({ message: \"Workflow run deadline exceeded\" }),\n worker_id: null,\n available_at: null,\n finished_at: this.knex.raw(\"NOW()\"),\n updated_at: this.knex.raw(\"NOW()\"),\n })\n .where(\"namespace_id\", this.namespaceId)\n .whereIn(\"status\", [\"pending\", \"running\", \"sleeping\"])\n .whereNotNull(\"deadline_at\")\n .where(\"deadline_at\", \"<=\", this.knex.raw(\"NOW()\"))\n .returning(\"id\"),\n )\n .with(\"candidate\", (qb) =>\n qb\n .withSchema(DEFAULT_SCHEMA)\n .select(\"id\")\n .from(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .whereIn(\"status\", [\"pending\", \"running\", \"sleeping\"])\n .where(\"available_at\", \"<=\", this.knex.raw(\"NOW()\"))\n .where((qb2) => {\n qb2.whereNull(\"deadline_at\").orWhere(\"deadline_at\", \">\", this.knex.raw(\"NOW()\"));\n })\n .orderByRaw(\"CASE WHEN status = 'pending' THEN 0 ELSE 1 END\")\n .orderBy(\"available_at\", \"asc\")\n .orderBy(\"created_at\", \"asc\")\n .limit(1)\n .forUpdate()\n .skipLocked(),\n )\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs as wr\")\n .where(\"wr.namespace_id\", this.namespaceId)\n .where(\"wr.id\", this.knex.ref(\"candidate.id\"))\n .update({\n status: \"running\",\n attempts: this.knex.raw(\"wr.attempts + 1\"),\n worker_id: params.workerId,\n available_at: this.knex.raw(`NOW() + ${params.leaseDurationMs} * INTERVAL '1 millisecond'`),\n started_at: this.knex.raw(\"COALESCE(wr.started_at, NOW())\"),\n updated_at: this.knex.raw(\"NOW()\"),\n })\n .updateFrom(\"candidate\")\n .returning(\"wr.*\");\n\n return claimed[0] ?? null;\n }\n\n async extendWorkflowRunLease(params: ExtendWorkflowRunLeaseParams): Promise<WorkflowRun> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Extending workflow run lease: {workflowRunId}, {workerId}, {leaseDurationMs}\", {\n workflowRunId: params.workflowRunId,\n workerId: params.workerId,\n leaseDurationMs: params.leaseDurationMs,\n });\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", params.workflowRunId)\n .where(\"status\", \"running\")\n .where(\"worker_id\", params.workerId)\n .update({\n available_at: this.knex.raw(`NOW() + ${params.leaseDurationMs} * INTERVAL '1 millisecond'`),\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n if (!updated) {\n logger.error(\"Failed to extend lease for workflow run: {params}\", { params });\n throw new Error(\"Failed to extend lease for workflow run\");\n }\n\n return updated;\n }\n\n async sleepWorkflowRun(params: SleepWorkflowRunParams): Promise<WorkflowRun> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Sleeping workflow run: {workflowRunId}, {workerId}, {availableAt}\", {\n workflowRunId: params.workflowRunId,\n workerId: params.workerId,\n availableAt: params.availableAt,\n });\n\n // 'succeeded' status is deprecated\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", params.workflowRunId)\n .whereNotIn(\"status\", [\"succeeded\", \"completed\", \"failed\", \"canceled\"])\n .where(\"worker_id\", params.workerId)\n .update({\n status: \"sleeping\",\n available_at: params.availableAt,\n worker_id: null,\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n if (!updated) {\n logger.error(\"Failed to sleep workflow run: {params}\", { params });\n throw new Error(\"Failed to sleep workflow run\");\n }\n\n return updated;\n }\n\n async completeWorkflowRun(params: CompleteWorkflowRunParams): Promise<WorkflowRun> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Completing workflow run: {workflowRunId}, {workerId}, {output}\", {\n workflowRunId: params.workflowRunId,\n workerId: params.workerId,\n output: params.output,\n });\n\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", params.workflowRunId)\n .where(\"status\", \"running\")\n .where(\"worker_id\", params.workerId)\n .update({\n status: \"completed\",\n output: JSON.stringify(params.output),\n error: null,\n worker_id: params.workerId,\n available_at: null,\n finished_at: this.knex.fn.now(),\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n if (!updated) {\n logger.error(\"Failed to complete workflow run: {params}\", { params });\n throw new Error(\"Failed to complete workflow run\");\n }\n\n return updated;\n }\n\n async failWorkflowRun(params: FailWorkflowRunParams): Promise<WorkflowRun> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n const { workflowRunId, error, forceComplete, customDelayMs } = params;\n\n logger.info(\"Failing workflow run: {workflowRunId}, {workerId}, {error}\", {\n workflowRunId: params.workflowRunId,\n workerId: params.workerId,\n error: params.error,\n });\n\n const workflowRun = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", workflowRunId)\n .first();\n\n if (!workflowRun) {\n throw new Error(\"Workflow run not found\");\n }\n\n const config =\n typeof workflowRun.config === \"string\" ? JSON.parse(workflowRun.config) : workflowRun.config;\n const savedRetryPolicy: SerializableRetryPolicy | undefined = config?.retryPolicy;\n const retryPolicy = mergeRetryPolicy(savedRetryPolicy);\n\n const { initialIntervalMs, backoffCoefficient, maximumIntervalMs, maxAttempts } = retryPolicy;\n\n const currentAttempts = workflowRun.attempts ?? 0;\n const shouldForceComplete = forceComplete || currentAttempts >= maxAttempts;\n\n if (shouldForceComplete) {\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", workflowRunId)\n .where(\"status\", \"running\")\n .where(\"worker_id\", params.workerId)\n .update({\n status: \"failed\",\n available_at: null,\n finished_at: this.knex.fn.now(),\n error: JSON.stringify(error),\n worker_id: null,\n started_at: null,\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n if (!updated) {\n logger.error(\"Failed to mark workflow run failed: {params}\", { params });\n throw new Error(\"Failed to mark workflow run failed\");\n }\n return updated;\n }\n\n // this beefy query updates a workflow's status, available_at, and\n // finished_at based on the workflow's deadline and retry policy\n //\n // if the next retry would exceed the deadline, the run is marked as\n // 'failed' and finalized, otherwise, the run is rescheduled with an updated\n // 'available_at' timestamp for the next retry\n const retryIntervalExpr = customDelayMs\n ? `${customDelayMs} * INTERVAL '1 millisecond'`\n : `LEAST(${initialIntervalMs} * POWER(${backoffCoefficient}, \"attempts\" - 1), ${maximumIntervalMs}) * INTERVAL '1 millisecond'`;\n const deadlineExceededCondition = `\"deadline_at\" IS NOT NULL AND NOW() + (${retryIntervalExpr}) >= \"deadline_at\"`;\n\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", workflowRunId)\n .where(\"status\", \"running\")\n .where(\"worker_id\", params.workerId)\n .update({\n status: this.knex.raw(\n `CASE WHEN ${deadlineExceededCondition} THEN 'failed' ELSE 'pending' END`,\n ),\n available_at: this.knex.raw(\n `CASE WHEN ${deadlineExceededCondition} THEN NULL ELSE NOW() + (${retryIntervalExpr}) END`,\n ),\n finished_at: this.knex.raw(\n `CASE WHEN ${deadlineExceededCondition} THEN NOW() ELSE NULL END`,\n ),\n error: JSON.stringify(error),\n worker_id: null,\n started_at: null,\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n if (!updated) {\n logger.error(\"Failed to mark workflow run failed: {params}\", { params });\n throw new Error(\"Failed to mark workflow run failed\");\n }\n\n return updated;\n }\n\n async cancelWorkflowRun(params: CancelWorkflowRunParams): Promise<WorkflowRun> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Canceling workflow run: {workflowRunId}\", { workflowRunId: params.workflowRunId });\n\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"workflow_runs\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", params.workflowRunId)\n .whereIn(\"status\", [\"pending\", \"running\", \"sleeping\"])\n .update({\n status: \"canceled\",\n worker_id: null,\n available_at: null,\n finished_at: this.knex.fn.now(),\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n if (!updated) {\n // workflow may already be in a terminal state\n const existing = await this.getWorkflowRun({\n workflowRunId: params.workflowRunId,\n });\n if (!existing) {\n throw new Error(`Workflow run ${params.workflowRunId} does not exist`);\n }\n\n // if already canceled, just return it\n if (existing.status === \"canceled\") {\n return existing;\n }\n\n // throw error for completed/failed workflows\n // 'succeeded' status is deprecated\n if ([\"succeeded\", \"completed\", \"failed\"].includes(existing.status)) {\n logger.error(\"Cannot cancel workflow run: {params} with status {status}\", {\n params,\n status: existing.status,\n });\n throw new Error(\n `Cannot cancel workflow run ${params.workflowRunId} with status ${existing.status}`,\n );\n }\n\n logger.error(\"Failed to cancel workflow run: {params}\", { params });\n throw new Error(\"Failed to cancel workflow run\");\n }\n\n return updated;\n }\n\n async createStepAttempt(params: CreateStepAttemptParams): Promise<StepAttempt> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Creating step attempt: {workflowRunId}, {stepName}, {kind}\", {\n workflowRunId: params.workflowRunId,\n stepName: params.stepName,\n kind: params.kind,\n });\n\n const [stepAttempt] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"step_attempts\")\n .insert({\n namespace_id: this.namespaceId,\n id: crypto.randomUUID(),\n workflow_run_id: params.workflowRunId,\n step_name: params.stepName,\n kind: params.kind,\n status: \"running\",\n config: JSON.stringify(params.config),\n context: JSON.stringify(params.context),\n started_at: this.knex.fn.now(),\n created_at: this.knex.raw(\"date_trunc('milliseconds', NOW())\"),\n updated_at: this.knex.fn.now(),\n })\n .returning(\"*\");\n\n if (!stepAttempt) {\n logger.error(\"Failed to create step attempt: {params}\", { params });\n throw new Error(\"Failed to create step attempt\");\n }\n\n return stepAttempt;\n }\n\n async getStepAttempt(params: GetStepAttemptParams): Promise<StepAttempt | null> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Getting step attempt: {stepAttemptId}\", { stepAttemptId: params.stepAttemptId });\n\n const stepAttempt = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"step_attempts\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"id\", params.stepAttemptId)\n .first();\n\n return stepAttempt ?? null;\n }\n\n async listStepAttempts(params: ListStepAttemptsParams): Promise<PaginatedResponse<StepAttempt>> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Listing step attempts: {workflowRunId}, {after}, {before}\", {\n workflowRunId: params.workflowRunId,\n after: params.after,\n before: params.before,\n });\n\n const limit = params.limit ?? DEFAULT_PAGINATION_PAGE_SIZE;\n const { after, before } = params;\n\n let cursor: Cursor | null = null;\n if (after) {\n cursor = decodeCursor(after);\n } else if (before) {\n cursor = decodeCursor(before);\n }\n\n const qb = this.buildListStepAttemptsWhere(params, cursor);\n const rows = await qb\n .orderBy(\"created_at\", before ? \"desc\" : \"asc\")\n .orderBy(\"id\", before ? \"desc\" : \"asc\")\n .limit(limit + 1);\n\n return this.processPaginationResults(\n rows,\n limit,\n typeof after === \"string\",\n typeof before === \"string\",\n );\n }\n\n private buildListStepAttemptsWhere(params: ListStepAttemptsParams, cursor: Cursor | null) {\n const { after } = params;\n const qb = this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"step_attempts\")\n .where(\"namespace_id\", this.namespaceId)\n .where(\"workflow_run_id\", params.workflowRunId);\n\n if (cursor) {\n const operator = after ? \">\" : \"<\";\n return qb.whereRaw(`(\"created_at\", \"id\") ${operator} (?, ?)`, [\n cursor.createdAt.toISOString(),\n cursor.id,\n ]);\n }\n\n return qb;\n }\n\n private processPaginationResults<T extends Cursor>(\n rows: T[],\n limit: number,\n hasAfter: boolean,\n hasBefore: boolean,\n ): PaginatedResponse<T> {\n const data = rows;\n let hasNext = false;\n let hasPrev = false;\n\n if (hasBefore) {\n data.reverse();\n if (data.length > limit) {\n hasPrev = true;\n data.shift();\n }\n hasNext = true;\n } else {\n if (data.length > limit) {\n hasNext = true;\n data.pop();\n }\n if (hasAfter) {\n hasPrev = true;\n }\n }\n\n const lastItem = data.at(-1);\n const nextCursor = hasNext && lastItem ? encodeCursor(lastItem) : null;\n const firstItem = data[0];\n const prevCursor = hasPrev && firstItem ? encodeCursor(firstItem) : null;\n\n return {\n data,\n pagination: {\n next: nextCursor,\n prev: prevCursor,\n },\n };\n }\n\n // NOTE: 실제 서비스에서 이게 안 되는 것 같은데, 쿼리 등을 체크할 필요가 있음.\n async completeStepAttempt(params: CompleteStepAttemptParams): Promise<StepAttempt> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Marking step attempt as completed: {workflowRunId}, {stepAttemptId}, {workerId}\", {\n workflowRunId: params.workflowRunId,\n stepAttemptId: params.stepAttemptId,\n workerId: params.workerId,\n });\n\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"step_attempts as sa\")\n .update({\n status: \"completed\",\n output: JSON.stringify(params.output),\n error: null,\n finished_at: this.knex.fn.now(),\n updated_at: this.knex.fn.now(),\n })\n .updateFrom(`${DEFAULT_SCHEMA}.workflow_runs as wr`)\n .where(\"sa.namespace_id\", this.namespaceId)\n .where(\"sa.workflow_run_id\", params.workflowRunId)\n .where(\"sa.id\", params.stepAttemptId)\n .where(\"sa.status\", \"running\")\n .where(\"wr.namespace_id\", this.knex.ref(\"sa.namespace_id\"))\n .where(\"wr.id\", this.knex.ref(\"sa.workflow_run_id\"))\n .where(\"wr.status\", \"running\")\n .where(\"wr.worker_id\", params.workerId)\n .returning(\"sa.*\");\n\n if (!updated) {\n logger.error(\"Failed to mark step attempt completed: {params}\", { params });\n throw new Error(\"Failed to mark step attempt completed\");\n }\n\n return updated;\n }\n\n async failStepAttempt(params: FailStepAttemptParams): Promise<StepAttempt> {\n if (!this.initialized) {\n throw new Error(\"Backend not initialized\");\n }\n\n logger.info(\"Marking step attempt as failed: {workflowRunId}, {stepAttemptId}, {workerId}\", {\n workflowRunId: params.workflowRunId,\n stepAttemptId: params.stepAttemptId,\n workerId: params.workerId,\n });\n logger.info(\"Error: {error.message}\", { error: params.error.message });\n\n const [updated] = await this.knex\n .withSchema(DEFAULT_SCHEMA)\n .table(\"step_attempts as sa\")\n .update({\n status: \"failed\",\n output: null,\n error: JSON.stringify(params.error),\n finished_at: this.knex.fn.now(),\n updated_at: this.knex.fn.now(),\n })\n .updateFrom(`${DEFAULT_SCHEMA}.workflow_runs as wr`)\n .where(\"sa.namespace_id\", this.namespaceId)\n .where(\"sa.workflow_run_id\", params.workflowRunId)\n .where(\"sa.id\", params.stepAttemptId)\n .where(\"sa.status\", \"running\")\n .where(\"wr.namespace_id\", this.knex.ref(\"sa.namespace_id\"))\n .where(\"wr.id\", this.knex.ref(\"sa.workflow_run_id\"))\n .where(\"wr.status\", \"running\")\n .where(\"wr.worker_id\", params.workerId)\n .returning(\"sa.*\");\n\n if (!updated) {\n logger.error(\"Failed to mark step attempt failed: {params}\", { params });\n throw new Error(\"Failed to mark step attempt failed\");\n }\n\n return updated;\n }\n}\n\n/**\n * Cursor used for pagination. Requires created_at and id fields. Because JS\n * Date does not natively support microsecond precision dates, created_at should\n * be stored with millisecond precision in paginated tables to avoid issues with\n * cursor comparisons.\n */\ninterface Cursor {\n createdAt: Date;\n id: string;\n}\n\nfunction encodeCursor(item: Cursor): string {\n const encoded = Buffer.from(\n JSON.stringify({ createdAt: item.createdAt.toISOString(), id: item.id }),\n ).toString(\"base64\");\n return encoded;\n}\n\nexport function decodeCursor(cursor: string): Cursor {\n const decoded = Buffer.from(cursor, \"base64\").toString(\"utf8\");\n const parsed = JSON.parse(decoded) as { createdAt: string; id: string };\n return {\n createdAt: new Date(parsed.createdAt),\n id: parsed.id,\n };\n}\n"],"names":["getLogger","camelize","knex","DEFAULT_NAMESPACE_ID","mergeRetryPolicy","DEFAULT_SCHEMA","migrate","PostgresPubSub","DEFAULT_LISTEN_CHANNEL","DEFAULT_PAGINATION_PAGE_SIZE","logger","queryLogger","BackendPostgres","config","namespaceId","usePubSub","pubsub","initialized","runMigrations","_knex","on","query","debug","sql","bindings","options","postProcessResponse","result","_queryContext","undefined","camelizeRow","row","Object","fromEntries","entries","map","key","value","Array","isArray","initialize","subscribe","callback","Error","create","listenEvent","publish","payload","raw","stop","destroy","createWorkflowRun","params","info","workflowName","version","configWithRetryPolicy","retryPolicy","qb","withSchema","table","insert","namespace_id","id","crypto","randomUUID","workflow_name","status","idempotency_key","idempotencyKey","JSON","stringify","context","input","attempts","available_at","availableAt","fn","now","deadline_at","deadlineAt","created_at","updated_at","returning","workflowRun","error","getWorkflowRun","workflowRunId","where","select","first","listWorkflowRuns","after","before","limit","cursor","decodeCursor","buildListWorkflowRunsWhere","rows","orderBy","processPaginationResults","operator","whereRaw","createdAt","toISOString","claimWorkflowRun","workerId","leaseDurationMs","claimed","with","update","message","worker_id","finished_at","whereIn","whereNotNull","from","qb2","whereNull","orWhere","orderByRaw","forUpdate","skipLocked","ref","started_at","updateFrom","extendWorkflowRunLease","updated","sleepWorkflowRun","whereNotIn","completeWorkflowRun","output","failWorkflowRun","forceComplete","customDelayMs","parse","savedRetryPolicy","initialIntervalMs","backoffCoefficient","maximumIntervalMs","maxAttempts","currentAttempts","shouldForceComplete","retryIntervalExpr","deadlineExceededCondition","cancelWorkflowRun","existing","includes","createStepAttempt","stepName","kind","stepAttempt","workflow_run_id","step_name","getStepAttempt","stepAttemptId","listStepAttempts","buildListStepAttemptsWhere","hasAfter","hasBefore","data","hasNext","hasPrev","reverse","length","shift","pop","lastItem","at","nextCursor","encodeCursor","firstItem","prevCursor","pagination","next","prev","completeStepAttempt","failStepAttempt","item","encoded","Buffer","toString","decoded","parsed","Date"],"mappings":"AAAA,SAASA,SAAS,QAAQ,mBAAmB;AAC7C,SAASC,QAAQ,QAAQ,aAAa;AACtC,OAAOC,UAAyB,OAAO;AACvC,SAQEC,oBAAoB,QAUf,gBAAa;AACpB,SAASC,gBAAgB,QAAsC,mBAAgB;AAG/E,SAASC,cAAc,EAAEC,OAAO,QAAQ,YAAS;AACjD,SAA4BC,cAAc,QAAQ,cAAW;AAE7D,OAAO,MAAMC,yBAAyB,YAAqB;AAC3D,MAAMC,+BAA+B;AAUrC,MAAMC,SAASV,UAAU;IAAC;IAAU;IAAY;CAAQ;AACxD,MAAMW,cAAcX,UAAU;IAAC;IAAU;IAAY;IAAS;CAAQ;AAEtE;;CAEC,GACD,OAAO,MAAMY;IACHC,OAAoB;IACpBC,YAAoB;IACpBC,UAAmB;IACnBC,SAAgC,KAAK;IACrCC,cAAuB,MAAM;IAC7BC,cAAuB;IAEvBC,QAAqB,KAAK;IAClC,IAAYjB,OAAa;QACvB,IAAI,CAAC,IAAI,CAACiB,KAAK,EAAE;YACf,IAAI,CAACA,KAAK,GAAGjB,KAAK,IAAI,CAACW,MAAM;YAC7B,IAAI,CAACM,KAAK,CAACC,EAAE,CAAC,SAAS,CAACC;gBACtBV,YAAYW,KAAK,CAAC,oCAAoC;oBACpDD,OAAOA,MAAME,GAAG;oBAChBC,UAAUH,MAAMG,QAAQ;gBAC1B;YACF;QACF;QAEA,OAAO,IAAI,CAACL,KAAK;IACnB;IAEA,YAAYN,MAAmB,EAAEY,OAAgC,CAAE;QACjE,IAAI,CAACZ,MAAM,GAAG;YACZ,GAAGA,MAAM;YACTa,qBAAqB,CAACC,QAAQC;gBAC5B,IAAID,WAAW,QAAQA,WAAWE,WAAW;oBAC3C,OAAOF;gBACT;gBAEA,IAAId,QAAQa,qBAAqB;oBAC/BC,SAASd,OAAOa,mBAAmB,CAACC,QAAQC;gBAC9C;gBAEA,MAAME,cAAc,CAACC,MACnBC,OAAOC,WAAW,CAChBD,OAAOE,OAAO,CAACH,KAAKI,GAAG,CAAC,CAAC,CAACC,KAAKC,MAAM,GAAK;4BAACpC,SAASmC,KAAK;4BAAOC;yBAAM;gBAG1E,IAAIC,MAAMC,OAAO,CAACZ,SAAS;oBACzB,OAAOA,OAAOQ,GAAG,CAACL;gBACpB;gBAEA,OAAOA,YAAYH;YACrB;QACF;QAEA,MAAM,EAAEb,WAAW,EAAEC,SAAS,EAAEG,aAAa,EAAE,GAAG;YAChDJ,aAAaX;YACbY,WAAW;YACXG,eAAe;YACf,GAAGO,OAAO;QACZ;QAEA,IAAI,CAACX,WAAW,GAAGA;QACnB,IAAI,CAACC,SAAS,GAAGA;QACjB,IAAI,CAACG,aAAa,GAAGA;IACvB;IAEA,MAAMsB,aAAa;QACjB,IAAI,IAAI,CAACvB,WAAW,EAAE;YACpB;QACF;QAEA,IAAI,IAAI,CAACC,aAAa,EAAE;YACtB,MAAMZ,QAAQ,IAAI,CAACO,MAAM,EAAER;QAC7B;QAEA,IAAI,CAACY,WAAW,GAAG;IACrB;IAEA,MAAMwB,UAAUC,QAAsB,EAAE;QACtC,IAAI,CAAC,IAAI,CAACzB,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEA,IAAI,CAAC,IAAI,CAAC5B,SAAS,EAAE;YACnB;QACF;QAEA,IAAI,CAAC,IAAI,CAACC,MAAM,EAAE;YAChB,IAAI,CAACA,MAAM,GAAG,MAAMT,eAAeqC,MAAM,CAAC,IAAI,CAAC1C,IAAI;QACrD;QAEA,IAAI,CAACc,MAAM,CAAC6B,WAAW,CAACrC,wBAAwBkC;IAClD;IAEA,MAAMI,QAAQC,OAAgB,EAAiB;QAC7C,IAAI,CAAC,IAAI,CAAC9B,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEA,IAAI,CAAC,IAAI,CAAC5B,SAAS,EAAE;YACnB;QACF;QAEA,MAAM,IAAI,CAACb,IAAI,CAAC8C,GAAG,CACjBD,UACI,CAAC,OAAO,EAAEvC,uBAAuB,GAAG,EAAEuC,QAAQ,CAAC,CAAC,GAChD,CAAC,OAAO,EAAEvC,wBAAwB;IAE1C;IAEA,MAAMyC,OAAsB;QAC1B,IAAI,CAAC,IAAI,CAAChC,WAAW,EAAE;YACrB;QACF;QAEA,MAAM,IAAI,CAACD,MAAM,EAAEkC;QACnB,IAAI,CAAClC,MAAM,GAAG;QACd,MAAM,IAAI,CAACd,IAAI,CAACgD,OAAO;IACzB;IAEA,MAAMC,kBAAkBC,MAA+B,EAAwB;QAC7E,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEAjC,OAAO2C,IAAI,CAAC,mDAAmD;YAC7DC,cAAcF,OAAOE,YAAY;YACjCC,SAASH,OAAOG,OAAO;QACzB;QAEA,+BAA+B;QAC/B,MAAMC,wBAAwB;YAC5B,GAAI,OAAOJ,OAAOvC,MAAM,KAAK,YAAYuC,OAAOvC,MAAM,KAAK,OAAOuC,OAAOvC,MAAM,GAAG,CAAC,CAAC;YACpF4C,aAAaL,OAAOK,WAAW,IAAI5B;QACrC;QAEA,MAAM6B,KAAK,IAAI,CAACxD,IAAI,CACjByD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACNC,MAAM,CAAC;YACNC,cAAc,IAAI,CAAChD,WAAW;YAC9BiD,IAAIC,OAAOC,UAAU;YACrBC,eAAed,OAAOE,YAAY;YAClCC,SAASH,OAAOG,OAAO;YACvBY,QAAQ;YACRC,iBAAiBhB,OAAOiB,cAAc;YACtCxD,QAAQyD,KAAKC,SAAS,CAACf;YACvBgB,SAASpB,OAAOoB,OAAO;YACvBC,OAAOrB,OAAOqB,KAAK;YACnBC,UAAU;YACVC,cAAcvB,OAAOwB,WAAW,IAAI,IAAI,CAAC1E,IAAI,CAAC2E,EAAE,CAACC,GAAG;YACpDC,aAAa3B,OAAO4B,UAAU;YAC9BC,YAAY,IAAI,CAAC/E,IAAI,CAAC2E,EAAE,CAACC,GAAG;YAC5BI,YAAY,IAAI,CAAChF,IAAI,CAAC2E,EAAE,CAACC,GAAG;QAC9B,GACCK,SAAS,CAAC;QAEb,MAAMC,cAAc,MAAM1B;QAC1B,IAAI,CAAC0B,WAAW,CAAC,EAAE,EAAE;YACnB1E,OAAO2E,KAAK,CAAC,2CAA2C;gBAAEjC;YAAO;YACjE,MAAM,IAAIT,MAAM;QAClB;QAEA,OAAOyC,WAAW,CAAC,EAAE;IACvB;IAEA,MAAME,eAAelC,MAA4B,EAA+B;QAC9E,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEAjC,OAAO2C,IAAI,CAAC,yCAAyC;YAAEkC,eAAenC,OAAOmC,aAAa;QAAC;QAC3F,MAAMH,cAAc,MAAM,IAAI,CAAClF,IAAI,CAChCyD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACN4B,KAAK,CAAC,gBAAgB,IAAI,CAAC1E,WAAW,EACtC0E,KAAK,CAAC,MAAMpC,OAAOmC,aAAa,EAChCE,MAAM,CACL,gBACA,MACA,iBACA,WACA,UACA,mBACA,UACA,WACA,SACA,UACA,SACA,YACA,oCACA,0BACA,aACA,gBACA,eACA,cACA,eACA,cACA,cAEDC,KAAK;QAER,OAAON,eAAe;IACxB;IAEA,MAAMO,iBAAiBvC,MAA8B,EAA2C;QAC9F,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEAjC,OAAO2C,IAAI,CAAC,4CAA4C;YACtDuC,OAAOxC,OAAOwC,KAAK;YACnBC,QAAQzC,OAAOyC,MAAM;QACvB;QACA,MAAMC,QAAQ1C,OAAO0C,KAAK,IAAIrF;QAC9B,MAAM,EAAEmF,KAAK,EAAEC,MAAM,EAAE,GAAGzC;QAE1B,IAAI2C,SAAwB;QAC5B,IAAIH,OAAO;YACTG,SAASC,aAAaJ;QACxB,OAAO,IAAIC,QAAQ;YACjBE,SAASC,aAAaH;QACxB;QAEA,MAAMnC,KAAK,IAAI,CAACuC,0BAA0B,CAAC7C,QAAQ2C;QACnD,MAAMG,OAAO,MAAMxC,GAChByC,OAAO,CAAC,cAAcN,SAAS,SAAS,OACxCM,OAAO,CAAC,MAAMN,SAAS,SAAS,OAChCC,KAAK,CAACA,QAAQ;QAEjB,OAAO,IAAI,CAACM,wBAAwB,CAClCF,MACAJ,OACA,OAAOF,UAAU,UACjB,OAAOC,WAAW;IAEtB;IAEQI,2BAA2B7C,MAA8B,EAAE2C,MAAqB,EAAE;QACxF,MAAM,EAAEH,KAAK,EAAE,GAAGxC;QAClB,MAAMM,KAAK,IAAI,CAACxD,IAAI,CACjByD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACN4B,KAAK,CAAC,gBAAgB,IAAI,CAAC1E,WAAW;QAEzC,IAAIiF,QAAQ;YACV,MAAMM,WAAWT,QAAQ,MAAM;YAC/B,OAAOlC,GAAG4C,QAAQ,CAAC,CAAC,qBAAqB,EAAED,SAAS,OAAO,CAAC,EAAE;gBAC5DN,OAAOQ,SAAS,CAACC,WAAW;gBAC5BT,OAAOhC,EAAE;aACV;QACH;QAEA,OAAOL;IACT;IAEA,MAAM+C,iBAAiBrD,MAA8B,EAA+B;QAClF,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEAjC,OAAO2C,IAAI,CAAC,wDAAwD;YAClEqD,UAAUtD,OAAOsD,QAAQ;YACzBC,iBAAiBvD,OAAOuD,eAAe;QACzC;QACA,MAAMC,UAAU,MAAM,IAAI,CAAC1G,IAAI,CAC5B2G,IAAI,CAAC,WAAW,CAACnD,KAChBA,GACGC,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACNkD,MAAM,CAAC;gBACN3C,QAAQ;gBACRkB,OAAOf,KAAKC,SAAS,CAAC;oBAAEwC,SAAS;gBAAiC;gBAClEC,WAAW;gBACXrC,cAAc;gBACdsC,aAAa,IAAI,CAAC/G,IAAI,CAAC8C,GAAG,CAAC;gBAC3BkC,YAAY,IAAI,CAAChF,IAAI,CAAC8C,GAAG,CAAC;YAC5B,GACCwC,KAAK,CAAC,gBAAgB,IAAI,CAAC1E,WAAW,EACtCoG,OAAO,CAAC,UAAU;gBAAC;gBAAW;gBAAW;aAAW,EACpDC,YAAY,CAAC,eACb3B,KAAK,CAAC,eAAe,MAAM,IAAI,CAACtF,IAAI,CAAC8C,GAAG,CAAC,UACzCmC,SAAS,CAAC,OAEd0B,IAAI,CAAC,aAAa,CAACnD,KAClBA,GACGC,UAAU,CAACtD,gBACXoF,MAAM,CAAC,MACP2B,IAAI,CAAC,iBACL5B,KAAK,CAAC,gBAAgB,IAAI,CAAC1E,WAAW,EACtCoG,OAAO,CAAC,UAAU;gBAAC;gBAAW;gBAAW;aAAW,EACpD1B,KAAK,CAAC,gBAAgB,MAAM,IAAI,CAACtF,IAAI,CAAC8C,GAAG,CAAC,UAC1CwC,KAAK,CAAC,CAAC6B;gBACNA,IAAIC,SAAS,CAAC,eAAeC,OAAO,CAAC,eAAe,KAAK,IAAI,CAACrH,IAAI,CAAC8C,GAAG,CAAC;YACzE,GACCwE,UAAU,CAAC,kDACXrB,OAAO,CAAC,gBAAgB,OACxBA,OAAO,CAAC,cAAc,OACtBL,KAAK,CAAC,GACN2B,SAAS,GACTC,UAAU,IAEd/D,UAAU,CAACtD,gBACXuD,KAAK,CAAC,uBACN4B,KAAK,CAAC,mBAAmB,IAAI,CAAC1E,WAAW,EACzC0E,KAAK,CAAC,SAAS,IAAI,CAACtF,IAAI,CAACyH,GAAG,CAAC,iBAC7Bb,MAAM,CAAC;YACN3C,QAAQ;YACRO,UAAU,IAAI,CAACxE,IAAI,CAAC8C,GAAG,CAAC;YACxBgE,WAAW5D,OAAOsD,QAAQ;YAC1B/B,cAAc,IAAI,CAACzE,IAAI,CAAC8C,GAAG,CAAC,CAAC,QAAQ,EAAEI,OAAOuD,eAAe,CAAC,2BAA2B,CAAC;YAC1FiB,YAAY,IAAI,CAAC1H,IAAI,CAAC8C,GAAG,CAAC;YAC1BkC,YAAY,IAAI,CAAChF,IAAI,CAAC8C,GAAG,CAAC;QAC5B,GACC6E,UAAU,CAAC,aACX1C,SAAS,CAAC;QAEb,OAAOyB,OAAO,CAAC,EAAE,IAAI;IACvB;IAEA,MAAMkB,uBAAuB1E,MAAoC,EAAwB;QACvF,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEAjC,OAAO2C,IAAI,CAAC,gFAAgF;YAC1FkC,eAAenC,OAAOmC,aAAa;YACnCmB,UAAUtD,OAAOsD,QAAQ;YACzBC,iBAAiBvD,OAAOuD,eAAe;QACzC;QACA,MAAM,CAACoB,QAAQ,GAAG,MAAM,IAAI,CAAC7H,IAAI,CAC9ByD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACN4B,KAAK,CAAC,gBAAgB,IAAI,CAAC1E,WAAW,EACtC0E,KAAK,CAAC,MAAMpC,OAAOmC,aAAa,EAChCC,KAAK,CAAC,UAAU,WAChBA,KAAK,CAAC,aAAapC,OAAOsD,QAAQ,EAClCI,MAAM,CAAC;YACNnC,cAAc,IAAI,CAACzE,IAAI,CAAC8C,GAAG,CAAC,CAAC,QAAQ,EAAEI,OAAOuD,eAAe,CAAC,2BAA2B,CAAC;YAC1FzB,YAAY,IAAI,CAAChF,IAAI,CAAC2E,EAAE,CAACC,GAAG;QAC9B,GACCK,SAAS,CAAC;QAEb,IAAI,CAAC4C,SAAS;YACZrH,OAAO2E,KAAK,CAAC,qDAAqD;gBAAEjC;YAAO;YAC3E,MAAM,IAAIT,MAAM;QAClB;QAEA,OAAOoF;IACT;IAEA,MAAMC,iBAAiB5E,MAA8B,EAAwB;QAC3E,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEAjC,OAAO2C,IAAI,CAAC,qEAAqE;YAC/EkC,eAAenC,OAAOmC,aAAa;YACnCmB,UAAUtD,OAAOsD,QAAQ;YACzB9B,aAAaxB,OAAOwB,WAAW;QACjC;QAEA,mCAAmC;QACnC,MAAM,CAACmD,QAAQ,GAAG,MAAM,IAAI,CAAC7H,IAAI,CAC9ByD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACN4B,KAAK,CAAC,gBAAgB,IAAI,CAAC1E,WAAW,EACtC0E,KAAK,CAAC,MAAMpC,OAAOmC,aAAa,EAChC0C,UAAU,CAAC,UAAU;YAAC;YAAa;YAAa;YAAU;SAAW,EACrEzC,KAAK,CAAC,aAAapC,OAAOsD,QAAQ,EAClCI,MAAM,CAAC;YACN3C,QAAQ;YACRQ,cAAcvB,OAAOwB,WAAW;YAChCoC,WAAW;YACX9B,YAAY,IAAI,CAAChF,IAAI,CAAC2E,EAAE,CAACC,GAAG;QAC9B,GACCK,SAAS,CAAC;QAEb,IAAI,CAAC4C,SAAS;YACZrH,OAAO2E,KAAK,CAAC,0CAA0C;gBAAEjC;YAAO;YAChE,MAAM,IAAIT,MAAM;QAClB;QAEA,OAAOoF;IACT;IAEA,MAAMG,oBAAoB9E,MAAiC,EAAwB;QACjF,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEAjC,OAAO2C,IAAI,CAAC,kEAAkE;YAC5EkC,eAAenC,OAAOmC,aAAa;YACnCmB,UAAUtD,OAAOsD,QAAQ;YACzByB,QAAQ/E,OAAO+E,MAAM;QACvB;QAEA,MAAM,CAACJ,QAAQ,GAAG,MAAM,IAAI,CAAC7H,IAAI,CAC9ByD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACN4B,KAAK,CAAC,gBAAgB,IAAI,CAAC1E,WAAW,EACtC0E,KAAK,CAAC,MAAMpC,OAAOmC,aAAa,EAChCC,KAAK,CAAC,UAAU,WAChBA,KAAK,CAAC,aAAapC,OAAOsD,QAAQ,EAClCI,MAAM,CAAC;YACN3C,QAAQ;YACRgE,QAAQ7D,KAAKC,SAAS,CAACnB,OAAO+E,MAAM;YACpC9C,OAAO;YACP2B,WAAW5D,OAAOsD,QAAQ;YAC1B/B,cAAc;YACdsC,aAAa,IAAI,CAAC/G,IAAI,CAAC2E,EAAE,CAACC,GAAG;YAC7BI,YAAY,IAAI,CAAChF,IAAI,CAAC2E,EAAE,CAACC,GAAG;QAC9B,GACCK,SAAS,CAAC;QAEb,IAAI,CAAC4C,SAAS;YACZrH,OAAO2E,KAAK,CAAC,6CAA6C;gBAAEjC;YAAO;YACnE,MAAM,IAAIT,MAAM;QAClB;QAEA,OAAOoF;IACT;IAEA,MAAMK,gBAAgBhF,MAA6B,EAAwB;QACzE,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEA,MAAM,EAAE4C,aAAa,EAAEF,KAAK,EAAEgD,aAAa,EAAEC,aAAa,EAAE,GAAGlF;QAE/D1C,OAAO2C,IAAI,CAAC,8DAA8D;YACxEkC,eAAenC,OAAOmC,aAAa;YACnCmB,UAAUtD,OAAOsD,QAAQ;YACzBrB,OAAOjC,OAAOiC,KAAK;QACrB;QAEA,MAAMD,cAAc,MAAM,IAAI,CAAClF,IAAI,CAChCyD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACN4B,KAAK,CAAC,gBAAgB,IAAI,CAAC1E,WAAW,EACtC0E,KAAK,CAAC,MAAMD,eACZG,KAAK;QAER,IAAI,CAACN,aAAa;YAChB,MAAM,IAAIzC,MAAM;QAClB;QAEA,MAAM9B,SACJ,OAAOuE,YAAYvE,MAAM,KAAK,WAAWyD,KAAKiE,KAAK,CAACnD,YAAYvE,MAAM,IAAIuE,YAAYvE,MAAM;QAC9F,MAAM2H,mBAAwD3H,QAAQ4C;QACtE,MAAMA,cAAcrD,iBAAiBoI;QAErC,MAAM,EAAEC,iBAAiB,EAAEC,kBAAkB,EAAEC,iBAAiB,EAAEC,WAAW,EAAE,GAAGnF;QAElF,MAAMoF,kBAAkBzD,YAAYV,QAAQ,IAAI;QAChD,MAAMoE,sBAAsBT,iBAAiBQ,mBAAmBD;QAEhE,IAAIE,qBAAqB;YACvB,MAAM,CAACf,QAAQ,GAAG,MAAM,IAAI,CAAC7H,IAAI,CAC9ByD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACN4B,KAAK,CAAC,gBAAgB,IAAI,CAAC1E,WAAW,EACtC0E,KAAK,CAAC,MAAMD,eACZC,KAAK,CAAC,UAAU,WAChBA,KAAK,CAAC,aAAapC,OAAOsD,QAAQ,EAClCI,MAAM,CAAC;gBACN3C,QAAQ;gBACRQ,cAAc;gBACdsC,aAAa,IAAI,CAAC/G,IAAI,CAAC2E,EAAE,CAACC,GAAG;gBAC7BO,OAAOf,KAAKC,SAAS,CAACc;gBACtB2B,WAAW;gBACXY,YAAY;gBACZ1C,YAAY,IAAI,CAAChF,IAAI,CAAC2E,EAAE,CAACC,GAAG;YAC9B,GACCK,SAAS,CAAC;YAEb,IAAI,CAAC4C,SAAS;gBACZrH,OAAO2E,KAAK,CAAC,gDAAgD;oBAAEjC;gBAAO;gBACtE,MAAM,IAAIT,MAAM;YAClB;YACA,OAAOoF;QACT;QAEA,kEAAkE;QAClE,gEAAgE;QAChE,EAAE;QACF,oEAAoE;QACpE,4EAA4E;QAC5E,8CAA8C;QAC9C,MAAMgB,oBAAoBT,gBACtB,GAAGA,cAAc,2BAA2B,CAAC,GAC7C,CAAC,MAAM,EAAEG,kBAAkB,SAAS,EAAEC,mBAAmB,mBAAmB,EAAEC,kBAAkB,4BAA4B,CAAC;QACjI,MAAMK,4BAA4B,CAAC,uCAAuC,EAAED,kBAAkB,kBAAkB,CAAC;QAEjH,MAAM,CAAChB,QAAQ,GAAG,MAAM,IAAI,CAAC7H,IAAI,CAC9ByD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACN4B,KAAK,CAAC,gBAAgB,IAAI,CAAC1E,WAAW,EACtC0E,KAAK,CAAC,MAAMD,eACZC,KAAK,CAAC,UAAU,WAChBA,KAAK,CAAC,aAAapC,OAAOsD,QAAQ,EAClCI,MAAM,CAAC;YACN3C,QAAQ,IAAI,CAACjE,IAAI,CAAC8C,GAAG,CACnB,CAAC,UAAU,EAAEgG,0BAA0B,iCAAiC,CAAC;YAE3ErE,cAAc,IAAI,CAACzE,IAAI,CAAC8C,GAAG,CACzB,CAAC,UAAU,EAAEgG,0BAA0B,yBAAyB,EAAED,kBAAkB,KAAK,CAAC;YAE5F9B,aAAa,IAAI,CAAC/G,IAAI,CAAC8C,GAAG,CACxB,CAAC,UAAU,EAAEgG,0BAA0B,yBAAyB,CAAC;YAEnE3D,OAAOf,KAAKC,SAAS,CAACc;YACtB2B,WAAW;YACXY,YAAY;YACZ1C,YAAY,IAAI,CAAChF,IAAI,CAAC2E,EAAE,CAACC,GAAG;QAC9B,GACCK,SAAS,CAAC;QAEb,IAAI,CAAC4C,SAAS;YACZrH,OAAO2E,KAAK,CAAC,gDAAgD;gBAAEjC;YAAO;YACtE,MAAM,IAAIT,MAAM;QAClB;QAEA,OAAOoF;IACT;IAEA,MAAMkB,kBAAkB7F,MAA+B,EAAwB;QAC7E,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEAjC,OAAO2C,IAAI,CAAC,2CAA2C;YAAEkC,eAAenC,OAAOmC,aAAa;QAAC;QAE7F,MAAM,CAACwC,QAAQ,GAAG,MAAM,IAAI,CAAC7H,IAAI,CAC9ByD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACN4B,KAAK,CAAC,gBAAgB,IAAI,CAAC1E,WAAW,EACtC0E,KAAK,CAAC,MAAMpC,OAAOmC,aAAa,EAChC2B,OAAO,CAAC,UAAU;YAAC;YAAW;YAAW;SAAW,EACpDJ,MAAM,CAAC;YACN3C,QAAQ;YACR6C,WAAW;YACXrC,cAAc;YACdsC,aAAa,IAAI,CAAC/G,IAAI,CAAC2E,EAAE,CAACC,GAAG;YAC7BI,YAAY,IAAI,CAAChF,IAAI,CAAC2E,EAAE,CAACC,GAAG;QAC9B,GACCK,SAAS,CAAC;QAEb,IAAI,CAAC4C,SAAS;YACZ,8CAA8C;YAC9C,MAAMmB,WAAW,MAAM,IAAI,CAAC5D,cAAc,CAAC;gBACzCC,eAAenC,OAAOmC,aAAa;YACrC;YACA,IAAI,CAAC2D,UAAU;gBACb,MAAM,IAAIvG,MAAM,CAAC,aAAa,EAAES,OAAOmC,aAAa,CAAC,eAAe,CAAC;YACvE;YAEA,sCAAsC;YACtC,IAAI2D,SAAS/E,MAAM,KAAK,YAAY;gBAClC,OAAO+E;YACT;YAEA,6CAA6C;YAC7C,mCAAmC;YACnC,IAAI;gBAAC;gBAAa;gBAAa;aAAS,CAACC,QAAQ,CAACD,SAAS/E,MAAM,GAAG;gBAClEzD,OAAO2E,KAAK,CAAC,6DAA6D;oBACxEjC;oBACAe,QAAQ+E,SAAS/E,MAAM;gBACzB;gBACA,MAAM,IAAIxB,MACR,CAAC,2BAA2B,EAAES,OAAOmC,aAAa,CAAC,aAAa,EAAE2D,SAAS/E,MAAM,EAAE;YAEvF;YAEAzD,OAAO2E,KAAK,CAAC,2CAA2C;gBAAEjC;YAAO;YACjE,MAAM,IAAIT,MAAM;QAClB;QAEA,OAAOoF;IACT;IAEA,MAAMqB,kBAAkBhG,MAA+B,EAAwB;QAC7E,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEAjC,OAAO2C,IAAI,CAAC,8DAA8D;YACxEkC,eAAenC,OAAOmC,aAAa;YACnC8D,UAAUjG,OAAOiG,QAAQ;YACzBC,MAAMlG,OAAOkG,IAAI;QACnB;QAEA,MAAM,CAACC,YAAY,GAAG,MAAM,IAAI,CAACrJ,IAAI,CAClCyD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACNC,MAAM,CAAC;YACNC,cAAc,IAAI,CAAChD,WAAW;YAC9BiD,IAAIC,OAAOC,UAAU;YACrBuF,iBAAiBpG,OAAOmC,aAAa;YACrCkE,WAAWrG,OAAOiG,QAAQ;YAC1BC,MAAMlG,OAAOkG,IAAI;YACjBnF,QAAQ;YACRtD,QAAQyD,KAAKC,SAAS,CAACnB,OAAOvC,MAAM;YACpC2D,SAASF,KAAKC,SAAS,CAACnB,OAAOoB,OAAO;YACtCoD,YAAY,IAAI,CAAC1H,IAAI,CAAC2E,EAAE,CAACC,GAAG;YAC5BG,YAAY,IAAI,CAAC/E,IAAI,CAAC8C,GAAG,CAAC;YAC1BkC,YAAY,IAAI,CAAChF,IAAI,CAAC2E,EAAE,CAACC,GAAG;QAC9B,GACCK,SAAS,CAAC;QAEb,IAAI,CAACoE,aAAa;YAChB7I,OAAO2E,KAAK,CAAC,2CAA2C;gBAAEjC;YAAO;YACjE,MAAM,IAAIT,MAAM;QAClB;QAEA,OAAO4G;IACT;IAEA,MAAMG,eAAetG,MAA4B,EAA+B;QAC9E,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEAjC,OAAO2C,IAAI,CAAC,yCAAyC;YAAEsG,eAAevG,OAAOuG,aAAa;QAAC;QAE3F,MAAMJ,cAAc,MAAM,IAAI,CAACrJ,IAAI,CAChCyD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACN4B,KAAK,CAAC,gBAAgB,IAAI,CAAC1E,WAAW,EACtC0E,KAAK,CAAC,MAAMpC,OAAOuG,aAAa,EAChCjE,KAAK;QAER,OAAO6D,eAAe;IACxB;IAEA,MAAMK,iBAAiBxG,MAA8B,EAA2C;QAC9F,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEAjC,OAAO2C,IAAI,CAAC,6DAA6D;YACvEkC,eAAenC,OAAOmC,aAAa;YACnCK,OAAOxC,OAAOwC,KAAK;YACnBC,QAAQzC,OAAOyC,MAAM;QACvB;QAEA,MAAMC,QAAQ1C,OAAO0C,KAAK,IAAIrF;QAC9B,MAAM,EAAEmF,KAAK,EAAEC,MAAM,EAAE,GAAGzC;QAE1B,IAAI2C,SAAwB;QAC5B,IAAIH,OAAO;YACTG,SAASC,aAAaJ;QACxB,OAAO,IAAIC,QAAQ;YACjBE,SAASC,aAAaH;QACxB;QAEA,MAAMnC,KAAK,IAAI,CAACmG,0BAA0B,CAACzG,QAAQ2C;QACnD,MAAMG,OAAO,MAAMxC,GAChByC,OAAO,CAAC,cAAcN,SAAS,SAAS,OACxCM,OAAO,CAAC,MAAMN,SAAS,SAAS,OAChCC,KAAK,CAACA,QAAQ;QAEjB,OAAO,IAAI,CAACM,wBAAwB,CAClCF,MACAJ,OACA,OAAOF,UAAU,UACjB,OAAOC,WAAW;IAEtB;IAEQgE,2BAA2BzG,MAA8B,EAAE2C,MAAqB,EAAE;QACxF,MAAM,EAAEH,KAAK,EAAE,GAAGxC;QAClB,MAAMM,KAAK,IAAI,CAACxD,IAAI,CACjByD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,iBACN4B,KAAK,CAAC,gBAAgB,IAAI,CAAC1E,WAAW,EACtC0E,KAAK,CAAC,mBAAmBpC,OAAOmC,aAAa;QAEhD,IAAIQ,QAAQ;YACV,MAAMM,WAAWT,QAAQ,MAAM;YAC/B,OAAOlC,GAAG4C,QAAQ,CAAC,CAAC,qBAAqB,EAAED,SAAS,OAAO,CAAC,EAAE;gBAC5DN,OAAOQ,SAAS,CAACC,WAAW;gBAC5BT,OAAOhC,EAAE;aACV;QACH;QAEA,OAAOL;IACT;IAEQ0C,yBACNF,IAAS,EACTJ,KAAa,EACbgE,QAAiB,EACjBC,SAAkB,EACI;QACtB,MAAMC,OAAO9D;QACb,IAAI+D,UAAU;QACd,IAAIC,UAAU;QAEd,IAAIH,WAAW;YACbC,KAAKG,OAAO;YACZ,IAAIH,KAAKI,MAAM,GAAGtE,OAAO;gBACvBoE,UAAU;gBACVF,KAAKK,KAAK;YACZ;YACAJ,UAAU;QACZ,OAAO;YACL,IAAID,KAAKI,MAAM,GAAGtE,OAAO;gBACvBmE,UAAU;gBACVD,KAAKM,GAAG;YACV;YACA,IAAIR,UAAU;gBACZI,UAAU;YACZ;QACF;QAEA,MAAMK,WAAWP,KAAKQ,EAAE,CAAC,CAAC;QAC1B,MAAMC,aAAaR,WAAWM,WAAWG,aAAaH,YAAY;QAClE,MAAMI,YAAYX,IAAI,CAAC,EAAE;QACzB,MAAMY,aAAaV,WAAWS,YAAYD,aAAaC,aAAa;QAEpE,OAAO;YACLX;YACAa,YAAY;gBACVC,MAAML;gBACNM,MAAMH;YACR;QACF;IACF;IAEA,kDAAkD;IAClD,MAAMI,oBAAoB5H,MAAiC,EAAwB;QACjF,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEAjC,OAAO2C,IAAI,CAAC,mFAAmF;YAC7FkC,eAAenC,OAAOmC,aAAa;YACnCoE,eAAevG,OAAOuG,aAAa;YACnCjD,UAAUtD,OAAOsD,QAAQ;QAC3B;QAEA,MAAM,CAACqB,QAAQ,GAAG,MAAM,IAAI,CAAC7H,IAAI,CAC9ByD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,uBACNkD,MAAM,CAAC;YACN3C,QAAQ;YACRgE,QAAQ7D,KAAKC,SAAS,CAACnB,OAAO+E,MAAM;YACpC9C,OAAO;YACP4B,aAAa,IAAI,CAAC/G,IAAI,CAAC2E,EAAE,CAACC,GAAG;YAC7BI,YAAY,IAAI,CAAChF,IAAI,CAAC2E,EAAE,CAACC,GAAG;QAC9B,GACC+C,UAAU,CAAC,GAAGxH,eAAe,oBAAoB,CAAC,EAClDmF,KAAK,CAAC,mBAAmB,IAAI,CAAC1E,WAAW,EACzC0E,KAAK,CAAC,sBAAsBpC,OAAOmC,aAAa,EAChDC,KAAK,CAAC,SAASpC,OAAOuG,aAAa,EACnCnE,KAAK,CAAC,aAAa,WACnBA,KAAK,CAAC,mBAAmB,IAAI,CAACtF,IAAI,CAACyH,GAAG,CAAC,oBACvCnC,KAAK,CAAC,SAAS,IAAI,CAACtF,IAAI,CAACyH,GAAG,CAAC,uBAC7BnC,KAAK,CAAC,aAAa,WACnBA,KAAK,CAAC,gBAAgBpC,OAAOsD,QAAQ,EACrCvB,SAAS,CAAC;QAEb,IAAI,CAAC4C,SAAS;YACZrH,OAAO2E,KAAK,CAAC,mDAAmD;gBAAEjC;YAAO;YACzE,MAAM,IAAIT,MAAM;QAClB;QAEA,OAAOoF;IACT;IAEA,MAAMkD,gBAAgB7H,MAA6B,EAAwB;QACzE,IAAI,CAAC,IAAI,CAACnC,WAAW,EAAE;YACrB,MAAM,IAAI0B,MAAM;QAClB;QAEAjC,OAAO2C,IAAI,CAAC,gFAAgF;YAC1FkC,eAAenC,OAAOmC,aAAa;YACnCoE,eAAevG,OAAOuG,aAAa;YACnCjD,UAAUtD,OAAOsD,QAAQ;QAC3B;QACAhG,OAAO2C,IAAI,CAAC,0BAA0B;YAAEgC,OAAOjC,OAAOiC,KAAK,CAAC0B,OAAO;QAAC;QAEpE,MAAM,CAACgB,QAAQ,GAAG,MAAM,IAAI,CAAC7H,IAAI,CAC9ByD,UAAU,CAACtD,gBACXuD,KAAK,CAAC,uBACNkD,MAAM,CAAC;YACN3C,QAAQ;YACRgE,QAAQ;YACR9C,OAAOf,KAAKC,SAAS,CAACnB,OAAOiC,KAAK;YAClC4B,aAAa,IAAI,CAAC/G,IAAI,CAAC2E,EAAE,CAACC,GAAG;YAC7BI,YAAY,IAAI,CAAChF,IAAI,CAAC2E,EAAE,CAACC,GAAG;QAC9B,GACC+C,UAAU,CAAC,GAAGxH,eAAe,oBAAoB,CAAC,EAClDmF,KAAK,CAAC,mBAAmB,IAAI,CAAC1E,WAAW,EACzC0E,KAAK,CAAC,sBAAsBpC,OAAOmC,aAAa,EAChDC,KAAK,CAAC,SAASpC,OAAOuG,aAAa,EACnCnE,KAAK,CAAC,aAAa,WACnBA,KAAK,CAAC,mBAAmB,IAAI,CAACtF,IAAI,CAACyH,GAAG,CAAC,oBACvCnC,KAAK,CAAC,SAAS,IAAI,CAACtF,IAAI,CAACyH,GAAG,CAAC,uBAC7BnC,KAAK,CAAC,aAAa,WACnBA,KAAK,CAAC,gBAAgBpC,OAAOsD,QAAQ,EACrCvB,SAAS,CAAC;QAEb,IAAI,CAAC4C,SAAS;YACZrH,OAAO2E,KAAK,CAAC,gDAAgD;gBAAEjC;YAAO;YACtE,MAAM,IAAIT,MAAM;QAClB;QAEA,OAAOoF;IACT;AACF;AAaA,SAAS2C,aAAaQ,IAAY;IAChC,MAAMC,UAAUC,OAAOhE,IAAI,CACzB9C,KAAKC,SAAS,CAAC;QAAEgC,WAAW2E,KAAK3E,SAAS,CAACC,WAAW;QAAIzC,IAAImH,KAAKnH,EAAE;IAAC,IACtEsH,QAAQ,CAAC;IACX,OAAOF;AACT;AAEA,OAAO,SAASnF,aAAaD,MAAc;IACzC,MAAMuF,UAAUF,OAAOhE,IAAI,CAACrB,QAAQ,UAAUsF,QAAQ,CAAC;IACvD,MAAME,SAASjH,KAAKiE,KAAK,CAAC+C;IAC1B,OAAO;QACL/E,WAAW,IAAIiF,KAAKD,OAAOhF,SAAS;QACpCxC,IAAIwH,OAAOxH,EAAE;IACf;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"backend.testsuite.d.ts","sourceRoot":"","sources":["../../src/database/backend.testsuite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAI3C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B;;OAEG;IACH,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAswC7D"}
1
+ {"version":3,"file":"backend.testsuite.d.ts","sourceRoot":"","sources":["../../src/database/backend.testsuite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAK3C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B;;OAEG;IACH,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CA43C7D"}
@@ -453,6 +453,112 @@ import { afterAll, beforeAll, describe, expect, test } from "vitest";
453
453
  expect(delayMs).toBeLessThan(2500);
454
454
  await teardown(backend);
455
455
  });
456
+ test("marks workflow run as failed when maxAttempts is reached", async ()=>{
457
+ const backend = await setup();
458
+ // retryPolicy에 maxAttempts: 2를 지정하여 생성
459
+ const retryPolicy = {
460
+ maxAttempts: 2,
461
+ initialIntervalMs: 100
462
+ };
463
+ await backend.createWorkflowRun({
464
+ workflowName: randomUUID(),
465
+ version: null,
466
+ idempotencyKey: null,
467
+ input: null,
468
+ config: {},
469
+ context: null,
470
+ availableAt: null,
471
+ deadlineAt: null,
472
+ retryPolicy
473
+ });
474
+ // 첫 번째 시도 - 실패하면 pending으로 스케줄링
475
+ let workerId = randomUUID();
476
+ let claimed = await backend.claimWorkflowRun({
477
+ workerId,
478
+ leaseDurationMs: 100
479
+ });
480
+ if (!claimed) throw new Error("Expected workflow run to be claimed");
481
+ expect(claimed.attempts).toBe(1);
482
+ const firstFailed = await backend.failWorkflowRun({
483
+ workflowRunId: claimed.id,
484
+ workerId,
485
+ error: {
486
+ message: "first failure"
487
+ }
488
+ });
489
+ expect(firstFailed.status).toBe("pending"); // 아직 maxAttempts(2) 미달
490
+ await sleep(150); // 100ms backoff 대기
491
+ // 두 번째 시도 - maxAttempts에 도달하면 failed로 종료
492
+ workerId = randomUUID();
493
+ claimed = await backend.claimWorkflowRun({
494
+ workerId,
495
+ leaseDurationMs: 100
496
+ });
497
+ if (!claimed) throw new Error("Expected workflow run to be claimed");
498
+ expect(claimed.attempts).toBe(2);
499
+ const secondFailed = await backend.failWorkflowRun({
500
+ workflowRunId: claimed.id,
501
+ workerId,
502
+ error: {
503
+ message: "second failure"
504
+ }
505
+ });
506
+ // maxAttempts에 도달했으므로 failed로 종료
507
+ expect(secondFailed.status).toBe("failed");
508
+ expect(secondFailed.availableAt).toBeNull();
509
+ expect(secondFailed.finishedAt).not.toBeNull();
510
+ await teardown(backend);
511
+ });
512
+ test("marks workflow run as failed immediately when forceComplete is true", async ()=>{
513
+ const backend = await setup();
514
+ await createPendingWorkflowRun(backend);
515
+ const workerId = randomUUID();
516
+ const claimed = await backend.claimWorkflowRun({
517
+ workerId,
518
+ leaseDurationMs: 100
519
+ });
520
+ if (!claimed) throw new Error("Expected workflow run to be claimed");
521
+ // forceComplete: true로 호출하면 재시도 없이 즉시 failed
522
+ const failed = await backend.failWorkflowRun({
523
+ workflowRunId: claimed.id,
524
+ workerId,
525
+ error: {
526
+ message: "forced failure"
527
+ },
528
+ forceComplete: true
529
+ });
530
+ expect(failed.status).toBe("failed");
531
+ expect(failed.availableAt).toBeNull();
532
+ expect(failed.finishedAt).not.toBeNull();
533
+ await teardown(backend);
534
+ });
535
+ test("stores retryPolicy in config when creating workflow run", async ()=>{
536
+ const backend = await setup();
537
+ const retryPolicy = {
538
+ maxAttempts: 10,
539
+ initialIntervalMs: 500,
540
+ backoffCoefficient: 1.5,
541
+ maximumIntervalMs: 30000
542
+ };
543
+ const created = await backend.createWorkflowRun({
544
+ workflowName: randomUUID(),
545
+ version: null,
546
+ idempotencyKey: null,
547
+ input: null,
548
+ config: {
549
+ existingKey: "existingValue"
550
+ },
551
+ context: null,
552
+ availableAt: null,
553
+ deadlineAt: null,
554
+ retryPolicy
555
+ });
556
+ // config에 retryPolicy가 저장되어 있는지 확인
557
+ const config = created.config;
558
+ expect(config.existingKey).toBe("existingValue");
559
+ expect(config.retryPolicy).toEqual(retryPolicy);
560
+ await teardown(backend);
561
+ });
456
562
  });
457
563
  describe("createStepAttempt()", ()=>{
458
564
  test("creates a step attempt", async ()=>{