@brandboostinggmbh/observable-workflows 0.20.0-beta.5 → 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 +30 -40
- package/dist/index.js +123 -74
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -62,43 +62,8 @@ declare function finalizeWorkflowRecord(options: InternalWorkflowContextOptions,
|
|
|
62
62
|
instanceId: string;
|
|
63
63
|
result?: any;
|
|
64
64
|
}): Promise<D1Result<Record<string, unknown>>>;
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
* Optionally creates workflow dependencies atomically in the same transaction.
|
|
68
|
-
*
|
|
69
|
-
* @example
|
|
70
|
-
* ```typescript
|
|
71
|
-
* // Create a workflow with dependencies
|
|
72
|
-
* await insertWorkflowRecord(context, {
|
|
73
|
-
* instanceId: 'workflow-c',
|
|
74
|
-
* workflowType: 'process-data',
|
|
75
|
-
* workflowName: 'Data Processing',
|
|
76
|
-
* workflowMetadata: {},
|
|
77
|
-
* input: { data: 'value' },
|
|
78
|
-
* workflowStatus: 'pending',
|
|
79
|
-
* startTime: Date.now(),
|
|
80
|
-
* tenantId: 'tenant-1',
|
|
81
|
-
* dependencies: [
|
|
82
|
-
* { instanceId: 'workflow-a' },
|
|
83
|
-
* { instanceId: 'workflow-b' }
|
|
84
|
-
* ] // This workflow depends on A and B
|
|
85
|
-
* })
|
|
86
|
-
* ```
|
|
87
|
-
*/
|
|
88
|
-
declare function insertWorkflowRecord(options: InternalWorkflowContextOptions, {
|
|
89
|
-
instanceId,
|
|
90
|
-
workflowType,
|
|
91
|
-
workflowName,
|
|
92
|
-
workflowMetadata,
|
|
93
|
-
input,
|
|
94
|
-
workflowStatus,
|
|
95
|
-
startTime,
|
|
96
|
-
endTime,
|
|
97
|
-
parentInstanceId,
|
|
98
|
-
tenantId,
|
|
99
|
-
triggerId,
|
|
100
|
-
dependencies
|
|
101
|
-
}: {
|
|
65
|
+
/** Parameters for preparing workflow insert statements */
|
|
66
|
+
interface PrepareWorkflowInsertParams {
|
|
102
67
|
/** Unique identifier for this workflow instance */
|
|
103
68
|
instanceId: string;
|
|
104
69
|
/** Type/category of the workflow */
|
|
@@ -123,11 +88,36 @@ declare function insertWorkflowRecord(options: InternalWorkflowContextOptions, {
|
|
|
123
88
|
triggerId?: string | null;
|
|
124
89
|
/**
|
|
125
90
|
* Optional array of workflow dependencies that this workflow depends on.
|
|
126
|
-
* When provided, dependency
|
|
127
|
-
* 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.
|
|
128
92
|
*/
|
|
129
93
|
dependencies?: Array<WorkflowDependency>;
|
|
130
|
-
}
|
|
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<{
|
|
131
121
|
success: boolean;
|
|
132
122
|
meta: any;
|
|
133
123
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -444,6 +444,55 @@ async function finalizeWorkflowRecord(options, { workflowStatus, endTime, instan
|
|
|
444
444
|
WHERE instanceId = ?`).bind(workflowStatus, endTime, instanceId).run(), options.retryConfig);
|
|
445
445
|
}
|
|
446
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
|
+
};
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
447
496
|
* Insert a new workflow record into the database.
|
|
448
497
|
* Optionally creates workflow dependencies atomically in the same transaction.
|
|
449
498
|
*
|
|
@@ -466,27 +515,16 @@ async function finalizeWorkflowRecord(options, { workflowStatus, endTime, instan
|
|
|
466
515
|
* })
|
|
467
516
|
* ```
|
|
468
517
|
*/
|
|
469
|
-
async function insertWorkflowRecord(options,
|
|
470
|
-
const {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(instanceId, workflowType, workflowName, options.serializer.serialize(workflowMetadata), inputData, inputRef, tenantId, workflowStatus, startTime, endTime ?? null, parentInstanceId ?? null, triggerId ?? null);
|
|
474
|
-
if (!dependencies || dependencies.length === 0) {
|
|
475
|
-
const result = await retryD1Operation(() => insertWorkflowStatement.run(), options.retryConfig);
|
|
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);
|
|
476
522
|
return {
|
|
477
523
|
success: result.success,
|
|
478
524
|
meta: result.meta
|
|
479
525
|
};
|
|
480
526
|
}
|
|
481
|
-
const
|
|
482
|
-
const dependencyStatements = dependencies.map((dep) => prepareWorkflowDependencyStatement({
|
|
483
|
-
D1: options.D1,
|
|
484
|
-
dependencyWorkflowId: dep.instanceId,
|
|
485
|
-
dependentWorkflowId: instanceId,
|
|
486
|
-
tenantId,
|
|
487
|
-
createdAt
|
|
488
|
-
}));
|
|
489
|
-
const results = await retryD1Operation(() => options.D1.batch([insertWorkflowStatement, ...dependencyStatements]), options.retryConfig);
|
|
527
|
+
const results = await retryD1Operation(() => options.D1.batch(statements), options.retryConfig);
|
|
490
528
|
const allSucceeded = results.every((result) => result.success);
|
|
491
529
|
return {
|
|
492
530
|
success: allSucceeded,
|
|
@@ -1784,68 +1822,79 @@ function createQueueWorkflowContext(options) {
|
|
|
1784
1822
|
queueIdentifier: options.queueIdentifier
|
|
1785
1823
|
});
|
|
1786
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
|
+
*/
|
|
1787
1842
|
const enqueueWorkflowBatch = async (workflows) => {
|
|
1788
|
-
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 }) => {
|
|
1789
1846
|
const instanceId = idFactory();
|
|
1790
1847
|
const triggerId = idFactory();
|
|
1791
|
-
const
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
} else {
|
|
1814
|
-
await insertWorkflowRecord(internalContext, {
|
|
1815
|
-
instanceId,
|
|
1816
|
-
workflowType: workflow.workflowType,
|
|
1817
|
-
workflowName: initialName,
|
|
1818
|
-
workflowMetadata: workflow.metadata,
|
|
1819
|
-
input,
|
|
1820
|
-
workflowStatus: "scheduled",
|
|
1821
|
-
startTime,
|
|
1822
|
-
endTime: null,
|
|
1823
|
-
parentInstanceId: void 0,
|
|
1824
|
-
tenantId,
|
|
1825
|
-
triggerId
|
|
1826
|
-
});
|
|
1827
|
-
return {
|
|
1828
|
-
instanceId,
|
|
1829
|
-
workflowType: workflow.workflowType,
|
|
1830
|
-
shouldQueue: true,
|
|
1831
|
-
queueMessage: {
|
|
1832
|
-
type: "workflow-run",
|
|
1833
|
-
workflowType: workflow.workflowType,
|
|
1834
|
-
workflowName: initialName,
|
|
1835
|
-
input,
|
|
1836
|
-
tenantId,
|
|
1837
|
-
triggerId,
|
|
1838
|
-
queueIdentifier: options.queueIdentifier,
|
|
1839
|
-
scheduledInstanceId: instanceId
|
|
1840
|
-
}
|
|
1841
|
-
};
|
|
1842
|
-
}
|
|
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
|
+
};
|
|
1843
1870
|
}));
|
|
1844
|
-
const
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
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
|
|
1849
1898
|
}));
|
|
1850
1899
|
};
|
|
1851
1900
|
const handleWorkflowQueueMessage = async ({ message, env, workflowResolver }) => {
|