@brandboostinggmbh/observable-workflows 0.19.0-beta.4 → 0.20.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -358,6 +358,48 @@ The library provides comprehensive error handling:
358
358
  - **Workflow Failures**: Marked with appropriate status and error information
359
359
  - **Retry Logic**: Failed workflows can be retried with step result reuse
360
360
  - **Validation**: Input validation and type checking
361
+ - **Automatic D1 Retry**: Built-in retry logic with exponential backoff for transient D1 network errors
362
+
363
+ ### D1 Network Error Retry
364
+
365
+ The library automatically retries D1 database operations that fail due to transient network errors (e.g., "D1_ERROR: Network connection lost"). This makes workflows more resilient to temporary network issues.
366
+
367
+ **Default Retry Configuration:**
368
+ - Maximum retry attempts: 3
369
+ - Initial delay: 100ms
370
+ - Maximum delay: 5000ms
371
+ - Exponential backoff multiplier: 2
372
+ - Jitter: Enabled
373
+
374
+ **Custom Retry Configuration:**
375
+
376
+ You can customize the retry behavior when creating the workflow context:
377
+
378
+ ```typescript
379
+ const workflowContext = createWorkflowContext({
380
+ D1: env.D1,
381
+ retryConfig: {
382
+ maxAttempts: 5, // Retry up to 5 times
383
+ initialDelayMs: 200, // Start with 200ms delay
384
+ maxDelayMs: 10000, // Cap delay at 10 seconds
385
+ backoffMultiplier: 3, // Triple the delay each time
386
+ useJitter: true, // Add randomization to prevent thundering herd
387
+ },
388
+ })
389
+ ```
390
+
391
+ **Manual Retry for Custom Operations:**
392
+
393
+ You can also use the retry utility directly for custom D1 operations:
394
+
395
+ ```typescript
396
+ import { retryD1Operation } from '@brandboostinggmbh/observable-workflows'
397
+
398
+ const result = await retryD1Operation(
399
+ () => db.prepare("SELECT * FROM custom_table").first(),
400
+ { maxAttempts: 3, initialDelayMs: 100 }
401
+ )
402
+ ```
361
403
 
362
404
  ## Best Practices
363
405
 
