@brandboostinggmbh/observable-workflows 0.8.5 → 0.9.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
  };
package/dist/index.js CHANGED
@@ -207,7 +207,7 @@ async function insertWorkflowRecord(options, { instanceId, workflowType, workflo
207
207
  `INSERT INTO WorkflowTable
208
208
  (instanceId, workflowType, workflowName, workflowMetadata, input, inputRef, tenantId, workflowStatus, startTime, endTime, parentInstanceId)
209
209
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
210
- ).bind(instanceId, workflowType, workflowName, options.serializer.serialize(workflowMetadata), inputData ?? null, inputRef, tenantId, workflowStatus, startTime, endTime ?? null, parentInstanceId ?? null).run();
210
+ ).bind(instanceId, workflowType, workflowName, options.serializer.serialize(workflowMetadata), inputData, inputRef, tenantId, workflowStatus, startTime, endTime ?? null, parentInstanceId ?? null).run();
211
211
  }
212
212
  function insertStepRecordFull(context, { instanceId, name, status, metadata, startTime, endTime, result, error, resultRef, errorRef }) {
213
213
  return context.D1.prepare(
@@ -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}%`
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)
418
482
  });
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
430
- });
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,8 +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) => {
561
- const result$1 = await deserializeWithExternalStorage(row.result, row.resultRef, internalSerializer, context.externalBlobStorage);
562
- const error = await deserializeWithExternalStorage(row.error, row.errorRef, internalSerializer, context.externalBlobStorage);
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
+ }
563
611
  return {
564
612
  instanceId: row.instanceId,
565
613
  name: row.stepName,
@@ -567,8 +615,8 @@ const createLogAccessor = (context) => {
567
615
  status: row.stepStatus,
568
616
  startTime: row.startTime,
569
617
  endTime: row.endTime,
570
- result: result$1,
571
- error
618
+ result: deserializedResult,
619
+ error: deserializedError
572
620
  };
573
621
  }));
574
622
  return steps;
@@ -625,10 +673,13 @@ const createLogAccessor = (context) => {
625
673
  const workflow = await workflowTableRowToWorkflowRun(result, internalSerializer, context.externalBlobStorage);
626
674
  return workflow;
627
675
  };
