@brandboostinggmbh/observable-workflows 0.14.0 → 0.15.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
@@ -351,6 +351,7 @@ type ExternalBlobStorage = {
351
351
  threshold: number;
352
352
  set: (data: string) => Promise<string>;
353
353
  get: (id: string) => Promise<string>;
354
+ delete: (...keys: string[]) => Promise<number>;
354
355
  };
355
356
  type WorkflowContextOptions = {
356
357
  D1: D1Database;
@@ -545,6 +546,7 @@ type DeleteConfig = {
545
546
  /** Workflows that are still in progress after a long time, might have failed non-gracefully and are therefore not correctly marked as failed.*/
546
547
  inProgressWorkflows: boolean;
547
548
  };
549
+ deleteRefsFromExternalStorage: boolean;
548
550
  };
549
551
  declare const createCleanupManager: (context: {
550
552
  D1: D1Database;
@@ -552,11 +554,29 @@ declare const createCleanupManager: (context: {
552
554
  serializer?: Serializer;
553
555
  externalBlobStorage?: ExternalBlobStorage;
554
556
  }) => {
555
- countAffectedWorkflows: (config: DeleteConfig) => Promise<{
557
+ countAffectedWorkflows: (config: DeleteConfig, limit: number) => Promise<{
556
558
  count: number;
559
+ affectedSteps: {
560
+ instanceId: string;
561
+ stepName: string;
562
+ stepStatus: string;
563
+ errorRef: string | null;
564
+ resultRef: string | null;
565
+ workflowName: string;
566
+ }[];
567
+ externalStorageKeysCount: number;
557
568
  }>;
558
- deleteOldWorkflows: (config: DeleteConfig) => Promise<{
569
+ deleteOldWorkflows: (config: DeleteConfig, limit: number) => Promise<{
559
570
  deletedCount: number;
571
+ deletedSteps: {
572
+ instanceId: string;
573
+ stepName: string;
574
+ stepStatus: string;
575
+ errorRef: string | null;
576
+ resultRef: string | null;
577
+ workflowName: string;
578
+ }[];
579
+ deletedExternalStorageKeysCount: number;
560
580
  }>;
561
581
  };
562
582
 
package/dist/index.js CHANGED
@@ -1307,6 +1307,23 @@ function createR2ExternalBlobStorage(options) {
1307
1307
  } catch (error) {
1308
1308
  throw new Error(`Failed to retrieve data from R2: ${error instanceof Error ? error.message : String(error)}`);
1309
1309
  }
1310
+ },
1311
+ async delete(...keys) {
1312
+ if (keys.length === 0) return 0;
1313
+ try {
1314
+ await bucket.delete(keys);
1315
+ return keys.length;
1316
+ } catch (error) {
1317
+ console.warn(`Bulk delete failed, falling back to individual deletes: ${error instanceof Error ? error.message : String(error)}`);
1318
+ let deletedCount = 0;
1319
+ for (const key of keys) try {
1320
+ await bucket.delete(key);
1321
+ deletedCount++;
1322
+ } catch (individualError) {
1323
+ console.warn(`Failed to delete key ${key} from R2: ${individualError instanceof Error ? individualError.message : String(individualError)}`);
1324
+ }
1325
+ return deletedCount;
1326
+ }
1310
1327
  }
1311
1328
  };
1312
1329
  }
@@ -1314,42 +1331,114 @@ function createR2ExternalBlobStorage(options) {
1314
1331
  //#endregion
1315
1332
  //#region src/observableWorkflows/createCleanupManager.ts
1316
1333
  const createCleanupManager = (context) => {
1317
- const countAffectedWorkflows = async (config) => {
1334
+ const buildStatusesAndPlaceholders = (config) => {
1318
1335
  const statuses = [];
1319
1336
  if (config.delete.successfulWorkflows) statuses.push("completed");
1320
1337
  if (config.delete.failedWorkflows) statuses.push("failed");
1321
1338
  if (config.delete.inProgressWorkflows) statuses.push("pending");
1322
- if (statuses.length === 0) return { count: 0 };
1323
1339
  const statusPlaceholders = statuses.map(() => "?").join(", ");
1340
+ return {
1341
+ statuses,
1342
+ statusPlaceholders
1343
+ };
1344
+ };
1345
+ const getAffectedWorkflows = async (config, limit) => {
1346
+ const { statuses, statusPlaceholders } = buildStatusesAndPlaceholders(config);
1347
+ if (statuses.length === 0) return [];
1324
1348
  const result = await context.D1.prepare(
1325
1349
  /* sql */
1326
1350
  `
1327
- SELECT COUNT(*) as count
1328
- FROM WorkflowTable
1351
+ SELECT instanceId, workflowName, inputRef
1352
+ FROM WorkflowTable
1329
1353
  WHERE tenantId = ?
1330
1354
  AND startTime < (strftime('%s', 'now', '-' || ? || ' days') * 1000)
1331
1355
  AND workflowStatus IN (${statusPlaceholders})
1356
+ ORDER BY workflowName, instanceId
1357
+ LIMIT ?
1332
1358
  `
1333
- ).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses).first();
1334
- return result;
1359
+ ).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all();
1360
+ return result.results;
1335
1361
  };
1336
- const deleteOldWorkflows = async (config) => {
1337
- const statuses = [];
1338
- if (config.delete.successfulWorkflows) statuses.push("completed");
1339
- if (config.delete.failedWorkflows) statuses.push("failed");
1340
- if (config.delete.inProgressWorkflows) statuses.push("pending");
1341
- if (statuses.length === 0) return { deletedCount: 0 };
1342
- const statusPlaceholders = statuses.map(() => "?").join(", ");
1362
+ const getAffectedSteps = async (workflows) => {
1363
+ if (workflows.length === 0) return [];
1364
+ const workflowIds = workflows.map(() => "?").join(", ");
1343
1365
  const result = await context.D1.prepare(
1344
1366
  /* sql */
1345
1367
  `
1346
- DELETE FROM WorkflowTable
1347
- WHERE tenantId = ?
1348
- AND startTime < (strftime('%s', 'now', '-' || ? || ' days') * 1000)
1349
- AND workflowStatus IN (${statusPlaceholders})
1368
+ SELECT s.instanceId, s.stepName, s.stepStatus, s.errorRef, s.resultRef, w.workflowName
1369
+ FROM StepTable s
1370
+ JOIN WorkflowTable w ON s.instanceId = w.instanceId
1371
+ WHERE s.instanceId IN (${workflowIds})
1372
+ ORDER BY w.workflowName, s.instanceId, s.stepName
1350
1373
  `
1351
- ).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses).run();
1352
- return { deletedCount: result.meta.changes || 0 };
1374
+ ).bind(...workflows.map((w) => w.instanceId)).all();
1375
+ return result.results;
1376
+ };
1377
+ const collectExternalStorageKeys = (workflows, steps) => {
1378
+ const keys = [];
1379
+ for (const workflow of workflows) if (workflow.inputRef) keys.push(workflow.inputRef);
1380
+ for (const step of steps) {
1381
+ if (step.errorRef) keys.push(step.errorRef);
1382
+ if (step.resultRef) keys.push(step.resultRef);
1383
+ }
1384
+ return keys;
1385
+ };
1386
+ const countAffectedWorkflows = async (config, limit) => {
1387
+ if (buildStatusesAndPlaceholders(config).statuses.length === 0) return {
1388
+ count: 0,
1389
+ affectedSteps: [],
1390
+ externalStorageKeysCount: 0
1391
+ };
1392
+ const affectedWorkflows = await getAffectedWorkflows(config, limit);
1393
+ const affectedSteps = await getAffectedSteps(affectedWorkflows);
1394
+ let externalStorageKeysCount = 0;
1395
+ if (config.deleteRefsFromExternalStorage && context.externalBlobStorage) {
1396
+ const externalKeys = collectExternalStorageKeys(affectedWorkflows, affectedSteps);
1397
+ externalStorageKeysCount = externalKeys.length;
1398
+ }
1399
+ return {
1400
+ count: affectedWorkflows.length,
1401
+ affectedSteps,
1402
+ externalStorageKeysCount
1403
+ };
1404
+ };
1405
+ const deleteOldWorkflows = async (config, limit) => {
1406
+ if (buildStatusesAndPlaceholders(config).statuses.length === 0) return {
1407
+ deletedCount: 0,
1408
+ deletedSteps: [],
1409
+ deletedExternalStorageKeysCount: 0
1410
+ };
1411
+ const deletedWorkflows = await getAffectedWorkflows(config, limit);
1412
+ const deletedSteps = await getAffectedSteps(deletedWorkflows);
1413
+ let deletedExternalStorageKeysCount = 0;
1414
+ if (config.deleteRefsFromExternalStorage && context.externalBlobStorage) {
1415
+ const externalKeys = collectExternalStorageKeys(deletedWorkflows, deletedSteps);
1416
+ if (externalKeys.length > 0) try {
1417
+ deletedExternalStorageKeysCount = await context.externalBlobStorage.delete(...externalKeys);
1418
+ } catch (error) {
1419
+ console.warn(`Failed to delete some external storage keys: ${error instanceof Error ? error.message : String(error)}`);
1420
+ }
1421
+ }
1422
+ if (deletedWorkflows.length > 0) {
1423
+ const workflowIds = deletedWorkflows.map(() => "?").join(", ");
1424
+ const result = await context.D1.prepare(
1425
+ /* sql */
1426
+ `
1427
+ DELETE FROM WorkflowTable
1428
+ WHERE instanceId IN (${workflowIds})
1429
+ `
1430
+ ).bind(...deletedWorkflows.map((w) => w.instanceId)).run();
1431
+ return {
1432
+ deletedCount: result.meta.changes || 0,
1433
+ deletedSteps,
1434
+ deletedExternalStorageKeysCount
1435
+ };
1436
+ }
1437
+ return {
1438
+ deletedCount: 0,
1439
+ deletedSteps,
1440
+ deletedExternalStorageKeysCount
1441
+ };
1353
1442
  };
1354
1443
  return {
1355
1444
  countAffectedWorkflows,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brandboostinggmbh/observable-workflows",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "My awesome typescript library",
5
5
  "type": "module",
6
6
  "license": "MIT",