package/dist/index.d.ts CHANGED
@@ -18,6 +18,38 @@ declare function createStepContext(context: StepContextOptions): Promise<{
18
18
  declare function ensureTables(db: D1Database): Promise<void>;
19
19
  //#endregion
20
20
  //#region src/observableWorkflows/helperFunctions.d.ts
21
+ /**
22
+ * Configuration for retry behavior on D1 operations
23
+ */
24
+ interface RetryConfig$1 {
25
+ /** Maximum number of retry attempts (default: 3) */
26
+ maxAttempts?: number;
27
+ /** Initial delay in milliseconds (default: 100) */
28
+ initialDelayMs?: number;
29
+ /** Maximum delay in milliseconds (default: 5000) */
30
+ maxDelayMs?: number;
31
+ /** Multiplier for exponential backoff (default: 2) */
32
+ backoffMultiplier?: number;
33
+ /** Whether to add jitter to delays (default: true) */
34
+ useJitter?: boolean;
35
+ }
36
+ /**
37
+ * Retry a D1 operation with exponential backoff for transient network errors
38
+ *
39
+ * @param operation - The async operation to retry
40
+ * @param config - Retry configuration options
41
+ * @returns The result of the operation
42
+ * @throws The last error if all retry attempts fail
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const result = await retryD1Operation(
47
+ * () => db.prepare("SELECT * FROM table").first(),
48
+ * { maxAttempts: 5, initialDelayMs: 200 }
49
+ * )
50
+ * ```
51
+ */
52
+ declare function retryD1Operation<T>(operation: () => Promise<T>, config?: RetryConfig$1): Promise<T>;
21
53
  declare function finalizeWorkflowRecord(options: InternalWorkflowContextOptions, {
22
54
  workflowStatus,
23
55
  endTime,
@@ -605,11 +637,25 @@ type ExternalBlobStorage = {
605
637
  get: (id: string) => Promise<string>;
606
638
  delete: (...keys: string[]) => Promise<number>;
607
639
  };
640
+ type RetryConfig = {
641
+ /** Maximum number of retry attempts (default: 3) */
642
+ maxAttempts?: number;
643
+ /** Initial delay in milliseconds (default: 100) */
644
+ initialDelayMs?: number;
645
+ /** Maximum delay in milliseconds (default: 5000) */
646
+ maxDelayMs?: number;
647
+ /** Multiplier for exponential backoff (default: 2) */
648
+ backoffMultiplier?: number;
649
+ /** Whether to add jitter to delays (default: true) */
650
+ useJitter?: boolean;
651
+ };
608
652
  type WorkflowContextOptions = {
609
653
  D1: D1Database;
610
654
  idFactory?: () => string;
611
655
  serializer?: Serializer;
612
656
  externalBlobStorage?: ExternalBlobStorage;
657
+ /** Optional retry configuration for D1 operations (defaults: maxAttempts: 3, initialDelayMs: 100, maxDelayMs: 5000, backoffMultiplier: 2, useJitter: true) */
658
+ retryConfig?: RetryConfig;
613
659
  };
614
660
  type InternalWorkflowContextOptions = WorkflowContextOptions & Required<Pick<WorkflowContextOptions, 'serializer' | 'idFactory'>>;
615
661
  type WorkflowContextInstance = {
@@ -906,4 +952,4 @@ type R2ExternalBlobStorageOptions = {
906
952
  */
907
953
  declare function createR2ExternalBlobStorage(options: R2ExternalBlobStorageOptions): ExternalBlobStorage;
908
954
  //#endregion
909
- export { ConsoleWrapper, DateRangeFilter, ExternalBlobStorage, HandleWorkflowQueueMessageParams, InternalWorkflowContextOptions, Log, PossibleValueTypeNames, PossibleValueTypes, PropertyFilter, QueueWorkflowContextOptions, R2ExternalBlobStorageOptions, Serializer, Step, StepContextOptions, StepCtx, StepWorkflowStatus, StringFilter, ValueTypeMap, WorkflowContext, WorkflowContextInstance, WorkflowContextOptions, WorkflowEnqueueBatchItem, WorkflowFilter, WorkflowFunction, WorkflowProperty, WorkflowPropertyDefinition, WorkflowQueueMessage, WorkflowRun, WorkflowStatus, createCleanupManager, createLogAccessor, createQueueWorkflowContext, createR2ExternalBlobStorage, createStepContext, createWorkflowContext, createWorkflowDependencies, createWorkflowDependency, defaultIdFactory, defaultSerializer, defineWorkflow, deleteWorkflowDependency, deserializeWithExternalStorage, ensureTables, finalizeWorkflowRecord, insertStepRecordFull, insertWorkflowRecord, pushLogToDB, serializeWithExternalStorage, tryDeserializeObj, updateWorkflow, updateWorkflowName, upsertWorkflowProperty, workflowTableRowToWorkflowRun };
955
+ export { ConsoleWrapper, DateRangeFilter, ExternalBlobStorage, HandleWorkflowQueueMessageParams, InternalWorkflowContextOptions, Log, PossibleValueTypeNames, PossibleValueTypes, PropertyFilter, QueueWorkflowContextOptions, R2ExternalBlobStorageOptions, RetryConfig, Serializer, Step, StepContextOptions, StepCtx, StepWorkflowStatus, StringFilter, ValueTypeMap, WorkflowContext, WorkflowContextInstance, WorkflowContextOptions, WorkflowEnqueueBatchItem, WorkflowFilter, WorkflowFunction, WorkflowProperty, WorkflowPropertyDefinition, WorkflowQueueMessage, WorkflowRun, WorkflowStatus, createCleanupManager, createLogAccessor, createQueueWorkflowContext, createR2ExternalBlobStorage, createStepContext, createWorkflowContext, createWorkflowDependencies, createWorkflowDependency, defaultIdFactory, defaultSerializer, defineWorkflow, deleteWorkflowDependency, deserializeWithExternalStorage, ensureTables, finalizeWorkflowRecord, insertStepRecordFull, insertWorkflowRecord, pushLogToDB, retryD1Operation, serializeWithExternalStorage, tryDeserializeObj, updateWorkflow, updateWorkflowName, upsertWorkflowProperty, workflowTableRowToWorkflowRun };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Detect the current schema version for each table
4
4
  */
5
5
  async function detectSchemaVersion(db) {
6
- const workflowTableInfo = await db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first();
6
+ const workflowTableInfo = await retryD1Operation(() => db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first());
7
7
  let workflowTable = "missing";
8
8
  if (workflowTableInfo) {
9
9
  const hasInputRef = workflowTableInfo.sql.includes("inputRef");
@@ -16,7 +16,7 @@ async function detectSchemaVersion(db) {
16
16
  else if (!hasInputRef && inputHasNotNull) workflowTable = "v1";
17
17
  else workflowTable = "v1";
18
18
  }
19
- const stepTableInfo = await db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='StepTable'`).first();
19
+ const stepTableInfo = await retryD1Operation(() => db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='StepTable'`).first());
20
20
  let stepTable = "missing";
21
21
  if (stepTableInfo) {
22
22
  const hasResultRef = stepTableInfo.sql.includes("resultRef");
@@ -26,7 +26,7 @@ async function detectSchemaVersion(db) {
26
26
  else if (hasResultRef && hasErrorRef) stepTable = "v2";
27
27
  else stepTable = "v1";
28
28
  }
29
- const logTableInfo = await db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='LogTable'`).first();
29
+ const logTableInfo = await retryD1Operation(() => db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='LogTable'`).first());
30
30
  let logTable = "missing";
31
31
  if (logTableInfo) {
32
32
  const hasStepTableCascadeFK = logTableInfo.sql.includes("FOREIGN KEY (instanceId, stepName) REFERENCES StepTable(instanceId, stepName) ON DELETE CASCADE");
@@ -34,7 +34,7 @@ async function detectSchemaVersion(db) {
34
34
  if (hasStepTableCascadeFK && hasWorkflowTableFK) logTable = "v5";
35
35
  else logTable = "v1";
36
36
  }
37
- const workflowPropertiesInfo = await db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowProperties'`).first();
37
+ const workflowPropertiesInfo = await retryD1Operation(() => db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowProperties'`).first());
38
38
  let workflowProperties = "missing";
39
39
  if (workflowPropertiesInfo) workflowProperties = "v1";
40
40
  const workflowDependenciesInfo = await db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowDependencies'`).first();
@@ -53,10 +53,10 @@ async function detectSchemaVersion(db) {
53
53
  * Adds inputRef column and makes input nullable
54
54
  */
55
55
  async function migrateWorkflowTableV1ToV2(db) {
56
- const workflowTableInfo = await db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first();
56
+ const workflowTableInfo = await retryD1Operation(() => db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first());
57
57
  const hasInputRef = workflowTableInfo.sql.includes("inputRef");
58
58
  const inputHasNotNull = workflowTableInfo.sql.includes("input TEXT NOT NULL");
59
- if (!hasInputRef || inputHasNotNull) await db.batch([
59
+ if (!hasInputRef || inputHasNotNull) await retryD1Operation(() => db.batch([
60
60
  db.prepare(`CREATE TABLE WorkflowTable_new (
61
61
  instanceId TEXT NOT NULL,
62
62
  workflowType TEXT NOT NULL,
@@ -78,16 +78,16 @@ async function migrateWorkflowTableV1ToV2(db) {
78
78
  FROM WorkflowTable`),
79
79
  db.prepare(`DROP TABLE WorkflowTable`),
80
80
  db.prepare(`ALTER TABLE WorkflowTable_new RENAME TO WorkflowTable`)
81
- ]);
81
+ ]));
82
82
  }
83
83
  /**
84
84
  * Migrate WorkflowTable from V2/V3 to V4 schema
85
85
  * Adds triggerId column with UNIQUE constraint
86
86
  */
87
87
  async function migrateWorkflowTableV2V3ToV4(db) {
88
- const workflowTableInfo = await db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first();
88
+ const workflowTableInfo = await retryD1Operation(() => db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first());
89
89
  const hasTriggerId = workflowTableInfo.sql.includes("triggerId");
90
- if (!hasTriggerId) await db.batch([
90
+ if (!hasTriggerId) await retryD1Operation(() => db.batch([
91
91
  db.prepare(`CREATE TABLE WorkflowTable_new (
92
92
  instanceId TEXT NOT NULL,
93
93
  workflowType TEXT NOT NULL,
@@ -111,7 +111,7 @@ async function migrateWorkflowTableV2V3ToV4(db) {
111
111
  FROM WorkflowTable`),
112
112
  db.prepare(`DROP TABLE WorkflowTable`),
113
113
  db.prepare(`ALTER TABLE WorkflowTable_new RENAME TO WorkflowTable`)
114
- ]);
114
+ ]));
115
115
  }
116
116
  /**
117
117
  * Migrate WorkflowTable from V4 to V6 schema
@@ -127,7 +127,7 @@ async function migrateWorkflowTableV4ToV6(db) {
127
127
  */
128
128
  async function migrateWorkflowTable(db, currentVersion) {
129
129
  if (currentVersion === "missing") {
130
- await db.prepare(`CREATE TABLE WorkflowTable (
130
+ await retryD1Operation(() => db.prepare(`CREATE TABLE WorkflowTable (
131
131
  instanceId TEXT NOT NULL,
132
132
  workflowType TEXT NOT NULL,
133
133
  workflowName TEXT NOT NULL,
@@ -144,7 +144,7 @@ async function migrateWorkflowTable(db, currentVersion) {
144
144
  triggerId TEXT,
145
145
  PRIMARY KEY (instanceId),
146
146
  UNIQUE (triggerId)
147
- )`).run();
147
+ )`).run());
148
148
  return;
149
149
  }
150
150
  if (currentVersion === "v1") {
@@ -168,7 +168,7 @@ async function migrateWorkflowTable(db, currentVersion) {
168
168
  */
169
169
  async function migrateStepTable(db, currentVersion) {
170
170
  if (currentVersion === "missing") {
171
- await db.prepare(`CREATE TABLE StepTable (
171
+ await retryD1Operation(() => db.prepare(`CREATE TABLE StepTable (
172
172
  instanceId TEXT NOT NULL,
173
173
  stepName TEXT NOT NULL,
174
174
  stepStatus TEXT NOT NULL,
@@ -182,15 +182,15 @@ async function migrateStepTable(db, currentVersion) {
182
182
  tenantId TEXT NOT NULL,
183
183
  PRIMARY KEY (instanceId, stepName),
184
184
  FOREIGN KEY (instanceId) REFERENCES WorkflowTable(instanceId) ON DELETE CASCADE
185
- )`).run();
185
+ )`).run());
186
186
  return;
187
187
  }
188
188
  if (currentVersion === "v1") {
189
- const stepTableInfo = await db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='StepTable'`).first();
189
+ const stepTableInfo = await retryD1Operation(() => db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='StepTable'`).first());
190
190
  const hasResultRef = stepTableInfo.sql.includes("resultRef");
191
191
  const hasErrorRef = stepTableInfo.sql.includes("errorRef");
192
- if (!hasResultRef) await db.prepare(`ALTER TABLE StepTable ADD COLUMN resultRef TEXT`).run();
193
- if (!hasErrorRef) await db.prepare(`ALTER TABLE StepTable ADD COLUMN errorRef TEXT`).run();
192
+ if (!hasResultRef) await retryD1Operation(() => db.prepare(`ALTER TABLE StepTable ADD COLUMN resultRef TEXT`).run());
193
+ if (!hasErrorRef) await retryD1Operation(() => db.prepare(`ALTER TABLE StepTable ADD COLUMN errorRef TEXT`).run());
194
194
  }
195
195
  }
196
196
  /**
@@ -198,7 +198,7 @@ async function migrateStepTable(db, currentVersion) {
198
198
  */
199
199
  async function migrateLogTable(db, currentVersion) {
200
200
  if (currentVersion === "missing") {
201
- await db.prepare(`CREATE TABLE LogTable (
201
+ await retryD1Operation(() => db.prepare(`CREATE TABLE LogTable (
202
202
  instanceId TEXT NOT NULL,
203
203
  stepName TEXT,
204
204
  log TEXT NOT NULL,
@@ -208,7 +208,7 @@ async function migrateLogTable(db, currentVersion) {
208
208
  tenantId TEXT NOT NULL,
209
209
  FOREIGN KEY (instanceId, stepName) REFERENCES StepTable(instanceId, stepName) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
210
210
  FOREIGN KEY (instanceId) REFERENCES WorkflowTable(instanceId) ON DELETE CASCADE
211
- )`).run();
211
+ )`).run());
212
212
  return;
213
213
  }
214
214
  }
@@ -217,7 +217,7 @@ async function migrateLogTable(db, currentVersion) {
217
217
  */
218
218
  async function migrateWorkflowPropertiesTable(db, currentVersion) {
219
219
  if (currentVersion === "missing") {
220
- await db.prepare(`CREATE TABLE WorkflowProperties (
220
+ await retryD1Operation(() => db.prepare(`CREATE TABLE WorkflowProperties (
221
221
  instanceId TEXT NOT NULL,
222
222
  key TEXT NOT NULL,
223
223
  value TEXT NOT NULL,
@@ -226,7 +226,7 @@ async function migrateWorkflowPropertiesTable(db, currentVersion) {
226
226
  PRIMARY KEY (instanceId, key),
227
227
  FOREIGN KEY (instanceId) REFERENCES WorkflowTable(instanceId)
228
228
  ON DELETE CASCADE
229
- )`).run();
229
+ )`).run());
230
230
  return;
231
231
  }
232
232
  }
@@ -251,7 +251,7 @@ async function migrateWorkflowDependenciesTable(db, currentVersion) {
251
251
  * Create necessary indexes
252
252
  */
253
253
  async function createIndexes(db) {
254
- const existingIndexes = await db.prepare(`SELECT name FROM sqlite_master WHERE type='index' AND name LIKE 'idx_%'`).all();
254
+ const existingIndexes = await retryD1Operation(() => db.prepare(`SELECT name FROM sqlite_master WHERE type='index' AND name LIKE 'idx_%'`).all());
255
255
  const existingIndexNames = new Set(existingIndexes.results?.map((row) => row.name) || []);
256
256
  const indexes = [
257
257
  {
@@ -341,7 +341,7 @@ async function createIndexes(db) {
341
341
  ];
342
342
  const preparedStatements = [];
343
343
  for (const index of indexes) if (!existingIndexNames.has(index.name)) preparedStatements.push(db.prepare(index.sql));
344
- if (preparedStatements.length > 0) await db.batch(preparedStatements);
344
+ if (preparedStatements.length > 0) await retryD1Operation(() => db.batch(preparedStatements));
345
345
  }
346
346
  /**
347
347
  * Main migration function that ensures all tables exist and are up-to-date.
@@ -350,7 +350,7 @@ async function createIndexes(db) {
350
350
  * @param db - D1Database instance
351
351
  */
352
352
  async function ensureTables(db) {
353
- await db.prepare(`PRAGMA foreign_keys = ON`).run();
353
+ await retryD1Operation(() => db.prepare(`PRAGMA foreign_keys = ON`).run());
354
354
  const schemaVersion = await detectSchemaVersion(db);
355
355
  await migrateWorkflowTable(db, schemaVersion.workflowTable);
356
356
  await migrateStepTable(db, schemaVersion.stepTable);
@@ -362,16 +362,85 @@ async function ensureTables(db) {
362
362
 
363
363
  //#endregion
364
364
  //#region src/observableWorkflows/helperFunctions.ts
365
+ /**
366
+ * Default retry configuration for D1 operations
367
+ */
368
+ const DEFAULT_RETRY_CONFIG = {
369
+ maxAttempts: 3,
370
+ initialDelayMs: 100,
371
+ maxDelayMs: 5e3,
372
+ backoffMultiplier: 2,
373
+ useJitter: true
374
+ };
375
+ /**
376
+ * Check if an error is a transient D1 network error that should be retried
377
+ */
378
+ function isRetriableD1Error(error) {
379
+ if (!error) return false;
380
+ const errorMessage = error.message || String(error);
381
+ const errorString = errorMessage.toLowerCase();
382
+ return errorString.includes("network connection lost") || errorString.includes("d1_error") || errorString.includes("connection reset") || errorString.includes("econnreset") || errorString.includes("timeout") || errorString.includes("etimedout");
383
+ }
384
+ /**
385
+ * Calculate delay with exponential backoff and optional jitter
386
+ */
387
+ function calculateDelay(attempt, config) {
388
+ const exponentialDelay = Math.min(config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt), config.maxDelayMs);
389
+ if (config.useJitter) return Math.floor(Math.random() * exponentialDelay);
390
+ return exponentialDelay;
391
+ }
392
+ /**
393
+ * Sleep for the specified number of milliseconds
394
+ */
395
+ function sleep(ms) {
396
+ return new Promise((resolve) => setTimeout(resolve, ms));
397
+ }
398
+ /**
399
+ * Retry a D1 operation with exponential backoff for transient network errors
400
+ *
401
+ * @param operation - The async operation to retry
402
+ * @param config - Retry configuration options
403
+ * @returns The result of the operation
404
+ * @throws The last error if all retry attempts fail
405
+ *
406
+ * @example
407
+ * ```typescript
408
+ * const result = await retryD1Operation(
409
+ * () => db.prepare("SELECT * FROM table").first(),
410
+ * { maxAttempts: 5, initialDelayMs: 200 }
411
+ * )
412
+ * ```
413
+ */
414
+ async function retryD1Operation(operation, config = {}) {
415
+ const finalConfig = {
416
+ ...DEFAULT_RETRY_CONFIG,
417
+ ...config
418
+ };
419
+ let lastError;
420
+ for (let attempt = 0; attempt < finalConfig.maxAttempts; attempt++) try {
421
+ return await operation();
422
+ } catch (error) {
423
+ lastError = error;
424
+ if (!isRetriableD1Error(error)) throw error;
425
+ if (attempt < finalConfig.maxAttempts - 1) {
426
+ const delay = calculateDelay(attempt, finalConfig);
427
+ console.warn(`D1 operation failed with retriable error (attempt ${attempt + 1}/${finalConfig.maxAttempts}), retrying in ${delay}ms:`, error instanceof Error ? error.message : String(error));
428
+ await sleep(delay);
429
+ }
430
+ }
431
+ console.error(`D1 operation failed after ${finalConfig.maxAttempts} attempts:`, lastError instanceof Error ? lastError.message : String(lastError));
432
+ throw lastError;
433
+ }
365
434
  async function finalizeWorkflowRecord(options, { workflowStatus, endTime, instanceId, result }) {
366
435
  if (result !== void 0) {
367
436
  const { data: resultData, externalRef: resultRef } = await serializeWithExternalStorage(result, options.serializer, options.externalBlobStorage);
368
- return options.D1.prepare(`UPDATE WorkflowTable
437
+ return retryD1Operation(() => options.D1.prepare(`UPDATE WorkflowTable
369
438
  SET workflowStatus = ?, endTime = ?, result = ?, resultRef = ?
370
- WHERE instanceId = ?`).bind(workflowStatus, endTime, resultData, resultRef, instanceId).run();
439
+ WHERE instanceId = ?`).bind(workflowStatus, endTime, resultData, resultRef, instanceId).run());
371
440
  }
372
- return options.D1.prepare(`UPDATE WorkflowTable
441
+ return retryD1Operation(() => options.D1.prepare(`UPDATE WorkflowTable
373
442
  SET workflowStatus = ?, endTime = ?
374
- WHERE instanceId = ?`).bind(workflowStatus, endTime, instanceId).run();
443
+ WHERE instanceId = ?`).bind(workflowStatus, endTime, instanceId).run());
375
444
  }
376
445
  /**
377
446
  * Insert a new workflow record into the database.
@@ -402,7 +471,7 @@ async function insertWorkflowRecord(options, { instanceId, workflowType, workflo
402
471
  (instanceId, workflowType, workflowName, workflowMetadata, input, inputRef, tenantId, workflowStatus, startTime, endTime, parentInstanceId, triggerId)
403
472
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, workflowType, workflowName, options.serializer.serialize(workflowMetadata), inputData, inputRef, tenantId, workflowStatus, startTime, endTime ?? null, parentInstanceId ?? null, triggerId ?? null);
404
473
  if (!dependencies || dependencies.length === 0) {
405
- const result = await insertWorkflowStatement.run();
474
+ const result = await retryD1Operation(() => insertWorkflowStatement.run());
406
475
  return {
407
476
  success: result.success,
408
477
  meta: result.meta
@@ -416,7 +485,7 @@ async function insertWorkflowRecord(options, { instanceId, workflowType, workflo
416
485
  tenantId,
417
486
  createdAt
418
487
  }));
419
- const results = await options.D1.batch([insertWorkflowStatement, ...dependencyStatements]);
488
+ const results = await retryD1Operation(() => options.D1.batch([insertWorkflowStatement, ...dependencyStatements]));
420
489
  const allSucceeded = results.every((result) => result.success);
421
490
  return {
422
491
  success: allSucceeded,
@@ -424,12 +493,12 @@ async function insertWorkflowRecord(options, { instanceId, workflowType, workflo
424
493
  };
425
494
  }
426
495
  function insertStepRecordFull(context, { instanceId, name, status, metadata, startTime, endTime, result, error, resultRef, errorRef }) {
427
- return context.D1.prepare(`INSERT INTO StepTable
496
+ return retryD1Operation(() => context.D1.prepare(`INSERT INTO StepTable
428
497
  (instanceId, stepName, stepStatus, stepMetadata, startTime, endTime, result, error, resultRef, errorRef, tenantId)
429
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, name, status, metadata, startTime, endTime, result, error, resultRef ?? null, errorRef ?? null, context.tenantId).run();
498
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, name, status, metadata, startTime, endTime, result, error, resultRef ?? null, errorRef ?? null, context.tenantId).run());
430
499
  }
431
500
  async function getStepRecord(context, stepName, instanceId) {
432
- const existingStep = await context.D1.prepare(`SELECT * FROM StepTable WHERE instanceId = ? AND stepName = ? AND tenantId = ?`).bind(instanceId, stepName, context.tenantId).first();
501
+ const existingStep = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM StepTable WHERE instanceId = ? AND stepName = ? AND tenantId = ?`).bind(instanceId, stepName, context.tenantId).first());
433
502
  const row = existingStep;
434
503
  if (row) {
435
504
  const result = await deserializeWithExternalStorage(row.result, row.resultRef, context.serializer, context.externalBlobStorage);
@@ -503,9 +572,9 @@ function truncateLogMessage(message) {
503
572
  }
504
573
  function pushLogToDB(options, { instanceId, stepName, message, timestamp, type, logOrder, tenantId }) {
505
574
  const truncatedMessage = truncateLogMessage(message);
506
- return options.D1.prepare(`INSERT INTO LogTable
575
+ return retryD1Operation(() => options.D1.prepare(`INSERT INTO LogTable
507
576
  (instanceId, stepName, log, timestamp, type, logOrder, tenantId)
508
- VALUES (?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, stepName, truncatedMessage, timestamp, type, logOrder, tenantId).run();
577
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, stepName, truncatedMessage, timestamp, type, logOrder, tenantId).run());
509
578
  }
510
579
  var LogBatcher = class {
511
580
  batch = [];
@@ -540,10 +609,12 @@ var LogBatcher = class {
540
609
  if (this.batch.length === 0) return;
541
610
  const logsToFlush = this.batch.splice(0);
542
611
  try {
543
- const statements = logsToFlush.map((entry) => this.options.D1.prepare(`INSERT INTO LogTable
612
+ await retryD1Operation(async () => {
613
+ const statements = logsToFlush.map((entry) => this.options.D1.prepare(`INSERT INTO LogTable
544
614
  (instanceId, stepName, log, timestamp, type, logOrder, tenantId)
545
615
  VALUES (?, ?, ?, ?, ?, ?, ?)`).bind(entry.instanceId, entry.stepName, entry.message, entry.timestamp, entry.type, entry.logOrder, entry.tenantId));
546
- await this.options.D1.batch(statements);
616
+ return await this.options.D1.batch(statements);
617
+ });
547
618
  } catch (error) {
548
619
  console.error("Batch log insert failed, falling back to individual inserts:", error);
549
620
  await Promise.all(logsToFlush.map((entry) => pushLogToDB(this.options, entry)));
@@ -671,9 +742,9 @@ async function workflowTableRowToWorkflowRun({ row, serializer, externalBlobStor
671
742
  };
672
743
  }
673
744
  async function updateWorkflowName(context, instanceId, newWorkflowName) {
674
- return await context.D1.prepare(`UPDATE WorkflowTable
745
+ return await retryD1Operation(() => context.D1.prepare(`UPDATE WorkflowTable
675
746
  SET workflowName = ?
676
- WHERE instanceId = ?`).bind(newWorkflowName, instanceId).run();
747
+ WHERE instanceId = ?`).bind(newWorkflowName, instanceId).run());
677
748
  }
678
749
  /**
679
750
  * Update workflow fields by instanceId. Only provided fields will be updated.
@@ -750,7 +821,7 @@ async function updateWorkflow(context, instanceId, updates) {
750
821
  };
751
822
  bindings.push(instanceId);
752
823
  const sql = `UPDATE WorkflowTable SET ${setClauses.join(", ")} WHERE instanceId = ?`;
753
- return await context.D1.prepare(sql).bind(...bindings).run();
824
+ return await retryD1Operation(() => context.D1.prepare(sql).bind(...bindings).run());
754
825
  }
755
826
  /**
756
827
  * Serialize a workflow property value for storage
@@ -783,9 +854,9 @@ function deserializeWorkflowPropertyValue(serializedValue, valueType) {
783
854
  }
784
855
  async function upsertWorkflowProperty({ context, instanceId, key, value, tenantId }) {
785
856
  const { serializedValue, valueType } = serializeWorkflowPropertyValue(value);
786
- const res = await context.D1.prepare(`INSERT OR REPLACE INTO WorkflowProperties
857
+ const res = await retryD1Operation(() => context.D1.prepare(`INSERT OR REPLACE INTO WorkflowProperties
787
858
  (instanceId, key, value, valueType, tenantId)
788
- VALUES (?, ?, ?, ?, ?)`).bind(instanceId, key, serializedValue, valueType, tenantId).run();
859
+ VALUES (?, ?, ?, ?, ?)`).bind(instanceId, key, serializedValue, valueType, tenantId).run());
789
860
  if (res.error) {
790
861
  console.error("Error inserting workflow property:", res.error);
791
862
  return false;
@@ -841,12 +912,12 @@ function prepareWorkflowDependencyStatement({ D1, dependencyWorkflowId, dependen
841
912
  * ```
842
913
  */
843
914
  async function createWorkflowDependency({ context, dependencyWorkflowId, dependentWorkflowId, tenantId }) {
844
- return await prepareWorkflowDependencyStatement({
915
+ return await retryD1Operation(() => prepareWorkflowDependencyStatement({
845
916
  D1: context.D1,
846
917
  dependencyWorkflowId,
847
918
  dependentWorkflowId,
848
919
  tenantId
849
- }).run();
920
+ }).run());
850
921
  }
851
922
  /**
852
923
  * Create multiple workflow dependencies in a single transaction.
@@ -879,7 +950,7 @@ async function createWorkflowDependencies({ context, dependencies, tenantId }) {
879
950
  tenantId,
880
951
  createdAt
881
952
  }));
882
- return await context.D1.batch(statements);
953
+ return await retryD1Operation(() => context.D1.batch(statements));
883
954
  }
884
955
  /**
885
956
  * Delete a specific workflow dependency relationship.
@@ -896,8 +967,8 @@ async function createWorkflowDependencies({ context, dependencies, tenantId }) {
896
967
  * ```
897
968
  */
898
969
  async function deleteWorkflowDependency({ context, dependencyWorkflowId, dependentWorkflowId, tenantId }) {
899
- return await context.D1.prepare(`DELETE FROM WorkflowDependencies
900
- WHERE dependencyWorkflowId = ? AND dependentWorkflowId = ? AND tenantId = ?`).bind(dependencyWorkflowId, dependentWorkflowId, tenantId).run();
970
+ return await retryD1Operation(() => context.D1.prepare(`DELETE FROM WorkflowDependencies
971
+ WHERE dependencyWorkflowId = ? AND dependentWorkflowId = ? AND tenantId = ?`).bind(dependencyWorkflowId, dependentWorkflowId, tenantId).run());
901
972
  }
902
973
 
903
974
  //#endregion
@@ -917,7 +988,7 @@ const createCleanupManager = (context) => {
917
988
  const getAffectedWorkflows = async (config, limit) => {
918
989
  const { statuses, statusPlaceholders } = buildStatusesAndPlaceholders(config);
919
990
  if (statuses.length === 0) return [];
920
- const result = await context.D1.prepare(`
991
+ const result = await retryD1Operation(() => context.D1.prepare(`
921
992
  SELECT instanceId, inputRef
922
993
  FROM WorkflowTable
923
994
  WHERE tenantId = ?
@@ -925,13 +996,13 @@ const createCleanupManager = (context) => {
925
996
  AND workflowStatus IN (${statusPlaceholders})
926
997
  ORDER BY instanceId
927
998
  LIMIT ?
928
- `).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all();
999
+ `).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all());
929
1000
  return result.results;
930
1001
  };
931
1002
  const getAffectedSteps = async (config, limit) => {
932
1003
  const { statuses, statusPlaceholders } = buildStatusesAndPlaceholders(config);
933
1004
  if (statuses.length === 0) return [];
934
- const result = await context.D1.prepare(`
1005
+ const result = await retryD1Operation(() => context.D1.prepare(`
935
1006
  SELECT s.instanceId, s.stepName, s.stepStatus, s.errorRef, s.resultRef
936
1007
  FROM StepTable s
937
1008
  WHERE s.instanceId IN (
@@ -944,7 +1015,7 @@ const createCleanupManager = (context) => {
944
1015
  LIMIT ?
945
1016
  )
946
1017
  ORDER BY s.instanceId, s.stepName
947
- `).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all();
1018
+ `).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all());
948
1019
  return result.results;
949
1020
  };
950
1021
  const collectExternalStorageKeys = (workflows, steps) => {
@@ -997,15 +1068,15 @@ const createCleanupManager = (context) => {
997
1068
  }
998
1069
  if (deletedWorkflows.length > 0) {
999
1070
  console.log(`Proceeding to delete workflows from the database...`);
1000
- await context.D1.prepare(`PRAGMA foreign_keys = ON`).run();
1071
+ await retryD1Operation(() => context.D1.prepare(`PRAGMA foreign_keys = ON`).run());
1001
1072
  let totalDeletedCount = 0;
1002
1073
  for (let i = 0; i < deletedWorkflows.length; i += 100) {
1003
1074
  const batch = deletedWorkflows.slice(i, i + 100);
1004
1075
  const workflowIds = batch.map(() => "?").join(", ");
1005
- const result = await context.D1.prepare(`
1076
+ const result = await retryD1Operation(() => context.D1.prepare(`
1006
1077
  DELETE FROM WorkflowTable
1007
1078
  WHERE instanceId IN (${workflowIds})
1008
- `).bind(...batch.map((w) => w.instanceId)).run();
1079
+ `).bind(...batch.map((w) => w.instanceId)).run());
1009
1080
  totalDeletedCount += result.meta.changes || 0;
1010
1081
  }
1011
1082
  return {
@@ -1021,8 +1092,8 @@ const createCleanupManager = (context) => {
1021
1092
  };
1022
1093
  };
1023
1094
  const cleanupOrphanedSteps = async (config, limit) => {
1024
- await context.D1.prepare(`PRAGMA foreign_keys = ON`).run();
1025
- const orphanedSteps = await context.D1.prepare(`
1095
+ await retryD1Operation(() => context.D1.prepare(`PRAGMA foreign_keys = ON`).run());
1096
+ const orphanedSteps = await retryD1Operation(() => context.D1.prepare(`
1026
1097
  SELECT s.instanceId, s.stepName, s.errorRef, s.resultRef
1027
1098
  FROM StepTable s
1028
1099
  WHERE s.tenantId = ?
@@ -1032,7 +1103,7 @@ const createCleanupManager = (context) => {
1032
1103
  )
1033
1104
  ORDER BY s.instanceId, s.stepName
1034
1105
  LIMIT ?
1035
- `).bind(context.tenantId, context.tenantId, limit).all();
1106
+ `).bind(context.tenantId, context.tenantId, limit).all());
1036
1107
  const orphanedStepResults = orphanedSteps.results;
1037
1108
  let deletedExternalStorageKeysCount = 0;
1038
1109
  if (config.deleteRefsFromExternalStorage && context.externalBlobStorage && orphanedStepResults.length > 0) {
@@ -1048,11 +1119,11 @@ const createCleanupManager = (context) => {
1048
1119
  for (let i = 0; i < orphanedStepResults.length; i += 99) {
1049
1120
  const batch = orphanedStepResults.slice(i, i + 99);
1050
1121
  const instanceIds = batch.map(() => "?").join(", ");
1051
- const deletedStepsResult = await context.D1.prepare(`
1122
+ const deletedStepsResult = await retryD1Operation(() => context.D1.prepare(`
1052
1123
  DELETE FROM StepTable
1053
1124
  WHERE tenantId = ?
1054
1125
  AND instanceId IN (${instanceIds})
1055
- `).bind(context.tenantId, ...batch.map((s) => s.instanceId)).run();
1126
+ `).bind(context.tenantId, ...batch.map((s) => s.instanceId)).run());
1056
1127
  totalDeletedSteps += deletedStepsResult.meta.changes || 0;
1057
1128
  }
1058
1129
  return {
@@ -1068,8 +1139,8 @@ const createCleanupManager = (context) => {
1068
1139
  };
1069
1140
  };
1070
1141
  const cleanupOrphanedLogs = async (limit) => {
1071
- await context.D1.prepare(`PRAGMA foreign_keys = ON`).run();
1072
- const deletedLogsResult = await context.D1.prepare(`
1142
+ await retryD1Operation(() => context.D1.prepare(`PRAGMA foreign_keys = ON`).run());
1143
+ const deletedLogsResult = await retryD1Operation(() => context.D1.prepare(`
1073
1144
  DELETE FROM LogTable
1074
1145
  WHERE tenantId = ?
1075
1146
  AND (
@@ -1084,7 +1155,7 @@ const createCleanupManager = (context) => {
1084
1155
  ))
1085
1156
  )
1086
1157
  LIMIT ?
1087
- `).bind(context.tenantId, context.tenantId, context.tenantId, limit).run();
1158
+ `).bind(context.tenantId, context.tenantId, context.tenantId, limit).run());
1088
1159
  return { deletedOrphanedLogs: deletedLogsResult.meta.changes || 0 };
1089
1160
  };
1090
1161
  return {
@@ -1272,7 +1343,7 @@ const createLogAccessor = (context) => {
1272
1343
  const limitClause = limit !== void 0 && actualOffset !== void 0 ? "LIMIT ? OFFSET ?" : "";
1273
1344
  const sql = `SELECT * FROM StepTable ${whereClause} ORDER BY startTime ASC ${limitClause}`;
1274
1345
  if (limit !== void 0 && actualOffset !== void 0) bindings.push(limit, actualOffset);
1275
- result = await context.D1.prepare(sql).bind(...bindings).all();
1346
+ result = await retryD1Operation(() => context.D1.prepare(sql).bind(...bindings).all());
1276
1347
  if (result.results) {
1277
1348
  const steps = await Promise.all(result.results.map(async (row) => {
1278
1349
  let deserializedResult = null;
@@ -1299,7 +1370,7 @@ const createLogAccessor = (context) => {
1299
1370
  const listWorkflows = async (limit, offset, filter, options) => {
1300
1371
  const { sql, bindings } = buildFilteredWorkflowQuery(filter, { ignoreTenant: options?.ignoreTenant });
1301
1372
  if (options?.debugLogs) console.log("listWorkflows SQL:", sql, "Bindings:", JSON.stringify(bindings));
1302
- const result = await context.D1.prepare(sql).bind(...bindings, limit, offset).all();
1373
+ const result = await retryD1Operation(() => context.D1.prepare(sql).bind(...bindings, limit, offset).all());
1303
1374
  if (options?.debugLogs) console.log("listWorkflows SQL Query executed");
1304
1375
  if (result.results) {
1305
1376
  const workflows = await Promise.all(result.results.map((row) => workflowTableRowToWorkflowRun({
@@ -1316,7 +1387,7 @@ const createLogAccessor = (context) => {
1316
1387
  for (let i = 0; i < instanceIds.length; i += BATCH_SIZE) {
1317
1388
  const batchIds = instanceIds.slice(i, i + BATCH_SIZE);
1318
1389
  const placeholders = batchIds.map(() => "?").join(", ");
1319
- const propertiesResult = await context.D1.prepare(`SELECT * FROM WorkflowProperties WHERE instanceId IN (${placeholders})`).bind(...batchIds).all();
1390
+ const propertiesResult = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowProperties WHERE instanceId IN (${placeholders})`).bind(...batchIds).all());
1320
1391
  if (propertiesResult.results) for (const row of propertiesResult.results) {
1321
1392
  const property = {
1322
1393
  key: row.key,
@@ -1337,10 +1408,10 @@ const createLogAccessor = (context) => {
1337
1408
  for (let i = 0; i < instanceIds.length; i += BATCH_SIZE) {
1338
1409
  const batchIds = instanceIds.slice(i, i + BATCH_SIZE);
1339
1410
  const placeholders = batchIds.map(() => "?").join(", ");
1340
- const dependenciesResult = await context.D1.prepare(`SELECT w.*, wd.createdAt, wd.dependentWorkflowId
1411
+ const dependenciesResult = await retryD1Operation(() => context.D1.prepare(`SELECT w.*, wd.createdAt, wd.dependentWorkflowId
1341
1412
  FROM WorkflowDependencies wd
1342
1413
  JOIN WorkflowTable w ON wd.dependencyWorkflowId = w.instanceId
1343
- WHERE wd.dependentWorkflowId IN (${placeholders})`).bind(...batchIds).all();
1414
+ WHERE wd.dependentWorkflowId IN (${placeholders})`).bind(...batchIds).all());
1344
1415
  if (dependenciesResult.results) for (const row of dependenciesResult.results) {
1345
1416
  const depWorkflow = await workflowTableRowToWorkflowRun({
1346
1417
  row,
@@ -1367,10 +1438,10 @@ const createLogAccessor = (context) => {
1367
1438
  for (let i = 0; i < instanceIds.length; i += BATCH_SIZE) {
1368
1439
  const batchIds = instanceIds.slice(i, i + BATCH_SIZE);
1369
1440
  const placeholders = batchIds.map(() => "?").join(", ");
1370
- const dependentsResult = await context.D1.prepare(`SELECT w.*, wd.createdAt, wd.dependencyWorkflowId
1441
+ const dependentsResult = await retryD1Operation(() => context.D1.prepare(`SELECT w.*, wd.createdAt, wd.dependencyWorkflowId
1371
1442
  FROM WorkflowDependencies wd
1372
1443
  JOIN WorkflowTable w ON wd.dependentWorkflowId = w.instanceId
1373
- WHERE wd.dependencyWorkflowId IN (${placeholders})`).bind(...batchIds).all();
1444
+ WHERE wd.dependencyWorkflowId IN (${placeholders})`).bind(...batchIds).all());
1374
1445
  if (dependentsResult.results) for (const row of dependentsResult.results) {
1375
1446
  const depWorkflow = await workflowTableRowToWorkflowRun({
1376
1447
  row,
@@ -1395,7 +1466,7 @@ const createLogAccessor = (context) => {
1395
1466
  return [];
1396
1467
  };
1397
1468
  const getWorkflowByParentId = async (parentInstanceId) => {
1398
- const result = await context.D1.prepare(`SELECT * FROM WorkflowTable WHERE parentInstanceId = ? AND tenantId = ?`).bind(parentInstanceId, context.tenantId).all();
1469
+ const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowTable WHERE parentInstanceId = ? AND tenantId = ?`).bind(parentInstanceId, context.tenantId).all());
1399
1470
  if (result.results) {
1400
1471
  const workflows = await Promise.all(result.results.map((row) => workflowTableRowToWorkflowRun({
1401
1472
  row,
@@ -1407,7 +1478,7 @@ const createLogAccessor = (context) => {
1407
1478
  return null;
1408
1479
  };
1409
1480
  const getWorkflowByTriggerId = async (triggerId) => {
1410
- const result = await context.D1.prepare(`SELECT * FROM WorkflowTable WHERE triggerId = ? AND tenantId = ?`).bind(triggerId, context.tenantId).first();
1481
+ const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowTable WHERE triggerId = ? AND tenantId = ?`).bind(triggerId, context.tenantId).first());
1411
1482
  if (result) {
1412
1483
  const workflow = await workflowTableRowToWorkflowRun({
1413
1484
  row: result,
@@ -1419,12 +1490,12 @@ const createLogAccessor = (context) => {
1419
1490
  return null;
1420
1491
  };
1421
1492
  const getWorkflowTypesByTenantId = async (tenantId) => {
1422
- const result = await context.D1.prepare(`SELECT DISTINCT workflowType FROM WorkflowTable WHERE tenantId = ?`).bind(tenantId).all();
1493
+ const result = await retryD1Operation(() => context.D1.prepare(`SELECT DISTINCT workflowType FROM WorkflowTable WHERE tenantId = ?`).bind(tenantId).all());
1423
1494
  if (!result.results) return [];
1424
1495
  return result.results.map((row) => row.workflowType);
1425
1496
  };
1426
1497
  const getWorkflowProperties = async (instanceId) => {
1427
- const result = await context.D1.prepare(`SELECT * FROM WorkflowProperties WHERE instanceId = ?`).bind(instanceId).all();
1498
+ const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowProperties WHERE instanceId = ?`).bind(instanceId).all());
1428
1499
  if (!result.results) return [];
1429
1500
  return result.results.map((row) => ({
1430
1501
  key: row.key,
@@ -1434,7 +1505,7 @@ const createLogAccessor = (context) => {
1434
1505
  };
1435
1506
  /** This function gets the basic data of a workflow, without populating any of it's complex fields */
1436
1507
  const getWorkflowShallow = async (instanceId, options) => {
1437
- const result = await context.D1.prepare(`SELECT * FROM WorkflowTable WHERE instanceId = ? AND tenantId = ?`).bind(instanceId, context.tenantId).first();
1508
+ const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowTable WHERE instanceId = ? AND tenantId = ?`).bind(instanceId, context.tenantId).first());
1438
1509
  if (!result) return null;
1439
1510
  const workflow = await workflowTableRowToWorkflowRun({
1440
1511
  row: result,
@@ -1465,7 +1536,7 @@ const createLogAccessor = (context) => {
1465
1536
  retryWorkflow.isRetryOf = workflow;
1466
1537
  });
1467
1538
  workflow.isRetryOf = parentWorkflow;
1468
- const allLogs = await context.D1.prepare(`SELECT * FROM LogTable WHERE instanceId = ? ORDER BY timestamp ASC, logOrder ASC`).bind(instanceId).all();
1539
+ const allLogs = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM LogTable WHERE instanceId = ? ORDER BY timestamp ASC, logOrder ASC`).bind(instanceId).all());
1469
1540
  const logs = allLogs.results?.map((logRow) => ({
1470
1541
  instanceId: logRow.instanceId,
1471
1542
  stepName: logRow.stepName,
@@ -1481,10 +1552,10 @@ const createLogAccessor = (context) => {
1481
1552
  const properties = await getWorkflowProperties(instanceId);
1482
1553
  workflow.properties = properties;
1483
1554
  if (populateDependencies) {
1484
- const dependenciesResult = await context.D1.prepare(`SELECT w.*, wd.createdAt
1555
+ const dependenciesResult = await retryD1Operation(() => context.D1.prepare(`SELECT w.*, wd.createdAt
1485
1556
  FROM WorkflowDependencies wd
1486
1557
  JOIN WorkflowTable w ON wd.dependencyWorkflowId = w.instanceId
1487
- WHERE wd.dependentWorkflowId = ? AND wd.tenantId = ?`).bind(instanceId, context.tenantId).all();
1558
+ WHERE wd.dependentWorkflowId = ? AND wd.tenantId = ?`).bind(instanceId, context.tenantId).all());
1488
1559
  if (dependenciesResult.results) workflow.dependencies = await Promise.all(dependenciesResult.results.map(async (row) => {
1489
1560
  const depWorkflow = await workflowTableRowToWorkflowRun({
1490
1561
  row,
@@ -1500,10 +1571,10 @@ const createLogAccessor = (context) => {
1500
1571
  }));
1501
1572
  }
1502
1573
  if (populateDependents) {
1503
- const dependentsResult = await context.D1.prepare(`SELECT w.*, wd.createdAt
1574
+ const dependentsResult = await retryD1Operation(() => context.D1.prepare(`SELECT w.*, wd.createdAt
1504
1575
  FROM WorkflowDependencies wd
1505
1576
  JOIN WorkflowTable w ON wd.dependentWorkflowId = w.instanceId
1506
- WHERE wd.dependencyWorkflowId = ? AND wd.tenantId = ?`).bind(instanceId, context.tenantId).all();
1577
+ WHERE wd.dependencyWorkflowId = ? AND wd.tenantId = ?`).bind(instanceId, context.tenantId).all());
1507
1578
  if (dependentsResult.results) workflow.dependents = await Promise.all(dependentsResult.results.map(async (row) => {
1508
1579
  const depWorkflow = await workflowTableRowToWorkflowRun({
1509
1580
  row,
@@ -1521,7 +1592,7 @@ const createLogAccessor = (context) => {
1521
1592
  return workflow;
1522
1593
  };
1523
1594
  const getStep = async (instanceId, stepName) => {
1524
- const result = await context.D1.prepare(`SELECT * FROM StepTable WHERE instanceId = ? AND stepName = ? AND tenantId = ?`).bind(instanceId, stepName, context.tenantId).all();
1595
+ const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM StepTable WHERE instanceId = ? AND stepName = ? AND tenantId = ?`).bind(instanceId, stepName, context.tenantId).all());
1525
1596
  if (!result.results || result.results.length === 0) return null;
1526
1597
  const row = result.results[0];
1527
1598
  if (!row) return null;
@@ -1538,7 +1609,7 @@ const createLogAccessor = (context) => {
1538
1609
  error: deserializedError,
1539
1610
  logs: []
1540
1611
  };
1541
- const logResult = await context.D1.prepare(`SELECT * FROM LogTable WHERE instanceId = ? AND stepName = ? ORDER BY logOrder ASC`).bind(instanceId, stepName).all();
1612
+ const logResult = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM LogTable WHERE instanceId = ? AND stepName = ? ORDER BY logOrder ASC`).bind(instanceId, stepName).all());
1542
1613
  if (logResult.results) step.logs = logResult.results.map((logRow) => ({
1543
1614
  instanceId: logRow.instanceId,
1544
1615
  stepName: logRow.stepName,
@@ -1549,10 +1620,10 @@ const createLogAccessor = (context) => {
1549
1620
  return step;
1550
1621
  };
1551
1622
  const getPropertiesKeys = async (instanceId) => {
1552
- const result = await context.D1.prepare(`
1623
+ const result = await retryD1Operation(() => context.D1.prepare(`
1553
1624
  SELECT DISTINCT key, valueType FROM WorkflowProperties
1554
1625
  WHERE tenantId = ? AND (? IS NULL OR instanceId = ?)
1555
- `).bind(context.tenantId, instanceId ?? null, instanceId ?? null).all();
1626
+ `).bind(context.tenantId, instanceId ?? null, instanceId ?? null).all());
1556
1627
  console.log(`debug getPropertiesKeys: ${JSON.stringify(result)}`);
1557
1628
  if (!result.results) return [];
1558
1629
  return result.results.map((row) => ({
@@ -1575,7 +1646,7 @@ const createLogAccessor = (context) => {
1575
1646
  bindings.push(toTime);
1576
1647
  }
1577
1648
  const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : "";
1578
- const result = await context.D1.prepare(`
1649
+ const result = await retryD1Operation(() => context.D1.prepare(`
1579
1650
  SELECT
1580
1651
  workflowType,
1581
1652
  COUNT(*) as workflowCount,
@@ -1618,7 +1689,7 @@ const createLogAccessor = (context) => {
1618
1689
  ${whereClause}
1619
1690
  GROUP BY workflowType
1620
1691
  ORDER BY workflowCount DESC
1621
- `).bind(...bindings).all();
1692
+ `).bind(...bindings).all());
1622
1693
  if (!result.results) return [];
1623
1694
  return result.results.map((row) => ({
1624
1695
  workflowType: row.workflowType,
@@ -1931,9 +2002,9 @@ async function createStepContext(context) {
1931
2002
  const stepStatus$1 = "completed";
1932
2003
  const { data: resultData, externalRef: resultRef } = await serializeWithExternalStorage(result, context.serializer, context.externalBlobStorage);
1933
2004
  const stepError$1 = null;
1934
- await context.D1.prepare(`UPDATE StepTable
2005
+ await retryD1Operation(() => context.D1.prepare(`UPDATE StepTable
1935
2006
  SET stepStatus = ?, endTime = ?, result = ?, error = ?, resultRef = ?, errorRef = ?
1936
- WHERE instanceId = ? AND stepName = ?`).bind(stepStatus$1, endTime, resultData, stepError$1, resultRef, null, instanceId, stepName).run();
2007
+ WHERE instanceId = ? AND stepName = ?`).bind(stepStatus$1, endTime, resultData, stepError$1, resultRef, null, instanceId, stepName).run());
1937
2008
  await logBatcher.destroy();
1938
2009
  return result;
1939
2010
  } catch (error) {
@@ -1941,9 +2012,9 @@ async function createStepContext(context) {
1941
2012
  const stepStatus$1 = "failed";
1942
2013
  const stepResult$1 = null;
1943
2014
  const { data: errorData, externalRef: errorRef } = await serializeWithExternalStorage(error, context.serializer, context.externalBlobStorage);
1944
- await context.D1.prepare(`UPDATE StepTable
2015
+ await retryD1Operation(() => context.D1.prepare(`UPDATE StepTable
1945
2016
  SET stepStatus = ?, endTime = ?, result = ?, error = ?, resultRef = ?, errorRef = ?
1946
- WHERE instanceId = ? AND stepName = ?`).bind(stepStatus$1, endTime, stepResult$1, errorData, null, errorRef, instanceId, stepName).run();
2017
+ WHERE instanceId = ? AND stepName = ?`).bind(stepStatus$1, endTime, stepResult$1, errorData, null, errorRef, instanceId, stepName).run());
1947
2018
  await logBatcher.destroy();
1948
2019
  throw error;
1949
2020
  }
@@ -2098,7 +2169,7 @@ function createWorkflowContext(options) {
2098
2169
  await ensureTables(options.D1);
2099
2170
  ensuredTables = true;
2100
2171
  }
2101
- const oldRun = await options.D1.prepare(`SELECT input, workflowName, tenantId, inputRef FROM WorkflowTable WHERE instanceId = ? `).bind(retryInstanceId).first();
2172
+ const oldRun = await retryD1Operation(() => options.D1.prepare(`SELECT input, workflowName, tenantId, inputRef FROM WorkflowTable WHERE instanceId = ? `).bind(retryInstanceId).first());
2102
2173
  const oldWorkflowName = oldRun?.workflowName;
2103
2174
  const tenantId = oldRun?.tenantId;
2104
2175
  if (!tenantId) throw new Error(`No tenantId found for instanceId ${retryInstanceId}`);
@@ -2204,4 +2275,4 @@ function createR2ExternalBlobStorage(options) {
2204
2275
  }
2205
2276
 
2206
2277
  //#endregion
2207
- export { createCleanupManager, createLogAccessor, createQueueWorkflowContext, createR2ExternalBlobStorage, createStepContext, createWorkflowContext, createWorkflowDependencies, createWorkflowDependency, defaultIdFactory, defaultSerializer, defineWorkflow, deleteWorkflowDependency, deserializeWithExternalStorage, ensureTables, finalizeWorkflowRecord, insertStepRecordFull, insertWorkflowRecord, pushLogToDB, serializeWithExternalStorage, tryDeserializeObj, updateWorkflow, updateWorkflowName, upsertWorkflowProperty, workflowTableRowToWorkflowRun };
2278
+ export { createCleanupManager, createLogAccessor, createQueueWorkflowContext, createR2ExternalBlobStorage, createStepContext, createWorkflowContext, createWorkflowDependencies, createWorkflowDependency, defaultIdFactory, defaultSerializer, defineWorkflow, deleteWorkflowDependency, deserializeWithExternalStorage, ensureTables, finalizeWorkflowRecord, insertStepRecordFull, insertWorkflowRecord, pushLogToDB, retryD1Operation, serializeWithExternalStorage, tryDeserializeObj, updateWorkflow, updateWorkflowName, upsertWorkflowProperty, workflowTableRowToWorkflowRun };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brandboostinggmbh/observable-workflows",
3
- "version": "0.19.0-beta.4",
3
+ "version": "0.20.0-beta.4",
4
4
  "description": "My awesome typescript library",
5
5
  "type": "module",
6
6
  "license": "MIT",