| 278 | * `OptimisticConcurrencyException` when no row matches the prior etag. |
| 279 | */ |
| 280 | const replaceExec = ( |
| 281 | state: ExecState, |
| 282 | next: Partial<Omit<ExecState, "executionId" | "etag" | "workflowName" | "payload" | "parent">> |
| 283 | ) => |
| 284 | Effect |
| 285 | .gen(function*() { |
| 286 | const newEtag = randomUUID() |
| 287 | const merged = { ...state, ...next, etag: newEtag } |
| 288 | const rows = yield* exec( |
| 289 | `UPDATE "${execTable}" |
| 290 | SET status = ?, |
| 291 | suspended = ?, |
| 292 | interrupted = ?, |
| 293 | completed_result = ?, |
| 294 | worker = ?, |
| 295 | lease_expires_at = ?, |
| 296 | etag = ? |
| 297 | WHERE execution_id = ? AND etag = ? |
| 298 | RETURNING etag`, |
| 299 | [ |
| 300 | merged.status, |
| 301 | merged.suspended ? 1 : 0, |
| 302 | merged.interrupted ? 1 : 0, |
| 303 | merged.completedResult ?? null, |
| 304 | merged.worker ?? null, |
| 305 | merged.leaseExpiresAt ?? null, |
| 306 | newEtag, |
| 307 | state.executionId, |
| 308 | state.etag |
| 309 | ] |
| 310 | ) |
| 311 | if ((rows as ReadonlyArray<unknown>).length === 0) { |
| 312 | return yield* new OptimisticConcurrencyException({ |
| 313 | type: "workflow.exec", |
| 314 | id: state.executionId, |
| 315 | code: 412 |
| 316 | }) |
| 317 | } |
| 318 | return merged |
| 319 | }) |
| 320 | .pipe(annotate("replaceExec", state.executionId)) |
| 321 | |
| 322 | const createExec = (initial: ExecState): Effect.Effect<boolean> => |
| 323 | exec( |