@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 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
- if (filter.properties && Object.keys(filter.properties).length > 0) {
406
- let propIndex = 0;
407
- for (const [key, propertyFilter] of Object.entries(filter.properties)) {
408
- const propAlias = `p${propIndex}`;
409
- joinClause += ` LEFT JOIN WorkflowProperties ${propAlias} ON w.instanceId = ${propAlias}.instanceId AND ${propAlias}.key = ? AND ${propAlias}.tenantId = ?`;
410
- bindings.push(key, context.tenantId);
411
- if (propertyFilter.equals !== void 0) propertyWhereConditions.push({
412
- condition: `${propAlias}.value = ?`,
413
- binding: String(propertyFilter.equals)
414
- });
415
- if (propertyFilter.contains !== void 0) propertyWhereConditions.push({
416
- condition: `${propAlias}.value LIKE ?`,
417
- binding: `%${propertyFilter.contains}%`
418
- });
419
- if (propertyFilter.gt !== void 0) propertyWhereConditions.push({
420
- condition: `CAST(${propAlias}.value AS REAL) > ?`,
421
- binding: propertyFilter.gt
422
- });
423
- if (propertyFilter.gte !== void 0) propertyWhereConditions.push({
424
- condition: `CAST(${propAlias}.value AS REAL) >= ?`,
425
- binding: propertyFilter.gte
426
- });
427
- if (propertyFilter.lt !== void 0) propertyWhereConditions.push({
428
- condition: `CAST(${propAlias}.value AS REAL) < ?`,
429
- binding: propertyFilter.lt
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: null,
569
- error: null
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({ instanceId });
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
- } else console.warn("temp: found existing step with different status", row);
792
- } else console.warn("temp: no existing step found");
793
- } else console.warn("temp: not trying to reuse successful steps");
794
- const waitFor = [];
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
- const logPromise = pushLogToDB(context, {
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 Promise.allSettled(waitFor);
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 Promise.allSettled(waitFor);
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 waitFor = [];
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
- const logPromise = pushLogToDB(internalContext, {
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 Promise.allSettled(waitFor);
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 Promise.allSettled(waitFor);
1031
+ await logBatcher.destroy();
974
1032
  throw error;
975
1033
  }
976
1034
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brandboostinggmbh/observable-workflows",
3
- "version": "0.8.6",
3
+ "version": "0.10.0",
4
4
  "description": "My awesome typescript library",
5
5
  "type": "module",
6
6
  "license": "MIT",