@arronqzy/vue-blueprint 0.1.8 → 0.1.9
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/package.json +5 -5
- package/src/composables/useBlueprintDebugSession.ts +17 -9
- package/src/graph/index.ts +1 -0
- package/src/graph/parse-blueprint-data.ts +106 -0
- package/src/library/blueprint-library-db.ts +31 -17
- package/src/library/execution-log-db.ts +73 -57
- package/src/library/execution-log-settings.ts +14 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/vue-blueprint",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Vue 3 blueprint editor for Abuilder (Vue Flow + Ant Design Vue)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"ant-design-vue": "^4.2.6",
|
|
20
20
|
"dayjs": "^1.11.13",
|
|
21
21
|
"vue": "^3.5.13",
|
|
22
|
+
"@arronqzy/blueprint-dsl": "1.0.11",
|
|
22
23
|
"@arronqzy/i18n": "0.1.8",
|
|
23
|
-
"@arronqzy/vue-rx-store": "0.1.3"
|
|
24
|
-
"@arronqzy/blueprint-dsl": "1.0.10"
|
|
24
|
+
"@arronqzy/vue-rx-store": "0.1.3"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"vue": "^3.4.0"
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"eslint": "^8.57.0",
|
|
32
32
|
"typescript": "^5.5.4",
|
|
33
33
|
"vue-tsc": "^2.2.0",
|
|
34
|
-
"@arronqzy/
|
|
35
|
-
"@arronqzy/
|
|
34
|
+
"@arronqzy/eslint-config": "0.0.0",
|
|
35
|
+
"@arronqzy/typescript-config": "1.0.0"
|
|
36
36
|
},
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
|
@@ -41,6 +41,7 @@ export type UseBlueprintDebugSessionOptions = {
|
|
|
41
41
|
libraryNameById?: MaybeRefOrGetter<ReadonlyMap<string, string> | undefined>;
|
|
42
42
|
onExecutionBlocked?: (message: string) => void;
|
|
43
43
|
onViewScopeUpdate?: (viewElementIds: string[], scope: unknown) => void;
|
|
44
|
+
nameSpace?: MaybeRefOrGetter<string | null | undefined>;
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
function buildNodeLabelMap(graph: BlueprintGraph): Record<string, string> {
|
|
@@ -63,13 +64,20 @@ export function useBlueprintDebugSession(options: UseBlueprintDebugSessionOption
|
|
|
63
64
|
const chainComplete = ref(false);
|
|
64
65
|
const logPanelOpen = ref(false);
|
|
65
66
|
const running = ref(false);
|
|
66
|
-
const settings = ref<ExecutionLogSettings>(
|
|
67
|
+
const settings = ref<ExecutionLogSettings>(
|
|
68
|
+
readExecutionLogSettings(toValue(options.nameSpace))
|
|
69
|
+
);
|
|
67
70
|
const savedRuns = ref<ExecutionRunRecord[]>([]);
|
|
68
71
|
const totalSavedRunCount = ref(0);
|
|
69
72
|
|
|
70
73
|
const graph = computed(() => toValue(options.graph));
|
|
71
74
|
const blueprintId = computed(() => toValue(options.blueprintId));
|
|
72
75
|
const blueprintName = computed(() => toValue(options.blueprintName));
|
|
76
|
+
const nameSpace = computed(() => toValue(options.nameSpace) ?? null);
|
|
77
|
+
|
|
78
|
+
watch(nameSpace, (ns) => {
|
|
79
|
+
settings.value = readExecutionLogSettings(ns);
|
|
80
|
+
});
|
|
73
81
|
|
|
74
82
|
const lifecycleNodes = computed<LifecycleNodeOption[]>(() =>
|
|
75
83
|
graph.value.document.nodes
|
|
@@ -145,7 +153,7 @@ export function useBlueprintDebugSession(options: UseBlueprintDebugSessionOption
|
|
|
145
153
|
}
|
|
146
154
|
|
|
147
155
|
async function refreshSavedRuns() {
|
|
148
|
-
const all = await listExecutionRunRecords();
|
|
156
|
+
const all = await listExecutionRunRecords(undefined, nameSpace.value);
|
|
149
157
|
totalSavedRunCount.value = all.length;
|
|
150
158
|
savedRuns.value = blueprintId.value
|
|
151
159
|
? all.filter((item) => item.blueprintId === blueprintId.value)
|
|
@@ -158,13 +166,13 @@ export function useBlueprintDebugSession(options: UseBlueprintDebugSessionOption
|
|
|
158
166
|
|
|
159
167
|
async function enforceSavedRunLimits() {
|
|
160
168
|
const cutoff = retentionCutoffMs(settings.value.retentionDays);
|
|
161
|
-
await purgeExecutionRunsOlderThan(cutoff);
|
|
162
|
-
await trimExecutionRunRecordsToMax(settings.value.maxSavedRuns);
|
|
169
|
+
await purgeExecutionRunsOlderThan(cutoff, nameSpace.value);
|
|
170
|
+
await trimExecutionRunRecordsToMax(settings.value.maxSavedRuns, nameSpace.value);
|
|
163
171
|
await refreshSavedRuns();
|
|
164
172
|
}
|
|
165
173
|
|
|
166
174
|
async function clearAllSavedRuns() {
|
|
167
|
-
const removed = await clearAllExecutionRunRecords();
|
|
175
|
+
const removed = await clearAllExecutionRunRecords(nameSpace.value);
|
|
168
176
|
savedRuns.value = [];
|
|
169
177
|
totalSavedRunCount.value = 0;
|
|
170
178
|
return removed;
|
|
@@ -194,7 +202,7 @@ export function useBlueprintDebugSession(options: UseBlueprintDebugSessionOption
|
|
|
194
202
|
|
|
195
203
|
async function persistRunIfNeeded(record: ExecutionRunRecord) {
|
|
196
204
|
if (!settings.value.autoSave) return;
|
|
197
|
-
await putExecutionRunRecord(record);
|
|
205
|
+
await putExecutionRunRecord(record, nameSpace.value);
|
|
198
206
|
await enforceSavedRunLimits();
|
|
199
207
|
}
|
|
200
208
|
|
|
@@ -356,7 +364,7 @@ export function useBlueprintDebugSession(options: UseBlueprintDebugSessionOption
|
|
|
356
364
|
runnerRef.value?.isDebugQueueEmpty() ? "completed" : "paused",
|
|
357
365
|
entries.value
|
|
358
366
|
);
|
|
359
|
-
await putExecutionRunRecord(record);
|
|
367
|
+
await putExecutionRunRecord(record, nameSpace.value);
|
|
360
368
|
await enforceSavedRunLimits();
|
|
361
369
|
}
|
|
362
370
|
|
|
@@ -373,10 +381,10 @@ export function useBlueprintDebugSession(options: UseBlueprintDebugSessionOption
|
|
|
373
381
|
function updateSettings(patch: Partial<ExecutionLogSettings>) {
|
|
374
382
|
const next = { ...settings.value, ...patch };
|
|
375
383
|
settings.value = next;
|
|
376
|
-
writeExecutionLogSettings(next);
|
|
384
|
+
writeExecutionLogSettings(next, nameSpace.value);
|
|
377
385
|
if (patch.maxSavedRuns !== undefined) {
|
|
378
386
|
const maxSavedRuns = Math.max(1, Math.floor(patch.maxSavedRuns));
|
|
379
|
-
void trimExecutionRunRecordsToMax(maxSavedRuns).then(() => refreshSavedRuns());
|
|
387
|
+
void trimExecutionRunRecordsToMax(maxSavedRuns, nameSpace.value).then(() => refreshSavedRuns());
|
|
380
388
|
}
|
|
381
389
|
}
|
|
382
390
|
|
package/src/graph/index.ts
CHANGED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sanitizeBlueprintDocument,
|
|
3
|
+
type BlueprintDocument,
|
|
4
|
+
type BlueprintGraphEdge,
|
|
5
|
+
type BlueprintGraphNode,
|
|
6
|
+
type BlueprintNodeRole,
|
|
7
|
+
} from "./document";
|
|
8
|
+
|
|
9
|
+
export type ParseCheckResult = {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const NODE_ROLES = new Set<BlueprintNodeRole>([
|
|
15
|
+
"blueprint",
|
|
16
|
+
"logic",
|
|
17
|
+
"and",
|
|
18
|
+
"lifecycle",
|
|
19
|
+
"fetch",
|
|
20
|
+
"json",
|
|
21
|
+
"storage",
|
|
22
|
+
"clock",
|
|
23
|
+
"event",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
27
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function extractBlueprintDocument(data: unknown): unknown {
|
|
31
|
+
if (!isRecord(data)) return data;
|
|
32
|
+
if (isRecord(data.blueprintDocument)) return data.blueprintDocument;
|
|
33
|
+
if (isRecord(data.document) && Array.isArray(data.document.nodes)) {
|
|
34
|
+
return data.document;
|
|
35
|
+
}
|
|
36
|
+
return data;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isFiniteNumber(value: unknown): value is number {
|
|
40
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isBlueprintNode(value: unknown): value is BlueprintGraphNode {
|
|
44
|
+
if (!isRecord(value)) return false;
|
|
45
|
+
if (typeof value.id !== "string" || !value.id) return false;
|
|
46
|
+
if (typeof value.role !== "string" || !NODE_ROLES.has(value.role as BlueprintNodeRole)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (!isRecord(value.position) || !isFiniteNumber(value.position.x) || !isFiniteNumber(value.position.y)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isBlueprintEdge(value: unknown): value is BlueprintGraphEdge {
|
|
56
|
+
if (!isRecord(value)) return false;
|
|
57
|
+
return (
|
|
58
|
+
typeof value.id === "string" &&
|
|
59
|
+
value.id.length > 0 &&
|
|
60
|
+
typeof value.source === "string" &&
|
|
61
|
+
value.source.length > 0 &&
|
|
62
|
+
typeof value.target === "string" &&
|
|
63
|
+
value.target.length > 0
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 校验数据能否被蓝图解析(工作区 `blueprintDocument`、蓝图导出 JSON 或裸文档均可)。
|
|
69
|
+
*/
|
|
70
|
+
export function parseBlueprintData(
|
|
71
|
+
data: unknown
|
|
72
|
+
): ParseCheckResult & { value?: BlueprintDocument } {
|
|
73
|
+
if (data == null) {
|
|
74
|
+
return { ok: false, error: "empty" };
|
|
75
|
+
}
|
|
76
|
+
const raw = extractBlueprintDocument(data);
|
|
77
|
+
if (!isRecord(raw)) {
|
|
78
|
+
return { ok: false, error: "invalid-blueprint-data" };
|
|
79
|
+
}
|
|
80
|
+
if (!Array.isArray(raw.nodes) || !Array.isArray(raw.edges)) {
|
|
81
|
+
return { ok: false, error: "invalid-blueprint-data" };
|
|
82
|
+
}
|
|
83
|
+
if (!raw.nodes.every(isBlueprintNode) || !raw.edges.every(isBlueprintEdge)) {
|
|
84
|
+
return { ok: false, error: "invalid-blueprint-data" };
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const value = sanitizeBlueprintDocument({
|
|
88
|
+
id: typeof raw.id === "string" && raw.id ? raw.id : "default",
|
|
89
|
+
name: typeof raw.name === "string" ? raw.name : undefined,
|
|
90
|
+
nodes: raw.nodes as BlueprintGraphNode[],
|
|
91
|
+
edges: raw.edges as BlueprintGraphEdge[],
|
|
92
|
+
allowFalseSignalPropagation:
|
|
93
|
+
typeof raw.allowFalseSignalPropagation === "boolean"
|
|
94
|
+
? raw.allowFalseSignalPropagation
|
|
95
|
+
: undefined,
|
|
96
|
+
});
|
|
97
|
+
return { ok: true, value };
|
|
98
|
+
} catch {
|
|
99
|
+
return { ok: false, error: "invalid-blueprint-data" };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function validateBlueprintData(data: unknown): ParseCheckResult {
|
|
104
|
+
const parsed = parseBlueprintData(data);
|
|
105
|
+
return parsed.ok ? { ok: true } : { ok: false, error: parsed.error };
|
|
106
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appStorageKey } from "@arronqzy/blueprint-dsl";
|
|
1
2
|
import type {
|
|
2
3
|
BlueprintLibraryListItem,
|
|
3
4
|
BlueprintLibraryRecord,
|
|
@@ -7,9 +8,9 @@ const DB_NAME = "arronqzy-blueprint-library";
|
|
|
7
8
|
const DB_VERSION = 1;
|
|
8
9
|
const STORE_NAME = "blueprints";
|
|
9
10
|
|
|
10
|
-
function openDb(): Promise<IDBDatabase> {
|
|
11
|
+
function openDb(nameSpace?: string | null): Promise<IDBDatabase> {
|
|
11
12
|
return new Promise((resolve, reject) => {
|
|
12
|
-
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
13
|
+
const request = indexedDB.open(appStorageKey(DB_NAME, nameSpace), DB_VERSION);
|
|
13
14
|
request.onerror = () => reject(request.error ?? new Error("indexeddb-open-failed"));
|
|
14
15
|
request.onupgradeneeded = () => {
|
|
15
16
|
const db = request.result;
|
|
@@ -25,9 +26,10 @@ function openDb(): Promise<IDBDatabase> {
|
|
|
25
26
|
|
|
26
27
|
function runTransaction<T>(
|
|
27
28
|
mode: IDBTransactionMode,
|
|
28
|
-
runner: (store: IDBObjectStore) => IDBRequest<T
|
|
29
|
+
runner: (store: IDBObjectStore) => IDBRequest<T>,
|
|
30
|
+
nameSpace?: string | null
|
|
29
31
|
): Promise<T> {
|
|
30
|
-
return openDb().then(
|
|
32
|
+
return openDb(nameSpace).then(
|
|
31
33
|
(db) =>
|
|
32
34
|
new Promise<T>((resolve, reject) => {
|
|
33
35
|
const tx = db.transaction(STORE_NAME, mode);
|
|
@@ -53,9 +55,13 @@ function runTransaction<T>(
|
|
|
53
55
|
);
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
export async function listBlueprintLibrary(
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
export async function listBlueprintLibrary(
|
|
59
|
+
nameSpace?: string | null
|
|
60
|
+
): Promise<BlueprintLibraryListItem[]> {
|
|
61
|
+
const records = await runTransaction<BlueprintLibraryRecord[]>(
|
|
62
|
+
"readonly",
|
|
63
|
+
(store) => store.getAll(),
|
|
64
|
+
nameSpace
|
|
59
65
|
);
|
|
60
66
|
return records
|
|
61
67
|
.map((record) => ({
|
|
@@ -69,26 +75,31 @@ export async function listBlueprintLibrary(): Promise<BlueprintLibraryListItem[]
|
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
export async function getBlueprintLibraryRecord(
|
|
72
|
-
id: string
|
|
78
|
+
id: string,
|
|
79
|
+
nameSpace?: string | null
|
|
73
80
|
): Promise<BlueprintLibraryRecord | null> {
|
|
74
|
-
const record = await runTransaction<BlueprintLibraryRecord | undefined>(
|
|
75
|
-
|
|
81
|
+
const record = await runTransaction<BlueprintLibraryRecord | undefined>(
|
|
82
|
+
"readonly",
|
|
83
|
+
(store) => store.get(id),
|
|
84
|
+
nameSpace
|
|
76
85
|
);
|
|
77
86
|
return record ?? null;
|
|
78
87
|
}
|
|
79
88
|
|
|
80
89
|
export async function putBlueprintLibraryRecord(
|
|
81
|
-
record: BlueprintLibraryRecord
|
|
90
|
+
record: BlueprintLibraryRecord,
|
|
91
|
+
nameSpace?: string | null
|
|
82
92
|
): Promise<BlueprintLibraryRecord> {
|
|
83
|
-
await runTransaction<IDBValidKey>("readwrite", (store) => store.put(record));
|
|
93
|
+
await runTransaction<IDBValidKey>("readwrite", (store) => store.put(record), nameSpace);
|
|
84
94
|
return record;
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
export async function updateBlueprintLibraryMeta(
|
|
88
98
|
id: string,
|
|
89
|
-
patch: { name?: string; remark?: string }
|
|
99
|
+
patch: { name?: string; remark?: string },
|
|
100
|
+
nameSpace?: string | null
|
|
90
101
|
): Promise<BlueprintLibraryRecord | null> {
|
|
91
|
-
const existing = await getBlueprintLibraryRecord(id);
|
|
102
|
+
const existing = await getBlueprintLibraryRecord(id, nameSpace);
|
|
92
103
|
if (!existing) return null;
|
|
93
104
|
|
|
94
105
|
const name = patch.name?.trim() || existing.name;
|
|
@@ -103,10 +114,13 @@ export async function updateBlueprintLibraryMeta(
|
|
|
103
114
|
},
|
|
104
115
|
};
|
|
105
116
|
|
|
106
|
-
await putBlueprintLibraryRecord(record);
|
|
117
|
+
await putBlueprintLibraryRecord(record, nameSpace);
|
|
107
118
|
return record;
|
|
108
119
|
}
|
|
109
120
|
|
|
110
|
-
export async function deleteBlueprintLibraryRecord(
|
|
111
|
-
|
|
121
|
+
export async function deleteBlueprintLibraryRecord(
|
|
122
|
+
id: string,
|
|
123
|
+
nameSpace?: string | null
|
|
124
|
+
): Promise<void> {
|
|
125
|
+
await runTransaction<undefined>("readwrite", (store) => store.delete(id), nameSpace);
|
|
112
126
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { appStorageKey } from "@arronqzy/blueprint-dsl";
|
|
1
2
|
import type { ExecutionRunRecord } from "@arronqzy/blueprint-dsl";
|
|
2
3
|
|
|
3
4
|
const DB_NAME = "arronqzy-blueprint-execution-log";
|
|
4
5
|
const DB_VERSION = 1;
|
|
5
6
|
const STORE_NAME = "runs";
|
|
6
7
|
|
|
7
|
-
function openDb(): Promise<IDBDatabase> {
|
|
8
|
+
function openDb(nameSpace?: string | null): Promise<IDBDatabase> {
|
|
8
9
|
return new Promise((resolve, reject) => {
|
|
9
|
-
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
10
|
+
const request = indexedDB.open(appStorageKey(DB_NAME, nameSpace), DB_VERSION);
|
|
10
11
|
request.onerror = () => reject(request.error ?? new Error("indexeddb-open-failed"));
|
|
11
12
|
request.onupgradeneeded = () => {
|
|
12
13
|
const db = request.result;
|
|
@@ -22,9 +23,10 @@ function openDb(): Promise<IDBDatabase> {
|
|
|
22
23
|
|
|
23
24
|
function runTransaction<T>(
|
|
24
25
|
mode: IDBTransactionMode,
|
|
25
|
-
runner: (store: IDBObjectStore) => IDBRequest<T
|
|
26
|
+
runner: (store: IDBObjectStore) => IDBRequest<T>,
|
|
27
|
+
nameSpace?: string | null
|
|
26
28
|
): Promise<T> {
|
|
27
|
-
return openDb().then(
|
|
29
|
+
return openDb(nameSpace).then(
|
|
28
30
|
(db) =>
|
|
29
31
|
new Promise<T>((resolve, reject) => {
|
|
30
32
|
const tx = db.transaction(STORE_NAME, mode);
|
|
@@ -50,28 +52,55 @@ function runTransaction<T>(
|
|
|
50
52
|
);
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
function deleteMany(ids: string[], nameSpace?: string | null): Promise<void> {
|
|
56
|
+
return openDb(nameSpace).then(
|
|
57
|
+
(db) =>
|
|
58
|
+
new Promise<void>((resolve, reject) => {
|
|
59
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
60
|
+
const store = tx.objectStore(STORE_NAME);
|
|
61
|
+
for (const id of ids) {
|
|
62
|
+
store.delete(id);
|
|
63
|
+
}
|
|
64
|
+
tx.oncomplete = () => {
|
|
65
|
+
db.close();
|
|
66
|
+
resolve();
|
|
67
|
+
};
|
|
68
|
+
tx.onerror = () => {
|
|
69
|
+
db.close();
|
|
70
|
+
reject(tx.error ?? new Error("indexeddb-transaction-failed"));
|
|
71
|
+
};
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
53
76
|
export async function putExecutionRunRecord(
|
|
54
|
-
record: ExecutionRunRecord
|
|
77
|
+
record: ExecutionRunRecord,
|
|
78
|
+
nameSpace?: string | null
|
|
55
79
|
): Promise<ExecutionRunRecord> {
|
|
56
|
-
await runTransaction<IDBValidKey>("readwrite", (store) => store.put(record));
|
|
80
|
+
await runTransaction<IDBValidKey>("readwrite", (store) => store.put(record), nameSpace);
|
|
57
81
|
return record;
|
|
58
82
|
}
|
|
59
83
|
|
|
60
84
|
export async function getExecutionRunRecord(
|
|
61
|
-
runId: string
|
|
85
|
+
runId: string,
|
|
86
|
+
nameSpace?: string | null
|
|
62
87
|
): Promise<ExecutionRunRecord | null> {
|
|
63
88
|
const record = await runTransaction<ExecutionRunRecord | undefined>(
|
|
64
89
|
"readonly",
|
|
65
|
-
(store) => store.get(runId)
|
|
90
|
+
(store) => store.get(runId),
|
|
91
|
+
nameSpace
|
|
66
92
|
);
|
|
67
93
|
return record ?? null;
|
|
68
94
|
}
|
|
69
95
|
|
|
70
96
|
export async function listExecutionRunRecords(
|
|
71
|
-
blueprintId?: string | null
|
|
97
|
+
blueprintId?: string | null,
|
|
98
|
+
nameSpace?: string | null
|
|
72
99
|
): Promise<ExecutionRunRecord[]> {
|
|
73
|
-
const records = await runTransaction<ExecutionRunRecord[]>(
|
|
74
|
-
|
|
100
|
+
const records = await runTransaction<ExecutionRunRecord[]>(
|
|
101
|
+
"readonly",
|
|
102
|
+
(store) => store.getAll(),
|
|
103
|
+
nameSpace
|
|
75
104
|
);
|
|
76
105
|
const filtered = blueprintId
|
|
77
106
|
? records.filter((item) => item.blueprintId === blueprintId)
|
|
@@ -79,27 +108,37 @@ export async function listExecutionRunRecords(
|
|
|
79
108
|
return filtered.sort((a, b) => b.startedAt - a.startedAt);
|
|
80
109
|
}
|
|
81
110
|
|
|
82
|
-
export async function deleteExecutionRunRecord(
|
|
83
|
-
|
|
111
|
+
export async function deleteExecutionRunRecord(
|
|
112
|
+
runId: string,
|
|
113
|
+
nameSpace?: string | null
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
await runTransaction<undefined>("readwrite", (store) => store.delete(runId), nameSpace);
|
|
84
116
|
}
|
|
85
117
|
|
|
86
|
-
export async function clearAllExecutionRunRecords(
|
|
87
|
-
|
|
88
|
-
|
|
118
|
+
export async function clearAllExecutionRunRecords(
|
|
119
|
+
nameSpace?: string | null
|
|
120
|
+
): Promise<number> {
|
|
121
|
+
const records = await runTransaction<ExecutionRunRecord[]>(
|
|
122
|
+
"readonly",
|
|
123
|
+
(store) => store.getAll(),
|
|
124
|
+
nameSpace
|
|
89
125
|
);
|
|
90
126
|
if (records.length === 0) return 0;
|
|
91
127
|
|
|
92
|
-
await runTransaction<undefined>("readwrite", (store) => store.clear());
|
|
128
|
+
await runTransaction<undefined>("readwrite", (store) => store.clear(), nameSpace);
|
|
93
129
|
return records.length;
|
|
94
130
|
}
|
|
95
131
|
|
|
96
132
|
export async function trimExecutionRunRecordsToMax(
|
|
97
|
-
maxCount: number
|
|
133
|
+
maxCount: number,
|
|
134
|
+
nameSpace?: string | null
|
|
98
135
|
): Promise<number> {
|
|
99
136
|
if (!Number.isFinite(maxCount) || maxCount < 1) return 0;
|
|
100
137
|
|
|
101
|
-
const records = await runTransaction<ExecutionRunRecord[]>(
|
|
102
|
-
|
|
138
|
+
const records = await runTransaction<ExecutionRunRecord[]>(
|
|
139
|
+
"readonly",
|
|
140
|
+
(store) => store.getAll(),
|
|
141
|
+
nameSpace
|
|
103
142
|
);
|
|
104
143
|
if (records.length <= maxCount) return 0;
|
|
105
144
|
|
|
@@ -107,52 +146,29 @@ export async function trimExecutionRunRecordsToMax(
|
|
|
107
146
|
const overflow = sorted.slice(maxCount);
|
|
108
147
|
if (overflow.length === 0) return 0;
|
|
109
148
|
|
|
110
|
-
await
|
|
111
|
-
(
|
|
112
|
-
|
|
113
|
-
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
114
|
-
const store = tx.objectStore(STORE_NAME);
|
|
115
|
-
for (const item of overflow) {
|
|
116
|
-
store.delete(item.runId);
|
|
117
|
-
}
|
|
118
|
-
tx.oncomplete = () => {
|
|
119
|
-
db.close();
|
|
120
|
-
resolve();
|
|
121
|
-
};
|
|
122
|
-
tx.onerror = () => {
|
|
123
|
-
db.close();
|
|
124
|
-
reject(tx.error ?? new Error("indexeddb-transaction-failed"));
|
|
125
|
-
};
|
|
126
|
-
})
|
|
149
|
+
await deleteMany(
|
|
150
|
+
overflow.map((item) => item.runId),
|
|
151
|
+
nameSpace
|
|
127
152
|
);
|
|
128
153
|
|
|
129
154
|
return overflow.length;
|
|
130
155
|
}
|
|
131
156
|
|
|
132
|
-
export async function purgeExecutionRunsOlderThan(
|
|
133
|
-
|
|
134
|
-
|
|
157
|
+
export async function purgeExecutionRunsOlderThan(
|
|
158
|
+
cutoffMs: number,
|
|
159
|
+
nameSpace?: string | null
|
|
160
|
+
): Promise<number> {
|
|
161
|
+
const records = await runTransaction<ExecutionRunRecord[]>(
|
|
162
|
+
"readonly",
|
|
163
|
+
(store) => store.getAll(),
|
|
164
|
+
nameSpace
|
|
135
165
|
);
|
|
136
166
|
const stale = records.filter((item) => item.startedAt < cutoffMs);
|
|
137
167
|
if (stale.length === 0) return 0;
|
|
138
168
|
|
|
139
|
-
await
|
|
140
|
-
(
|
|
141
|
-
|
|
142
|
-
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
143
|
-
const store = tx.objectStore(STORE_NAME);
|
|
144
|
-
for (const item of stale) {
|
|
145
|
-
store.delete(item.runId);
|
|
146
|
-
}
|
|
147
|
-
tx.oncomplete = () => {
|
|
148
|
-
db.close();
|
|
149
|
-
resolve();
|
|
150
|
-
};
|
|
151
|
-
tx.onerror = () => {
|
|
152
|
-
db.close();
|
|
153
|
-
reject(tx.error ?? new Error("indexeddb-transaction-failed"));
|
|
154
|
-
};
|
|
155
|
-
})
|
|
169
|
+
await deleteMany(
|
|
170
|
+
stale.map((item) => item.runId),
|
|
171
|
+
nameSpace
|
|
156
172
|
);
|
|
157
173
|
|
|
158
174
|
return stale.length;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { appStorageKey } from "@arronqzy/blueprint-dsl";
|
|
2
|
+
|
|
1
3
|
export type ExecutionLogSettings = {
|
|
2
4
|
retentionDays: number;
|
|
3
5
|
autoSave: boolean;
|
|
@@ -13,9 +15,11 @@ export const DEFAULT_EXECUTION_LOG_SETTINGS: ExecutionLogSettings = {
|
|
|
13
15
|
maxSavedRuns: 80,
|
|
14
16
|
};
|
|
15
17
|
|
|
16
|
-
export function readExecutionLogSettings(
|
|
18
|
+
export function readExecutionLogSettings(
|
|
19
|
+
nameSpace?: string | null
|
|
20
|
+
): ExecutionLogSettings {
|
|
17
21
|
try {
|
|
18
|
-
const raw = localStorage.getItem(SETTINGS_KEY);
|
|
22
|
+
const raw = localStorage.getItem(appStorageKey(SETTINGS_KEY, nameSpace));
|
|
19
23
|
if (!raw) return { ...DEFAULT_EXECUTION_LOG_SETTINGS };
|
|
20
24
|
const parsed = JSON.parse(raw) as Partial<ExecutionLogSettings>;
|
|
21
25
|
return {
|
|
@@ -37,9 +41,15 @@ export function readExecutionLogSettings(): ExecutionLogSettings {
|
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
export function writeExecutionLogSettings(
|
|
44
|
+
export function writeExecutionLogSettings(
|
|
45
|
+
settings: ExecutionLogSettings,
|
|
46
|
+
nameSpace?: string | null
|
|
47
|
+
) {
|
|
41
48
|
try {
|
|
42
|
-
localStorage.setItem(
|
|
49
|
+
localStorage.setItem(
|
|
50
|
+
appStorageKey(SETTINGS_KEY, nameSpace),
|
|
51
|
+
JSON.stringify(settings)
|
|
52
|
+
);
|
|
43
53
|
} catch {
|
|
44
54
|
// ignore storage errors
|
|
45
55
|
}
|