@entity-access/entity-access 1.0.434 → 1.0.435
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/tsconfig.tsbuildinfo +1 -1
- package/dist/workflows/WorkflowContext.d.ts +1 -1
- package/dist/workflows/WorkflowContext.d.ts.map +1 -1
- package/dist/workflows/WorkflowContext.js +26 -15
- package/dist/workflows/WorkflowContext.js.map +1 -1
- package/dist/workflows/WorkflowDbContext.d.ts +31 -0
- package/dist/workflows/WorkflowDbContext.d.ts.map +1 -0
- package/dist/workflows/WorkflowDbContext.js +162 -0
- package/dist/workflows/WorkflowDbContext.js.map +1 -0
- package/dist/workflows/WorkflowStorage.d.ts +3 -24
- package/dist/workflows/WorkflowStorage.d.ts.map +1 -1
- package/dist/workflows/WorkflowStorage.js +19 -184
- package/dist/workflows/WorkflowStorage.js.map +1 -1
- package/dist/workflows/WorkflowTask.d.ts +12 -0
- package/dist/workflows/WorkflowTask.d.ts.map +1 -0
- package/dist/workflows/WorkflowTask.js +33 -0
- package/dist/workflows/WorkflowTask.js.map +1 -0
- package/package.json +1 -1
- package/src/workflows/WorkflowContext.ts +5 -3
- package/src/workflows/WorkflowDbContext.ts +123 -0
- package/src/workflows/WorkflowStorage.ts +21 -146
- package/src/workflows/WorkflowTask.ts +39 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WorkflowDbContext, WorkflowItem } from "./WorkflowDbContext.js";
|
|
2
|
+
export default class WorkflowTask implements Disposable {
|
|
3
|
+
readonly item: WorkflowItem;
|
|
4
|
+
readonly context: WorkflowDbContext;
|
|
5
|
+
timer: NodeJS.Timer;
|
|
6
|
+
readonly signal: any;
|
|
7
|
+
private ac;
|
|
8
|
+
constructor(item: WorkflowItem, context: WorkflowDbContext);
|
|
9
|
+
[Symbol.dispose](): void;
|
|
10
|
+
renewLock: () => void;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=WorkflowTask.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkflowTask.d.ts","sourceRoot":"","sources":["../../src/workflows/WorkflowTask.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE9E,MAAM,CAAC,OAAO,OAAO,YAAa,YAAW,UAAU;aAQ/B,IAAI,EAAE,YAAY;aAClB,OAAO,EAAE,iBAAiB;IAP9C,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC;IAEpB,SAAgB,MAAM,MAAC;IACvB,OAAO,CAAC,EAAE,CAAkB;gBAGR,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,iBAAiB;IAO9C,CAAC,MAAM,CAAC,OAAO,CAAC;IAOhB,SAAS,aAUP;CACL"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import DateTime from "../types/DateTime.js";
|
|
3
|
+
export default class WorkflowTask {
|
|
4
|
+
item;
|
|
5
|
+
context;
|
|
6
|
+
timer;
|
|
7
|
+
signal;
|
|
8
|
+
ac;
|
|
9
|
+
constructor(item, context) {
|
|
10
|
+
this.item = item;
|
|
11
|
+
this.context = context;
|
|
12
|
+
this.timer = setInterval(this.renewLock, 1000);
|
|
13
|
+
this.ac = new AbortController();
|
|
14
|
+
this.signal = this.ac.signal;
|
|
15
|
+
}
|
|
16
|
+
[Symbol.dispose]() {
|
|
17
|
+
clearInterval(this.timer);
|
|
18
|
+
const { id, lockToken } = this.item;
|
|
19
|
+
this.context.workflows.statements.update({ lockedTTL: null, lockToken: null }, { id, lockToken })
|
|
20
|
+
.catch(console.error);
|
|
21
|
+
}
|
|
22
|
+
renewLock = () => {
|
|
23
|
+
const { id, lockToken, lockedTTL } = this.item;
|
|
24
|
+
const now = DateTime.now;
|
|
25
|
+
if (DateTime.from(lockedTTL).msSinceEpoch < now.msSinceEpoch) {
|
|
26
|
+
this.ac.abort(new Error("Timed out"));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.context.workflows.statements.update({ lockedTTL: now.addSeconds(15) }, { id, lockToken })
|
|
30
|
+
.catch(console.error);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=WorkflowTask.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkflowTask.js","sourceRoot":"","sources":["../../src/workflows/WorkflowTask.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,OAAO,QAAQ,MAAM,sBAAsB,CAAC;AAG5C,MAAM,CAAC,OAAO,OAAO,YAAY;IAQT;IACA;IAPpB,KAAK,CAAe;IAEJ,MAAM,CAAC;IACf,EAAE,CAAkB;IAE5B,YACoB,IAAkB,EAClB,OAA0B;QAD1B,SAAI,GAAJ,IAAI,CAAc;QAClB,YAAO,GAAP,OAAO,CAAmB;QAE1C,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC/C,IAAI,CAAC,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC;IACjC,CAAC;IAED,CAAC,MAAM,CAAC,OAAO,CAAC;QACZ,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC;QACpC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAC,EAAE,EAAE,EAAE,EAAE,SAAS,EAAC,CAAC;aAC1F,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,SAAS,GAAG,GAAG,EAAE;QACb,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC;QAC/C,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC;QACzB,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,YAAY,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;YAC3D,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;YACtC,OAAO;QACX,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,EAAC,EAAE,EAAE,SAAS,EAAC,CAAC;aACvF,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC,CAAC;CACL"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,6 @@ import EntityAccessError from "../common/EntityAccessError.js";
|
|
|
4
4
|
import { IClassOf } from "../decorators/IClassOf.js";
|
|
5
5
|
import Inject, { RegisterSingleton, ServiceProvider, injectServiceKeysSymbol } from "../di/di.js";
|
|
6
6
|
import DateTime from "../types/DateTime.js";
|
|
7
|
-
import WorkflowStorage, { WorkflowItem } from "./WorkflowStorage.js";
|
|
8
7
|
import type Workflow from "./Workflow.js";
|
|
9
8
|
import { ActivitySuspendedError } from "./ActivitySuspendedError.js";
|
|
10
9
|
import { IWorkflowSchema, WorkflowRegistry } from "./WorkflowRegistry.js";
|
|
@@ -12,6 +11,8 @@ import crypto from "crypto";
|
|
|
12
11
|
import TimeSpan from "../types/TimeSpan.js";
|
|
13
12
|
import sleep from "../common/sleep.js";
|
|
14
13
|
import Waiter from "./Waiter.js";
|
|
14
|
+
import { WorkflowItem } from "./WorkflowDbContext.js";
|
|
15
|
+
import WorkflowStorage from "./WorkflowStorage.js";
|
|
15
16
|
|
|
16
17
|
export const runChildSymbol = Symbol("runChild");
|
|
17
18
|
|
|
@@ -310,7 +311,8 @@ export default class WorkflowContext {
|
|
|
310
311
|
// run...
|
|
311
312
|
for (const iterator of pending) {
|
|
312
313
|
try {
|
|
313
|
-
|
|
314
|
+
using l = iterator;
|
|
315
|
+
await this.run(iterator.item, iterator.signal);
|
|
314
316
|
} catch (error) {
|
|
315
317
|
console.error(error);
|
|
316
318
|
}
|
|
@@ -366,7 +368,7 @@ export default class WorkflowContext {
|
|
|
366
368
|
}
|
|
367
369
|
}
|
|
368
370
|
|
|
369
|
-
private async run(workflow: WorkflowItem) {
|
|
371
|
+
private async run(workflow: WorkflowItem, signal: AbortSignal) {
|
|
370
372
|
|
|
371
373
|
const clock = this.storage.clock;
|
|
372
374
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import Column from "../decorators/Column.js";
|
|
2
|
+
import Index from "../decorators/Index.js";
|
|
3
|
+
import { RelateTo } from "../decorators/Relate.js";
|
|
4
|
+
import Table from "../decorators/Table.js";
|
|
5
|
+
import { RegisterScoped } from "../di/di.js";
|
|
6
|
+
import EntityContext from "../model/EntityContext.js";
|
|
7
|
+
import DateTime from "../types/DateTime.js";
|
|
8
|
+
|
|
9
|
+
@Table("Workflows")
|
|
10
|
+
@Index({
|
|
11
|
+
name: "IX_Workflows_Group",
|
|
12
|
+
columns: [{ name: (x) => x.groupName, descending: false }],
|
|
13
|
+
filter: (x) => x.groupName !== null
|
|
14
|
+
})
|
|
15
|
+
@Index({
|
|
16
|
+
name: "IX_Workflows_taskGroup_ETA",
|
|
17
|
+
columns: [
|
|
18
|
+
{ name: (x) => x.eta, descending: false },
|
|
19
|
+
{ name: (x) => x.taskGroup, descending: false }
|
|
20
|
+
],
|
|
21
|
+
filter: (x) => x.isWorkflow === true
|
|
22
|
+
})
|
|
23
|
+
@Index({
|
|
24
|
+
name: "IX_Workflows_Throttle_Group",
|
|
25
|
+
columns: [
|
|
26
|
+
{ name: (x) => x.throttleGroup, descending: false },
|
|
27
|
+
{ name: (x) => x.queued, descending: false }
|
|
28
|
+
],
|
|
29
|
+
filter: (x) => x.isWorkflow === true && x.throttleGroup !== null
|
|
30
|
+
})
|
|
31
|
+
@Index({
|
|
32
|
+
name: "IX_Workflows_Parent_ID",
|
|
33
|
+
columns: [
|
|
34
|
+
{ name: (x) => x.parentID, descending: true }
|
|
35
|
+
],
|
|
36
|
+
filter: (x) => x.parentID !== null
|
|
37
|
+
})
|
|
38
|
+
export class WorkflowItem {
|
|
39
|
+
|
|
40
|
+
@Column({ dataType: "Char", length: 400, key: true })
|
|
41
|
+
public id: string;
|
|
42
|
+
|
|
43
|
+
@Column({ dataType: "Boolean" })
|
|
44
|
+
public isWorkflow: boolean;
|
|
45
|
+
|
|
46
|
+
@Column({ dataType: "Char", nullable: true })
|
|
47
|
+
public name: string;
|
|
48
|
+
|
|
49
|
+
@Column({ dataType: "Char", length: 200, nullable: true })
|
|
50
|
+
public groupName: string;
|
|
51
|
+
|
|
52
|
+
@Column({ dataType: "Char"})
|
|
53
|
+
public input: string;
|
|
54
|
+
|
|
55
|
+
@Column({ dataType: "Char", nullable: true})
|
|
56
|
+
public output: string;
|
|
57
|
+
|
|
58
|
+
@Column({ })
|
|
59
|
+
public eta: DateTime;
|
|
60
|
+
|
|
61
|
+
@Column({ })
|
|
62
|
+
public queued: DateTime;
|
|
63
|
+
|
|
64
|
+
@Column({ })
|
|
65
|
+
public updated: DateTime;
|
|
66
|
+
|
|
67
|
+
@Column({
|
|
68
|
+
dataType: "Char", length: 50,
|
|
69
|
+
default: () => `default`
|
|
70
|
+
})
|
|
71
|
+
public taskGroup: string;
|
|
72
|
+
|
|
73
|
+
@Column({
|
|
74
|
+
dataType: "Char", length: 200, nullable: true
|
|
75
|
+
})
|
|
76
|
+
public throttleGroup: string;
|
|
77
|
+
|
|
78
|
+
@Column({ dataType: "Int", default: () => 0})
|
|
79
|
+
public priority: number;
|
|
80
|
+
|
|
81
|
+
@Column({ nullable: true })
|
|
82
|
+
public lockedTTL: DateTime;
|
|
83
|
+
|
|
84
|
+
@Column({ nullable: true })
|
|
85
|
+
public lockToken: string;
|
|
86
|
+
|
|
87
|
+
@Column({ dataType: "AsciiChar", length: 10})
|
|
88
|
+
public state: "queued" | "failed" | "done";
|
|
89
|
+
|
|
90
|
+
@Column({ dataType: "Char", nullable: true})
|
|
91
|
+
public error: string;
|
|
92
|
+
|
|
93
|
+
@Column({ dataType: "Char", nullable: true})
|
|
94
|
+
public extra: string;
|
|
95
|
+
|
|
96
|
+
@Column({ dataType: "Char", length: 400 , nullable: true})
|
|
97
|
+
@RelateTo(WorkflowItem, {
|
|
98
|
+
property: (x) => x.parent,
|
|
99
|
+
inverseProperty: (x) => x.children,
|
|
100
|
+
foreignKeyConstraint: {
|
|
101
|
+
name: "FC_Workflows_Parent_ID",
|
|
102
|
+
cascade: "delete"
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
public parentID: string;
|
|
106
|
+
|
|
107
|
+
@Column({ dataType: "Char", length: 400 , nullable: true})
|
|
108
|
+
public lastID: string;
|
|
109
|
+
|
|
110
|
+
parent: WorkflowItem;
|
|
111
|
+
children: WorkflowItem[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@RegisterScoped
|
|
115
|
+
export class WorkflowDbContext extends EntityContext {
|
|
116
|
+
|
|
117
|
+
public workflows = this.model.register(WorkflowItem);
|
|
118
|
+
|
|
119
|
+
verifyFilters: boolean = false;
|
|
120
|
+
|
|
121
|
+
raiseEvents: boolean = false;
|
|
122
|
+
|
|
123
|
+
}
|
|
@@ -1,134 +1,18 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
import { randomUUID } from "crypto";
|
|
3
|
-
import
|
|
4
|
-
import Index from "../decorators/Index.js";
|
|
5
|
-
import Table from "../decorators/Table.js";
|
|
6
|
-
import Inject, { RegisterScoped, RegisterSingleton, ServiceProvider } from "../di/di.js";
|
|
3
|
+
import Inject, { RegisterSingleton } from "../di/di.js";
|
|
7
4
|
import { BaseDriver } from "../drivers/base/BaseDriver.js";
|
|
8
|
-
import
|
|
9
|
-
import { BinaryExpression, CallExpression, Expression, NullExpression, NumberLiteral, UpdateStatement } from "../query/ast/Expressions.js";
|
|
5
|
+
import { CallExpression, Expression, NullExpression, NumberLiteral, UpdateStatement } from "../query/ast/Expressions.js";
|
|
10
6
|
import DateTime from "../types/DateTime.js";
|
|
11
7
|
import WorkflowClock from "./WorkflowClock.js";
|
|
12
8
|
import RawQuery from "../compiler/RawQuery.js";
|
|
13
|
-
import {
|
|
9
|
+
import { WorkflowDbContext, WorkflowItem } from "./WorkflowDbContext.js";
|
|
10
|
+
import WorkflowTask from "./WorkflowTask.js";
|
|
11
|
+
import Sql from "../sql/Sql.js";
|
|
14
12
|
|
|
15
13
|
const loadedFromDb = Symbol("loadedFromDB");
|
|
16
14
|
|
|
17
|
-
@Table("Workflows")
|
|
18
|
-
@Index({
|
|
19
|
-
name: "IX_Workflows_Group",
|
|
20
|
-
columns: [{ name: (x) => x.groupName, descending: false }],
|
|
21
|
-
filter: (x) => x.groupName !== null
|
|
22
|
-
})
|
|
23
|
-
@Index({
|
|
24
|
-
name: "IX_Workflows_taskGroup_ETA",
|
|
25
|
-
columns: [
|
|
26
|
-
{ name: (x) => x.eta, descending: false },
|
|
27
|
-
{ name: (x) => x.taskGroup, descending: false }
|
|
28
|
-
],
|
|
29
|
-
filter: (x) => x.isWorkflow === true
|
|
30
|
-
})
|
|
31
|
-
@Index({
|
|
32
|
-
name: "IX_Workflows_Throttle_Group",
|
|
33
|
-
columns: [
|
|
34
|
-
{ name: (x) => x.throttleGroup, descending: false },
|
|
35
|
-
{ name: (x) => x.queued, descending: false }
|
|
36
|
-
],
|
|
37
|
-
filter: (x) => x.isWorkflow === true && x.throttleGroup !== null
|
|
38
|
-
})
|
|
39
|
-
@Index({
|
|
40
|
-
name: "IX_Workflows_Parent_ID",
|
|
41
|
-
columns: [
|
|
42
|
-
{ name: (x) => x.parentID, descending: true }
|
|
43
|
-
],
|
|
44
|
-
filter: (x) => x.parentID !== null
|
|
45
|
-
})
|
|
46
|
-
export class WorkflowItem {
|
|
47
15
|
|
|
48
|
-
@Column({ dataType: "Char", length: 400, key: true })
|
|
49
|
-
public id: string;
|
|
50
|
-
|
|
51
|
-
@Column({ dataType: "Boolean" })
|
|
52
|
-
public isWorkflow: boolean;
|
|
53
|
-
|
|
54
|
-
@Column({ dataType: "Char", nullable: true })
|
|
55
|
-
public name: string;
|
|
56
|
-
|
|
57
|
-
@Column({ dataType: "Char", length: 200, nullable: true })
|
|
58
|
-
public groupName: string;
|
|
59
|
-
|
|
60
|
-
@Column({ dataType: "Char"})
|
|
61
|
-
public input: string;
|
|
62
|
-
|
|
63
|
-
@Column({ dataType: "Char", nullable: true})
|
|
64
|
-
public output: string;
|
|
65
|
-
|
|
66
|
-
@Column({ })
|
|
67
|
-
public eta: DateTime;
|
|
68
|
-
|
|
69
|
-
@Column({ })
|
|
70
|
-
public queued: DateTime;
|
|
71
|
-
|
|
72
|
-
@Column({ })
|
|
73
|
-
public updated: DateTime;
|
|
74
|
-
|
|
75
|
-
@Column({
|
|
76
|
-
dataType: "Char", length: 50,
|
|
77
|
-
default: () => `default`
|
|
78
|
-
})
|
|
79
|
-
public taskGroup: string;
|
|
80
|
-
|
|
81
|
-
@Column({
|
|
82
|
-
dataType: "Char", length: 200, nullable: true
|
|
83
|
-
})
|
|
84
|
-
public throttleGroup: string;
|
|
85
|
-
|
|
86
|
-
@Column({ dataType: "Int", default: () => 0})
|
|
87
|
-
public priority: number;
|
|
88
|
-
|
|
89
|
-
@Column({ nullable: true })
|
|
90
|
-
public lockedTTL: DateTime;
|
|
91
|
-
|
|
92
|
-
@Column({ nullable: true })
|
|
93
|
-
public lockToken: string;
|
|
94
|
-
|
|
95
|
-
@Column({ dataType: "AsciiChar", length: 10})
|
|
96
|
-
public state: "queued" | "failed" | "done";
|
|
97
|
-
|
|
98
|
-
@Column({ dataType: "Char", nullable: true})
|
|
99
|
-
public error: string;
|
|
100
|
-
|
|
101
|
-
@Column({ dataType: "Char", nullable: true})
|
|
102
|
-
public extra: string;
|
|
103
|
-
|
|
104
|
-
@Column({ dataType: "Char", length: 400 , nullable: true})
|
|
105
|
-
@RelateTo(WorkflowItem, {
|
|
106
|
-
property: (x) => x.parent,
|
|
107
|
-
inverseProperty: (x) => x.children,
|
|
108
|
-
foreignKeyConstraint: {
|
|
109
|
-
name: "FC_Workflows_Parent_ID",
|
|
110
|
-
cascade: "delete"
|
|
111
|
-
}
|
|
112
|
-
})
|
|
113
|
-
public parentID: string;
|
|
114
|
-
|
|
115
|
-
@Column({ dataType: "Char", length: 400 , nullable: true})
|
|
116
|
-
public lastID: string;
|
|
117
|
-
|
|
118
|
-
parent: WorkflowItem;
|
|
119
|
-
children: WorkflowItem[];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
@RegisterScoped
|
|
123
|
-
class WorkflowContext extends EntityContext {
|
|
124
|
-
|
|
125
|
-
public workflows = this.model.register(WorkflowItem);
|
|
126
|
-
|
|
127
|
-
verifyFilters: boolean = false;
|
|
128
|
-
|
|
129
|
-
raiseEvents: boolean = false;
|
|
130
|
-
|
|
131
|
-
}
|
|
132
16
|
|
|
133
17
|
@RegisterSingleton
|
|
134
18
|
export default class WorkflowStorage {
|
|
@@ -146,7 +30,7 @@ export default class WorkflowStorage {
|
|
|
146
30
|
|
|
147
31
|
async getNextEta(throttle: { group: string, maxPerSecond: number }) {
|
|
148
32
|
|
|
149
|
-
const db = new
|
|
33
|
+
const db = new WorkflowDbContext(this.driver);
|
|
150
34
|
const last = await db.workflows.where(throttle, (p) => (x) => x.throttleGroup === p.group
|
|
151
35
|
&& x.isWorkflow === true)
|
|
152
36
|
.orderByDescending(void 0, (p) => (x) => x.queued)
|
|
@@ -163,7 +47,7 @@ export default class WorkflowStorage {
|
|
|
163
47
|
}
|
|
164
48
|
|
|
165
49
|
async getWorkflow(id: string) {
|
|
166
|
-
const db = new
|
|
50
|
+
const db = new WorkflowDbContext(this.driver);
|
|
167
51
|
const r = await db.workflows.statements.select({}, { id, isWorkflow: true });
|
|
168
52
|
if (r) {
|
|
169
53
|
return {
|
|
@@ -188,7 +72,7 @@ export default class WorkflowStorage {
|
|
|
188
72
|
|
|
189
73
|
|
|
190
74
|
async getAny(id: string) {
|
|
191
|
-
const db = new
|
|
75
|
+
const db = new WorkflowDbContext(this.driver);
|
|
192
76
|
const r = await db.workflows.statements.select({}, { id });
|
|
193
77
|
if (r) {
|
|
194
78
|
return {
|
|
@@ -210,7 +94,7 @@ export default class WorkflowStorage {
|
|
|
210
94
|
}
|
|
211
95
|
|
|
212
96
|
async extra(id, text?) {
|
|
213
|
-
const db = new
|
|
97
|
+
const db = new WorkflowDbContext(this.driver);
|
|
214
98
|
if (text) {
|
|
215
99
|
// save..
|
|
216
100
|
await db.workflows.statements.update({ extra: text }, { id });
|
|
@@ -229,7 +113,7 @@ export default class WorkflowStorage {
|
|
|
229
113
|
* @returns true if all items are deleted
|
|
230
114
|
*/
|
|
231
115
|
async delete(id) {
|
|
232
|
-
const db = new
|
|
116
|
+
const db = new WorkflowDbContext(this.driver);
|
|
233
117
|
await db.workflows.where({ id}, (p) => (x) => x.parentID === p.id)
|
|
234
118
|
.limit(100)
|
|
235
119
|
.delete({ id }, (p) => (x) => x.parentID === p.id);
|
|
@@ -242,7 +126,7 @@ export default class WorkflowStorage {
|
|
|
242
126
|
}
|
|
243
127
|
|
|
244
128
|
async save(state: Partial<WorkflowItem>) {
|
|
245
|
-
const db = new
|
|
129
|
+
const db = new WorkflowDbContext(this.driver);
|
|
246
130
|
const connection = db.connection;
|
|
247
131
|
await using tx = await connection.createTransaction();
|
|
248
132
|
state.state ||= "queued";
|
|
@@ -260,7 +144,7 @@ export default class WorkflowStorage {
|
|
|
260
144
|
}
|
|
261
145
|
|
|
262
146
|
async dequeue(taskGroup: string, signal?: AbortSignal) {
|
|
263
|
-
const db = new
|
|
147
|
+
const db = new WorkflowDbContext(this.driver);
|
|
264
148
|
const now = this.clock.utcNow;
|
|
265
149
|
|
|
266
150
|
if(!this.lockQuery) {
|
|
@@ -281,7 +165,7 @@ export default class WorkflowStorage {
|
|
|
281
165
|
Expression.assign(
|
|
282
166
|
Expression.identifier(lockTTLField),
|
|
283
167
|
CallExpression.create({
|
|
284
|
-
callee: Expression.identifier("Sql.date.
|
|
168
|
+
callee: Expression.identifier("Sql.date.addSeconds"),
|
|
285
169
|
arguments: [CallExpression.create({
|
|
286
170
|
callee: Expression.identifier("Sql.date.now")
|
|
287
171
|
}),
|
|
@@ -312,6 +196,8 @@ export default class WorkflowStorage {
|
|
|
312
196
|
|
|
313
197
|
const q = this.lockQuery;
|
|
314
198
|
|
|
199
|
+
const uuid = randomUUID();
|
|
200
|
+
|
|
315
201
|
const items = await db.workflows
|
|
316
202
|
.where({now, taskGroup}, (p) => (x) => x.eta <= p.now
|
|
317
203
|
&& ( x.lockedTTL === null
|
|
@@ -323,26 +209,15 @@ export default class WorkflowStorage {
|
|
|
323
209
|
.thenBy({}, (p) => (x) => x.priority)
|
|
324
210
|
.limit(20)
|
|
325
211
|
.withSignal(signal)
|
|
326
|
-
.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
iterator.lockToken = uuid;
|
|
332
|
-
try {
|
|
333
|
-
const r = await q.invoke(db.connection, iterator);
|
|
334
|
-
if (r.updated > 0) {
|
|
335
|
-
list.push(iterator);
|
|
336
|
-
}
|
|
337
|
-
} catch (error) {
|
|
338
|
-
console.error(error);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
return list;
|
|
212
|
+
.updateSelect({ uuid}, (p) => (x) => ({
|
|
213
|
+
lockedTTL: Sql.date.addSeconds(Sql.date.now(), 15),
|
|
214
|
+
lockToken: p.uuid
|
|
215
|
+
}));
|
|
216
|
+
return items.map((x) => new WorkflowTask(x, db));
|
|
342
217
|
}
|
|
343
218
|
|
|
344
219
|
async seed(version?) {
|
|
345
|
-
const db = new
|
|
220
|
+
const db = new WorkflowDbContext(this.driver);
|
|
346
221
|
await db.connection.ensureDatabase();
|
|
347
222
|
await db.connection.automaticMigrations().migrate(db, { version, name: "workflows" });
|
|
348
223
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import DateTime from "../types/DateTime.js";
|
|
3
|
+
import type { WorkflowDbContext, WorkflowItem } from "./WorkflowDbContext.js";
|
|
4
|
+
|
|
5
|
+
export default class WorkflowTask implements Disposable {
|
|
6
|
+
|
|
7
|
+
timer: NodeJS.Timer;
|
|
8
|
+
|
|
9
|
+
public readonly signal;
|
|
10
|
+
private ac: AbortController;
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
public readonly item: WorkflowItem,
|
|
14
|
+
public readonly context: WorkflowDbContext,
|
|
15
|
+
) {
|
|
16
|
+
this.timer = setInterval(this.renewLock, 1000);
|
|
17
|
+
this.ac = new AbortController();
|
|
18
|
+
this.signal = this.ac.signal;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
[Symbol.dispose]() {
|
|
22
|
+
clearInterval(this.timer);
|
|
23
|
+
const { id, lockToken } = this.item;
|
|
24
|
+
this.context.workflows.statements.update({ lockedTTL: null, lockToken: null}, { id, lockToken})
|
|
25
|
+
.catch(console.error);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
renewLock = () => {
|
|
29
|
+
const { id, lockToken, lockedTTL } = this.item;
|
|
30
|
+
const now = DateTime.now;
|
|
31
|
+
if (DateTime.from(lockedTTL).msSinceEpoch < now.msSinceEpoch) {
|
|
32
|
+
this.ac.abort(new Error("Timed out"));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.context.workflows.statements.update({ lockedTTL: now.addSeconds(15) }, {id, lockToken})
|
|
37
|
+
.catch(console.error);
|
|
38
|
+
};
|
|
39
|
+
}
|