@brandboostinggmbh/observable-workflows 0.8.6 → 0.10.0
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 +8 -1
- package/dist/index.js +116 -58
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -406,11 +406,12 @@ declare const createLogAccessor: (context: {
|
|
|
406
406
|
limit?: number;
|
|
407
407
|
offset?: number;
|
|
408
408
|
instanceId?: string;
|
|
409
|
+
populateData?: boolean;
|
|
409
410
|
}): Promise<Step[]>;
|
|
410
411
|
};
|
|
411
412
|
getStep: (instanceId: string, stepName: string) => Promise<Step | null>;
|
|
412
413
|
listWorkflows: (limit: number, offset: number, filter?: WorkflowFilter) => Promise<WorkflowRun[]>;
|
|
413
|
-
getWorkflow: (instanceId: string) => Promise<WorkflowRun | null>;
|
|
414
|
+
getWorkflow: (instanceId: string, populateData?: boolean) => Promise<WorkflowRun | null>;
|
|
414
415
|
getWorkflowTypesByTenantId: (tenantId: string) => Promise<string[]>;
|
|
415
416
|
getPropertiesKeys: (instanceId?: string) => Promise<WorkflowPropertyDefinition[]>;
|
|
416
417
|
};
|
|
@@ -419,6 +420,12 @@ declare const createLogAccessor: (context: {
|
|
|
419
420
|
//#region src/observableWorkflows/createQueueWorkflowContext.d.ts
|
|
420
421
|
declare function createQueueWorkflowContext(options: QueueWorkflowContextOptions): {
|
|
421
422
|
enqueueWorkflow: <I>(workflow: WorkflowFunction<I>, tenantId: string, input: I, initialName: string) => Promise<void>;
|
|
423
|
+
enqueueWorkflowBatch: <I>(workflows: Array<{
|
|
424
|
+
workflow: WorkflowFunction<I>;
|
|
425
|
+
tenantId: string;
|
|
426
|
+
input: I;
|
|
427
|
+
initialName: string;
|
|
428
|
+
}>) => Promise<void>;
|
|
422
429
|
enqueueRetryWorkflow: <I>(workflow: WorkflowFunction<I>, tenantId: string, oldInstanceId: string) => Promise<void>;
|
|
423
430
|
handleWorkflowQueueMessage: (message: WorkflowQueueMessage, env: {
|
|
424
431
|
LOG_DB: D1Database;
|
package/dist/index.js
CHANGED
|
@@ -247,6 +247,49 @@ function pushLogToDB(options, { instanceId, stepName, message, timestamp, type,
|
|
|
247
247
|
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
248
248
|
).bind(instanceId, stepName, message, timestamp, type, logOrder, tenantId).run();
|
|
249
249
|
}
|
|
250
|
+
var LogBatcher = class {
|
|
251
|
+
batch = [];
|
|
252
|
+
flushTimer = null;
|
|
253
|
+
isDestroyed = false;
|
|
254
|
+
constructor(options, batchSize = 100, flushInterval = 2e3) {
|
|
255
|
+
this.options = options;
|
|
256
|
+
this.batchSize = batchSize;
|
|
257
|
+
this.flushInterval = flushInterval;
|
|
258
|
+
}
|
|
259
|
+
addLog(entry) {
|
|
260
|
+
if (this.isDestroyed) return pushLogToDB(this.options, entry);
|
|
261
|
+
this.batch.push(entry);
|
|
262
|
+
if (!this.flushTimer) this.flushTimer = setTimeout(() => {
|
|
263
|
+
this.flush();
|
|
264
|
+
}, this.flushInterval);
|
|
265
|
+
if (this.batch.length >= this.batchSize) return this.flush();
|
|
266
|
+
return Promise.resolve();
|
|
267
|
+
}
|
|
268
|
+
async flush() {
|
|
269
|
+
if (this.flushTimer) {
|
|
270
|
+
clearTimeout(this.flushTimer);
|
|
271
|
+
this.flushTimer = null;
|
|
272
|
+
}
|
|
273
|
+
if (this.batch.length === 0) return;
|
|
274
|
+
const logsToFlush = this.batch.splice(0);
|
|
275
|
+
try {
|
|
276
|
+
const statements = logsToFlush.map((entry) => this.options.D1.prepare(
|
|
277
|
+
/* sql */
|
|
278
|
+
`INSERT INTO LogTable
|
|
279
|
+
(instanceId, stepName, log, timestamp, type, logOrder, tenantId)
|
|
280
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
281
|
+
).bind(entry.instanceId, entry.stepName, entry.message, entry.timestamp, entry.type, entry.logOrder, entry.tenantId));
|
|
282
|
+
await this.options.D1.batch(statements);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error("Batch log insert failed, falling back to individual inserts:", error);
|
|
285
|
+
await Promise.all(logsToFlush.map((entry) => pushLogToDB(this.options, entry)));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async destroy() {
|
|
289
|
+
this.isDestroyed = true;
|
|
290
|
+
await this.flush();
|
|
291
|
+
}
|
|
292
|
+
};
|
|
250
293
|
function trySerializeObj(obj, serializer) {
|
|
251
294
|
try {
|
|
252
295
|
return serializer.serialize(obj);
|
|
@@ -401,46 +444,44 @@ const createLogAccessor = (context) => {
|
|
|
401
444
|
const selectClause = "SELECT DISTINCT w.*";
|
|
402
445
|
const fromClause = "FROM WorkflowTable w";
|
|
403
446
|
const propertyWhereConditions = [];
|
|
404
|
-
if (filter) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
447
|
+
if (filter && filter.properties && Object.keys(filter.properties).length > 0) {
|
|
448
|
+
let propIndex = 0;
|
|
449
|
+
for (const [key, propertyFilter] of Object.entries(filter.properties)) {
|
|
450
|
+
const propAlias = `p${propIndex}`;
|
|
451
|
+
joinClause += ` LEFT JOIN WorkflowProperties ${propAlias} ON w.instanceId = ${propAlias}.instanceId AND ${propAlias}.key = ? AND ${propAlias}.tenantId = ?`;
|
|
452
|
+
bindings.push(key, context.tenantId);
|
|
453
|
+
if (propertyFilter.equals !== void 0) propertyWhereConditions.push({
|
|
454
|
+
condition: `${propAlias}.value = ?`,
|
|
455
|
+
binding: String(propertyFilter.equals)
|
|
456
|
+
});
|
|
457
|
+
if (propertyFilter.contains !== void 0) propertyWhereConditions.push({
|
|
458
|
+
condition: `${propAlias}.value LIKE ?`,
|
|
459
|
+
binding: `%${propertyFilter.contains}%`
|
|
460
|
+
});
|
|
461
|
+
if (propertyFilter.gt !== void 0) propertyWhereConditions.push({
|
|
462
|
+
condition: `CAST(${propAlias}.value AS REAL) > ?`,
|
|
463
|
+
binding: propertyFilter.gt
|
|
464
|
+
});
|
|
465
|
+
if (propertyFilter.gte !== void 0) propertyWhereConditions.push({
|
|
466
|
+
condition: `CAST(${propAlias}.value AS REAL) >= ?`,
|
|
467
|
+
binding: propertyFilter.gte
|
|
468
|
+
});
|
|
469
|
+
if (propertyFilter.lt !== void 0) propertyWhereConditions.push({
|
|
470
|
+
condition: `CAST(${propAlias}.value AS REAL) < ?`,
|
|
471
|
+
binding: propertyFilter.lt
|
|
472
|
+
});
|
|
473
|
+
if (propertyFilter.lte !== void 0) propertyWhereConditions.push({
|
|
474
|
+
condition: `CAST(${propAlias}.value AS REAL) <= ?`,
|
|
475
|
+
binding: propertyFilter.lte
|
|
476
|
+
});
|
|
477
|
+
if (propertyFilter.in !== void 0 && Array.isArray(propertyFilter.in)) {
|
|
478
|
+
const placeholders = propertyFilter.in.map(() => "?").join(", ");
|
|
479
|
+
propertyWhereConditions.push({
|
|
480
|
+
condition: `${propAlias}.value IN (${placeholders})`,
|
|
481
|
+
bindings: propertyFilter.in.map(String)
|
|
430
482
|
});
|
|
431
|
-
if (propertyFilter.lte !== void 0) propertyWhereConditions.push({
|
|
432
|
-
condition: `CAST(${propAlias}.value AS REAL) <= ?`,
|
|
433
|
-
binding: propertyFilter.lte
|
|
434
|
-
});
|
|
435
|
-
if (propertyFilter.in !== void 0 && Array.isArray(propertyFilter.in)) {
|
|
436
|
-
const placeholders = propertyFilter.in.map(() => "?").join(", ");
|
|
437
|
-
propertyWhereConditions.push({
|
|
438
|
-
condition: `${propAlias}.value IN (${placeholders})`,
|
|
439
|
-
bindings: propertyFilter.in.map(String)
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
propIndex++;
|
|
443
483
|
}
|
|
484
|
+
propIndex++;
|
|
444
485
|
}
|
|
445
486
|
}
|
|
446
487
|
propertyWhereConditions.forEach((item) => {
|
|
@@ -533,6 +574,7 @@ const createLogAccessor = (context) => {
|
|
|
533
574
|
let limit;
|
|
534
575
|
let actualOffset;
|
|
535
576
|
let actualInstanceId;
|
|
577
|
+
let populateData = void 0;
|
|
536
578
|
if (typeof limitOrOptions === "number") {
|
|
537
579
|
limit = limitOrOptions;
|
|
538
580
|
actualOffset = offset;
|
|
@@ -542,7 +584,9 @@ const createLogAccessor = (context) => {
|
|
|
542
584
|
limit = options.limit;
|
|
543
585
|
actualOffset = options.offset;
|
|
544
586
|
actualInstanceId = options.instanceId;
|
|
587
|
+
populateData = options.populateData;
|
|
545
588
|
}
|
|
589
|
+
if (populateData === void 0) populateData = false;
|
|
546
590
|
let result;
|
|
547
591
|
if (limit !== void 0 && actualOffset !== void 0) result = await context.D1.prepare(
|
|
548
592
|
/* sql */
|
|
@@ -558,6 +602,12 @@ const createLogAccessor = (context) => {
|
|
|
558
602
|
).bind(context.tenantId, actualInstanceId ?? null, actualInstanceId ?? null).all();
|
|
559
603
|
if (result.results) {
|
|
560
604
|
const steps = await Promise.all(result.results.map(async (row) => {
|
|
605
|
+
let deserializedResult = null;
|
|
606
|
+
let deserializedError = null;
|
|
607
|
+
if (populateData) {
|
|
608
|
+
deserializedResult = await deserializeWithExternalStorage(row.result, row.resultRef, internalSerializer, context.externalBlobStorage);
|
|
609
|
+
deserializedError = await deserializeWithExternalStorage(row.error, row.errorRef, internalSerializer, context.externalBlobStorage);
|
|
610
|
+
}
|
|
561
611
|
return {
|
|
562
612
|
instanceId: row.instanceId,
|
|
563
613
|
name: row.stepName,
|
|
@@ -565,8 +615,8 @@ const createLogAccessor = (context) => {
|
|
|
565
615
|
status: row.stepStatus,
|
|
566
616
|
startTime: row.startTime,
|
|
567
617
|
endTime: row.endTime,
|
|
568
|
-
result:
|
|
569
|
-
error:
|
|
618
|
+
result: deserializedResult,
|
|
619
|
+
error: deserializedError
|
|
570
620
|
};
|
|
571
621
|
}));
|
|
572
622
|
return steps;
|
|
@@ -623,10 +673,13 @@ const createLogAccessor = (context) => {
|
|
|
623
673
|
const workflow = await workflowTableRowToWorkflowRun(result, internalSerializer, context.externalBlobStorage);
|
|
624
674
|
return workflow;
|
|
625
675
|
};
|
|
626
|
-
const getWorkflow = async (instanceId) => {
|
|
676
|
+
const getWorkflow = async (instanceId, populateData = false) => {
|
|
627
677
|
const workflow = await getWorkflowShallow(instanceId);
|
|
628
678
|
if (!workflow) return null;
|
|
629
|
-
const steps = await listSteps({
|
|
679
|
+
const steps = await listSteps({
|
|
680
|
+
instanceId,
|
|
681
|
+
populateData
|
|
682
|
+
});
|
|
630
683
|
const retryWorkflows = await getWorkflowByParentId(instanceId);
|
|
631
684
|
const parentWorkflow = workflow.parentInstanceId ? await getWorkflowShallow(workflow.parentInstanceId) : null;
|
|
632
685
|
workflow.retries = retryWorkflows;
|
|
@@ -732,6 +785,15 @@ function createQueueWorkflowContext(options) {
|
|
|
732
785
|
tenantId
|
|
733
786
|
});
|
|
734
787
|
};
|
|
788
|
+
const enqueueWorkflowBatch = async (workflows) => {
|
|
789
|
+
await options.QUEUE.sendBatch(workflows.map(({ workflow, tenantId, input, initialName }) => ({ body: {
|
|
790
|
+
type: "workflow-run",
|
|
791
|
+
workflowType: workflow.workflowType,
|
|
792
|
+
workflowName: initialName,
|
|
793
|
+
input,
|
|
794
|
+
tenantId
|
|
795
|
+
} })));
|
|
796
|
+
};
|
|
735
797
|
const handleWorkflowQueueMessage = async (message, env, workflowResolver) => {
|
|
736
798
|
const workflowFunction = workflowResolver(message.workflowType);
|
|
737
799
|
if (!workflowFunction) throw new Error(`Workflow ${message.workflowType} not found`);
|
|
@@ -751,6 +813,7 @@ function createQueueWorkflowContext(options) {
|
|
|
751
813
|
};
|
|
752
814
|
return {
|
|
753
815
|
enqueueWorkflow,
|
|
816
|
+
enqueueWorkflowBatch,
|
|
754
817
|
enqueueRetryWorkflow,
|
|
755
818
|
handleWorkflowQueueMessage
|
|
756
819
|
};
|
|
@@ -766,13 +829,10 @@ async function createStepContext(context) {
|
|
|
766
829
|
const stepNameParam = typeof step$1 === "string" ? step$1 : step$1.name;
|
|
767
830
|
const stepMetadataParam = typeof step$1 === "string" ? void 0 : step$1.metadata;
|
|
768
831
|
if (context.parentInstanceId && reuseSuccessfulSteps) {
|
|
769
|
-
console.warn("temp: try to reuse successful steps");
|
|
770
832
|
const existingStep = await getStepRecord(context, stepNameParam, context.parentInstanceId);
|
|
771
833
|
if (existingStep) {
|
|
772
|
-
console.warn("temp: found existing step", existingStep);
|
|
773
834
|
const row = existingStep;
|
|
774
835
|
if (row.status === "completed") {
|
|
775
|
-
console.warn("temp: found existing step with completed status", row);
|
|
776
836
|
const { data: resultData, externalRef: resultRef } = await serializeWithExternalStorage(row.result, context.serializer, context.externalBlobStorage);
|
|
777
837
|
const { data: errorData, externalRef: errorRef } = await serializeWithExternalStorage(row.error, context.serializer, context.externalBlobStorage);
|
|
778
838
|
await insertStepRecordFull(context, {
|
|
@@ -788,10 +848,10 @@ async function createStepContext(context) {
|
|
|
788
848
|
errorRef
|
|
789
849
|
});
|
|
790
850
|
return row.result;
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
const
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const logBatcher = new LogBatcher(context);
|
|
795
855
|
const startTime = Date.now();
|
|
796
856
|
const stepStatus = "pending";
|
|
797
857
|
const stepMetadata = context.serializer.serialize(stepMetadataParam || {});
|
|
@@ -812,7 +872,7 @@ async function createStepContext(context) {
|
|
|
812
872
|
const log = (message, type = "info") => {
|
|
813
873
|
logOrder++;
|
|
814
874
|
const timestamp = Date.now();
|
|
815
|
-
|
|
875
|
+
logBatcher.addLog({
|
|
816
876
|
stepName: stepNameParam,
|
|
817
877
|
instanceId,
|
|
818
878
|
message,
|
|
@@ -821,7 +881,6 @@ async function createStepContext(context) {
|
|
|
821
881
|
logOrder,
|
|
822
882
|
tenantId: context.tenantId
|
|
823
883
|
});
|
|
824
|
-
waitFor.push(logPromise);
|
|
825
884
|
};
|
|
826
885
|
const ctx = { console: {
|
|
827
886
|
log: (message, ...optionalParams) => {
|
|
@@ -849,7 +908,7 @@ async function createStepContext(context) {
|
|
|
849
908
|
SET stepStatus = ?, endTime = ?, result = ?, error = ?, resultRef = ?, errorRef = ?
|
|
850
909
|
WHERE instanceId = ? AND stepName = ?`
|
|
851
910
|
).bind(stepStatus$1, endTime, resultData, stepError$1, resultRef, null, instanceId, stepName).run();
|
|
852
|
-
await
|
|
911
|
+
await logBatcher.destroy();
|
|
853
912
|
return result;
|
|
854
913
|
} catch (error) {
|
|
855
914
|
const endTime = Date.now();
|
|
@@ -862,7 +921,7 @@ async function createStepContext(context) {
|
|
|
862
921
|
SET stepStatus = ?, endTime = ?, result = ?, error = ?, resultRef = ?, errorRef = ?
|
|
863
922
|
WHERE instanceId = ? AND stepName = ?`
|
|
864
923
|
).bind(stepStatus$1, endTime, stepResult$1, errorData, null, errorRef, instanceId, stepName).run();
|
|
865
|
-
await
|
|
924
|
+
await logBatcher.destroy();
|
|
866
925
|
throw error;
|
|
867
926
|
}
|
|
868
927
|
};
|
|
@@ -897,12 +956,12 @@ function createWorkflowContext(options) {
|
|
|
897
956
|
parentInstanceId,
|
|
898
957
|
tenantId
|
|
899
958
|
});
|
|
900
|
-
const
|
|
959
|
+
const logBatcher = new LogBatcher(internalContext);
|
|
901
960
|
let logOrder = 0;
|
|
902
961
|
const log = (message, type = "info") => {
|
|
903
962
|
logOrder++;
|
|
904
963
|
const timestamp = Date.now();
|
|
905
|
-
|
|
964
|
+
logBatcher.addLog({
|
|
906
965
|
instanceId,
|
|
907
966
|
stepName: null,
|
|
908
967
|
message,
|
|
@@ -911,7 +970,6 @@ function createWorkflowContext(options) {
|
|
|
911
970
|
logOrder,
|
|
912
971
|
tenantId
|
|
913
972
|
});
|
|
914
|
-
waitFor.push(logPromise);
|
|
915
973
|
};
|
|
916
974
|
const stepContext = await createStepContext({
|
|
917
975
|
D1: options.D1,
|
|
@@ -961,7 +1019,7 @@ function createWorkflowContext(options) {
|
|
|
961
1019
|
endTime,
|
|
962
1020
|
instanceId
|
|
963
1021
|
});
|
|
964
|
-
await
|
|
1022
|
+
await logBatcher.destroy();
|
|
965
1023
|
} catch (error) {
|
|
966
1024
|
const endTime = Date.now();
|
|
967
1025
|
const workflowStatus = "failed";
|
|
@@ -970,7 +1028,7 @@ function createWorkflowContext(options) {
|
|
|
970
1028
|
endTime,
|
|
971
1029
|
instanceId
|
|
972
1030
|
});
|
|
973
|
-
await
|
|
1031
|
+
await logBatcher.destroy();
|
|
974
1032
|
throw error;
|
|
975
1033
|
}
|
|
976
1034
|
};
|