@dbos-inc/dbos-sdk 4.26.7-preview → 4.26.8-preview

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.
@@ -564,14 +564,12 @@ class SystemDatabase {
564
564
  await this.pool.end();
565
565
  }
566
566
  // ==================== Workflow Status ====================
567
- async initWorkflowStatus(initStatus, ownerXid, options) {
567
+ async initWorkflowStatus(initStatus, ownerXid) {
568
568
  const client = await this.pool.connect();
569
569
  let shouldCommit = false;
570
570
  try {
571
571
  await client.query('BEGIN ISOLATION LEVEL READ COMMITTED');
572
- // Moving from enqueued to pending asks to increment recovery attempts... rather than in the recovery process
573
- // where it moves from pending back to enqueued.
574
- const resRow = await this.insertWorkflowStatus(client, initStatus, ownerXid, !!options?.isRecoveryRequest || !!options?.isDequeuedRequest);
572
+ const resRow = await this.insertWorkflowStatus(client, initStatus, ownerXid);
575
573
  if (resRow.name !== initStatus.workflowName) {
576
574
  const msg = `Workflow already exists with a different function name: ${resRow.name}, but the provided function name is: ${initStatus.workflowName}`;
577
575
  throw new error_1.DBOSConflictingWorkflowError(initStatus.workflowUUID, msg);
@@ -590,31 +588,12 @@ class SystemDatabase {
590
588
  }
591
589
  const status = resRow.status;
592
590
  const deadlineEpochMS = resRow.workflow_deadline_epoch_ms ?? undefined;
593
- // If there is an existing DB record and we aren't here to recover it,
594
- // leave it be. Roll back the change to max recovery attempts.
595
- if (ownerXid !== resRow.owner_xid && !options?.isRecoveryRequest && !options?.isDequeuedRequest) {
596
- // It is not clear if getting the handle should throw the error, or getting the result from the handle should error.
597
- // Current precedent is the former.
598
- if (status === workflow_1.StatusString.MAX_RECOVERY_ATTEMPTS_EXCEEDED) {
599
- throw new error_1.DBOSMaxRecoveryAttemptsExceededError(initStatus.workflowUUID, options?.maxRetries ?? -1);
600
- }
591
+ // If there is an existing DB record and we aren't here to recover it, leave it be.
592
+ if (ownerXid !== resRow.owner_xid) {
601
593
  return { status, deadlineEpochMS, shouldExecuteOnThisExecutor: false, serialization: resRow.serialization };
602
594
  }
603
- // Upsert above already set executor assignment and incremented the recovery attempt
595
+ // Upsert above already set executor assignment
604
596
  shouldCommit = true;
605
- // recovery_attempt means "attempts" (we kept the name for backward compatibility). It's default value is 1.
606
- // Every time we init the status, we increment `recovery_attempts` by 1.
607
- // Thus, when this number becomes equal to `maxRetries + 1`, we should mark the workflow as `MAX_RECOVERY_ATTEMPTS_EXCEEDED`.
608
- const attempts = resRow.recovery_attempts;
609
- if (options?.maxRetries && attempts > options?.maxRetries + 1) {
610
- await this.updateWorkflowStatus(client, initStatus.workflowUUID, workflow_1.StatusString.MAX_RECOVERY_ATTEMPTS_EXCEEDED, {
611
- where: { status: workflow_1.StatusString.PENDING },
612
- throwOnFailure: false,
613
- update: { resetDeduplicationID: true },
614
- });
615
- throw new error_1.DBOSMaxRecoveryAttemptsExceededError(initStatus.workflowUUID, options.maxRetries);
616
- }
617
- this.logger.debug(`Workflow ${initStatus.workflowUUID} attempt number: ${attempts}.`);
618
597
  return {
619
598
  status,
620
599
  deadlineEpochMS,
@@ -637,6 +616,19 @@ class SystemDatabase {
637
616
  }
638
617
  }
639
618
  }
619
+ /** Move claimed workflows that exhausted their attempts off the queue, leaving rows others have moved on alone. */
620
+ async deadLetterWorkflows(workflowIDs, minRecoveryAttempts) {
621
+ if (workflowIDs.length === 0)
622
+ return;
623
+ await this.pool.query(`UPDATE "${this.schemaName}".workflow_status
624
+ SET status = $1,
625
+ deduplication_id = NULL,
626
+ started_at_epoch_ms = NULL,
627
+ queue_name = NULL,
628
+ updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint,
629
+ completed_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint
630
+ WHERE workflow_uuid = ANY($2::text[]) AND status = $3 AND recovery_attempts >= $4`, [workflow_1.StatusString.MAX_RECOVERY_ATTEMPTS_EXCEEDED, workflowIDs, workflow_1.StatusString.PENDING, minRecoveryAttempts]);
631
+ }
640
632
  /** Highest created_at among still-active rows per partition key, used to seed the in-memory cursor. */
641
633
  async #maxPartitionKeyCreatedAt(keys) {
642
634
  const maxima = new Map();
@@ -832,24 +824,17 @@ class SystemDatabase {
832
824
  }
833
825
  // Recovery re-enqueues rather than executing directly so the queue's atomic dequeue admits exactly one runner, and the executor ID predicate rejects sweeps for rows a live executor has already claimed.
834
826
  async reenqueueWorkflowsForRecovery(executorID, appVersion, recoveryQueueName) {
835
- const params = [
836
- workflow_1.StatusString.ENQUEUED,
837
- Date.now(),
838
- recoveryQueueName,
839
- workflow_1.StatusString.PENDING,
840
- executorID,
841
- appVersion,
842
- ];
827
+ const params = [workflow_1.StatusString.ENQUEUED, recoveryQueueName, workflow_1.StatusString.PENDING, executorID, appVersion];
843
828
  // executor_id defaults to "local", so it collides across applications.
844
829
  const scope = this.#appNameFilter('application_name', this.appName, params);
845
830
  const result = await this.pool.query(`UPDATE "${this.schemaName}".workflow_status
846
831
  SET started_at_epoch_ms = NULL,
847
832
  status = $1,
848
- updated_at = $2,
849
- queue_name = COALESCE(queue_name, $3)
850
- WHERE status = $4
851
- AND executor_id = $5
852
- AND application_version = $6
833
+ updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint,
834
+ queue_name = COALESCE(queue_name, $2)
835
+ WHERE status = $3
836
+ AND executor_id = $4
837
+ AND application_version = $5
853
838
  AND ${scope}
854
839
  RETURNING workflow_uuid`, params);
855
840
  return result.rows.map((row) => row.workflow_uuid);
@@ -879,12 +864,33 @@ class SystemDatabase {
879
864
  return json ? JSON.parse(json) : null;
880
865
  }
881
866
  }
867
+ /** Max IDs per {@link getWorkflowStatuses} fetch: listWorkflows binds one parameter per ID. */
868
+ statusFetchChunkSize = 500;
869
+ // Retried per chunk so a reconnect refetches one chunk, not every chunk before it.
870
+ async fetchWorkflowStatusChunk(workflowIDs) {
871
+ return await this.listWorkflows({ workflowIDs, loadInput: true, loadOutput: false });
872
+ }
873
+ /** Fetch many statuses in as few round trips as possible. IDs with no row are omitted. */
874
+ async getWorkflowStatuses(workflowIDs) {
875
+ const statuses = new Map();
876
+ for (let start = 0; start < workflowIDs.length; start += this.statusFetchChunkSize) {
877
+ for (const status of await this.fetchWorkflowStatusChunk(workflowIDs.slice(start, start + this.statusFetchChunkSize))) {
878
+ statuses.set(status.workflowUUID, status);
879
+ }
880
+ }
881
+ return statuses;
882
+ }
882
883
  // Only used in tests
883
884
  async setWorkflowStatus(workflowID, status, resetRecoveryAttempts, internalOptions) {
884
885
  const client = await this.pool.connect();
885
886
  try {
886
887
  await this.updateWorkflowStatus(client, workflowID, status, {
887
- update: { resetRecoveryAttempts, resetNameTo: internalOptions?.updateName },
888
+ update: {
889
+ resetRecoveryAttempts,
890
+ resetNameTo: internalOptions?.updateName,
891
+ queueName: internalOptions?.queueName,
892
+ resetStartedAtEpochMs: internalOptions?.resetStartedAtEpochMs,
893
+ },
888
894
  });
889
895
  }
890
896
  finally {
@@ -1023,15 +1029,15 @@ class SystemDatabase {
1023
1029
  }
1024
1030
  async setWorkflowPriority(workflowID, priority) {
1025
1031
  await this.pool.query(`UPDATE "${this.schemaName}".workflow_status
1026
- SET priority = $1, updated_at = $2
1027
- WHERE workflow_uuid = $3
1028
- AND status IN ($4, $5)`, [priority, Date.now(), workflowID, workflow_1.StatusString.ENQUEUED, workflow_1.StatusString.DELAYED]);
1032
+ SET priority = $1, updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint
1033
+ WHERE workflow_uuid = $2
1034
+ AND status IN ($3, $4)`, [priority, workflowID, workflow_1.StatusString.ENQUEUED, workflow_1.StatusString.DELAYED]);
1029
1035
  }
1030
1036
  async setWorkflowDelay(workflowID, delayUntilEpochMS) {
1031
1037
  await this.pool.query(`UPDATE "${this.schemaName}".workflow_status
1032
- SET delay_until_epoch_ms = $1, updated_at = $2
1033
- WHERE workflow_uuid = $3
1034
- AND status = $4`, [delayUntilEpochMS, Date.now(), workflowID, workflow_1.StatusString.DELAYED]);
1038
+ SET delay_until_epoch_ms = $1, updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint
1039
+ WHERE workflow_uuid = $2
1040
+ AND status = $3`, [delayUntilEpochMS, workflowID, workflow_1.StatusString.DELAYED]);
1035
1041
  }
1036
1042
  /**
1037
1043
  * Extend an existing debounced DELAYED workflow's delay and update its inputs, atomically.
@@ -1071,7 +1077,6 @@ class SystemDatabase {
1071
1077
  params.delayUntilEpochMS,
1072
1078
  params.input,
1073
1079
  params.serialization,
1074
- Date.now(),
1075
1080
  params.workflowName,
1076
1081
  classNameOrNull,
1077
1082
  params.queueName,
@@ -1087,12 +1092,13 @@ class SystemDatabase {
1087
1092
  THEN debounce_deadline_epoch_ms
1088
1093
  ELSE $1
1089
1094
  END,
1090
- inputs = $2, serialization = $3, updated_at = $4,
1095
+ inputs = $2, serialization = $3,
1096
+ updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint,
1091
1097
  -- Claim it for the target, as its dequeue would: left unclaimed, every peer coalesces onto the one workflow and the last inputs win.
1092
- application_name = COALESCE(application_name, $10)
1093
- WHERE name = $5 AND class_name IS NOT DISTINCT FROM $6
1094
- AND queue_name = $7 AND deduplication_id = $8
1095
- AND status = $9 AND is_debounced = TRUE
1098
+ application_name = COALESCE(application_name, $9)
1099
+ WHERE name = $4 AND class_name IS NOT DISTINCT FROM $5
1100
+ AND queue_name = $6 AND deduplication_id = $7
1101
+ AND status = $8 AND is_debounced = TRUE
1096
1102
  AND ${ownScope}
1097
1103
  RETURNING workflow_uuid`, updateParams);
1098
1104
  if (updated.rows.length > 0) {
@@ -2216,7 +2222,7 @@ class SystemDatabase {
2216
2222
  // Only what this application would dequeue: a peer's debounce key is not ours to clear.
2217
2223
  const scope = this.#appNameFilter('application_name', this.appName, params);
2218
2224
  await this.pool.query(`UPDATE "${this.schemaName}".workflow_status
2219
- SET status = $1, updated_at = $2,
2225
+ SET status = $1, updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint,
2220
2226
  deduplication_id = CASE WHEN is_debounced THEN NULL ELSE deduplication_id END
2221
2227
  WHERE status = $3 AND delay_until_epoch_ms <= $2 AND ${scope}`, params);
2222
2228
  }
@@ -2248,7 +2254,6 @@ class SystemDatabase {
2248
2254
  return rows.map((row) => row.pk);
2249
2255
  }
2250
2256
  async findAndMarkStartableWorkflows(queue, executorID, appVersion, queuePartitionKey) {
2251
- const startTimeMs = Date.now();
2252
2257
  const limiterPeriodMS = queue.rateLimit ? queue.rateLimit.periodSec * 1000 : 0;
2253
2258
  const claimedIDs = [];
2254
2259
  const localRunningForQueue = this.countRunningWorkflowsForQueue(queue.name, queuePartitionKey);
@@ -2275,7 +2280,7 @@ class SystemDatabase {
2275
2280
  queue.name,
2276
2281
  workflow_1.StatusString.ENQUEUED,
2277
2282
  workflow_1.StatusString.DELAYED,
2278
- startTimeMs - limiterPeriodMS,
2283
+ limiterPeriodMS,
2279
2284
  ...partitionParams,
2280
2285
  ];
2281
2286
  // Count only what this application would dequeue, matching the select below.
@@ -2284,7 +2289,8 @@ class SystemDatabase {
2284
2289
  WHERE queue_name = $1
2285
2290
  AND rate_limited = TRUE
2286
2291
  AND status NOT IN ($2, $3)
2287
- AND started_at_epoch_ms > $4
2292
+ -- Database clock on both sides, as the claim stamps started_at_epoch_ms with it.
2293
+ AND started_at_epoch_ms > (EXTRACT(epoch FROM now()) * 1000)::bigint - $4
2288
2294
  AND ${scope}
2289
2295
  ${partitionFilter.replace('$PARTITION', '$5')}`, params);
2290
2296
  numRecentQueries = Number(countResult.rows[0].count);
@@ -2297,9 +2303,13 @@ class SystemDatabase {
2297
2303
  // If there is a global or local concurrency limit N, select only the N oldest enqueued
2298
2304
  // functions, else select all of them.
2299
2305
  let maxTasks = Infinity;
2306
+ if (queue.rateLimit) {
2307
+ // Bound the claim by the limiter's remaining slots so a backlogged queue locks only what it can start.
2308
+ maxTasks = Math.max(0, queue.rateLimit.limitPerPeriod - numRecentQueries);
2309
+ }
2300
2310
  if (queue.workerConcurrency !== undefined) {
2301
2311
  // Use the in-memory registry for this worker's running count — avoids a DB round trip.
2302
- maxTasks = Math.max(0, queue.workerConcurrency - localRunningForQueue);
2312
+ maxTasks = Math.min(maxTasks, Math.max(0, queue.workerConcurrency - localRunningForQueue));
2303
2313
  }
2304
2314
  if (queue.concurrency !== undefined) {
2305
2315
  // Global concurrency still requires a DB query since other workers may be running workflows too.
@@ -2327,7 +2337,10 @@ class SystemDatabase {
2327
2337
  const versionClause = isLatestVersion
2328
2338
  ? '(application_version = $3 OR application_version IS NULL)'
2329
2339
  : 'application_version = $3';
2330
- const lockMode = queue.concurrency ? 'FOR UPDATE NOWAIT' : 'FOR UPDATE SKIP LOCKED';
2340
+ // A limit shared across processes needs a consistent view of the table: NOWAIT makes an
2341
+ // overlapping dequeuer abort rather than claim the next rows and spend the same budget twice.
2342
+ const sharedBudget = queue.concurrency !== undefined || queue.rateLimit !== undefined;
2343
+ const lockMode = sharedBudget ? 'FOR UPDATE NOWAIT' : 'FOR UPDATE SKIP LOCKED';
2331
2344
  const limitClause = maxTasks !== Infinity ? `LIMIT ${maxTasks}` : '';
2332
2345
  const selectParams = [workflow_1.StatusString.ENQUEUED, queue.name, appVersion, ...partitionParams];
2333
2346
  const selectScope = this.#appNameFilter('application_name', this.appName, selectParams);
@@ -2349,43 +2362,41 @@ class SystemDatabase {
2349
2362
  await (0, debugpoint_1.debugTriggerPoint)(debugpoint_1.DEBUG_TRIGGER_FIND_AND_MARK_AFTER_SELECT);
2350
2363
  // Start the workflows
2351
2364
  const workflowIDs = rows.map((row) => row.workflow_uuid);
2352
- for (const id of workflowIDs) {
2353
- // If we have a rate limit, stop starting functions when the number
2354
- // of functions started this period exceeds the limit.
2355
- if (queue.rateLimit && claimedIDs.length + numRecentQueries >= queue.rateLimit.limitPerPeriod) {
2356
- break;
2357
- }
2365
+ if (workflowIDs.length > 0) {
2358
2366
  // Start the functions by marking them as pending and updating their executor IDs.
2359
- // Only claim the workflow if the UPDATE actually transitioned an ENQUEUED row —
2360
- // otherwise another worker won the race and we must not re-dispatch it.
2361
2367
  const updateParams = [
2362
2368
  workflow_1.StatusString.PENDING,
2363
2369
  executorID,
2364
2370
  appVersion,
2365
- startTimeMs,
2366
2371
  queue.rateLimit !== undefined,
2367
- id,
2372
+ workflowIDs,
2368
2373
  workflow_1.StatusString.ENQUEUED,
2369
2374
  // Claim an unclaimed row for this application; a nameless dequeuer leaves ownership untouched.
2370
2375
  this.appName ?? null,
2371
2376
  ];
2372
2377
  // Re-check ownership alongside status, as the partitioned claim guard does.
2373
2378
  const claimScope = this.#appNameFilter('application_name', this.appName, updateParams);
2374
- const updateRes = await client.query(`UPDATE "${this.schemaName}".workflow_status
2379
+ // RETURNING reports exactly the rows this statement flipped, so a row another worker won is absent.
2380
+ const flippedResult = await client.query(`UPDATE "${this.schemaName}".workflow_status
2375
2381
  SET status = $1,
2376
2382
  executor_id = $2,
2377
2383
  application_version = $3,
2378
- started_at_epoch_ms = $4,
2379
- rate_limited = $5,
2380
- application_name = COALESCE(application_name, $8),
2384
+ started_at_epoch_ms = (EXTRACT(epoch FROM now()) * 1000)::bigint,
2385
+ rate_limited = $4,
2386
+ application_name = COALESCE(application_name, $7),
2387
+ recovery_attempts = recovery_attempts + 1,
2388
+ updated_at = (EXTRACT(epoch FROM now()) * 1000)::bigint,
2381
2389
  workflow_deadline_epoch_ms = CASE
2382
2390
  WHEN workflow_timeout_ms IS NOT NULL AND workflow_deadline_epoch_ms IS NULL
2383
2391
  THEN (EXTRACT(epoch FROM now()) * 1000)::bigint + workflow_timeout_ms
2384
2392
  ELSE workflow_deadline_epoch_ms
2385
2393
  END
2386
- WHERE workflow_uuid = $6 AND status = $7 AND ${claimScope}`, updateParams);
2387
- if ((updateRes.rowCount ?? 0) > 0) {
2388
- claimedIDs.push(id);
2394
+ WHERE workflow_uuid = ANY($5::text[]) AND status = $6 AND ${claimScope}
2395
+ RETURNING workflow_uuid`, updateParams);
2396
+ const flippedIDs = new Set(flippedResult.rows.map((row) => row.workflow_uuid));
2397
+ for (const id of workflowIDs) {
2398
+ if (flippedIDs.has(id))
2399
+ claimedIDs.push(id);
2389
2400
  }
2390
2401
  }
2391
2402
  await client.query('COMMIT');
@@ -2408,7 +2419,6 @@ class SystemDatabase {
2408
2419
  throw new error_1.DBOSError(`Batched partitioned dequeue requires a queue with concurrency 1 and no rate limit: ${queue.name}`);
2409
2420
  }
2410
2421
  // workerConcurrency needs no handling here: dispatch routes 0 (the pause-dequeue idiom) to the fallback path, and validation caps any other value at concurrency=1, which the PENDING gate already enforces globally.
2411
- const startTimeMs = Date.now();
2412
2422
  const client = await this.pool.connect();
2413
2423
  try {
2414
2424
  await client.query('BEGIN');
@@ -2495,7 +2505,6 @@ class SystemDatabase {
2495
2505
  workflow_1.StatusString.ENQUEUED,
2496
2506
  queue.name,
2497
2507
  appVersion,
2498
- startTimeMs,
2499
2508
  // Claim the row, as the unpartitioned dequeue does.
2500
2509
  this.appName ?? null,
2501
2510
  ];
@@ -2505,9 +2514,11 @@ class SystemDatabase {
2505
2514
  SET status = $1,
2506
2515
  executor_id = $2,
2507
2516
  application_version = $6,
2508
- started_at_epoch_ms = $7,
2517
+ started_at_epoch_ms = (EXTRACT(epoch FROM now()) * 1000)::bigint,
2509
2518
  rate_limited = FALSE,
2510
- application_name = COALESCE(application_name, $8),
2519
+ application_name = COALESCE(application_name, $7),
2520
+ recovery_attempts = recovery_attempts + 1,
2521
+ updated_at = (EXTRACT(epoch FROM now()) * 1000)::bigint,
2511
2522
  workflow_deadline_epoch_ms = CASE
2512
2523
  WHEN workflow_timeout_ms IS NOT NULL AND workflow_deadline_epoch_ms IS NULL
2513
2524
  THEN (EXTRACT(epoch FROM now()) * 1000)::bigint + workflow_timeout_ms
@@ -3550,7 +3561,7 @@ class SystemDatabase {
3550
3561
  return { queues, schedules, versions, workflows: inFlight + terminal, steps };
3551
3562
  }
3552
3563
  // ==================== Internal ====================
3553
- async insertWorkflowStatus(client, initStatus, ownerXid, incrementAttempts = false) {
3564
+ async insertWorkflowStatus(client, initStatus, ownerXid) {
3554
3565
  try {
3555
3566
  const { rows } = await client.query(`INSERT INTO "${this.schemaName}".workflow_status (
3556
3567
  workflow_uuid,
@@ -3566,9 +3577,7 @@ class SystemDatabase {
3566
3577
  executor_id,
3567
3578
  application_version,
3568
3579
  application_id,
3569
- created_at,
3570
3580
  recovery_attempts,
3571
- updated_at,
3572
3581
  workflow_timeout_ms,
3573
3582
  workflow_deadline_epoch_ms,
3574
3583
  inputs,
@@ -3585,21 +3594,16 @@ class SystemDatabase {
3585
3594
  debounce_deadline_epoch_ms,
3586
3595
  is_debounced,
3587
3596
  application_name
3588
- ) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $26, $27, $28, $29, $30, $31, $32, $33)
3597
+ ) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30)
3589
3598
  ON CONFLICT (workflow_uuid)
3590
3599
  DO UPDATE SET
3591
- recovery_attempts = CASE
3592
- WHEN workflow_status.status != '${workflow_1.StatusString.ENQUEUED}' AND workflow_status.status != '${workflow_1.StatusString.DELAYED}'
3593
- THEN workflow_status.recovery_attempts + $25
3594
- ELSE workflow_status.recovery_attempts
3595
- END,
3596
- updated_at = EXCLUDED.updated_at,
3600
+ updated_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint,
3597
3601
  executor_id = CASE
3598
3602
  WHEN EXCLUDED.status != '${workflow_1.StatusString.ENQUEUED}' AND EXCLUDED.status != '${workflow_1.StatusString.DELAYED}'
3599
3603
  THEN EXCLUDED.executor_id
3600
3604
  ELSE workflow_status.executor_id
3601
3605
  END
3602
- RETURNING recovery_attempts, status, name, class_name, config_name, queue_name, workflow_deadline_epoch_ms, executor_id, owner_xid, serialization`, [
3606
+ RETURNING status, name, class_name, config_name, queue_name, workflow_deadline_epoch_ms, executor_id, owner_xid, serialization`, [
3603
3607
  initStatus.workflowUUID,
3604
3608
  initStatus.status,
3605
3609
  initStatus.workflowName,
@@ -3614,9 +3618,7 @@ class SystemDatabase {
3614
3618
  initStatus.executorId,
3615
3619
  initStatus.applicationVersion ?? null,
3616
3620
  initStatus.applicationID,
3617
- initStatus.createdAt,
3618
3621
  initStatus.status === workflow_1.StatusString.ENQUEUED || initStatus.status === workflow_1.StatusString.DELAYED ? 0 : 1,
3619
- initStatus.updatedAt ?? Date.now(),
3620
3622
  initStatus.timeoutMS ?? null,
3621
3623
  initStatus.deadlineEpochMS ?? null,
3622
3624
  initStatus.input ?? null,
@@ -3625,7 +3627,6 @@ class SystemDatabase {
3625
3627
  initStatus.queuePartitionKey ?? null,
3626
3628
  initStatus.forkedFrom ?? null,
3627
3629
  initStatus.parentWorkflowID ?? null,
3628
- (incrementAttempts ?? false) ? 1 : 0,
3629
3630
  initStatus.serialization,
3630
3631
  ownerXid,
3631
3632
  initStatus.delayUntilEpochMS ?? null,
@@ -3957,9 +3958,15 @@ exports.SystemDatabase = SystemDatabase;
3957
3958
  __decorate([
3958
3959
  dbRetry(),
3959
3960
  __metadata("design:type", Function),
3960
- __metadata("design:paramtypes", [Object, Object, Object]),
3961
+ __metadata("design:paramtypes", [Object, Object]),
3961
3962
  __metadata("design:returntype", Promise)
3962
3963
  ], SystemDatabase.prototype, "initWorkflowStatus", null);
3964
+ __decorate([
3965
+ dbRetry(),
3966
+ __metadata("design:type", Function),
3967
+ __metadata("design:paramtypes", [Array, Number]),
3968
+ __metadata("design:returntype", Promise)
3969
+ ], SystemDatabase.prototype, "deadLetterWorkflows", null);
3963
3970
  __decorate([
3964
3971
  dbRetry(),
3965
3972
  __metadata("design:type", Function),
@@ -3978,6 +3985,12 @@ __decorate([
3978
3985
  __metadata("design:paramtypes", [String, String, Number]),
3979
3986
  __metadata("design:returntype", Promise)
3980
3987
  ], SystemDatabase.prototype, "getWorkflowStatus", null);
3988
+ __decorate([
3989
+ dbRetry(),
3990
+ __metadata("design:type", Function),
3991
+ __metadata("design:paramtypes", [Array]),
3992
+ __metadata("design:returntype", Promise)
3993
+ ], SystemDatabase.prototype, "fetchWorkflowStatusChunk", null);
3981
3994
  __decorate([
3982
3995
  dbRetry(),
3983
3996
  __metadata("design:type", Function),