@brandboostinggmbh/observable-workflows 0.20.0-beta.4 → 0.20.0-beta.6
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/dist/index.d.ts +39 -55
- package/dist/index.js +226 -170
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -14,14 +14,15 @@ 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
|
|
21
22
|
/**
|
|
22
23
|
* Configuration for retry behavior on D1 operations
|
|
23
24
|
*/
|
|
24
|
-
interface RetryConfig
|
|
25
|
+
interface RetryConfig {
|
|
25
26
|
/** Maximum number of retry attempts (default: 3) */
|
|
26
27
|
maxAttempts?: number;
|
|
27
28
|
/** Initial delay in milliseconds (default: 100) */
|
|
@@ -49,7 +50,7 @@ interface RetryConfig$1 {
|
|
|
49
50
|
* )
|
|
50
51
|
* ```
|
|
51
52
|
*/
|
|
52
|
-
declare function retryD1Operation<T>(operation: () => Promise<T>, config?: RetryConfig
|
|
53
|
+
declare function retryD1Operation<T>(operation: () => Promise<T>, config?: RetryConfig): Promise<T>;
|
|
53
54
|
declare function finalizeWorkflowRecord(options: InternalWorkflowContextOptions, {
|
|
54
55
|
workflowStatus,
|
|
55
56
|
endTime,
|
|
@@ -61,43 +62,8 @@ declare function finalizeWorkflowRecord(options: InternalWorkflowContextOptions,
|
|
|
61
62
|
instanceId: string;
|
|
62
63
|
result?: any;
|
|
63
64
|
}): Promise<D1Result<Record<string, unknown>>>;
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
* Optionally creates workflow dependencies atomically in the same transaction.
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```typescript
|
|
70
|
-
* // Create a workflow with dependencies
|
|
71
|
-
* await insertWorkflowRecord(context, {
|
|
72
|
-
* instanceId: 'workflow-c',
|
|
73
|
-
* workflowType: 'process-data',
|
|
74
|
-
* workflowName: 'Data Processing',
|
|
75
|
-
* workflowMetadata: {},
|
|
76
|
-
* input: { data: 'value' },
|
|
77
|
-
* workflowStatus: 'pending',
|
|
78
|
-
* startTime: Date.now(),
|
|
79
|
-
* tenantId: 'tenant-1',
|
|
80
|
-
* dependencies: [
|
|
81
|
-
* { instanceId: 'workflow-a' },
|
|
82
|
-
* { instanceId: 'workflow-b' }
|
|
83
|
-
* ] // This workflow depends on A and B
|
|
84
|
-
* })
|
|
85
|
-
* ```
|
|
86
|
-
*/
|
|
87
|
-
declare function insertWorkflowRecord(options: InternalWorkflowContextOptions, {
|
|
88
|
-
instanceId,
|
|
89
|
-
workflowType,
|
|
90
|
-
workflowName,
|
|
91
|
-
workflowMetadata,
|
|
92
|
-
input,
|
|
93
|
-
workflowStatus,
|
|
94
|
-
startTime,
|
|
95
|
-
endTime,
|
|
96
|
-
parentInstanceId,
|
|
97
|
-
tenantId,
|
|
98
|
-
triggerId,
|
|
99
|
-
dependencies
|
|
100
|
-
}: {
|
|
65
|
+
/** Parameters for preparing workflow insert statements */
|
|
66
|
+
interface PrepareWorkflowInsertParams {
|
|
101
67
|
/** Unique identifier for this workflow instance */
|
|
102
68
|
instanceId: string;
|
|
103
69
|
/** Type/category of the workflow */
|
|
@@ -122,11 +88,36 @@ declare function insertWorkflowRecord(options: InternalWorkflowContextOptions, {
|
|
|
122
88
|
triggerId?: string | null;
|
|
123
89
|
/**
|
|
124
90
|
* Optional array of workflow dependencies that this workflow depends on.
|
|
125
|
-
* When provided, dependency
|
|
126
|
-
* This ensures that the workflow and all its dependencies are created in a single transaction.
|
|
91
|
+
* When provided, dependency insert statements are included in the returned statements array.
|
|
127
92
|
*/
|
|
128
93
|
dependencies?: Array<WorkflowDependency>;
|
|
129
|
-
}
|
|
94
|
+
}
|
|
95
|
+
/** Result from preparing workflow insert statements */
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Insert a new workflow record into the database.
|
|
99
|
+
* Optionally creates workflow dependencies atomically in the same transaction.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* // Create a workflow with dependencies
|
|
104
|
+
* await insertWorkflowRecord(context, {
|
|
105
|
+
* instanceId: 'workflow-c',
|
|
106
|
+
* workflowType: 'process-data',
|
|
107
|
+
* workflowName: 'Data Processing',
|
|
108
|
+
* workflowMetadata: {},
|
|
109
|
+
* input: { data: 'value' },
|
|
110
|
+
* workflowStatus: 'pending',
|
|
111
|
+
* startTime: Date.now(),
|
|
112
|
+
* tenantId: 'tenant-1',
|
|
113
|
+
* dependencies: [
|
|
114
|
+
* { instanceId: 'workflow-a' },
|
|
115
|
+
* { instanceId: 'workflow-b' }
|
|
116
|
+
* ] // This workflow depends on A and B
|
|
117
|
+
* })
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
declare function insertWorkflowRecord(options: InternalWorkflowContextOptions, params: PrepareWorkflowInsertParams): Promise<{
|
|
130
121
|
success: boolean;
|
|
131
122
|
meta: any;
|
|
132
123
|
}>;
|
|
@@ -231,6 +222,7 @@ declare function workflowTableRowToWorkflowRun({
|
|
|
231
222
|
}): Promise<WorkflowRun>;
|
|
232
223
|
declare function updateWorkflowName(context: {
|
|
233
224
|
D1: D1Database;
|
|
225
|
+
retryConfig?: RetryConfig;
|
|
234
226
|
}, instanceId: string, newWorkflowName: string): Promise<D1Result<Record<string, unknown>>>;
|
|
235
227
|
/**
|
|
236
228
|
* Update workflow fields by instanceId. Only provided fields will be updated.
|
|
@@ -441,6 +433,8 @@ type StepContextOptions = {
|
|
|
441
433
|
reuseSuccessfulSteps?: boolean;
|
|
442
434
|
/** Optional external blob storage for large data that exceeds D1 size limits */
|
|
443
435
|
externalBlobStorage?: ExternalBlobStorage;
|
|
436
|
+
/** Optional retry configuration for D1 operations */
|
|
437
|
+
retryConfig?: RetryConfig;
|
|
444
438
|
};
|
|
445
439
|
type ConsoleWrapper = {
|
|
446
440
|
log: (message?: any, ...optionalParams: any[]) => void;
|
|
@@ -637,18 +631,6 @@ type ExternalBlobStorage = {
|
|
|
637
631
|
get: (id: string) => Promise<string>;
|
|
638
632
|
delete: (...keys: string[]) => Promise<number>;
|
|
639
633
|
};
|
|
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
|
-
};
|
|
652
634
|
type WorkflowContextOptions = {
|
|
653
635
|
D1: D1Database;
|
|
654
636
|
idFactory?: () => string;
|
|
@@ -774,6 +756,7 @@ declare const createCleanupManager: (context: {
|
|
|
774
756
|
tenantId: string;
|
|
775
757
|
serializer?: Serializer;
|
|
776
758
|
externalBlobStorage?: ExternalBlobStorage;
|
|
759
|
+
retryConfig?: RetryConfig;
|
|
777
760
|
}) => {
|
|
778
761
|
countAffectedWorkflows: (config: DeleteConfig, limit: number) => Promise<{
|
|
779
762
|
count: number;
|
|
@@ -820,6 +803,7 @@ declare const createLogAccessor: (context: {
|
|
|
820
803
|
tenantId: string;
|
|
821
804
|
serializer?: Serializer;
|
|
822
805
|
externalBlobStorage?: ExternalBlobStorage;
|
|
806
|
+
retryConfig?: RetryConfig;
|
|
823
807
|
}) => {
|
|
824
808
|
listSteps: {
|
|
825
809
|
(limit: number, offset: number, instanceId?: string): Promise<Step[]>;
|
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 retryD1Operation(() => 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 retryD1Operation(() => 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 retryD1Operation(() => 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 retryD1Operation(() => 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,8 +52,8 @@ 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 retryD1Operation(() => 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
59
|
if (!hasInputRef || inputHasNotNull) await retryD1Operation(() => db.batch([
|
|
@@ -78,14 +78,14 @@ 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 retryD1Operation(() => 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
90
|
if (!hasTriggerId) await retryD1Operation(() => db.batch([
|
|
91
91
|
db.prepare(`CREATE TABLE WorkflowTable_new (
|
|
@@ -111,21 +111,21 @@ 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
130
|
await retryD1Operation(() => db.prepare(`CREATE TABLE WorkflowTable (
|
|
131
131
|
instanceId TEXT NOT NULL,
|
|
@@ -144,29 +144,29 @@ 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
171
|
await retryD1Operation(() => db.prepare(`CREATE TABLE StepTable (
|
|
172
172
|
instanceId TEXT NOT NULL,
|
|
@@ -182,21 +182,21 @@ 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 retryD1Operation(() => 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 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());
|
|
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
201
|
await retryD1Operation(() => db.prepare(`CREATE TABLE LogTable (
|
|
202
202
|
instanceId TEXT NOT NULL,
|
|
@@ -208,14 +208,14 @@ 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
220
|
await retryD1Operation(() => db.prepare(`CREATE TABLE WorkflowProperties (
|
|
221
221
|
instanceId 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 retryD1Operation(() => 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,23 +341,24 @@ 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 retryD1Operation(() => 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 retryD1Operation(() => 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
|
|
@@ -397,12 +398,12 @@ function sleep(ms) {
|
|
|
397
398
|
}
|
|
398
399
|
/**
|
|
399
400
|
* Retry a D1 operation with exponential backoff for transient network errors
|
|
400
|
-
*
|
|
401
|
+
*
|
|
401
402
|
* @param operation - The async operation to retry
|
|
402
403
|
* @param config - Retry configuration options
|
|
403
404
|
* @returns The result of the operation
|
|
404
405
|
* @throws The last error if all retry attempts fail
|
|
405
|
-
*
|
|
406
|
+
*
|
|
406
407
|
* @example
|
|
407
408
|
* ```typescript
|
|
408
409
|
* const result = await retryD1Operation(
|
|
@@ -436,11 +437,60 @@ async function finalizeWorkflowRecord(options, { workflowStatus, endTime, instan
|
|
|
436
437
|
const { data: resultData, externalRef: resultRef } = await serializeWithExternalStorage(result, options.serializer, options.externalBlobStorage);
|
|
437
438
|
return retryD1Operation(() => options.D1.prepare(`UPDATE WorkflowTable
|
|
438
439
|
SET workflowStatus = ?, endTime = ?, result = ?, resultRef = ?
|
|
439
|
-
WHERE instanceId = ?`).bind(workflowStatus, endTime, resultData, resultRef, instanceId).run());
|
|
440
|
+
WHERE instanceId = ?`).bind(workflowStatus, endTime, resultData, resultRef, instanceId).run(), options.retryConfig);
|
|
440
441
|
}
|
|
441
442
|
return retryD1Operation(() => options.D1.prepare(`UPDATE WorkflowTable
|
|
442
443
|
SET workflowStatus = ?, endTime = ?
|
|
443
|
-
WHERE instanceId = ?`).bind(workflowStatus, endTime, instanceId).run());
|
|
444
|
+
WHERE instanceId = ?`).bind(workflowStatus, endTime, instanceId).run(), options.retryConfig);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Prepare D1 statements for inserting a workflow record and its dependencies.
|
|
448
|
+
* This function handles input serialization (including external blob storage for large inputs)
|
|
449
|
+
* and returns prepared statements that can be executed individually or batched with other statements.
|
|
450
|
+
*
|
|
451
|
+
* This is an internal helper used by `insertWorkflowRecord` and `enqueueWorkflowBatch` to enable
|
|
452
|
+
* efficient batching of multiple workflow inserts into a single D1 batch operation.
|
|
453
|
+
*
|
|
454
|
+
* @param options - The internal workflow context options containing D1, serializer, etc.
|
|
455
|
+
* @param params - The workflow record parameters
|
|
456
|
+
* @returns Object containing prepared statements, instanceId, and workflowType
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* ```typescript
|
|
460
|
+
* // Prepare statements for multiple workflows, then batch execute
|
|
461
|
+
* const prepared = await Promise.all([
|
|
462
|
+
* prepareWorkflowInsertStatements(options, workflow1Params),
|
|
463
|
+
* prepareWorkflowInsertStatements(options, workflow2Params),
|
|
464
|
+
* ])
|
|
465
|
+
* const allStatements = prepared.flatMap(p => p.statements)
|
|
466
|
+
* await options.D1.batch(allStatements)
|
|
467
|
+
* ```
|
|
468
|
+
*
|
|
469
|
+
* @internal
|
|
470
|
+
*/
|
|
471
|
+
async function prepareWorkflowInsertStatements(options, { instanceId, workflowType, workflowName, workflowMetadata, input, workflowStatus, startTime, endTime, parentInstanceId, tenantId, triggerId, dependencies }) {
|
|
472
|
+
const { data: inputData, externalRef: inputRef } = await serializeWithExternalStorage(input, options.serializer, options.externalBlobStorage);
|
|
473
|
+
const insertWorkflowStatement = options.D1.prepare(`INSERT INTO WorkflowTable
|
|
474
|
+
(instanceId, workflowType, workflowName, workflowMetadata, input, inputRef, tenantId, workflowStatus, startTime, endTime, parentInstanceId, triggerId)
|
|
475
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, workflowType, workflowName, options.serializer.serialize(workflowMetadata), inputData, inputRef, tenantId, workflowStatus, startTime, endTime ?? null, parentInstanceId ?? null, triggerId ?? null);
|
|
476
|
+
if (!dependencies || dependencies.length === 0) return {
|
|
477
|
+
statements: [insertWorkflowStatement],
|
|
478
|
+
instanceId,
|
|
479
|
+
workflowType
|
|
480
|
+
};
|
|
481
|
+
const createdAt = Date.now();
|
|
482
|
+
const dependencyStatements = dependencies.map((dep) => prepareWorkflowDependencyStatement({
|
|
483
|
+
D1: options.D1,
|
|
484
|
+
dependencyWorkflowId: dep.instanceId,
|
|
485
|
+
dependentWorkflowId: instanceId,
|
|
486
|
+
tenantId,
|
|
487
|
+
createdAt
|
|
488
|
+
}));
|
|
489
|
+
return {
|
|
490
|
+
statements: [insertWorkflowStatement, ...dependencyStatements],
|
|
491
|
+
instanceId,
|
|
492
|
+
workflowType
|
|
493
|
+
};
|
|
444
494
|
}
|
|
445
495
|
/**
|
|
446
496
|
* Insert a new workflow record into the database.
|
|
@@ -465,27 +515,16 @@ async function finalizeWorkflowRecord(options, { workflowStatus, endTime, instan
|
|
|
465
515
|
* })
|
|
466
516
|
* ```
|
|
467
517
|
*/
|
|
468
|
-
async function insertWorkflowRecord(options,
|
|
469
|
-
const {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, workflowType, workflowName, options.serializer.serialize(workflowMetadata), inputData, inputRef, tenantId, workflowStatus, startTime, endTime ?? null, parentInstanceId ?? null, triggerId ?? null);
|
|
473
|
-
if (!dependencies || dependencies.length === 0) {
|
|
474
|
-
const result = await retryD1Operation(() => insertWorkflowStatement.run());
|
|
518
|
+
async function insertWorkflowRecord(options, params) {
|
|
519
|
+
const { statements } = await prepareWorkflowInsertStatements(options, params);
|
|
520
|
+
if (statements.length === 1) {
|
|
521
|
+
const result = await retryD1Operation(() => statements[0].run(), options.retryConfig);
|
|
475
522
|
return {
|
|
476
523
|
success: result.success,
|
|
477
524
|
meta: result.meta
|
|
478
525
|
};
|
|
479
526
|
}
|
|
480
|
-
const
|
|
481
|
-
const dependencyStatements = dependencies.map((dep) => prepareWorkflowDependencyStatement({
|
|
482
|
-
D1: options.D1,
|
|
483
|
-
dependencyWorkflowId: dep.instanceId,
|
|
484
|
-
dependentWorkflowId: instanceId,
|
|
485
|
-
tenantId,
|
|
486
|
-
createdAt
|
|
487
|
-
}));
|
|
488
|
-
const results = await retryD1Operation(() => options.D1.batch([insertWorkflowStatement, ...dependencyStatements]));
|
|
527
|
+
const results = await retryD1Operation(() => options.D1.batch(statements), options.retryConfig);
|
|
489
528
|
const allSucceeded = results.every((result) => result.success);
|
|
490
529
|
return {
|
|
491
530
|
success: allSucceeded,
|
|
@@ -495,10 +534,10 @@ async function insertWorkflowRecord(options, { instanceId, workflowType, workflo
|
|
|
495
534
|
function insertStepRecordFull(context, { instanceId, name, status, metadata, startTime, endTime, result, error, resultRef, errorRef }) {
|
|
496
535
|
return retryD1Operation(() => context.D1.prepare(`INSERT INTO StepTable
|
|
497
536
|
(instanceId, stepName, stepStatus, stepMetadata, startTime, endTime, result, error, resultRef, errorRef, tenantId)
|
|
498
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, name, status, metadata, startTime, endTime, result, error, resultRef ?? null, errorRef ?? null, context.tenantId).run());
|
|
537
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, name, status, metadata, startTime, endTime, result, error, resultRef ?? null, errorRef ?? null, context.tenantId).run(), context.retryConfig);
|
|
499
538
|
}
|
|
500
539
|
async function getStepRecord(context, stepName, instanceId) {
|
|
501
|
-
const existingStep = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM StepTable WHERE instanceId = ? AND stepName = ? AND tenantId = ?`).bind(instanceId, stepName, context.tenantId).first());
|
|
540
|
+
const existingStep = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM StepTable WHERE instanceId = ? AND stepName = ? AND tenantId = ?`).bind(instanceId, stepName, context.tenantId).first(), context.retryConfig);
|
|
502
541
|
const row = existingStep;
|
|
503
542
|
if (row) {
|
|
504
543
|
const result = await deserializeWithExternalStorage(row.result, row.resultRef, context.serializer, context.externalBlobStorage);
|
|
@@ -574,7 +613,7 @@ function pushLogToDB(options, { instanceId, stepName, message, timestamp, type,
|
|
|
574
613
|
const truncatedMessage = truncateLogMessage(message);
|
|
575
614
|
return retryD1Operation(() => options.D1.prepare(`INSERT INTO LogTable
|
|
576
615
|
(instanceId, stepName, log, timestamp, type, logOrder, tenantId)
|
|
577
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, stepName, truncatedMessage, timestamp, type, logOrder, tenantId).run());
|
|
616
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, stepName, truncatedMessage, timestamp, type, logOrder, tenantId).run(), options.retryConfig);
|
|
578
617
|
}
|
|
579
618
|
var LogBatcher = class {
|
|
580
619
|
batch = [];
|
|
@@ -614,7 +653,7 @@ var LogBatcher = class {
|
|
|
614
653
|
(instanceId, stepName, log, timestamp, type, logOrder, tenantId)
|
|
615
654
|
VALUES (?, ?, ?, ?, ?, ?, ?)`).bind(entry.instanceId, entry.stepName, entry.message, entry.timestamp, entry.type, entry.logOrder, entry.tenantId));
|
|
616
655
|
return await this.options.D1.batch(statements);
|
|
617
|
-
});
|
|
656
|
+
}, this.options.retryConfig);
|
|
618
657
|
} catch (error) {
|
|
619
658
|
console.error("Batch log insert failed, falling back to individual inserts:", error);
|
|
620
659
|
await Promise.all(logsToFlush.map((entry) => pushLogToDB(this.options, entry)));
|
|
@@ -744,7 +783,7 @@ async function workflowTableRowToWorkflowRun({ row, serializer, externalBlobStor
|
|
|
744
783
|
async function updateWorkflowName(context, instanceId, newWorkflowName) {
|
|
745
784
|
return await retryD1Operation(() => context.D1.prepare(`UPDATE WorkflowTable
|
|
746
785
|
SET workflowName = ?
|
|
747
|
-
WHERE instanceId = ?`).bind(newWorkflowName, instanceId).run());
|
|
786
|
+
WHERE instanceId = ?`).bind(newWorkflowName, instanceId).run(), context.retryConfig);
|
|
748
787
|
}
|
|
749
788
|
/**
|
|
750
789
|
* Update workflow fields by instanceId. Only provided fields will be updated.
|
|
@@ -821,7 +860,7 @@ async function updateWorkflow(context, instanceId, updates) {
|
|
|
821
860
|
};
|
|
822
861
|
bindings.push(instanceId);
|
|
823
862
|
const sql = `UPDATE WorkflowTable SET ${setClauses.join(", ")} WHERE instanceId = ?`;
|
|
824
|
-
return await retryD1Operation(() => context.D1.prepare(sql).bind(...bindings).run());
|
|
863
|
+
return await retryD1Operation(() => context.D1.prepare(sql).bind(...bindings).run(), context.retryConfig);
|
|
825
864
|
}
|
|
826
865
|
/**
|
|
827
866
|
* Serialize a workflow property value for storage
|
|
@@ -856,7 +895,7 @@ async function upsertWorkflowProperty({ context, instanceId, key, value, tenantI
|
|
|
856
895
|
const { serializedValue, valueType } = serializeWorkflowPropertyValue(value);
|
|
857
896
|
const res = await retryD1Operation(() => context.D1.prepare(`INSERT OR REPLACE INTO WorkflowProperties
|
|
858
897
|
(instanceId, key, value, valueType, tenantId)
|
|
859
|
-
VALUES (?, ?, ?, ?, ?)`).bind(instanceId, key, serializedValue, valueType, tenantId).run());
|
|
898
|
+
VALUES (?, ?, ?, ?, ?)`).bind(instanceId, key, serializedValue, valueType, tenantId).run(), context.retryConfig);
|
|
860
899
|
if (res.error) {
|
|
861
900
|
console.error("Error inserting workflow property:", res.error);
|
|
862
901
|
return false;
|
|
@@ -917,7 +956,7 @@ async function createWorkflowDependency({ context, dependencyWorkflowId, depende
|
|
|
917
956
|
dependencyWorkflowId,
|
|
918
957
|
dependentWorkflowId,
|
|
919
958
|
tenantId
|
|
920
|
-
}).run());
|
|
959
|
+
}).run(), context.retryConfig);
|
|
921
960
|
}
|
|
922
961
|
/**
|
|
923
962
|
* Create multiple workflow dependencies in a single transaction.
|
|
@@ -950,7 +989,7 @@ async function createWorkflowDependencies({ context, dependencies, tenantId }) {
|
|
|
950
989
|
tenantId,
|
|
951
990
|
createdAt
|
|
952
991
|
}));
|
|
953
|
-
return await retryD1Operation(() => context.D1.batch(statements));
|
|
992
|
+
return await retryD1Operation(() => context.D1.batch(statements), context.retryConfig);
|
|
954
993
|
}
|
|
955
994
|
/**
|
|
956
995
|
* Delete a specific workflow dependency relationship.
|
|
@@ -968,7 +1007,7 @@ async function createWorkflowDependencies({ context, dependencies, tenantId }) {
|
|
|
968
1007
|
*/
|
|
969
1008
|
async function deleteWorkflowDependency({ context, dependencyWorkflowId, dependentWorkflowId, tenantId }) {
|
|
970
1009
|
return await retryD1Operation(() => context.D1.prepare(`DELETE FROM WorkflowDependencies
|
|
971
|
-
WHERE dependencyWorkflowId = ? AND dependentWorkflowId = ? AND tenantId = ?`).bind(dependencyWorkflowId, dependentWorkflowId, tenantId).run());
|
|
1010
|
+
WHERE dependencyWorkflowId = ? AND dependentWorkflowId = ? AND tenantId = ?`).bind(dependencyWorkflowId, dependentWorkflowId, tenantId).run(), context.retryConfig);
|
|
972
1011
|
}
|
|
973
1012
|
|
|
974
1013
|
//#endregion
|
|
@@ -996,7 +1035,7 @@ const createCleanupManager = (context) => {
|
|
|
996
1035
|
AND workflowStatus IN (${statusPlaceholders})
|
|
997
1036
|
ORDER BY instanceId
|
|
998
1037
|
LIMIT ?
|
|
999
|
-
`).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all());
|
|
1038
|
+
`).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all(), context.retryConfig);
|
|
1000
1039
|
return result.results;
|
|
1001
1040
|
};
|
|
1002
1041
|
const getAffectedSteps = async (config, limit) => {
|
|
@@ -1015,7 +1054,7 @@ const createCleanupManager = (context) => {
|
|
|
1015
1054
|
LIMIT ?
|
|
1016
1055
|
)
|
|
1017
1056
|
ORDER BY s.instanceId, s.stepName
|
|
1018
|
-
`).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all());
|
|
1057
|
+
`).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all(), context.retryConfig);
|
|
1019
1058
|
return result.results;
|
|
1020
1059
|
};
|
|
1021
1060
|
const collectExternalStorageKeys = (workflows, steps) => {
|
|
@@ -1068,7 +1107,7 @@ const createCleanupManager = (context) => {
|
|
|
1068
1107
|
}
|
|
1069
1108
|
if (deletedWorkflows.length > 0) {
|
|
1070
1109
|
console.log(`Proceeding to delete workflows from the database...`);
|
|
1071
|
-
await retryD1Operation(() => context.D1.prepare(`PRAGMA foreign_keys = ON`).run());
|
|
1110
|
+
await retryD1Operation(() => context.D1.prepare(`PRAGMA foreign_keys = ON`).run(), context.retryConfig);
|
|
1072
1111
|
let totalDeletedCount = 0;
|
|
1073
1112
|
for (let i = 0; i < deletedWorkflows.length; i += 100) {
|
|
1074
1113
|
const batch = deletedWorkflows.slice(i, i + 100);
|
|
@@ -1076,7 +1115,7 @@ const createCleanupManager = (context) => {
|
|
|
1076
1115
|
const result = await retryD1Operation(() => context.D1.prepare(`
|
|
1077
1116
|
DELETE FROM WorkflowTable
|
|
1078
1117
|
WHERE instanceId IN (${workflowIds})
|
|
1079
|
-
`).bind(...batch.map((w) => w.instanceId)).run());
|
|
1118
|
+
`).bind(...batch.map((w) => w.instanceId)).run(), context.retryConfig);
|
|
1080
1119
|
totalDeletedCount += result.meta.changes || 0;
|
|
1081
1120
|
}
|
|
1082
1121
|
return {
|
|
@@ -1092,7 +1131,7 @@ const createCleanupManager = (context) => {
|
|
|
1092
1131
|
};
|
|
1093
1132
|
};
|
|
1094
1133
|
const cleanupOrphanedSteps = async (config, limit) => {
|
|
1095
|
-
await retryD1Operation(() => context.D1.prepare(`PRAGMA foreign_keys = ON`).run());
|
|
1134
|
+
await retryD1Operation(() => context.D1.prepare(`PRAGMA foreign_keys = ON`).run(), context.retryConfig);
|
|
1096
1135
|
const orphanedSteps = await retryD1Operation(() => context.D1.prepare(`
|
|
1097
1136
|
SELECT s.instanceId, s.stepName, s.errorRef, s.resultRef
|
|
1098
1137
|
FROM StepTable s
|
|
@@ -1103,7 +1142,7 @@ const createCleanupManager = (context) => {
|
|
|
1103
1142
|
)
|
|
1104
1143
|
ORDER BY s.instanceId, s.stepName
|
|
1105
1144
|
LIMIT ?
|
|
1106
|
-
`).bind(context.tenantId, context.tenantId, limit).all());
|
|
1145
|
+
`).bind(context.tenantId, context.tenantId, limit).all(), context.retryConfig);
|
|
1107
1146
|
const orphanedStepResults = orphanedSteps.results;
|
|
1108
1147
|
let deletedExternalStorageKeysCount = 0;
|
|
1109
1148
|
if (config.deleteRefsFromExternalStorage && context.externalBlobStorage && orphanedStepResults.length > 0) {
|
|
@@ -1123,7 +1162,7 @@ const createCleanupManager = (context) => {
|
|
|
1123
1162
|
DELETE FROM StepTable
|
|
1124
1163
|
WHERE tenantId = ?
|
|
1125
1164
|
AND instanceId IN (${instanceIds})
|
|
1126
|
-
`).bind(context.tenantId, ...batch.map((s) => s.instanceId)).run());
|
|
1165
|
+
`).bind(context.tenantId, ...batch.map((s) => s.instanceId)).run(), context.retryConfig);
|
|
1127
1166
|
totalDeletedSteps += deletedStepsResult.meta.changes || 0;
|
|
1128
1167
|
}
|
|
1129
1168
|
return {
|
|
@@ -1139,7 +1178,7 @@ const createCleanupManager = (context) => {
|
|
|
1139
1178
|
};
|
|
1140
1179
|
};
|
|
1141
1180
|
const cleanupOrphanedLogs = async (limit) => {
|
|
1142
|
-
await retryD1Operation(() => context.D1.prepare(`PRAGMA foreign_keys = ON`).run());
|
|
1181
|
+
await retryD1Operation(() => context.D1.prepare(`PRAGMA foreign_keys = ON`).run(), context.retryConfig);
|
|
1143
1182
|
const deletedLogsResult = await retryD1Operation(() => context.D1.prepare(`
|
|
1144
1183
|
DELETE FROM LogTable
|
|
1145
1184
|
WHERE tenantId = ?
|
|
@@ -1155,7 +1194,7 @@ const createCleanupManager = (context) => {
|
|
|
1155
1194
|
))
|
|
1156
1195
|
)
|
|
1157
1196
|
LIMIT ?
|
|
1158
|
-
`).bind(context.tenantId, context.tenantId, context.tenantId, limit).run());
|
|
1197
|
+
`).bind(context.tenantId, context.tenantId, context.tenantId, limit).run(), context.retryConfig);
|
|
1159
1198
|
return { deletedOrphanedLogs: deletedLogsResult.meta.changes || 0 };
|
|
1160
1199
|
};
|
|
1161
1200
|
return {
|
|
@@ -1343,7 +1382,7 @@ const createLogAccessor = (context) => {
|
|
|
1343
1382
|
const limitClause = limit !== void 0 && actualOffset !== void 0 ? "LIMIT ? OFFSET ?" : "";
|
|
1344
1383
|
const sql = `SELECT * FROM StepTable ${whereClause} ORDER BY startTime ASC ${limitClause}`;
|
|
1345
1384
|
if (limit !== void 0 && actualOffset !== void 0) bindings.push(limit, actualOffset);
|
|
1346
|
-
result = await retryD1Operation(() => context.D1.prepare(sql).bind(...bindings).all());
|
|
1385
|
+
result = await retryD1Operation(() => context.D1.prepare(sql).bind(...bindings).all(), context.retryConfig);
|
|
1347
1386
|
if (result.results) {
|
|
1348
1387
|
const steps = await Promise.all(result.results.map(async (row) => {
|
|
1349
1388
|
let deserializedResult = null;
|
|
@@ -1370,7 +1409,7 @@ const createLogAccessor = (context) => {
|
|
|
1370
1409
|
const listWorkflows = async (limit, offset, filter, options) => {
|
|
1371
1410
|
const { sql, bindings } = buildFilteredWorkflowQuery(filter, { ignoreTenant: options?.ignoreTenant });
|
|
1372
1411
|
if (options?.debugLogs) console.log("listWorkflows SQL:", sql, "Bindings:", JSON.stringify(bindings));
|
|
1373
|
-
const result = await retryD1Operation(() => context.D1.prepare(sql).bind(...bindings, limit, offset).all());
|
|
1412
|
+
const result = await retryD1Operation(() => context.D1.prepare(sql).bind(...bindings, limit, offset).all(), context.retryConfig);
|
|
1374
1413
|
if (options?.debugLogs) console.log("listWorkflows SQL Query executed");
|
|
1375
1414
|
if (result.results) {
|
|
1376
1415
|
const workflows = await Promise.all(result.results.map((row) => workflowTableRowToWorkflowRun({
|
|
@@ -1387,7 +1426,7 @@ const createLogAccessor = (context) => {
|
|
|
1387
1426
|
for (let i = 0; i < instanceIds.length; i += BATCH_SIZE) {
|
|
1388
1427
|
const batchIds = instanceIds.slice(i, i + BATCH_SIZE);
|
|
1389
1428
|
const placeholders = batchIds.map(() => "?").join(", ");
|
|
1390
|
-
const propertiesResult = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowProperties WHERE instanceId IN (${placeholders})`).bind(...batchIds).all());
|
|
1429
|
+
const propertiesResult = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowProperties WHERE instanceId IN (${placeholders})`).bind(...batchIds).all(), context.retryConfig);
|
|
1391
1430
|
if (propertiesResult.results) for (const row of propertiesResult.results) {
|
|
1392
1431
|
const property = {
|
|
1393
1432
|
key: row.key,
|
|
@@ -1411,7 +1450,7 @@ const createLogAccessor = (context) => {
|
|
|
1411
1450
|
const dependenciesResult = await retryD1Operation(() => context.D1.prepare(`SELECT w.*, wd.createdAt, wd.dependentWorkflowId
|
|
1412
1451
|
FROM WorkflowDependencies wd
|
|
1413
1452
|
JOIN WorkflowTable w ON wd.dependencyWorkflowId = w.instanceId
|
|
1414
|
-
WHERE wd.dependentWorkflowId IN (${placeholders})`).bind(...batchIds).all());
|
|
1453
|
+
WHERE wd.dependentWorkflowId IN (${placeholders})`).bind(...batchIds).all(), context.retryConfig);
|
|
1415
1454
|
if (dependenciesResult.results) for (const row of dependenciesResult.results) {
|
|
1416
1455
|
const depWorkflow = await workflowTableRowToWorkflowRun({
|
|
1417
1456
|
row,
|
|
@@ -1441,7 +1480,7 @@ const createLogAccessor = (context) => {
|
|
|
1441
1480
|
const dependentsResult = await retryD1Operation(() => context.D1.prepare(`SELECT w.*, wd.createdAt, wd.dependencyWorkflowId
|
|
1442
1481
|
FROM WorkflowDependencies wd
|
|
1443
1482
|
JOIN WorkflowTable w ON wd.dependentWorkflowId = w.instanceId
|
|
1444
|
-
WHERE wd.dependencyWorkflowId IN (${placeholders})`).bind(...batchIds).all());
|
|
1483
|
+
WHERE wd.dependencyWorkflowId IN (${placeholders})`).bind(...batchIds).all(), context.retryConfig);
|
|
1445
1484
|
if (dependentsResult.results) for (const row of dependentsResult.results) {
|
|
1446
1485
|
const depWorkflow = await workflowTableRowToWorkflowRun({
|
|
1447
1486
|
row,
|
|
@@ -1466,7 +1505,7 @@ const createLogAccessor = (context) => {
|
|
|
1466
1505
|
return [];
|
|
1467
1506
|
};
|
|
1468
1507
|
const getWorkflowByParentId = async (parentInstanceId) => {
|
|
1469
|
-
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowTable WHERE parentInstanceId = ? AND tenantId = ?`).bind(parentInstanceId, context.tenantId).all());
|
|
1508
|
+
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowTable WHERE parentInstanceId = ? AND tenantId = ?`).bind(parentInstanceId, context.tenantId).all(), context.retryConfig);
|
|
1470
1509
|
if (result.results) {
|
|
1471
1510
|
const workflows = await Promise.all(result.results.map((row) => workflowTableRowToWorkflowRun({
|
|
1472
1511
|
row,
|
|
@@ -1478,7 +1517,7 @@ const createLogAccessor = (context) => {
|
|
|
1478
1517
|
return null;
|
|
1479
1518
|
};
|
|
1480
1519
|
const getWorkflowByTriggerId = async (triggerId) => {
|
|
1481
|
-
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowTable WHERE triggerId = ? AND tenantId = ?`).bind(triggerId, context.tenantId).first());
|
|
1520
|
+
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowTable WHERE triggerId = ? AND tenantId = ?`).bind(triggerId, context.tenantId).first(), context.retryConfig);
|
|
1482
1521
|
if (result) {
|
|
1483
1522
|
const workflow = await workflowTableRowToWorkflowRun({
|
|
1484
1523
|
row: result,
|
|
@@ -1490,12 +1529,12 @@ const createLogAccessor = (context) => {
|
|
|
1490
1529
|
return null;
|
|
1491
1530
|
};
|
|
1492
1531
|
const getWorkflowTypesByTenantId = async (tenantId) => {
|
|
1493
|
-
const result = await retryD1Operation(() => context.D1.prepare(`SELECT DISTINCT workflowType FROM WorkflowTable WHERE tenantId = ?`).bind(tenantId).all());
|
|
1532
|
+
const result = await retryD1Operation(() => context.D1.prepare(`SELECT DISTINCT workflowType FROM WorkflowTable WHERE tenantId = ?`).bind(tenantId).all(), context.retryConfig);
|
|
1494
1533
|
if (!result.results) return [];
|
|
1495
1534
|
return result.results.map((row) => row.workflowType);
|
|
1496
1535
|
};
|
|
1497
1536
|
const getWorkflowProperties = async (instanceId) => {
|
|
1498
|
-
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowProperties WHERE instanceId = ?`).bind(instanceId).all());
|
|
1537
|
+
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowProperties WHERE instanceId = ?`).bind(instanceId).all(), context.retryConfig);
|
|
1499
1538
|
if (!result.results) return [];
|
|
1500
1539
|
return result.results.map((row) => ({
|
|
1501
1540
|
key: row.key,
|
|
@@ -1505,7 +1544,7 @@ const createLogAccessor = (context) => {
|
|
|
1505
1544
|
};
|
|
1506
1545
|
/** This function gets the basic data of a workflow, without populating any of it's complex fields */
|
|
1507
1546
|
const getWorkflowShallow = async (instanceId, options) => {
|
|
1508
|
-
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowTable WHERE instanceId = ? AND tenantId = ?`).bind(instanceId, context.tenantId).first());
|
|
1547
|
+
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM WorkflowTable WHERE instanceId = ? AND tenantId = ?`).bind(instanceId, context.tenantId).first(), context.retryConfig);
|
|
1509
1548
|
if (!result) return null;
|
|
1510
1549
|
const workflow = await workflowTableRowToWorkflowRun({
|
|
1511
1550
|
row: result,
|
|
@@ -1536,7 +1575,7 @@ const createLogAccessor = (context) => {
|
|
|
1536
1575
|
retryWorkflow.isRetryOf = workflow;
|
|
1537
1576
|
});
|
|
1538
1577
|
workflow.isRetryOf = parentWorkflow;
|
|
1539
|
-
const allLogs = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM LogTable WHERE instanceId = ? ORDER BY timestamp ASC, logOrder ASC`).bind(instanceId).all());
|
|
1578
|
+
const allLogs = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM LogTable WHERE instanceId = ? ORDER BY timestamp ASC, logOrder ASC`).bind(instanceId).all(), context.retryConfig);
|
|
1540
1579
|
const logs = allLogs.results?.map((logRow) => ({
|
|
1541
1580
|
instanceId: logRow.instanceId,
|
|
1542
1581
|
stepName: logRow.stepName,
|
|
@@ -1555,7 +1594,7 @@ const createLogAccessor = (context) => {
|
|
|
1555
1594
|
const dependenciesResult = await retryD1Operation(() => context.D1.prepare(`SELECT w.*, wd.createdAt
|
|
1556
1595
|
FROM WorkflowDependencies wd
|
|
1557
1596
|
JOIN WorkflowTable w ON wd.dependencyWorkflowId = w.instanceId
|
|
1558
|
-
WHERE wd.dependentWorkflowId = ? AND wd.tenantId = ?`).bind(instanceId, context.tenantId).all());
|
|
1597
|
+
WHERE wd.dependentWorkflowId = ? AND wd.tenantId = ?`).bind(instanceId, context.tenantId).all(), context.retryConfig);
|
|
1559
1598
|
if (dependenciesResult.results) workflow.dependencies = await Promise.all(dependenciesResult.results.map(async (row) => {
|
|
1560
1599
|
const depWorkflow = await workflowTableRowToWorkflowRun({
|
|
1561
1600
|
row,
|
|
@@ -1574,7 +1613,7 @@ const createLogAccessor = (context) => {
|
|
|
1574
1613
|
const dependentsResult = await retryD1Operation(() => context.D1.prepare(`SELECT w.*, wd.createdAt
|
|
1575
1614
|
FROM WorkflowDependencies wd
|
|
1576
1615
|
JOIN WorkflowTable w ON wd.dependentWorkflowId = w.instanceId
|
|
1577
|
-
WHERE wd.dependencyWorkflowId = ? AND wd.tenantId = ?`).bind(instanceId, context.tenantId).all());
|
|
1616
|
+
WHERE wd.dependencyWorkflowId = ? AND wd.tenantId = ?`).bind(instanceId, context.tenantId).all(), context.retryConfig);
|
|
1578
1617
|
if (dependentsResult.results) workflow.dependents = await Promise.all(dependentsResult.results.map(async (row) => {
|
|
1579
1618
|
const depWorkflow = await workflowTableRowToWorkflowRun({
|
|
1580
1619
|
row,
|
|
@@ -1592,7 +1631,7 @@ const createLogAccessor = (context) => {
|
|
|
1592
1631
|
return workflow;
|
|
1593
1632
|
};
|
|
1594
1633
|
const getStep = async (instanceId, stepName) => {
|
|
1595
|
-
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM StepTable WHERE instanceId = ? AND stepName = ? AND tenantId = ?`).bind(instanceId, stepName, context.tenantId).all());
|
|
1634
|
+
const result = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM StepTable WHERE instanceId = ? AND stepName = ? AND tenantId = ?`).bind(instanceId, stepName, context.tenantId).all(), context.retryConfig);
|
|
1596
1635
|
if (!result.results || result.results.length === 0) return null;
|
|
1597
1636
|
const row = result.results[0];
|
|
1598
1637
|
if (!row) return null;
|
|
@@ -1609,7 +1648,7 @@ const createLogAccessor = (context) => {
|
|
|
1609
1648
|
error: deserializedError,
|
|
1610
1649
|
logs: []
|
|
1611
1650
|
};
|
|
1612
|
-
const logResult = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM LogTable WHERE instanceId = ? AND stepName = ? ORDER BY logOrder ASC`).bind(instanceId, stepName).all());
|
|
1651
|
+
const logResult = await retryD1Operation(() => context.D1.prepare(`SELECT * FROM LogTable WHERE instanceId = ? AND stepName = ? ORDER BY logOrder ASC`).bind(instanceId, stepName).all(), context.retryConfig);
|
|
1613
1652
|
if (logResult.results) step.logs = logResult.results.map((logRow) => ({
|
|
1614
1653
|
instanceId: logRow.instanceId,
|
|
1615
1654
|
stepName: logRow.stepName,
|
|
@@ -1623,7 +1662,7 @@ const createLogAccessor = (context) => {
|
|
|
1623
1662
|
const result = await retryD1Operation(() => context.D1.prepare(`
|
|
1624
1663
|
SELECT DISTINCT key, valueType FROM WorkflowProperties
|
|
1625
1664
|
WHERE tenantId = ? AND (? IS NULL OR instanceId = ?)
|
|
1626
|
-
`).bind(context.tenantId, instanceId ?? null, instanceId ?? null).all());
|
|
1665
|
+
`).bind(context.tenantId, instanceId ?? null, instanceId ?? null).all(), context.retryConfig);
|
|
1627
1666
|
console.log(`debug getPropertiesKeys: ${JSON.stringify(result)}`);
|
|
1628
1667
|
if (!result.results) return [];
|
|
1629
1668
|
return result.results.map((row) => ({
|
|
@@ -1689,7 +1728,7 @@ const createLogAccessor = (context) => {
|
|
|
1689
1728
|
${whereClause}
|
|
1690
1729
|
GROUP BY workflowType
|
|
1691
1730
|
ORDER BY workflowCount DESC
|
|
1692
|
-
`).bind(...bindings).all());
|
|
1731
|
+
`).bind(...bindings).all(), context.retryConfig);
|
|
1693
1732
|
if (!result.results) return [];
|
|
1694
1733
|
return result.results.map((row) => ({
|
|
1695
1734
|
workflowType: row.workflowType,
|
|
@@ -1783,68 +1822,79 @@ function createQueueWorkflowContext(options) {
|
|
|
1783
1822
|
queueIdentifier: options.queueIdentifier
|
|
1784
1823
|
});
|
|
1785
1824
|
};
|
|
1825
|
+
/**
|
|
1826
|
+
* Enqueue multiple workflows in a single batch operation.
|
|
1827
|
+
*
|
|
1828
|
+
* This method is optimized for efficiency: it prepares all workflow insert statements
|
|
1829
|
+
* in parallel (which may involve external blob storage for large inputs), then executes
|
|
1830
|
+
* them in a single D1 batch operation, followed by a single queue batch send.
|
|
1831
|
+
*
|
|
1832
|
+
* **Network requests:** 2 + M, where M is the number of workflows with large inputs
|
|
1833
|
+
* that require external blob storage.
|
|
1834
|
+
*
|
|
1835
|
+
* **D1 batch limit:** Cloudflare D1 has a limit of ~100 statements per batch.
|
|
1836
|
+
* If you have many workflows with dependencies, this limit may be exceeded.
|
|
1837
|
+
* For very large batches, consider splitting into smaller chunks.
|
|
1838
|
+
*
|
|
1839
|
+
* @param workflows - Array of workflow items to enqueue
|
|
1840
|
+
* @returns Array of scheduled workflow stubs in the same order as inputs
|
|
1841
|
+
*/
|
|
1786
1842
|
const enqueueWorkflowBatch = async (workflows) => {
|
|
1787
|
-
const
|
|
1843
|
+
const startTime = Date.now();
|
|
1844
|
+
console.log(`enqueueWorkflowBatch: Starting batch of ${workflows.length} workflows`);
|
|
1845
|
+
const preparedWorkflows = await Promise.all(workflows.map(async ({ workflow, tenantId, input, initialName, dependencies }) => {
|
|
1788
1846
|
const instanceId = idFactory();
|
|
1789
1847
|
const triggerId = idFactory();
|
|
1790
|
-
const
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
} else {
|
|
1813
|
-
await insertWorkflowRecord(internalContext, {
|
|
1814
|
-
instanceId,
|
|
1815
|
-
workflowType: workflow.workflowType,
|
|
1816
|
-
workflowName: initialName,
|
|
1817
|
-
workflowMetadata: workflow.metadata,
|
|
1818
|
-
input,
|
|
1819
|
-
workflowStatus: "scheduled",
|
|
1820
|
-
startTime,
|
|
1821
|
-
endTime: null,
|
|
1822
|
-
parentInstanceId: void 0,
|
|
1823
|
-
tenantId,
|
|
1824
|
-
triggerId
|
|
1825
|
-
});
|
|
1826
|
-
return {
|
|
1827
|
-
instanceId,
|
|
1828
|
-
workflowType: workflow.workflowType,
|
|
1829
|
-
shouldQueue: true,
|
|
1830
|
-
queueMessage: {
|
|
1831
|
-
type: "workflow-run",
|
|
1832
|
-
workflowType: workflow.workflowType,
|
|
1833
|
-
workflowName: initialName,
|
|
1834
|
-
input,
|
|
1835
|
-
tenantId,
|
|
1836
|
-
triggerId,
|
|
1837
|
-
queueIdentifier: options.queueIdentifier,
|
|
1838
|
-
scheduledInstanceId: instanceId
|
|
1839
|
-
}
|
|
1840
|
-
};
|
|
1841
|
-
}
|
|
1848
|
+
const hasDependencies = dependencies && dependencies.length > 0;
|
|
1849
|
+
const prepared = await prepareWorkflowInsertStatements(internalContext, {
|
|
1850
|
+
instanceId,
|
|
1851
|
+
workflowType: workflow.workflowType,
|
|
1852
|
+
workflowName: initialName,
|
|
1853
|
+
workflowMetadata: workflow.metadata,
|
|
1854
|
+
input,
|
|
1855
|
+
workflowStatus: hasDependencies ? "waiting" : "scheduled",
|
|
1856
|
+
startTime,
|
|
1857
|
+
endTime: null,
|
|
1858
|
+
parentInstanceId: void 0,
|
|
1859
|
+
tenantId,
|
|
1860
|
+
triggerId,
|
|
1861
|
+
dependencies
|
|
1862
|
+
});
|
|
1863
|
+
return {
|
|
1864
|
+
...prepared,
|
|
1865
|
+
tenantId,
|
|
1866
|
+
triggerId,
|
|
1867
|
+
initialName,
|
|
1868
|
+
shouldQueue: !hasDependencies
|
|
1869
|
+
};
|
|
1842
1870
|
}));
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1871
|
+
const workflowsWithDeps = preparedWorkflows.filter((p) => !p.shouldQueue).length;
|
|
1872
|
+
const workflowsWithoutDeps = preparedWorkflows.filter((p) => p.shouldQueue).length;
|
|
1873
|
+
console.log(`enqueueWorkflowBatch: Prepared ${preparedWorkflows.length} workflows (${workflowsWithoutDeps} to queue, ${workflowsWithDeps} waiting on dependencies)`);
|
|
1874
|
+
const allStatements = preparedWorkflows.flatMap((p) => p.statements);
|
|
1875
|
+
if (allStatements.length > 0) {
|
|
1876
|
+
console.log(`enqueueWorkflowBatch: Executing D1 batch with ${allStatements.length} statements`);
|
|
1877
|
+
await retryD1Operation(() => options.D1.batch(allStatements), options.retryConfig);
|
|
1878
|
+
}
|
|
1879
|
+
const messagesToQueue = preparedWorkflows.filter((p) => p.shouldQueue).map((p) => ({ body: {
|
|
1880
|
+
type: "workflow-run",
|
|
1881
|
+
workflowType: p.workflowType,
|
|
1882
|
+
workflowName: p.initialName,
|
|
1883
|
+
input: workflows.find((w) => w.workflow.workflowType === p.workflowType && w.initialName === p.initialName)?.input,
|
|
1884
|
+
tenantId: p.tenantId,
|
|
1885
|
+
triggerId: p.triggerId,
|
|
1886
|
+
queueIdentifier: options.queueIdentifier,
|
|
1887
|
+
scheduledInstanceId: p.instanceId
|
|
1888
|
+
} }));
|
|
1889
|
+
if (messagesToQueue.length > 0) {
|
|
1890
|
+
console.log(`enqueueWorkflowBatch: Sending ${messagesToQueue.length} messages to queue`);
|
|
1891
|
+
await options.QUEUE.sendBatch(messagesToQueue);
|
|
1892
|
+
}
|
|
1893
|
+
const elapsed = Date.now() - startTime;
|
|
1894
|
+
console.log(`enqueueWorkflowBatch: Completed in ${elapsed}ms`);
|
|
1895
|
+
return preparedWorkflows.map((p) => ({
|
|
1896
|
+
instanceId: p.instanceId,
|
|
1897
|
+
workflowType: p.workflowType
|
|
1848
1898
|
}));
|
|
1849
1899
|
};
|
|
1850
1900
|
const handleWorkflowQueueMessage = async ({ message, env, workflowResolver }) => {
|
|
@@ -1877,7 +1927,8 @@ function createQueueWorkflowContext(options) {
|
|
|
1877
1927
|
D1: options.D1,
|
|
1878
1928
|
externalBlobStorage: options.externalBlobStorage,
|
|
1879
1929
|
serializer: internalContext.serializer,
|
|
1880
|
-
tenantId: "unknown"
|
|
1930
|
+
tenantId: "unknown",
|
|
1931
|
+
retryConfig: options.retryConfig
|
|
1881
1932
|
});
|
|
1882
1933
|
const waitingWorkflows = await logAccessor.listWorkflows(250, 0, { workflowStatus: "waiting" }, {
|
|
1883
1934
|
ignoreTenant: true,
|
|
@@ -1924,7 +1975,7 @@ function createQueueWorkflowContext(options) {
|
|
|
1924
1975
|
async function createStepContext(context) {
|
|
1925
1976
|
const instanceId = context.instanceId;
|
|
1926
1977
|
const reuseSuccessfulSteps = context.reuseSuccessfulSteps ?? true;
|
|
1927
|
-
await ensureTables(context.D1);
|
|
1978
|
+
await ensureTables(context.D1, context.retryConfig);
|
|
1928
1979
|
const step = async (step$1, callback) => {
|
|
1929
1980
|
const stepNameParam = typeof step$1 === "string" ? step$1 : step$1.name;
|
|
1930
1981
|
const stepMetadataParam = typeof step$1 === "string" ? void 0 : step$1.metadata;
|
|
@@ -2004,7 +2055,7 @@ async function createStepContext(context) {
|
|
|
2004
2055
|
const stepError$1 = null;
|
|
2005
2056
|
await retryD1Operation(() => context.D1.prepare(`UPDATE StepTable
|
|
2006
2057
|
SET stepStatus = ?, endTime = ?, result = ?, error = ?, resultRef = ?, errorRef = ?
|
|
2007
|
-
WHERE instanceId = ? AND stepName = ?`).bind(stepStatus$1, endTime, resultData, stepError$1, resultRef, null, instanceId, stepName).run());
|
|
2058
|
+
WHERE instanceId = ? AND stepName = ?`).bind(stepStatus$1, endTime, resultData, stepError$1, resultRef, null, instanceId, stepName).run(), context.retryConfig);
|
|
2008
2059
|
await logBatcher.destroy();
|
|
2009
2060
|
return result;
|
|
2010
2061
|
} catch (error) {
|
|
@@ -2014,7 +2065,7 @@ async function createStepContext(context) {
|
|
|
2014
2065
|
const { data: errorData, externalRef: errorRef } = await serializeWithExternalStorage(error, context.serializer, context.externalBlobStorage);
|
|
2015
2066
|
await retryD1Operation(() => context.D1.prepare(`UPDATE StepTable
|
|
2016
2067
|
SET stepStatus = ?, endTime = ?, result = ?, error = ?, resultRef = ?, errorRef = ?
|
|
2017
|
-
WHERE instanceId = ? AND stepName = ?`).bind(stepStatus$1, endTime, stepResult$1, errorData, null, errorRef, instanceId, stepName).run());
|
|
2068
|
+
WHERE instanceId = ? AND stepName = ?`).bind(stepStatus$1, endTime, stepResult$1, errorData, null, errorRef, instanceId, stepName).run(), context.retryConfig);
|
|
2018
2069
|
await logBatcher.destroy();
|
|
2019
2070
|
throw error;
|
|
2020
2071
|
}
|
|
@@ -2033,14 +2084,15 @@ function createWorkflowContext(options) {
|
|
|
2033
2084
|
};
|
|
2034
2085
|
const call = async ({ workflow, input, workflowName, tenantId, parentInstanceId, reuseSuccessfulSteps, triggerId, scheduledInstanceId }) => {
|
|
2035
2086
|
if (!ensuredTables) {
|
|
2036
|
-
await ensureTables(options.D1);
|
|
2087
|
+
await ensureTables(options.D1, options.retryConfig);
|
|
2037
2088
|
ensuredTables = true;
|
|
2038
2089
|
}
|
|
2039
2090
|
const logAccessor = createLogAccessor({
|
|
2040
2091
|
D1: internalContext.D1,
|
|
2041
2092
|
externalBlobStorage: internalContext.externalBlobStorage,
|
|
2042
2093
|
serializer: internalContext.serializer,
|
|
2043
|
-
tenantId
|
|
2094
|
+
tenantId,
|
|
2095
|
+
retryConfig: options.retryConfig
|
|
2044
2096
|
});
|
|
2045
2097
|
if (scheduledInstanceId) {
|
|
2046
2098
|
const existingWorkflow = await logAccessor.getWorkflowShallow(scheduledInstanceId);
|
|
@@ -2096,7 +2148,8 @@ function createWorkflowContext(options) {
|
|
|
2096
2148
|
idFactory: internalContext.idFactory,
|
|
2097
2149
|
parentInstanceId,
|
|
2098
2150
|
reuseSuccessfulSteps,
|
|
2099
|
-
externalBlobStorage: options.externalBlobStorage
|
|
2151
|
+
externalBlobStorage: options.externalBlobStorage,
|
|
2152
|
+
retryConfig: options.retryConfig
|
|
2100
2153
|
});
|
|
2101
2154
|
try {
|
|
2102
2155
|
const result = await workflow._callback(input, {
|
|
@@ -2116,7 +2169,10 @@ function createWorkflowContext(options) {
|
|
|
2116
2169
|
}
|
|
2117
2170
|
},
|
|
2118
2171
|
async setWorkflowName(newName) {
|
|
2119
|
-
await updateWorkflowName({
|
|
2172
|
+
await updateWorkflowName({
|
|
2173
|
+
D1: options.D1,
|
|
2174
|
+
retryConfig: options.retryConfig
|
|
2175
|
+
}, instanceId, newName);
|
|
2120
2176
|
},
|
|
2121
2177
|
async setWorkflowProperty(key, value) {
|
|
2122
2178
|
await upsertWorkflowProperty({
|
|
@@ -2166,10 +2222,10 @@ function createWorkflowContext(options) {
|
|
|
2166
2222
|
};
|
|
2167
2223
|
const retry = async ({ workflow, retryInstanceId, triggerId, retryOptions }) => {
|
|
2168
2224
|
if (!ensuredTables) {
|
|
2169
|
-
await ensureTables(options.D1);
|
|
2225
|
+
await ensureTables(options.D1, options.retryConfig);
|
|
2170
2226
|
ensuredTables = true;
|
|
2171
2227
|
}
|
|
2172
|
-
const oldRun = await retryD1Operation(() => options.D1.prepare(`SELECT input, workflowName, tenantId, inputRef FROM WorkflowTable WHERE instanceId = ? `).bind(retryInstanceId).first());
|
|
2228
|
+
const oldRun = await retryD1Operation(() => options.D1.prepare(`SELECT input, workflowName, tenantId, inputRef FROM WorkflowTable WHERE instanceId = ? `).bind(retryInstanceId).first(), options.retryConfig);
|
|
2173
2229
|
const oldWorkflowName = oldRun?.workflowName;
|
|
2174
2230
|
const tenantId = oldRun?.tenantId;
|
|
2175
2231
|
if (!tenantId) throw new Error(`No tenantId found for instanceId ${retryInstanceId}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandboostinggmbh/observable-workflows",
|
|
3
|
-
"version": "0.20.0-beta.
|
|
3
|
+
"version": "0.20.0-beta.6",
|
|
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
|
}
|