@brandboostinggmbh/observable-workflows 0.19.0-beta.4 → 0.20.0-beta.5
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 +42 -0
- package/dist/index.d.ts +42 -2
- package/dist/index.js +204 -126
- package/package.json +3 -3
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
|
@@ -14,10 +14,43 @@ declare function createStepContext(context: StepContextOptions): Promise<{
|
|
|
14
14
|
* This function is idempotent and can be safely run multiple times.
|
|
15
15
|
*
|
|
16
16
|
* @param db - D1Database instance
|
|
17
|
+
* @param retryConfig - Optional retry configuration for D1 operations
|
|
17
18
|
*/
|
|
18
|
-
declare function ensureTables(db: D1Database): Promise<void>;
|
|
19
|
+
declare function ensureTables(db: D1Database, retryConfig?: RetryConfig): Promise<void>;
|
|
19
20
|
//#endregion
|
|
20
21
|
//#region src/observableWorkflows/helperFunctions.d.ts
|
|
22
|
+
/**
|
|
23
|
+
* Configuration for retry behavior on D1 operations
|
|
24
|
+
*/
|
|
25
|
+
interface RetryConfig {
|
|
26
|
+
/** Maximum number of retry attempts (default: 3) */
|
|
27
|
+
maxAttempts?: number;
|
|
28
|
+
/** Initial delay in milliseconds (default: 100) */
|
|
29
|
+
initialDelayMs?: number;
|
|
30
|
+
/** Maximum delay in milliseconds (default: 5000) */
|
|
31
|
+
maxDelayMs?: number;
|
|
32
|
+
/** Multiplier for exponential backoff (default: 2) */
|
|
33
|
+
backoffMultiplier?: number;
|
|
34
|
+
/** Whether to add jitter to delays (default: true) */
|
|
35
|
+
useJitter?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Retry a D1 operation with exponential backoff for transient network errors
|
|
39
|
+
*
|
|
40
|
+
* @param operation - The async operation to retry
|
|
41
|
+
* @param config - Retry configuration options
|
|
42
|
+
* @returns The result of the operation
|
|
43
|
+
* @throws The last error if all retry attempts fail
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const result = await retryD1Operation(
|
|
48
|
+
* () => db.prepare("SELECT * FROM table").first(),
|
|
49
|
+
* { maxAttempts: 5, initialDelayMs: 200 }
|
|
50
|
+
* )
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function retryD1Operation<T>(operation: () => Promise<T>, config?: RetryConfig): Promise<T>;
|
|
21
54
|
declare function finalizeWorkflowRecord(options: InternalWorkflowContextOptions, {
|
|
22
55
|
workflowStatus,
|
|
23
56
|
endTime,
|
|
@@ -199,6 +232,7 @@ declare function workflowTableRowToWorkflowRun({
|
|
|
199
232
|
}): Promise<WorkflowRun>;
|
|
200
233
|
declare function updateWorkflowName(context: {
|
|
201
234
|
D1: D1Database;
|
|
235
|
+
retryConfig?: RetryConfig;
|
|
202
236
|
}, instanceId: string, newWorkflowName: string): Promise<D1Result<Record<string, unknown>>>;
|
|
203
237
|
/**
|
|
204
238
|
* Update workflow fields by instanceId. Only provided fields will be updated.
|
|
@@ -409,6 +443,8 @@ type StepContextOptions = {
|
|
|
409
443
|
reuseSuccessfulSteps?: boolean;
|
|
410
444
|
/** Optional external blob storage for large data that exceeds D1 size limits */
|
|
411
445
|
externalBlobStorage?: ExternalBlobStorage;
|
|
446
|
+
/** Optional retry configuration for D1 operations */
|
|
447
|
+
retryConfig?: RetryConfig;
|
|
412
448
|
};
|
|
413
449
|
type ConsoleWrapper = {
|
|
414
450
|
log: (message?: any, ...optionalParams: any[]) => void;
|
|
@@ -610,6 +646,8 @@ type WorkflowContextOptions = {
|
|
|
610
646
|
idFactory?: () => string;
|
|
611
647
|
serializer?: Serializer;
|
|
612
648
|
externalBlobStorage?: ExternalBlobStorage;
|
|
649
|
+
/** Optional retry configuration for D1 operations (defaults: maxAttempts: 3, initialDelayMs: 100, maxDelayMs: 5000, backoffMultiplier: 2, useJitter: true) */
|
|
650
|
+
retryConfig?: RetryConfig;
|
|
613
651
|
};
|
|
614
652
|
type InternalWorkflowContextOptions = WorkflowContextOptions & Required<Pick<WorkflowContextOptions, 'serializer' | 'idFactory'>>;
|
|
615
653
|
type WorkflowContextInstance = {
|
|
@@ -728,6 +766,7 @@ declare const createCleanupManager: (context: {
|
|
|
728
766
|
tenantId: string;
|
|
729
767
|
serializer?: Serializer;
|
|
730
768
|
externalBlobStorage?: ExternalBlobStorage;
|
|
769
|
+
retryConfig?: RetryConfig;
|
|
731
770
|
}) => {
|
|
732
771
|
countAffectedWorkflows: (config: DeleteConfig, limit: number) => Promise<{
|
|
733
772
|
count: number;
|
|
@@ -774,6 +813,7 @@ declare const createLogAccessor: (context: {
|
|
|
774
813
|
tenantId: string;
|
|
775
814
|
serializer?: Serializer;
|
|
776
815
|
externalBlobStorage?: ExternalBlobStorage;
|
|
816
|
+
retryConfig?: RetryConfig;
|
|
777
817
|
}) => {
|
|
778
818
|
listSteps: {
|
|
779
819
|
(limit: number, offset: number, instanceId?: string): Promise<Step[]>;
|
|
@@ -906,4 +946,4 @@ type R2ExternalBlobStorageOptions = {
|
|
|
906
946
|
*/
|
|
907
947
|
declare function createR2ExternalBlobStorage(options: R2ExternalBlobStorageOptions): ExternalBlobStorage;
|
|
908
948
|
//#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 };
|
|
949
|
+
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
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Detect the current schema version for each table
|
|
4
4
|
*/
|
|
5
|
-
async function detectSchemaVersion(db) {
|
|
6
|
-
const workflowTableInfo = await db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first();
|
|
5
|
+
async function detectSchemaVersion(db, retryConfig) {
|
|
6
|
+
const workflowTableInfo = await retryD1Operation(() => db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first(), retryConfig);
|
|
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(), retryConfig);
|
|
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(), retryConfig);
|
|
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(), retryConfig);
|
|
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();
|
|
@@ -52,11 +52,11 @@ async function detectSchemaVersion(db) {
|
|
|
52
52
|
* Migrate WorkflowTable from V1 to V2 schema
|
|
53
53
|
* Adds inputRef column and makes input nullable
|
|
54
54
|
*/
|
|
55
|
-
async function migrateWorkflowTableV1ToV2(db) {
|
|
56
|
-
const workflowTableInfo = await db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first();
|
|
55
|
+
async function migrateWorkflowTableV1ToV2(db, retryConfig) {
|
|
56
|
+
const workflowTableInfo = await retryD1Operation(() => db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first(), retryConfig);
|
|
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
|
+
]), retryConfig);
|
|
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
|
-
async function migrateWorkflowTableV2V3ToV4(db) {
|
|
88
|
-
const workflowTableInfo = await db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first();
|
|
87
|
+
async function migrateWorkflowTableV2V3ToV4(db, retryConfig) {
|
|
88
|
+
const workflowTableInfo = await retryD1Operation(() => db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first(), retryConfig);
|
|
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,23 +111,23 @@ 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
|
+
]), retryConfig);
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
117
|
* Migrate WorkflowTable from V4 to V6 schema
|
|
118
118
|
* Adds result and resultRef columns for external storage support
|
|
119
119
|
*/
|
|
120
|
-
async function migrateWorkflowTableV4ToV6(db) {
|
|
121
|
-
const workflowTableInfo = await db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first();
|
|
120
|
+
async function migrateWorkflowTableV4ToV6(db, retryConfig) {
|
|
121
|
+
const workflowTableInfo = await retryD1Operation(() => db.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='WorkflowTable'`).first(), retryConfig);
|
|
122
122
|
const hasResultRef = workflowTableInfo.sql.includes("resultRef");
|
|
123
|
-
if (!hasResultRef) await db.batch([db.prepare(`ALTER TABLE WorkflowTable ADD COLUMN result TEXT`), db.prepare(`ALTER TABLE WorkflowTable ADD COLUMN resultRef TEXT`)]);
|
|
123
|
+
if (!hasResultRef) await retryD1Operation(() => db.batch([db.prepare(`ALTER TABLE WorkflowTable ADD COLUMN result TEXT`), db.prepare(`ALTER TABLE WorkflowTable ADD COLUMN resultRef TEXT`)]), retryConfig);
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
126
126
|
* Create or migrate WorkflowTable to the latest schema
|
|
127
127
|
*/
|
|
128
|
-
async function migrateWorkflowTable(db, currentVersion) {
|
|
128
|
+
async function migrateWorkflowTable(db, currentVersion, retryConfig) {
|
|
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,31 +144,31 @@ async function migrateWorkflowTable(db, currentVersion) {
|
|
|
144
144
|
triggerId TEXT,
|
|
145
145
|
PRIMARY KEY (instanceId),
|
|
146
146
|
UNIQUE (triggerId)
|
|
147
|
-
)`).run();
|
|
147
|
+
)`).run(), retryConfig);
|
|
148
148
|
return;
|
|
149
149
|
}
|
|
150
150
|
if (currentVersion === "v1") {
|
|
151
|
-
await migrateWorkflowTableV1ToV2(db);
|
|
152
|
-
await migrateWorkflowTableV2V3ToV4(db);
|
|
153
|
-
await migrateWorkflowTableV4ToV6(db);
|
|
151
|
+
await migrateWorkflowTableV1ToV2(db, retryConfig);
|
|
152
|
+
await migrateWorkflowTableV2V3ToV4(db, retryConfig);
|
|
153
|
+
await migrateWorkflowTableV4ToV6(db, retryConfig);
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
156
|
if (currentVersion === "v2") {
|
|
157
|
-
await migrateWorkflowTableV2V3ToV4(db);
|
|
158
|
-
await migrateWorkflowTableV4ToV6(db);
|
|
157
|
+
await migrateWorkflowTableV2V3ToV4(db, retryConfig);
|
|
158
|
+
await migrateWorkflowTableV4ToV6(db, retryConfig);
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
if (currentVersion === "v4") {
|
|
162
|
-
await migrateWorkflowTableV4ToV6(db);
|
|
162
|
+
await migrateWorkflowTableV4ToV6(db, retryConfig);
|
|
163
163
|
return;
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
167
167
|
* Create or migrate StepTable to the latest schema
|
|
168
168
|
*/
|
|
169
|
-
async function migrateStepTable(db, currentVersion) {
|
|
169
|
+
async function migrateStepTable(db, currentVersion, retryConfig) {
|
|
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,23 +182,23 @@ 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(), retryConfig);
|
|
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(), retryConfig);
|
|
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(), retryConfig);
|
|
193
|
+
if (!hasErrorRef) await retryD1Operation(() => db.prepare(`ALTER TABLE StepTable ADD COLUMN errorRef TEXT`).run(), retryConfig);
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
/**
|
|
197
197
|
* Create or migrate LogTable to the latest schema
|
|
198
198
|
*/
|
|
199
|
-
async function migrateLogTable(db, currentVersion) {
|
|
199
|
+
async function migrateLogTable(db, currentVersion, retryConfig) {
|
|
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,16 +208,16 @@ 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(), retryConfig);
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
/**
|
|
216
216
|
* Create or migrate WorkflowProperties table to the latest schema
|
|
217
217
|
*/
|
|
218
|
-
async function migrateWorkflowPropertiesTable(db, currentVersion) {
|
|
218
|
+
async function migrateWorkflowPropertiesTable(db, currentVersion, retryConfig) {
|
|
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,16 +226,16 @@ 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(), retryConfig);
|
|
230
230
|
return;
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
/**
|
|
234
234
|
* Create or migrate WorkflowDependencies table to the latest schema
|
|
235
235
|
*/
|
|
236
|
-
async function migrateWorkflowDependenciesTable(db, currentVersion) {
|
|
236
|
+
async function migrateWorkflowDependenciesTable(db, currentVersion, retryConfig) {
|
|
237
237
|
if (currentVersion === "missing") {
|
|
238
|
-
await db.prepare(`CREATE TABLE WorkflowDependencies (
|
|
238
|
+
await retryD1Operation(() => db.prepare(`CREATE TABLE WorkflowDependencies (
|
|
239
239
|
dependencyWorkflowId TEXT NOT NULL,
|
|
240
240
|
dependentWorkflowId TEXT NOT NULL,
|
|
241
241
|
tenantId TEXT NOT NULL,
|
|
@@ -243,15 +243,15 @@ async function migrateWorkflowDependenciesTable(db, currentVersion) {
|
|
|
243
243
|
PRIMARY KEY (dependencyWorkflowId, dependentWorkflowId),
|
|
244
244
|
FOREIGN KEY (dependencyWorkflowId) REFERENCES WorkflowTable(instanceId) ON DELETE CASCADE,
|
|
245
245
|
FOREIGN KEY (dependentWorkflowId) REFERENCES WorkflowTable(instanceId) ON DELETE CASCADE
|
|
246
|
-
)`).run();
|
|
246
|
+
)`).run(), retryConfig);
|
|
247
247
|
return;
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
/**
|
|
251
251
|
* Create necessary indexes
|
|
252
252
|
*/
|
|
253
|
-
async function createIndexes(db) {
|
|
254
|
-
const existingIndexes = await db.prepare(`SELECT name FROM sqlite_master WHERE type='index' AND name LIKE 'idx_%'`).all();
|
|
253
|
+
async function createIndexes(db, retryConfig) {
|
|
254
|
+
const existingIndexes = await retryD1Operation(() => db.prepare(`SELECT name FROM sqlite_master WHERE type='index' AND name LIKE 'idx_%'`).all(), retryConfig);
|
|
255
255
|
const existingIndexNames = new Set(existingIndexes.results?.map((row) => row.name) || []);
|
|
256
256
|
const indexes = [
|
|
257
257
|
{
|
|
@@ -341,37 +341,107 @@ 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), retryConfig);
|
|
345
345
|
}
|
|
346
346
|
/**
|
|
347
347
|
* Main migration function that ensures all tables exist and are up-to-date.
|
|
348
348
|
* This function is idempotent and can be safely run multiple times.
|
|
349
349
|
*
|
|
350
350
|
* @param db - D1Database instance
|
|
351
|
+
* @param retryConfig - Optional retry configuration for D1 operations
|
|
351
352
|
*/
|
|
352
|
-
async function ensureTables(db) {
|
|
353
|
-
await db.prepare(`PRAGMA foreign_keys = ON`).run();
|
|
354
|
-
const schemaVersion = await detectSchemaVersion(db);
|
|
355
|
-
await migrateWorkflowTable(db, schemaVersion.workflowTable);
|
|
356
|
-
await migrateStepTable(db, schemaVersion.stepTable);
|
|
357
|
-
await migrateLogTable(db, schemaVersion.logTable);
|
|
358
|
-
await migrateWorkflowPropertiesTable(db, schemaVersion.workflowProperties);
|
|
359
|
-
await migrateWorkflowDependenciesTable(db, schemaVersion.workflowDependencies);
|
|
360
|
-
await createIndexes(db);
|
|
353
|
+
async function ensureTables(db, retryConfig) {
|
|
354
|
+
await retryD1Operation(() => db.prepare(`PRAGMA foreign_keys = ON`).run(), retryConfig);
|
|
355
|
+
const schemaVersion = await detectSchemaVersion(db, retryConfig);
|
|
356
|
+
await migrateWorkflowTable(db, schemaVersion.workflowTable, retryConfig);
|
|
357
|
+
await migrateStepTable(db, schemaVersion.stepTable, retryConfig);
|
|
358
|
+
await migrateLogTable(db, schemaVersion.logTable, retryConfig);
|
|
359
|
+
await migrateWorkflowPropertiesTable(db, schemaVersion.workflowProperties, retryConfig);
|
|
360
|
+
await migrateWorkflowDependenciesTable(db, schemaVersion.workflowDependencies, retryConfig);
|
|
361
|
+
await createIndexes(db, retryConfig);
|
|
361
362
|
}
|
|
362
363
|
|
|
363
364
|
//#endregion
|
|
364
365
|
//#region src/observableWorkflows/helperFunctions.ts
|
|
366
|
+
/**
|
|
367
|
+
* Default retry configuration for D1 operations
|
|
368
|
+
*/
|
|
369
|
+
const DEFAULT_RETRY_CONFIG = {
|
|
370
|
+
maxAttempts: 3,
|
|
371
|
+
initialDelayMs: 100,
|
|
372
|
+
maxDelayMs: 5e3,
|
|
373
|
+
backoffMultiplier: 2,
|
|
374
|
+
useJitter: true
|
|
375
|
+
};
|
|
376
|
+
/**
|
|
377
|
+
* Check if an error is a transient D1 network error that should be retried
|
|
378
|
+
*/
|
|
379
|
+
function isRetriableD1Error(error) {
|
|
380
|
+
if (!error) return false;
|
|
381
|
+
const errorMessage = error.message || String(error);
|
|
382
|
+
const errorString = errorMessage.toLowerCase();
|
|
383
|
+
return errorString.includes("network connection lost") || errorString.includes("d1_error") || errorString.includes("connection reset") || errorString.includes("econnreset") || errorString.includes("timeout") || errorString.includes("etimedout");
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Calculate delay with exponential backoff and optional jitter
|
|
387
|
+
*/
|
|
388
|
+
function calculateDelay(attempt, config) {
|
|
389
|
+
const exponentialDelay = Math.min(config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt), config.maxDelayMs);
|
|
390
|
+
if (config.useJitter) return Math.floor(Math.random() * exponentialDelay);
|
|
391
|
+
return exponentialDelay;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Sleep for the specified number of milliseconds
|
|
395
|
+
*/
|
|
396
|
+
function sleep(ms) {
|
|
397
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Retry a D1 operation with exponential backoff for transient network errors
|
|
401
|
+
*
|
|
402
|
+
* @param operation - The async operation to retry
|
|
403
|
+
* @param config - Retry configuration options
|
|
404
|
+
* @returns The result of the operation
|
|
405
|
+
* @throws The last error if all retry attempts fail
|
|
406
|
+
*
|
|
407
|
+
* @example
|
|
408
|
+
* ```typescript
|
|
409
|
+
* const result = await retryD1Operation(
|
|
410
|
+
* () => db.prepare("SELECT * FROM table").first(),
|
|
411
|
+
* { maxAttempts: 5, initialDelayMs: 200 }
|
|
412
|
+
* )
|
|
413
|
+
* ```
|
|
414
|
+
*/
|
|
415
|
+
async function retryD1Operation(operation, config = {}) {
|
|
416
|
+
const finalConfig = {
|
|
417
|
+
...DEFAULT_RETRY_CONFIG,
|
|
418
|
+
...config
|
|
419
|
+
};
|
|
420
|
+
let lastError;
|
|
421
|
+
for (let attempt = 0; attempt < finalConfig.maxAttempts; attempt++) try {
|
|
422
|
+
return await operation();
|
|
423
|
+
} catch (error) {
|
|
424
|
+
lastError = error;
|
|
425
|
+
if (!isRetriableD1Error(error)) throw error;
|
|
426
|
+
if (attempt < finalConfig.maxAttempts - 1) {
|
|
427
|
+
const delay = calculateDelay(attempt, finalConfig);
|
|
428
|
+
console.warn(`D1 operation failed with retriable error (attempt ${attempt + 1}/${finalConfig.maxAttempts}), retrying in ${delay}ms:`, error instanceof Error ? error.message : String(error));
|
|
429
|
+
await sleep(delay);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
console.error(`D1 operation failed after ${finalConfig.maxAttempts} attempts:`, lastError instanceof Error ? lastError.message : String(lastError));
|
|
433
|
+
throw lastError;
|
|
434
|
+
}
|
|
365
435
|
async function finalizeWorkflowRecord(options, { workflowStatus, endTime, instanceId, result }) {
|
|
366
436
|
if (result !== void 0) {
|
|
367
437
|
const { data: resultData, externalRef: resultRef } = await serializeWithExternalStorage(result, options.serializer, options.externalBlobStorage);
|
|
368
|
-
return options.D1.prepare(`UPDATE WorkflowTable
|
|
438
|
+
return retryD1Operation(() => options.D1.prepare(`UPDATE WorkflowTable
|
|
369
439
|
SET workflowStatus = ?, endTime = ?, result = ?, resultRef = ?
|
|
370
|
-
WHERE instanceId = ?`).bind(workflowStatus, endTime, resultData, resultRef, instanceId).run();
|
|
440
|
+
WHERE instanceId = ?`).bind(workflowStatus, endTime, resultData, resultRef, instanceId).run(), options.retryConfig);
|
|
371
441
|
}
|
|
372
|
-
return options.D1.prepare(`UPDATE WorkflowTable
|
|
442
|
+
return retryD1Operation(() => options.D1.prepare(`UPDATE WorkflowTable
|
|
373
443
|
SET workflowStatus = ?, endTime = ?
|
|
374
|
-
WHERE instanceId = ?`).bind(workflowStatus, endTime, instanceId).run();
|
|
444
|
+
WHERE instanceId = ?`).bind(workflowStatus, endTime, instanceId).run(), options.retryConfig);
|
|
375
445
|
}
|
|
376
446
|
/**
|
|
377
447
|
* Insert a new workflow record into the database.
|
|
@@ -402,7 +472,7 @@ async function insertWorkflowRecord(options, { instanceId, workflowType, workflo
|
|
|
402
472
|
(instanceId, workflowType, workflowName, workflowMetadata, input, inputRef, tenantId, workflowStatus, startTime, endTime, parentInstanceId, triggerId)
|
|
403
473
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, workflowType, workflowName, options.serializer.serialize(workflowMetadata), inputData, inputRef, tenantId, workflowStatus, startTime, endTime ?? null, parentInstanceId ?? null, triggerId ?? null);
|
|
404
474
|
if (!dependencies || dependencies.length === 0) {
|
|
405
|
-
const result = await insertWorkflowStatement.run();
|
|
475
|
+
const result = await retryD1Operation(() => insertWorkflowStatement.run(), options.retryConfig);
|
|
406
476
|
return {
|
|
407
477
|
success: result.success,
|
|
408
478
|
meta: result.meta
|
|
@@ -416,7 +486,7 @@ async function insertWorkflowRecord(options, { instanceId, workflowType, workflo
|
|
|
416
486
|
tenantId,
|
|
417
487
|
createdAt
|
|
418
488
|
}));
|
|
419
|
-
const results = await options.D1.batch([insertWorkflowStatement, ...dependencyStatements]);
|
|
489
|
+
const results = await retryD1Operation(() => options.D1.batch([insertWorkflowStatement, ...dependencyStatements]), options.retryConfig);
|
|
420
490
|
const allSucceeded = results.every((result) => result.success);
|
|
421
491
|
return {
|
|
422
492
|
success: allSucceeded,
|
|
@@ -424,12 +494,12 @@ async function insertWorkflowRecord(options, { instanceId, workflowType, workflo
|
|
|
424
494
|
};
|
|
425
495
|
}
|
|
426
496
|
function insertStepRecordFull(context, { instanceId, name, status, metadata, startTime, endTime, result, error, resultRef, errorRef }) {
|
|
427
|
-
return context.D1.prepare(`INSERT INTO StepTable
|
|
497
|
+
return retryD1Operation(() => context.D1.prepare(`INSERT INTO StepTable
|
|
428
498
|
(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();
|
|
499
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, name, status, metadata, startTime, endTime, result, error, resultRef ?? null, errorRef ?? null, context.tenantId).run(), context.retryConfig);
|
|
430
500
|
}
|
|
431
501
|
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();
|
|
502
|
+
const existingStep = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM StepTable WHERE instanceId = ? AND stepName = ? AND tenantId = ?`).bind(instanceId, stepName, context.tenantId).first(), context.retryConfig);
|
|
433
503
|
const row = existingStep;
|
|
434
504
|
if (row) {
|
|
435
505
|
const result = await deserializeWithExternalStorage(row.result, row.resultRef, context.serializer, context.externalBlobStorage);
|
|
@@ -503,9 +573,9 @@ function truncateLogMessage(message) {
|
|
|
503
573
|
}
|
|
504
574
|
function pushLogToDB(options, { instanceId, stepName, message, timestamp, type, logOrder, tenantId }) {
|
|
505
575
|
const truncatedMessage = truncateLogMessage(message);
|
|
506
|
-
return options.D1.prepare(`INSERT INTO LogTable
|
|
576
|
+
return retryD1Operation(() => options.D1.prepare(`INSERT INTO LogTable
|
|
507
577
|
(instanceId, stepName, log, timestamp, type, logOrder, tenantId)
|
|
508
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, stepName, truncatedMessage, timestamp, type, logOrder, tenantId).run();
|
|
578
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, stepName, truncatedMessage, timestamp, type, logOrder, tenantId).run(), options.retryConfig);
|
|
509
579
|
}
|
|
510
580
|
var LogBatcher = class {
|
|
511
581
|
batch = [];
|
|
@@ -540,10 +610,12 @@ var LogBatcher = class {
|
|
|
540
610
|
if (this.batch.length === 0) return;
|
|
541
611
|
const logsToFlush = this.batch.splice(0);
|
|
542
612
|
try {
|
|
543
|
-
|
|
613
|
+
await retryD1Operation(async () => {
|
|
614
|
+
const statements = logsToFlush.map((entry) => this.options.D1.prepare(`INSERT INTO LogTable
|
|
544
615
|
(instanceId, stepName, log, timestamp, type, logOrder, tenantId)
|
|
545
616
|
VALUES (?, ?, ?, ?, ?, ?, ?)`).bind(entry.instanceId, entry.stepName, entry.message, entry.timestamp, entry.type, entry.logOrder, entry.tenantId));
|
|
546
|
-
|
|
617
|
+
return await this.options.D1.batch(statements);
|
|
618
|
+
}, this.options.retryConfig);
|
|
547
619
|
} catch (error) {
|
|
548
620
|
console.error("Batch log insert failed, falling back to individual inserts:", error);
|
|
549
621
|
await Promise.all(logsToFlush.map((entry) => pushLogToDB(this.options, entry)));
|
|
@@ -671,9 +743,9 @@ async function workflowTableRowToWorkflowRun({ row, serializer, externalBlobStor
|
|
|
671
743
|
};
|
|
672
744
|
}
|
|
673
745
|
async function updateWorkflowName(context, instanceId, newWorkflowName) {
|
|
674
|
-
return await context.D1.prepare(`UPDATE WorkflowTable
|
|
746
|
+
return await retryD1Operation(() => context.D1.prepare(`UPDATE WorkflowTable
|
|
675
747
|
SET workflowName = ?
|
|
676
|
-
WHERE instanceId = ?`).bind(newWorkflowName, instanceId).run();
|
|
748
|
+
WHERE instanceId = ?`).bind(newWorkflowName, instanceId).run(), context.retryConfig);
|
|
677
749
|
}
|
|
678
750
|
/**
|
|
679
751
|
* Update workflow fields by instanceId. Only provided fields will be updated.
|
|
@@ -750,7 +822,7 @@ async function updateWorkflow(context, instanceId, updates) {
|
|
|
750
822
|
};
|
|
751
823
|
bindings.push(instanceId);
|
|
752
824
|
const sql = `UPDATE WorkflowTable SET ${setClauses.join(", ")} WHERE instanceId = ?`;
|
|
753
|
-
return await context.D1.prepare(sql).bind(...bindings).run();
|
|
825
|
+
return await retryD1Operation(() => context.D1.prepare(sql).bind(...bindings).run(), context.retryConfig);
|
|
754
826
|
}
|
|
755
827
|
/**
|
|
756
828
|
* Serialize a workflow property value for storage
|
|
@@ -783,9 +855,9 @@ function deserializeWorkflowPropertyValue(serializedValue, valueType) {
|
|
|
783
855
|
}
|
|
784
856
|
async function upsertWorkflowProperty({ context, instanceId, key, value, tenantId }) {
|
|
785
857
|
const { serializedValue, valueType } = serializeWorkflowPropertyValue(value);
|
|
786
|
-
const res = await context.D1.prepare(`INSERT OR REPLACE INTO WorkflowProperties
|
|
858
|
+
const res = await retryD1Operation(() => context.D1.prepare(`INSERT OR REPLACE INTO WorkflowProperties
|
|
787
859
|
(instanceId, key, value, valueType, tenantId)
|
|
788
|
-
VALUES (?, ?, ?, ?, ?)`).bind(instanceId, key, serializedValue, valueType, tenantId).run();
|
|
860
|
+
VALUES (?, ?, ?, ?, ?)`).bind(instanceId, key, serializedValue, valueType, tenantId).run(), context.retryConfig);
|
|
789
861
|
if (res.error) {
|
|
790
862
|
console.error("Error inserting workflow property:", res.error);
|
|
791
863
|
return false;
|
|
@@ -841,12 +913,12 @@ function prepareWorkflowDependencyStatement({ D1, dependencyWorkflowId, dependen
|
|
|
841
913
|
* ```
|
|
842
914
|
*/
|
|
843
915
|
async function createWorkflowDependency({ context, dependencyWorkflowId, dependentWorkflowId, tenantId }) {
|
|
844
|
-
return await prepareWorkflowDependencyStatement({
|
|
916
|
+
return await retryD1Operation(() => prepareWorkflowDependencyStatement({
|
|
845
917
|
D1: context.D1,
|
|
846
918
|
dependencyWorkflowId,
|
|
847
919
|
dependentWorkflowId,
|
|
848
920
|
tenantId
|
|
849
|
-
}).run();
|
|
921
|
+
}).run(), context.retryConfig);
|
|
850
922
|
}
|
|
851
923
|
/**
|
|
852
924
|
* Create multiple workflow dependencies in a single transaction.
|
|
@@ -879,7 +951,7 @@ async function createWorkflowDependencies({ context, dependencies, tenantId }) {
|
|
|
879
951
|
tenantId,
|
|
880
952
|
createdAt
|
|
881
953
|
}));
|
|
882
|
-
return await context.D1.batch(statements);
|
|
954
|
+
return await retryD1Operation(() => context.D1.batch(statements), context.retryConfig);
|
|
883
955
|
}
|
|
884
956
|
/**
|
|
885
957
|
* Delete a specific workflow dependency relationship.
|
|
@@ -896,8 +968,8 @@ async function createWorkflowDependencies({ context, dependencies, tenantId }) {
|
|
|
896
968
|
* ```
|
|
897
969
|
*/
|
|
898
970
|
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();
|
|
971
|
+
return await retryD1Operation(() => context.D1.prepare(`DELETE FROM WorkflowDependencies
|
|
972
|
+
WHERE dependencyWorkflowId = ? AND dependentWorkflowId = ? AND tenantId = ?`).bind(dependencyWorkflowId, dependentWorkflowId, tenantId).run(), context.retryConfig);
|
|
901
973
|
}
|
|
902
974
|
|
|
903
975
|
//#endregion
|
|
@@ -917,7 +989,7 @@ const createCleanupManager = (context) => {
|
|
|
917
989
|
const getAffectedWorkflows = async (config, limit) => {
|
|
918
990
|
const { statuses, statusPlaceholders } = buildStatusesAndPlaceholders(config);
|
|
919
991
|
if (statuses.length === 0) return [];
|
|
920
|
-
const result = await context.D1.prepare(`
|
|
992
|
+
const result = await retryD1Operation(() => context.D1.prepare(`
|
|
921
993
|
SELECT instanceId, inputRef
|
|
922
994
|
FROM WorkflowTable
|
|
923
995
|
WHERE tenantId = ?
|
|
@@ -925,13 +997,13 @@ const createCleanupManager = (context) => {
|
|
|
925
997
|
AND workflowStatus IN (${statusPlaceholders})
|
|
926
998
|
ORDER BY instanceId
|
|
927
999
|
LIMIT ?
|
|
928
|
-
`).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all();
|
|
1000
|
+
`).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all(), context.retryConfig);
|
|
929
1001
|
return result.results;
|
|
930
1002
|
};
|
|
931
1003
|
const getAffectedSteps = async (config, limit) => {
|
|
932
1004
|
const { statuses, statusPlaceholders } = buildStatusesAndPlaceholders(config);
|
|
933
1005
|
if (statuses.length === 0) return [];
|
|
934
|
-
const result = await context.D1.prepare(`
|
|
1006
|
+
const result = await retryD1Operation(() => context.D1.prepare(`
|
|
935
1007
|
SELECT s.instanceId, s.stepName, s.stepStatus, s.errorRef, s.resultRef
|
|
936
1008
|
FROM StepTable s
|
|
937
1009
|
WHERE s.instanceId IN (
|
|
@@ -944,7 +1016,7 @@ const createCleanupManager = (context) => {
|
|
|
944
1016
|
LIMIT ?
|
|
945
1017
|
)
|
|
946
1018
|
ORDER BY s.instanceId, s.stepName
|
|
947
|
-
`).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all();
|
|
1019
|
+
`).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all(), context.retryConfig);
|
|
948
1020
|
return result.results;
|
|
949
1021
|
};
|
|
950
1022
|
const collectExternalStorageKeys = (workflows, steps) => {
|
|
@@ -997,15 +1069,15 @@ const createCleanupManager = (context) => {
|
|
|
997
1069
|
}
|
|
998
1070
|
if (deletedWorkflows.length > 0) {
|
|
999
1071
|
console.log(`Proceeding to delete workflows from the database...`);
|
|
1000
|
-
await context.D1.prepare(`PRAGMA foreign_keys = ON`).run();
|
|
1072
|
+
await retryD1Operation(() => context.D1.prepare(`PRAGMA foreign_keys = ON`).run(), context.retryConfig);
|
|
1001
1073
|
let totalDeletedCount = 0;
|
|
1002
1074
|
for (let i = 0; i < deletedWorkflows.length; i += 100) {
|
|
1003
1075
|
const batch = deletedWorkflows.slice(i, i + 100);
|
|
1004
1076
|
const workflowIds = batch.map(() => "?").join(", ");
|
|
1005
|
-
const result = await context.D1.prepare(`
|
|
1077
|
+
const result = await retryD1Operation(() => context.D1.prepare(`
|
|
1006
1078
|
DELETE FROM WorkflowTable
|
|
1007
1079
|
WHERE instanceId IN (${workflowIds})
|
|
1008
|
-
`).bind(...batch.map((w) => w.instanceId)).run();
|
|
1080
|
+
`).bind(...batch.map((w) => w.instanceId)).run(), context.retryConfig);
|
|
1009
1081
|
totalDeletedCount += result.meta.changes || 0;
|
|
1010
1082
|
}
|
|
1011
1083
|
return {
|
|
@@ -1021,8 +1093,8 @@ const createCleanupManager = (context) => {
|
|
|
1021
1093
|
};
|
|
1022
1094
|
};
|
|
1023
1095
|
const cleanupOrphanedSteps = async (config, limit) => {
|
|
1024
|
-
await context.D1.prepare(`PRAGMA foreign_keys = ON`).run();
|
|
1025
|
-
const orphanedSteps = await context.D1.prepare(`
|
|
1096
|
+
await retryD1Operation(() => context.D1.prepare(`PRAGMA foreign_keys = ON`).run(), context.retryConfig);
|
|
1097
|
+
const orphanedSteps = await retryD1Operation(() => context.D1.prepare(`
|
|
1026
1098
|
SELECT s.instanceId, s.stepName, s.errorRef, s.resultRef
|
|
1027
1099
|
FROM StepTable s
|
|
1028
1100
|
WHERE s.tenantId = ?
|
|
@@ -1032,7 +1104,7 @@ const createCleanupManager = (context) => {
|
|
|
1032
1104
|
)
|
|
1033
1105
|
ORDER BY s.instanceId, s.stepName
|
|
1034
1106
|
LIMIT ?
|
|
1035
|
-
`).bind(context.tenantId, context.tenantId, limit).all();
|
|
1107
|
+
`).bind(context.tenantId, context.tenantId, limit).all(), context.retryConfig);
|
|
1036
1108
|
const orphanedStepResults = orphanedSteps.results;
|
|
1037
1109
|
let deletedExternalStorageKeysCount = 0;
|
|
1038
1110
|
if (config.deleteRefsFromExternalStorage && context.externalBlobStorage && orphanedStepResults.length > 0) {
|
|
@@ -1048,11 +1120,11 @@ const createCleanupManager = (context) => {
|
|
|
1048
1120
|
for (let i = 0; i < orphanedStepResults.length; i += 99) {
|
|
1049
1121
|
const batch = orphanedStepResults.slice(i, i + 99);
|
|
1050
1122
|
const instanceIds = batch.map(() => "?").join(", ");
|
|
1051
|
-
const deletedStepsResult = await context.D1.prepare(`
|
|
1123
|
+
const deletedStepsResult = await retryD1Operation(() => context.D1.prepare(`
|
|
1052
1124
|
DELETE FROM StepTable
|
|
1053
1125
|
WHERE tenantId = ?
|
|
1054
1126
|
AND instanceId IN (${instanceIds})
|
|
1055
|
-
`).bind(context.tenantId, ...batch.map((s) => s.instanceId)).run();
|
|
1127
|
+
`).bind(context.tenantId, ...batch.map((s) => s.instanceId)).run(), context.retryConfig);
|
|
1056
1128
|
totalDeletedSteps += deletedStepsResult.meta.changes || 0;
|
|
1057
1129
|
}
|
|
1058
1130
|
return {
|
|
@@ -1068,8 +1140,8 @@ const createCleanupManager = (context) => {
|
|
|
1068
1140
|
};
|
|
1069
1141
|
};
|
|
1070
1142
|
const cleanupOrphanedLogs = async (limit) => {
|
|
1071
|
-
await context.D1.prepare(`PRAGMA foreign_keys = ON`).run();
|
|
1072
|
-
const deletedLogsResult = await context.D1.prepare(`
|
|
1143
|
+
await retryD1Operation(() => context.D1.prepare(`PRAGMA foreign_keys = ON`).run(), context.retryConfig);
|
|
1144
|
+
const deletedLogsResult = await retryD1Operation(() => context.D1.prepare(`
|
|
1073
1145
|
DELETE FROM LogTable
|
|
1074
1146
|
WHERE tenantId = ?
|
|
1075
1147
|
AND (
|
|
@@ -1084,7 +1156,7 @@ const createCleanupManager = (context) => {
|
|
|
1084
1156
|
))
|
|
1085
1157
|
)
|
|
1086
1158
|
LIMIT ?
|
|
1087
|
-
`).bind(context.tenantId, context.tenantId, context.tenantId, limit).run();
|
|
1159
|
+
`).bind(context.tenantId, context.tenantId, context.tenantId, limit).run(), context.retryConfig);
|
|
1088
1160
|
return { deletedOrphanedLogs: deletedLogsResult.meta.changes || 0 };
|
|
1089
1161
|
};
|
|
1090
1162
|
return {
|
|
@@ -1272,7 +1344,7 @@ const createLogAccessor = (context) => {
|
|
|
1272
1344
|
const limitClause = limit !== void 0 && actualOffset !== void 0 ? "LIMIT ? OFFSET ?" : "";
|
|
1273
1345
|
const sql = `SELECT * FROM StepTable ${whereClause} ORDER BY startTime ASC ${limitClause}`;
|
|
1274
1346
|
if (limit !== void 0 && actualOffset !== void 0) bindings.push(limit, actualOffset);
|
|
1275
|
-
result = await context.D1.prepare(sql).bind(...bindings).all();
|
|
1347
|
+
result = await retryD1Operation(() => context.D1.prepare(sql).bind(...bindings).all(), context.retryConfig);
|
|
1276
1348
|
if (result.results) {
|
|
1277
1349
|
const steps = await Promise.all(result.results.map(async (row) => {
|
|
1278
1350
|
let deserializedResult = null;
|
|
@@ -1299,7 +1371,7 @@ const createLogAccessor = (context) => {
|
|
|
1299
1371
|
const listWorkflows = async (limit, offset, filter, options) => {
|
|
1300
1372
|
const { sql, bindings } = buildFilteredWorkflowQuery(filter, { ignoreTenant: options?.ignoreTenant });
|
|
1301
1373
|
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();
|
|
1374
|
+
const result = await retryD1Operation(() => context.D1.prepare(sql).bind(...bindings, limit, offset).all(), context.retryConfig);
|
|
1303
1375
|
if (options?.debugLogs) console.log("listWorkflows SQL Query executed");
|
|
1304
1376
|
if (result.results) {
|
|
1305
1377
|
const workflows = await Promise.all(result.results.map((row) => workflowTableRowToWorkflowRun({
|
|
@@ -1316,7 +1388,7 @@ const createLogAccessor = (context) => {
|
|
|
1316
1388
|
for (let i = 0; i < instanceIds.length; i += BATCH_SIZE) {
|
|
1317
1389
|
const batchIds = instanceIds.slice(i, i + BATCH_SIZE);
|
|
1318
1390
|
const placeholders = batchIds.map(() => "?").join(", ");
|
|
1319
|
-
const propertiesResult = await context.D1.prepare(`SELECT * FROM WorkflowProperties WHERE instanceId IN (${placeholders})`).bind(...batchIds).all();
|
|
1391
|
+
const propertiesResult = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowProperties WHERE instanceId IN (${placeholders})`).bind(...batchIds).all(), context.retryConfig);
|
|
1320
1392
|
if (propertiesResult.results) for (const row of propertiesResult.results) {
|
|
1321
1393
|
const property = {
|
|
1322
1394
|
key: row.key,
|
|
@@ -1337,10 +1409,10 @@ const createLogAccessor = (context) => {
|
|
|
1337
1409
|
for (let i = 0; i < instanceIds.length; i += BATCH_SIZE) {
|
|
1338
1410
|
const batchIds = instanceIds.slice(i, i + BATCH_SIZE);
|
|
1339
1411
|
const placeholders = batchIds.map(() => "?").join(", ");
|
|
1340
|
-
const dependenciesResult = await context.D1.prepare(`SELECT w.*, wd.createdAt, wd.dependentWorkflowId
|
|
1412
|
+
const dependenciesResult = await retryD1Operation(() => context.D1.prepare(`SELECT w.*, wd.createdAt, wd.dependentWorkflowId
|
|
1341
1413
|
FROM WorkflowDependencies wd
|
|
1342
1414
|
JOIN WorkflowTable w ON wd.dependencyWorkflowId = w.instanceId
|
|
1343
|
-
WHERE wd.dependentWorkflowId IN (${placeholders})`).bind(...batchIds).all();
|
|
1415
|
+
WHERE wd.dependentWorkflowId IN (${placeholders})`).bind(...batchIds).all(), context.retryConfig);
|
|
1344
1416
|
if (dependenciesResult.results) for (const row of dependenciesResult.results) {
|
|
1345
1417
|
const depWorkflow = await workflowTableRowToWorkflowRun({
|
|
1346
1418
|
row,
|
|
@@ -1367,10 +1439,10 @@ const createLogAccessor = (context) => {
|
|
|
1367
1439
|
for (let i = 0; i < instanceIds.length; i += BATCH_SIZE) {
|
|
1368
1440
|
const batchIds = instanceIds.slice(i, i + BATCH_SIZE);
|
|
1369
1441
|
const placeholders = batchIds.map(() => "?").join(", ");
|
|
1370
|
-
const dependentsResult = await context.D1.prepare(`SELECT w.*, wd.createdAt, wd.dependencyWorkflowId
|
|
1442
|
+
const dependentsResult = await retryD1Operation(() => context.D1.prepare(`SELECT w.*, wd.createdAt, wd.dependencyWorkflowId
|
|
1371
1443
|
FROM WorkflowDependencies wd
|
|
1372
1444
|
JOIN WorkflowTable w ON wd.dependentWorkflowId = w.instanceId
|
|
1373
|
-
WHERE wd.dependencyWorkflowId IN (${placeholders})`).bind(...batchIds).all();
|
|
1445
|
+
WHERE wd.dependencyWorkflowId IN (${placeholders})`).bind(...batchIds).all(), context.retryConfig);
|
|
1374
1446
|
if (dependentsResult.results) for (const row of dependentsResult.results) {
|
|
1375
1447
|
const depWorkflow = await workflowTableRowToWorkflowRun({
|
|
1376
1448
|
row,
|
|
@@ -1395,7 +1467,7 @@ const createLogAccessor = (context) => {
|
|
|
1395
1467
|
return [];
|
|
1396
1468
|
};
|
|
1397
1469
|
const getWorkflowByParentId = async (parentInstanceId) => {
|
|
1398
|
-
const result = await context.D1.prepare(`SELECT * FROM WorkflowTable WHERE parentInstanceId = ? AND tenantId = ?`).bind(parentInstanceId, context.tenantId).all();
|
|
1470
|
+
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowTable WHERE parentInstanceId = ? AND tenantId = ?`).bind(parentInstanceId, context.tenantId).all(), context.retryConfig);
|
|
1399
1471
|
if (result.results) {
|
|
1400
1472
|
const workflows = await Promise.all(result.results.map((row) => workflowTableRowToWorkflowRun({
|
|
1401
1473
|
row,
|
|
@@ -1407,7 +1479,7 @@ const createLogAccessor = (context) => {
|
|
|
1407
1479
|
return null;
|
|
1408
1480
|
};
|
|
1409
1481
|
const getWorkflowByTriggerId = async (triggerId) => {
|
|
1410
|
-
const result = await context.D1.prepare(`SELECT * FROM WorkflowTable WHERE triggerId = ? AND tenantId = ?`).bind(triggerId, context.tenantId).first();
|
|
1482
|
+
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowTable WHERE triggerId = ? AND tenantId = ?`).bind(triggerId, context.tenantId).first(), context.retryConfig);
|
|
1411
1483
|
if (result) {
|
|
1412
1484
|
const workflow = await workflowTableRowToWorkflowRun({
|
|
1413
1485
|
row: result,
|
|
@@ -1419,12 +1491,12 @@ const createLogAccessor = (context) => {
|
|
|
1419
1491
|
return null;
|
|
1420
1492
|
};
|
|
1421
1493
|
const getWorkflowTypesByTenantId = async (tenantId) => {
|
|
1422
|
-
const result = await context.D1.prepare(`SELECT DISTINCT workflowType FROM WorkflowTable WHERE tenantId = ?`).bind(tenantId).all();
|
|
1494
|
+
const result = await retryD1Operation(() => context.D1.prepare(`SELECT DISTINCT workflowType FROM WorkflowTable WHERE tenantId = ?`).bind(tenantId).all(), context.retryConfig);
|
|
1423
1495
|
if (!result.results) return [];
|
|
1424
1496
|
return result.results.map((row) => row.workflowType);
|
|
1425
1497
|
};
|
|
1426
1498
|
const getWorkflowProperties = async (instanceId) => {
|
|
1427
|
-
const result = await context.D1.prepare(`SELECT * FROM WorkflowProperties WHERE instanceId = ?`).bind(instanceId).all();
|
|
1499
|
+
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowProperties WHERE instanceId = ?`).bind(instanceId).all(), context.retryConfig);
|
|
1428
1500
|
if (!result.results) return [];
|
|
1429
1501
|
return result.results.map((row) => ({
|
|
1430
1502
|
key: row.key,
|
|
@@ -1434,7 +1506,7 @@ const createLogAccessor = (context) => {
|
|
|
1434
1506
|
};
|
|
1435
1507
|
/** This function gets the basic data of a workflow, without populating any of it's complex fields */
|
|
1436
1508
|
const getWorkflowShallow = async (instanceId, options) => {
|
|
1437
|
-
const result = await context.D1.prepare(`SELECT * FROM WorkflowTable WHERE instanceId = ? AND tenantId = ?`).bind(instanceId, context.tenantId).first();
|
|
1509
|
+
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowTable WHERE instanceId = ? AND tenantId = ?`).bind(instanceId, context.tenantId).first(), context.retryConfig);
|
|
1438
1510
|
if (!result) return null;
|
|
1439
1511
|
const workflow = await workflowTableRowToWorkflowRun({
|
|
1440
1512
|
row: result,
|
|
@@ -1465,7 +1537,7 @@ const createLogAccessor = (context) => {
|
|
|
1465
1537
|
retryWorkflow.isRetryOf = workflow;
|
|
1466
1538
|
});
|
|
1467
1539
|
workflow.isRetryOf = parentWorkflow;
|
|
1468
|
-
const allLogs = await context.D1.prepare(`SELECT * FROM LogTable WHERE instanceId = ? ORDER BY timestamp ASC, logOrder ASC`).bind(instanceId).all();
|
|
1540
|
+
const allLogs = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM LogTable WHERE instanceId = ? ORDER BY timestamp ASC, logOrder ASC`).bind(instanceId).all(), context.retryConfig);
|
|
1469
1541
|
const logs = allLogs.results?.map((logRow) => ({
|
|
1470
1542
|
instanceId: logRow.instanceId,
|
|
1471
1543
|
stepName: logRow.stepName,
|
|
@@ -1481,10 +1553,10 @@ const createLogAccessor = (context) => {
|
|
|
1481
1553
|
const properties = await getWorkflowProperties(instanceId);
|
|
1482
1554
|
workflow.properties = properties;
|
|
1483
1555
|
if (populateDependencies) {
|
|
1484
|
-
const dependenciesResult = await context.D1.prepare(`SELECT w.*, wd.createdAt
|
|
1556
|
+
const dependenciesResult = await retryD1Operation(() => context.D1.prepare(`SELECT w.*, wd.createdAt
|
|
1485
1557
|
FROM WorkflowDependencies wd
|
|
1486
1558
|
JOIN WorkflowTable w ON wd.dependencyWorkflowId = w.instanceId
|
|
1487
|
-
WHERE wd.dependentWorkflowId = ? AND wd.tenantId = ?`).bind(instanceId, context.tenantId).all();
|
|
1559
|
+
WHERE wd.dependentWorkflowId = ? AND wd.tenantId = ?`).bind(instanceId, context.tenantId).all(), context.retryConfig);
|
|
1488
1560
|
if (dependenciesResult.results) workflow.dependencies = await Promise.all(dependenciesResult.results.map(async (row) => {
|
|
1489
1561
|
const depWorkflow = await workflowTableRowToWorkflowRun({
|
|
1490
1562
|
row,
|
|
@@ -1500,10 +1572,10 @@ const createLogAccessor = (context) => {
|
|
|
1500
1572
|
}));
|
|
1501
1573
|
}
|
|
1502
1574
|
if (populateDependents) {
|
|
1503
|
-
const dependentsResult = await context.D1.prepare(`SELECT w.*, wd.createdAt
|
|
1575
|
+
const dependentsResult = await retryD1Operation(() => context.D1.prepare(`SELECT w.*, wd.createdAt
|
|
1504
1576
|
FROM WorkflowDependencies wd
|
|
1505
1577
|
JOIN WorkflowTable w ON wd.dependentWorkflowId = w.instanceId
|
|
1506
|
-
WHERE wd.dependencyWorkflowId = ? AND wd.tenantId = ?`).bind(instanceId, context.tenantId).all();
|
|
1578
|
+
WHERE wd.dependencyWorkflowId = ? AND wd.tenantId = ?`).bind(instanceId, context.tenantId).all(), context.retryConfig);
|
|
1507
1579
|
if (dependentsResult.results) workflow.dependents = await Promise.all(dependentsResult.results.map(async (row) => {
|
|
1508
1580
|
const depWorkflow = await workflowTableRowToWorkflowRun({
|
|
1509
1581
|
row,
|
|
@@ -1521,7 +1593,7 @@ const createLogAccessor = (context) => {
|
|
|
1521
1593
|
return workflow;
|
|
1522
1594
|
};
|
|
1523
1595
|
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();
|
|
1596
|
+
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM StepTable WHERE instanceId = ? AND stepName = ? AND tenantId = ?`).bind(instanceId, stepName, context.tenantId).all(), context.retryConfig);
|
|
1525
1597
|
if (!result.results || result.results.length === 0) return null;
|
|
1526
1598
|
const row = result.results[0];
|
|
1527
1599
|
if (!row) return null;
|
|
@@ -1538,7 +1610,7 @@ const createLogAccessor = (context) => {
|
|
|
1538
1610
|
error: deserializedError,
|
|
1539
1611
|
logs: []
|
|
1540
1612
|
};
|
|
1541
|
-
const logResult = await context.D1.prepare(`SELECT * FROM LogTable WHERE instanceId = ? AND stepName = ? ORDER BY logOrder ASC`).bind(instanceId, stepName).all();
|
|
1613
|
+
const logResult = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM LogTable WHERE instanceId = ? AND stepName = ? ORDER BY logOrder ASC`).bind(instanceId, stepName).all(), context.retryConfig);
|
|
1542
1614
|
if (logResult.results) step.logs = logResult.results.map((logRow) => ({
|
|
1543
1615
|
instanceId: logRow.instanceId,
|
|
1544
1616
|
stepName: logRow.stepName,
|
|
@@ -1549,10 +1621,10 @@ const createLogAccessor = (context) => {
|
|
|
1549
1621
|
return step;
|
|
1550
1622
|
};
|
|
1551
1623
|
const getPropertiesKeys = async (instanceId) => {
|
|
1552
|
-
const result = await context.D1.prepare(`
|
|
1624
|
+
const result = await retryD1Operation(() => context.D1.prepare(`
|
|
1553
1625
|
SELECT DISTINCT key, valueType FROM WorkflowProperties
|
|
1554
1626
|
WHERE tenantId = ? AND (? IS NULL OR instanceId = ?)
|
|
1555
|
-
`).bind(context.tenantId, instanceId ?? null, instanceId ?? null).all();
|
|
1627
|
+
`).bind(context.tenantId, instanceId ?? null, instanceId ?? null).all(), context.retryConfig);
|
|
1556
1628
|
console.log(`debug getPropertiesKeys: ${JSON.stringify(result)}`);
|
|
1557
1629
|
if (!result.results) return [];
|
|
1558
1630
|
return result.results.map((row) => ({
|
|
@@ -1575,7 +1647,7 @@ const createLogAccessor = (context) => {
|
|
|
1575
1647
|
bindings.push(toTime);
|
|
1576
1648
|
}
|
|
1577
1649
|
const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : "";
|
|
1578
|
-
const result = await context.D1.prepare(`
|
|
1650
|
+
const result = await retryD1Operation(() => context.D1.prepare(`
|
|
1579
1651
|
SELECT
|
|
1580
1652
|
workflowType,
|
|
1581
1653
|
COUNT(*) as workflowCount,
|
|
@@ -1618,7 +1690,7 @@ const createLogAccessor = (context) => {
|
|
|
1618
1690
|
${whereClause}
|
|
1619
1691
|
GROUP BY workflowType
|
|
1620
1692
|
ORDER BY workflowCount DESC
|
|
1621
|
-
`).bind(...bindings).all();
|
|
1693
|
+
`).bind(...bindings).all(), context.retryConfig);
|
|
1622
1694
|
if (!result.results) return [];
|
|
1623
1695
|
return result.results.map((row) => ({
|
|
1624
1696
|
workflowType: row.workflowType,
|
|
@@ -1806,7 +1878,8 @@ function createQueueWorkflowContext(options) {
|
|
|
1806
1878
|
D1: options.D1,
|
|
1807
1879
|
externalBlobStorage: options.externalBlobStorage,
|
|
1808
1880
|
serializer: internalContext.serializer,
|
|
1809
|
-
tenantId: "unknown"
|
|
1881
|
+
tenantId: "unknown",
|
|
1882
|
+
retryConfig: options.retryConfig
|
|
1810
1883
|
});
|
|
1811
1884
|
const waitingWorkflows = await logAccessor.listWorkflows(250, 0, { workflowStatus: "waiting" }, {
|
|
1812
1885
|
ignoreTenant: true,
|
|
@@ -1853,7 +1926,7 @@ function createQueueWorkflowContext(options) {
|
|
|
1853
1926
|
async function createStepContext(context) {
|
|
1854
1927
|
const instanceId = context.instanceId;
|
|
1855
1928
|
const reuseSuccessfulSteps = context.reuseSuccessfulSteps ?? true;
|
|
1856
|
-
await ensureTables(context.D1);
|
|
1929
|
+
await ensureTables(context.D1, context.retryConfig);
|
|
1857
1930
|
const step = async (step$1, callback) => {
|
|
1858
1931
|
const stepNameParam = typeof step$1 === "string" ? step$1 : step$1.name;
|
|
1859
1932
|
const stepMetadataParam = typeof step$1 === "string" ? void 0 : step$1.metadata;
|
|
@@ -1931,9 +2004,9 @@ async function createStepContext(context) {
|
|
|
1931
2004
|
const stepStatus$1 = "completed";
|
|
1932
2005
|
const { data: resultData, externalRef: resultRef } = await serializeWithExternalStorage(result, context.serializer, context.externalBlobStorage);
|
|
1933
2006
|
const stepError$1 = null;
|
|
1934
|
-
await context.D1.prepare(`UPDATE StepTable
|
|
2007
|
+
await retryD1Operation(() => context.D1.prepare(`UPDATE StepTable
|
|
1935
2008
|
SET stepStatus = ?, endTime = ?, result = ?, error = ?, resultRef = ?, errorRef = ?
|
|
1936
|
-
WHERE instanceId = ? AND stepName = ?`).bind(stepStatus$1, endTime, resultData, stepError$1, resultRef, null, instanceId, stepName).run();
|
|
2009
|
+
WHERE instanceId = ? AND stepName = ?`).bind(stepStatus$1, endTime, resultData, stepError$1, resultRef, null, instanceId, stepName).run(), context.retryConfig);
|
|
1937
2010
|
await logBatcher.destroy();
|
|
1938
2011
|
return result;
|
|
1939
2012
|
} catch (error) {
|
|
@@ -1941,9 +2014,9 @@ async function createStepContext(context) {
|
|
|
1941
2014
|
const stepStatus$1 = "failed";
|
|
1942
2015
|
const stepResult$1 = null;
|
|
1943
2016
|
const { data: errorData, externalRef: errorRef } = await serializeWithExternalStorage(error, context.serializer, context.externalBlobStorage);
|
|
1944
|
-
await context.D1.prepare(`UPDATE StepTable
|
|
2017
|
+
await retryD1Operation(() => context.D1.prepare(`UPDATE StepTable
|
|
1945
2018
|
SET stepStatus = ?, endTime = ?, result = ?, error = ?, resultRef = ?, errorRef = ?
|
|
1946
|
-
WHERE instanceId = ? AND stepName = ?`).bind(stepStatus$1, endTime, stepResult$1, errorData, null, errorRef, instanceId, stepName).run();
|
|
2019
|
+
WHERE instanceId = ? AND stepName = ?`).bind(stepStatus$1, endTime, stepResult$1, errorData, null, errorRef, instanceId, stepName).run(), context.retryConfig);
|
|
1947
2020
|
await logBatcher.destroy();
|
|
1948
2021
|
throw error;
|
|
1949
2022
|
}
|
|
@@ -1962,14 +2035,15 @@ function createWorkflowContext(options) {
|
|
|
1962
2035
|
};
|
|
1963
2036
|
const call = async ({ workflow, input, workflowName, tenantId, parentInstanceId, reuseSuccessfulSteps, triggerId, scheduledInstanceId }) => {
|
|
1964
2037
|
if (!ensuredTables) {
|
|
1965
|
-
await ensureTables(options.D1);
|
|
2038
|
+
await ensureTables(options.D1, options.retryConfig);
|
|
1966
2039
|
ensuredTables = true;
|
|
1967
2040
|
}
|
|
1968
2041
|
const logAccessor = createLogAccessor({
|
|
1969
2042
|
D1: internalContext.D1,
|
|
1970
2043
|
externalBlobStorage: internalContext.externalBlobStorage,
|
|
1971
2044
|
serializer: internalContext.serializer,
|
|
1972
|
-
tenantId
|
|
2045
|
+
tenantId,
|
|
2046
|
+
retryConfig: options.retryConfig
|
|
1973
2047
|
});
|
|
1974
2048
|
if (scheduledInstanceId) {
|
|
1975
2049
|
const existingWorkflow = await logAccessor.getWorkflowShallow(scheduledInstanceId);
|
|
@@ -2025,7 +2099,8 @@ function createWorkflowContext(options) {
|
|
|
2025
2099
|
idFactory: internalContext.idFactory,
|
|
2026
2100
|
parentInstanceId,
|
|
2027
2101
|
reuseSuccessfulSteps,
|
|
2028
|
-
externalBlobStorage: options.externalBlobStorage
|
|
2102
|
+
externalBlobStorage: options.externalBlobStorage,
|
|
2103
|
+
retryConfig: options.retryConfig
|
|
2029
2104
|
});
|
|
2030
2105
|
try {
|
|
2031
2106
|
const result = await workflow._callback(input, {
|
|
@@ -2045,7 +2120,10 @@ function createWorkflowContext(options) {
|
|
|
2045
2120
|
}
|
|
2046
2121
|
},
|
|
2047
2122
|
async setWorkflowName(newName) {
|
|
2048
|
-
await updateWorkflowName({
|
|
2123
|
+
await updateWorkflowName({
|
|
2124
|
+
D1: options.D1,
|
|
2125
|
+
retryConfig: options.retryConfig
|
|
2126
|
+
}, instanceId, newName);
|
|
2049
2127
|
},
|
|
2050
2128
|
async setWorkflowProperty(key, value) {
|
|
2051
2129
|
await upsertWorkflowProperty({
|
|
@@ -2095,10 +2173,10 @@ function createWorkflowContext(options) {
|
|
|
2095
2173
|
};
|
|
2096
2174
|
const retry = async ({ workflow, retryInstanceId, triggerId, retryOptions }) => {
|
|
2097
2175
|
if (!ensuredTables) {
|
|
2098
|
-
await ensureTables(options.D1);
|
|
2176
|
+
await ensureTables(options.D1, options.retryConfig);
|
|
2099
2177
|
ensuredTables = true;
|
|
2100
2178
|
}
|
|
2101
|
-
const oldRun = await options.D1.prepare(`SELECT input, workflowName, tenantId, inputRef FROM WorkflowTable WHERE instanceId = ? `).bind(retryInstanceId).first();
|
|
2179
|
+
const oldRun = await retryD1Operation(() => options.D1.prepare(`SELECT input, workflowName, tenantId, inputRef FROM WorkflowTable WHERE instanceId = ? `).bind(retryInstanceId).first(), options.retryConfig);
|
|
2102
2180
|
const oldWorkflowName = oldRun?.workflowName;
|
|
2103
2181
|
const tenantId = oldRun?.tenantId;
|
|
2104
2182
|
if (!tenantId) throw new Error(`No tenantId found for instanceId ${retryInstanceId}`);
|
|
@@ -2204,4 +2282,4 @@ function createR2ExternalBlobStorage(options) {
|
|
|
2204
2282
|
}
|
|
2205
2283
|
|
|
2206
2284
|
//#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 };
|
|
2285
|
+
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.
|
|
3
|
+
"version": "0.20.0-beta.5",
|
|
4
4
|
"description": "My awesome typescript library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"test": "vitest run",
|
|
54
54
|
"typecheck": "tsc --noEmit",
|
|
55
55
|
"format": "prettier --cache --write .",
|
|
56
|
-
"release": "bumpp && pnpm publish",
|
|
57
|
-
"release:beta": "bumpp --preid beta && pnpm publish --tag beta"
|
|
56
|
+
"release": "npm login && bumpp && pnpm publish",
|
|
57
|
+
"release:beta": "npm login && bumpp --preid beta && pnpm publish --tag beta"
|
|
58
58
|
}
|
|
59
59
|
}
|