@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 +22 -2
- package/dist/index.js +108 -19
- package/package.json +1 -1
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
|
|
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
|
|
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).
|
|
1334
|
-
return result;
|
|
1359
|
+
).bind(context.tenantId, config.deleteWorkflowsOlderThanDays, ...statuses, limit).all();
|
|
1360
|
+
return result.results;
|
|
1335
1361
|
};
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
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
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
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(
|
|
1352
|
-
return
|
|
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,
|