628
- const getWorkflow = async (instanceId) => {
676
+ const getWorkflow = async (instanceId, populateData = false) => {
629
677
  const workflow = await getWorkflowShallow(instanceId);
630
678
  if (!workflow) return null;
631
- const steps = await listSteps({ instanceId });
679
+ const steps = await listSteps({
680
+ instanceId,
681
+ populateData
682
+ });
632
683
  const retryWorkflows = await getWorkflowByParentId(instanceId);
633
684
  const parentWorkflow = workflow.parentInstanceId ? await getWorkflowShallow(workflow.parentInstanceId) : null;
634
685
  workflow.retries = retryWorkflows;
@@ -768,13 +819,10 @@ async function createStepContext(context) {
768
819
  const stepNameParam = typeof step$1 === "string" ? step$1 : step$1.name;
769
820
  const stepMetadataParam = typeof step$1 === "string" ? void 0 : step$1.metadata;
770
821
  if (context.parentInstanceId && reuseSuccessfulSteps) {
771
- console.warn("temp: try to reuse successful steps");
772
822
  const existingStep = await getStepRecord(context, stepNameParam, context.parentInstanceId);
773
823
  if (existingStep) {
774
- console.warn("temp: found existing step", existingStep);
775
824
  const row = existingStep;
776
825
  if (row.status === "completed") {
777
- console.warn("temp: found existing step with completed status", row);
778
826
  const { data: resultData, externalRef: resultRef } = await serializeWithExternalStorage(row.result, context.serializer, context.externalBlobStorage);
779
827
  const { data: errorData, externalRef: errorRef } = await serializeWithExternalStorage(row.error, context.serializer, context.externalBlobStorage);
780
828
  await insertStepRecordFull(context, {
@@ -790,10 +838,10 @@ async function createStepContext(context) {
790
838
  errorRef
791
839
  });
792
840
  return row.result;
793
- } else console.warn("temp: found existing step with different status", row);
794
- } else console.warn("temp: no existing step found");
795
- } else console.warn("temp: not trying to reuse successful steps");
796
- const waitFor = [];
841
+ }
842
+ }
843
+ }
844
+ const logBatcher = new LogBatcher(context);
797
845
  const startTime = Date.now();
798
846
  const stepStatus = "pending";
799
847
  const stepMetadata = context.serializer.serialize(stepMetadataParam || {});
@@ -814,7 +862,7 @@ async function createStepContext(context) {
814
862
  const log = (message, type = "info") => {
815
863
  logOrder++;
816
864
  const timestamp = Date.now();
817
- const logPromise = pushLogToDB(context, {
865
+ logBatcher.addLog({
818
866
  stepName: stepNameParam,
819
867
  instanceId,
820
868
  message,
@@ -823,7 +871,6 @@ async function createStepContext(context) {
823
871
  logOrder,
824
872
  tenantId: context.tenantId
825
873
  });
826
- waitFor.push(logPromise);
827
874
  };
828
875
  const ctx = { console: {
829
876
  log: (message, ...optionalParams) => {
@@ -851,7 +898,7 @@ async function createStepContext(context) {
851
898
  SET stepStatus = ?, endTime = ?, result = ?, error = ?, resultRef = ?, errorRef = ?
852
899
  WHERE instanceId = ? AND stepName = ?`
853
900
  ).bind(stepStatus$1, endTime, resultData, stepError$1, resultRef, null, instanceId, stepName).run();
854
- await Promise.allSettled(waitFor);
901
+ await logBatcher.destroy();
855
902
  return result;
856
903
  } catch (error) {
857
904
  const endTime = Date.now();
@@ -864,7 +911,7 @@ async function createStepContext(context) {
864
911
  SET stepStatus = ?, endTime = ?, result = ?, error = ?, resultRef = ?, errorRef = ?
865
912
  WHERE instanceId = ? AND stepName = ?`
866
913
  ).bind(stepStatus$1, endTime, stepResult$1, errorData, null, errorRef, instanceId, stepName).run();
867
- await Promise.allSettled(waitFor);
914
+ await logBatcher.destroy();
868
915
  throw error;
869
916
  }
870
917
  };
@@ -899,12 +946,12 @@ function createWorkflowContext(options) {
899
946
  parentInstanceId,
900
947
  tenantId
901
948
  });
902
- const waitFor = [];
949
+ const logBatcher = new LogBatcher(internalContext);
903
950
  let logOrder = 0;
904
951
  const log = (message, type = "info") => {
905
952
  logOrder++;
906
953
  const timestamp = Date.now();
907
- const logPromise = pushLogToDB(internalContext, {
954
+ logBatcher.addLog({
908
955
  instanceId,
909
956
  stepName: null,
910
957
  message,
@@ -913,7 +960,6 @@ function createWorkflowContext(options) {
913
960
  logOrder,
914
961
  tenantId
915
962
  });
916
- waitFor.push(logPromise);
917
963
  };
918
964
  const stepContext = await createStepContext({
919
965
  D1: options.D1,
@@ -963,7 +1009,7 @@ function createWorkflowContext(options) {
963
1009
  endTime,
964
1010
  instanceId
965
1011
  });
966
- await Promise.allSettled(waitFor);
1012
+ await logBatcher.destroy();
967
1013
  } catch (error) {
968
1014
  const endTime = Date.now();
969
1015
  const workflowStatus = "failed";
@@ -972,7 +1018,7 @@ function createWorkflowContext(options) {
972
1018
  endTime,
973
1019
  instanceId
974
1020
  });
975
- await Promise.allSettled(waitFor);
1021
+ await logBatcher.destroy();
976
1022
  throw error;
977
1023
  }
978
1024
  };
@@ -983,14 +1029,15 @@ function createWorkflowContext(options) {
983
1029
  }
984
1030
  const oldRun = await options.D1.prepare(
985
1031
  /* sql */
986
- `SELECT input, workflowName, tenantId FROM WorkflowTable WHERE instanceId = ? `
1032
+ `SELECT input, workflowName, tenantId, inputRef FROM WorkflowTable WHERE instanceId = ? `
987
1033
  ).bind(retryInstanceId).first();
988
1034
  const oldWorkflowName = oldRun?.workflowName;
989
1035
  const tenantId = oldRun?.tenantId;
990
1036
  if (!tenantId) throw new Error(`No tenantId found for instanceId ${retryInstanceId}`);
1037
+ const inputRef = oldRun?.inputRef;
991
1038
  const encodedInput = oldRun?.input;
992
- if (!encodedInput) throw new Error(`No input found for instanceId ${retryInstanceId}`);
993
- const input = internalContext.serializer.deserialize(encodedInput);
1039
+ if (inputRef === void 0 || encodedInput === void 0) throw new Error(`No input found for instanceId ${retryInstanceId}`);
1040
+ const input = await deserializeWithExternalStorage(encodedInput, inputRef, internalContext.serializer, options.externalBlobStorage);
994
1041
  await call({
995
1042
  workflow,
996
1043
  input,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brandboostinggmbh/observable-workflows",
3
- "version": "0.8.5",
3
+ "version": "0.9.0",
4
4
  "description": "My awesome typescript library",
5
5
  "type": "module",
6
6
  "license": "MIT",