@entity-access/entity-access 1.0.95 → 1.0.96
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/.vscode/launch.json +3 -0
- package/dist/compiler/QueryCompiler.d.ts +2 -0
- package/dist/compiler/QueryCompiler.d.ts.map +1 -1
- package/dist/compiler/QueryCompiler.js +6 -0
- package/dist/compiler/QueryCompiler.js.map +1 -1
- package/dist/compiler/RawQuery.d.ts +10 -0
- package/dist/compiler/RawQuery.d.ts.map +1 -0
- package/dist/compiler/RawQuery.js +26 -0
- package/dist/compiler/RawQuery.js.map +1 -0
- package/dist/compiler/postgres/PostgreSqlMethodTransformer.d.ts.map +1 -1
- package/dist/compiler/postgres/PostgreSqlMethodTransformer.js +3 -0
- package/dist/compiler/postgres/PostgreSqlMethodTransformer.js.map +1 -1
- package/dist/compiler/sql-server/SqlServerSqlMethodTransformer.d.ts.map +1 -1
- package/dist/compiler/sql-server/SqlServerSqlMethodTransformer.js +9 -6
- package/dist/compiler/sql-server/SqlServerSqlMethodTransformer.js.map +1 -1
- package/dist/drivers/base/BaseDriver.d.ts +15 -8
- package/dist/drivers/base/BaseDriver.d.ts.map +1 -1
- package/dist/drivers/base/BaseDriver.js +13 -3
- package/dist/drivers/base/BaseDriver.js.map +1 -1
- package/dist/drivers/postgres/PostgreSqlDriver.d.ts +2 -12
- package/dist/drivers/postgres/PostgreSqlDriver.d.ts.map +1 -1
- package/dist/drivers/postgres/PostgreSqlDriver.js +11 -2
- package/dist/drivers/postgres/PostgreSqlDriver.js.map +1 -1
- package/dist/drivers/sql-server/SqlServerDriver.d.ts +8 -2
- package/dist/drivers/sql-server/SqlServerDriver.d.ts.map +1 -1
- package/dist/drivers/sql-server/SqlServerDriver.js +18 -10
- package/dist/drivers/sql-server/SqlServerDriver.js.map +1 -1
- package/dist/eternity/ActivitySuspendedError.d.ts +3 -0
- package/dist/eternity/ActivitySuspendedError.d.ts.map +1 -1
- package/dist/eternity/ActivitySuspendedError.js +6 -0
- package/dist/eternity/ActivitySuspendedError.js.map +1 -1
- package/dist/eternity/EternityContext.d.ts +3 -1
- package/dist/eternity/EternityContext.d.ts.map +1 -1
- package/dist/eternity/EternityContext.js +54 -6
- package/dist/eternity/EternityContext.js.map +1 -1
- package/dist/eternity/EternityStorage.d.ts +6 -0
- package/dist/eternity/EternityStorage.d.ts.map +1 -1
- package/dist/eternity/EternityStorage.js +60 -19
- package/dist/eternity/EternityStorage.js.map +1 -1
- package/dist/eternity/Workflow.d.ts +15 -4
- package/dist/eternity/Workflow.d.ts.map +1 -1
- package/dist/eternity/Workflow.js +35 -22
- package/dist/eternity/Workflow.js.map +1 -1
- package/dist/migrations/postgres/PostgresAutomaticMigrations.d.ts +3 -3
- package/dist/migrations/postgres/PostgresAutomaticMigrations.d.ts.map +1 -1
- package/dist/migrations/postgres/PostgresAutomaticMigrations.js +2 -2
- package/dist/migrations/postgres/PostgresAutomaticMigrations.js.map +1 -1
- package/dist/migrations/sql-server/SqlServerAutomaticMigrations.d.ts +3 -3
- package/dist/migrations/sql-server/SqlServerAutomaticMigrations.d.ts.map +1 -1
- package/dist/migrations/sql-server/SqlServerAutomaticMigrations.js +2 -2
- package/dist/migrations/sql-server/SqlServerAutomaticMigrations.js.map +1 -1
- package/dist/model/EntityContext.d.ts +3 -1
- package/dist/model/EntityContext.d.ts.map +1 -1
- package/dist/model/EntityContext.js +6 -2
- package/dist/model/EntityContext.js.map +1 -1
- package/dist/model/EntityQuery.js +3 -3
- package/dist/model/EntityQuery.js.map +1 -1
- package/dist/model/verification/VerificationSession.js +1 -1
- package/dist/model/verification/VerificationSession.js.map +1 -1
- package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
- package/dist/query/ast/ExpressionToSql.js +21 -19
- package/dist/query/ast/ExpressionToSql.js.map +1 -1
- package/dist/query/ast/Expressions.d.ts +1 -0
- package/dist/query/ast/Expressions.d.ts.map +1 -1
- package/dist/query/ast/Expressions.js +3 -0
- package/dist/query/ast/Expressions.js.map +1 -1
- package/dist/sql/ISql.d.ts +2 -0
- package/dist/sql/ISql.d.ts.map +1 -1
- package/dist/tests/drivers/postgres/connection-test.js +1 -1
- package/dist/tests/drivers/postgres/connection-test.js.map +1 -1
- package/dist/tests/eternity/child-tests.d.ts +3 -0
- package/dist/tests/eternity/child-tests.d.ts.map +1 -0
- package/dist/tests/eternity/child-tests.js +103 -0
- package/dist/tests/eternity/child-tests.js.map +1 -0
- package/dist/tests/eternity/eternity-tests.d.ts.map +1 -1
- package/dist/tests/eternity/eternity-tests.js.map +1 -1
- package/dist/tests/model/createContext.js +2 -2
- package/dist/tests/model/createContext.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/compiler/QueryCompiler.ts +7 -0
- package/src/compiler/RawQuery.ts +29 -0
- package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +3 -0
- package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +9 -6
- package/src/drivers/base/BaseDriver.ts +28 -10
- package/src/drivers/postgres/PostgreSqlDriver.ts +15 -2
- package/src/drivers/sql-server/SqlServerDriver.ts +21 -11
- package/src/eternity/ActivitySuspendedError.ts +8 -1
- package/src/eternity/EternityContext.ts +69 -6
- package/src/eternity/EternityStorage.ts +79 -19
- package/src/eternity/Workflow.ts +51 -11
- package/src/migrations/postgres/PostgresAutomaticMigrations.ts +5 -5
- package/src/migrations/sql-server/SqlServerAutomaticMigrations.ts +5 -5
- package/src/model/EntityContext.ts +9 -3
- package/src/model/EntityQuery.ts +3 -3
- package/src/model/verification/VerificationSession.ts +1 -1
- package/src/query/ast/ExpressionToSql.ts +22 -20
- package/src/query/ast/Expressions.ts +4 -0
- package/src/sql/ISql.ts +3 -0
- package/src/tests/drivers/postgres/connection-test.ts +1 -1
- package/src/tests/eternity/child-tests.ts +119 -0
- package/src/tests/eternity/eternity-tests.ts +1 -0
- package/src/tests/model/createContext.ts +2 -2
|
@@ -33,23 +33,26 @@ export const SqlServerSqlHelper: ISqlHelpers = {
|
|
|
33
33
|
},
|
|
34
34
|
},
|
|
35
35
|
date: {
|
|
36
|
+
now() {
|
|
37
|
+
return prepareAny `GETUTCDATE()`;
|
|
38
|
+
},
|
|
36
39
|
addDays(d, n) {
|
|
37
|
-
return prepareAny `DateAdd(DAY, ${
|
|
40
|
+
return prepareAny `DateAdd(DAY, ${n}, ${d})`;
|
|
38
41
|
},
|
|
39
42
|
addHours(d, n) {
|
|
40
|
-
return prepareAny `DateAdd(HOUR, ${
|
|
43
|
+
return prepareAny `DateAdd(HOUR, ${n}, ${d})`;
|
|
41
44
|
},
|
|
42
45
|
addMinutes(d, n) {
|
|
43
|
-
return prepareAny `DateAdd(MINUTE, ${
|
|
46
|
+
return prepareAny `DateAdd(MINUTE, ${n}, ${d})`;
|
|
44
47
|
},
|
|
45
48
|
addMonths(d, n) {
|
|
46
|
-
return prepareAny `DateAdd(MONTH, ${
|
|
49
|
+
return prepareAny `DateAdd(MONTH, ${n}, ${d})`;
|
|
47
50
|
},
|
|
48
51
|
addSeconds(d, n) {
|
|
49
|
-
return prepareAny `DateAdd(SECOND, ${
|
|
52
|
+
return prepareAny `DateAdd(SECOND, ${n}, ${d})`;
|
|
50
53
|
},
|
|
51
54
|
addYears(d, n) {
|
|
52
|
-
return prepareAny `DateAdd(YEAR, ${
|
|
55
|
+
return prepareAny `DateAdd(YEAR, ${n}, ${d})`;
|
|
53
56
|
},
|
|
54
57
|
dayOf(d) {
|
|
55
58
|
return prepareAny `DATE_PART(day, ${d})`;
|
|
@@ -53,19 +53,33 @@ export interface IBaseTransaction {
|
|
|
53
53
|
dispose(): Promise<any>;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
export abstract class
|
|
57
|
-
|
|
56
|
+
export abstract class BaseConnection {
|
|
57
|
+
|
|
58
|
+
protected compiler: QueryCompiler;
|
|
59
|
+
|
|
60
|
+
protected connectionString: IDbConnectionString;
|
|
58
61
|
|
|
59
62
|
private currentTransaction: IBaseTransaction;
|
|
60
63
|
|
|
61
|
-
|
|
64
|
+
|
|
65
|
+
constructor(public driver: BaseDriver) {
|
|
66
|
+
this.compiler = driver.compiler;
|
|
67
|
+
this.connectionString = driver.connectionString;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public abstract ensureDatabase(): Promise<any>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* This migrations only support creation of missing items.
|
|
74
|
+
* However, you can provide events to change existing items.
|
|
75
|
+
*/
|
|
76
|
+
public abstract automaticMigrations(): Migrations;
|
|
77
|
+
|
|
62
78
|
|
|
63
79
|
public abstract executeReader(command: IQuery, signal?: AbortSignal): Promise<IDbReader>;
|
|
64
80
|
|
|
65
81
|
public abstract executeQuery(command: IQuery, signal?: AbortSignal): Promise<IQueryResult>;
|
|
66
82
|
|
|
67
|
-
public abstract ensureDatabase(): Promise<any>;
|
|
68
|
-
|
|
69
83
|
public abstract createTransaction(): Promise<IBaseTransaction>;
|
|
70
84
|
|
|
71
85
|
public async runInTransaction<T = any>(fx?: () => Promise<T>) {
|
|
@@ -90,12 +104,16 @@ export abstract class BaseDriver {
|
|
|
90
104
|
this.currentTransaction = null;
|
|
91
105
|
}
|
|
92
106
|
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export abstract class BaseDriver {
|
|
110
|
+
abstract get compiler(): QueryCompiler;
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
constructor(public readonly connectionString: IDbConnectionString) {}
|
|
114
|
+
|
|
115
|
+
abstract newConnection(): BaseConnection;
|
|
93
116
|
|
|
94
|
-
/**
|
|
95
|
-
* This migrations only support creation of missing items.
|
|
96
|
-
* However, you can provide events to change existing items.
|
|
97
|
-
*/
|
|
98
|
-
public abstract automaticMigrations(): Migrations;
|
|
99
117
|
|
|
100
118
|
createInsertExpression(type: EntityType, entity: any): InsertStatement {
|
|
101
119
|
const returnFields = [] as Identifier[];
|
|
@@ -5,7 +5,7 @@ import TimedCache from "../../common/cache/TimedCache.js";
|
|
|
5
5
|
import QueryCompiler from "../../compiler/QueryCompiler.js";
|
|
6
6
|
import Migrations from "../../migrations/Migrations.js";
|
|
7
7
|
import PostgresAutomaticMigrations from "../../migrations/postgres/PostgresAutomaticMigrations.js";
|
|
8
|
-
import { BaseDriver, IBaseTransaction, IDbConnectionString, IDbReader, IQuery, IRecord, toQuery } from "../base/BaseDriver.js";
|
|
8
|
+
import { BaseConnection, BaseDriver, IBaseTransaction, IDbConnectionString, IDbReader, IQuery, IRecord, toQuery } from "../base/BaseDriver.js";
|
|
9
9
|
import pg from "pg";
|
|
10
10
|
import Cursor from "pg-cursor";
|
|
11
11
|
export interface IPgSqlConnectionString extends IDbConnectionString {
|
|
@@ -66,13 +66,25 @@ export default class PostgreSqlDriver extends BaseDriver {
|
|
|
66
66
|
return this.myCompiler;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
private transaction: IPooledObject<pg.Client>;
|
|
70
69
|
private myCompiler = new QueryCompiler();
|
|
71
70
|
|
|
72
71
|
constructor(private readonly config: IPgSqlConnectionString) {
|
|
73
72
|
super(config);
|
|
74
73
|
}
|
|
75
74
|
|
|
75
|
+
newConnection(): BaseConnection {
|
|
76
|
+
return new PostgreSqlConnection(this);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class PostgreSqlConnection extends BaseConnection {
|
|
81
|
+
|
|
82
|
+
private transaction: IPooledObject<pg.Client>;
|
|
83
|
+
|
|
84
|
+
private get config() {
|
|
85
|
+
return this.connectionString;
|
|
86
|
+
}
|
|
87
|
+
|
|
76
88
|
public async createTransaction(): Promise<IBaseTransaction> {
|
|
77
89
|
const tx = await this.getConnection();
|
|
78
90
|
await tx.query("BEGIN");
|
|
@@ -102,6 +114,7 @@ export default class PostgreSqlDriver extends BaseDriver {
|
|
|
102
114
|
const q = toQuery(command);
|
|
103
115
|
text = q.text;
|
|
104
116
|
const result = await connection.query(q.text, q.values);
|
|
117
|
+
(result as any).updated = result.rowCount;
|
|
105
118
|
return result;
|
|
106
119
|
} catch (error) {
|
|
107
120
|
throw new Error(`Failed executing ${text}\n${error}`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
import QueryCompiler from "../../compiler/QueryCompiler.js";
|
|
3
3
|
import Migrations from "../../migrations/Migrations.js";
|
|
4
|
-
import { BaseDriver, IBaseTransaction, IDbConnectionString, IDbReader, IQuery, IRecord, disposableSymbol, toQuery } from "../base/BaseDriver.js";
|
|
4
|
+
import { BaseConnection, BaseDriver, IBaseTransaction, IDbConnectionString, IDbReader, IQuery, IRecord, disposableSymbol, toQuery } from "../base/BaseDriver.js";
|
|
5
5
|
import sql from "mssql";
|
|
6
6
|
import SqlServerQueryCompiler from "./SqlServerQueryCompiler.js";
|
|
7
7
|
import SqlServerAutomaticMigrations from "../../migrations/sql-server/SqlServerAutomaticMigrations.js";
|
|
@@ -19,13 +19,29 @@ export default class SqlServerDriver extends BaseDriver {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
private sqlQueryCompiler = new SqlServerQueryCompiler();
|
|
22
|
-
private transaction: sql.Transaction;
|
|
23
22
|
|
|
24
23
|
constructor(private readonly config: ISqlServerConnectionString) {
|
|
25
24
|
super(config);
|
|
26
25
|
config.server = config.host;
|
|
27
26
|
}
|
|
28
27
|
|
|
28
|
+
newConnection(): BaseConnection {
|
|
29
|
+
return new SqlServerConnection(this, this.config);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class SqlServerConnection extends BaseConnection {
|
|
34
|
+
|
|
35
|
+
private transaction: sql.Transaction;
|
|
36
|
+
|
|
37
|
+
private get sqlQueryCompiler() {
|
|
38
|
+
return this.compiler as SqlServerQueryCompiler;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
constructor(driver, private config: ISqlServerConnectionString) {
|
|
42
|
+
super(driver);
|
|
43
|
+
}
|
|
44
|
+
|
|
29
45
|
public async executeReader(command: IQuery, signal?: AbortSignal): Promise<IDbReader> {
|
|
30
46
|
command = toQuery(command);
|
|
31
47
|
let rq = await this.newRequest();
|
|
@@ -64,15 +80,10 @@ export default class SqlServerDriver extends BaseDriver {
|
|
|
64
80
|
|
|
65
81
|
public ensureDatabase() {
|
|
66
82
|
const create = async () => {
|
|
67
|
-
const
|
|
68
|
-
|
|
83
|
+
const config = { ... this.config, database: "master" };
|
|
69
84
|
const db = this.config.database;
|
|
70
|
-
this.config.database = defaultDb;
|
|
71
85
|
|
|
72
|
-
const connection = await this.
|
|
73
|
-
// @ts-expect-error readonly
|
|
74
|
-
this.config = { ... this.config };
|
|
75
|
-
this.config.database = db;
|
|
86
|
+
const connection = await this.newConnection(config);
|
|
76
87
|
|
|
77
88
|
const createSql = `IF NOT EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name = ${SqlServerLiteral.escapeLiteral(db)}) BEGIN
|
|
78
89
|
CREATE DATABASE ${db};
|
|
@@ -137,8 +148,7 @@ export default class SqlServerDriver extends BaseDriver {
|
|
|
137
148
|
return (await this.newConnection()).request();
|
|
138
149
|
}
|
|
139
150
|
|
|
140
|
-
private newConnection() {
|
|
141
|
-
const config = this.config;
|
|
151
|
+
private newConnection(config = this.config) {
|
|
142
152
|
const key = config.server + "//" + config.database + "/" + config.user;
|
|
143
153
|
return namedPool.getOrCreateAsync(config.server + "://" + config.database,
|
|
144
154
|
() => {
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import TimeSpan from "../types/TimeSpan.js";
|
|
1
2
|
|
|
2
3
|
|
|
3
|
-
export class ActivitySuspendedError extends Error {
|
|
4
|
+
export class ActivitySuspendedError extends Error {
|
|
5
|
+
|
|
6
|
+
constructor(public ttl: TimeSpan = TimeSpan.fromDays(1)) {
|
|
7
|
+
super("Activity Suspended");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
}
|
|
@@ -40,6 +40,8 @@ function bindStep(context: EternityContext, store: WorkflowStorage, name: string
|
|
|
40
40
|
|
|
41
41
|
store.lastID = id;
|
|
42
42
|
|
|
43
|
+
let ttl = TimeSpan.fromSeconds(0);
|
|
44
|
+
|
|
43
45
|
const step: Partial<WorkflowStorage> = {
|
|
44
46
|
id,
|
|
45
47
|
parentID: this.id,
|
|
@@ -63,10 +65,13 @@ function bindStep(context: EternityContext, store: WorkflowStorage, name: string
|
|
|
63
65
|
const eta = this.currentTime.add(maxTS);
|
|
64
66
|
step.eta = eta;
|
|
65
67
|
|
|
68
|
+
ttl = maxTS;
|
|
69
|
+
|
|
66
70
|
if (eta <= start) {
|
|
67
71
|
// time is up...
|
|
68
72
|
lastResult = "";
|
|
69
73
|
step.state = "done";
|
|
74
|
+
ttl = TimeSpan.fromSeconds(0);
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
} else {
|
|
@@ -74,9 +79,11 @@ function bindStep(context: EternityContext, store: WorkflowStorage, name: string
|
|
|
74
79
|
try {
|
|
75
80
|
|
|
76
81
|
const types = old[injectServiceKeysSymbol] as any[];
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
if (types) {
|
|
83
|
+
for (let index = a.length; index < types.length; index++) {
|
|
84
|
+
const element = ServiceProvider.resolve(this, types[index]);
|
|
85
|
+
a.push(element);
|
|
86
|
+
}
|
|
80
87
|
}
|
|
81
88
|
lastResult = (await old.apply(this, a)) ?? 0;
|
|
82
89
|
step.output = JSON.stringify(lastResult);
|
|
@@ -101,7 +108,7 @@ function bindStep(context: EternityContext, store: WorkflowStorage, name: string
|
|
|
101
108
|
throw lastError;
|
|
102
109
|
}
|
|
103
110
|
if (step.state !== "done") {
|
|
104
|
-
throw new ActivitySuspendedError();
|
|
111
|
+
throw new ActivitySuspendedError(ttl);
|
|
105
112
|
}
|
|
106
113
|
return lastResult;
|
|
107
114
|
};
|
|
@@ -163,7 +170,12 @@ export default class EternityContext {
|
|
|
163
170
|
public async queue<T>(
|
|
164
171
|
type: IClassOf<Workflow<T>>,
|
|
165
172
|
input: Partial<T>,
|
|
166
|
-
{ id, throwIfExists, eta
|
|
173
|
+
{ id, throwIfExists, eta, parentID }: {
|
|
174
|
+
id?: string,
|
|
175
|
+
throwIfExists?: boolean,
|
|
176
|
+
eta?: DateTime,
|
|
177
|
+
parentID?: string
|
|
178
|
+
} = {}) {
|
|
167
179
|
const clock = this.storage.clock;
|
|
168
180
|
if (id) {
|
|
169
181
|
const r = await this.storage.get(id);
|
|
@@ -193,6 +205,7 @@ export default class EternityContext {
|
|
|
193
205
|
isWorkflow: true,
|
|
194
206
|
queued: now,
|
|
195
207
|
updated: now,
|
|
208
|
+
parentID,
|
|
196
209
|
eta
|
|
197
210
|
});
|
|
198
211
|
|
|
@@ -216,6 +229,32 @@ export default class EternityContext {
|
|
|
216
229
|
|
|
217
230
|
return pending.length;
|
|
218
231
|
}
|
|
232
|
+
|
|
233
|
+
async runChild(w: Workflow, type, input) {
|
|
234
|
+
|
|
235
|
+
// there might still be some workflows pending
|
|
236
|
+
// this will ensure even empty workflow !!
|
|
237
|
+
const schema = WorkflowRegistry.register(type, void 0);
|
|
238
|
+
|
|
239
|
+
const id = w.id + `-child(${schema.name},${JSON.stringify(input)})`;
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
const result = await this.storage.get(id);
|
|
243
|
+
if (result) {
|
|
244
|
+
const { state } = result;
|
|
245
|
+
if (state === "done") {
|
|
246
|
+
return JSON.parse(result.output);
|
|
247
|
+
}
|
|
248
|
+
if (state === "failed") {
|
|
249
|
+
throw new Error(result.error);
|
|
250
|
+
}
|
|
251
|
+
throw new ActivitySuspendedError();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
await this.queue(type, input, { id, parentID: w.id });
|
|
255
|
+
throw new ActivitySuspendedError();
|
|
256
|
+
}
|
|
257
|
+
|
|
219
258
|
private async run(workflow: WorkflowStorage) {
|
|
220
259
|
|
|
221
260
|
const clock = this.storage.clock;
|
|
@@ -235,7 +274,7 @@ export default class EternityContext {
|
|
|
235
274
|
const schema = WorkflowRegistry.getByName(workflow.name);
|
|
236
275
|
const { eta, id, updated } = workflow;
|
|
237
276
|
const input = JSON.parse(workflow.input);
|
|
238
|
-
const instance = new (schema.type)({ input, eta, id, currentTime: DateTime.from(updated) });
|
|
277
|
+
const instance = new (schema.type)({ input, eta, id, currentTime: DateTime.from(updated) }, this);
|
|
239
278
|
for (const iterator of schema.activities) {
|
|
240
279
|
instance[iterator] = bindStep(this, workflow, iterator, instance[iterator]);
|
|
241
280
|
}
|
|
@@ -251,6 +290,9 @@ export default class EternityContext {
|
|
|
251
290
|
} catch (error) {
|
|
252
291
|
if (error instanceof ActivitySuspendedError) {
|
|
253
292
|
// this will update last id...
|
|
293
|
+
workflow.eta = clock.utcNow.add(error.ttl);
|
|
294
|
+
workflow.lockedTTL = null;
|
|
295
|
+
workflow.lockToken = null;
|
|
254
296
|
await this.storage.save(workflow);
|
|
255
297
|
return;
|
|
256
298
|
}
|
|
@@ -260,8 +302,29 @@ export default class EternityContext {
|
|
|
260
302
|
workflow.eta = clock.utcNow.add(instance.failedPreserveTime);
|
|
261
303
|
}
|
|
262
304
|
|
|
305
|
+
// in case of child workflow...
|
|
306
|
+
// eta will be set to one year...
|
|
307
|
+
if (workflow.parentID) {
|
|
308
|
+
workflow.eta = clock.utcNow.addYears(1);
|
|
309
|
+
// since we have finished.. we should
|
|
310
|
+
// make parent's eta approach now sooner..
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
workflow.lockedTTL = null;
|
|
314
|
+
workflow.lockToken = null;
|
|
263
315
|
await this.storage.save(workflow);
|
|
264
316
|
|
|
317
|
+
if (workflow.parentID) {
|
|
318
|
+
const parent = await this.storage.get(workflow.parentID);
|
|
319
|
+
if (parent) {
|
|
320
|
+
parent.lockTTL = null;
|
|
321
|
+
parent.lockToken = null;
|
|
322
|
+
parent.eta = clock.utcNow;
|
|
323
|
+
await this.storage.save(parent);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// workflow finished successfully...
|
|
327
|
+
|
|
265
328
|
} finally {
|
|
266
329
|
scope.dispose();
|
|
267
330
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
1
2
|
import Column from "../decorators/Column.js";
|
|
2
3
|
import Index from "../decorators/Index.js";
|
|
3
4
|
import Table from "../decorators/Table.js";
|
|
4
5
|
import Inject, { RegisterScoped, RegisterSingleton, ServiceProvider } from "../di/di.js";
|
|
5
6
|
import { BaseDriver } from "../drivers/base/BaseDriver.js";
|
|
6
7
|
import EntityContext from "../model/EntityContext.js";
|
|
8
|
+
import { BinaryExpression, CallExpression, Expression, NullExpression, NumberLiteral, UpdateStatement } from "../query/ast/Expressions.js";
|
|
7
9
|
import DateTime from "../types/DateTime.js";
|
|
8
10
|
import WorkflowClock from "./WorkflowClock.js";
|
|
11
|
+
import RawQuery from "../compiler/RawQuery.js";
|
|
9
12
|
|
|
10
13
|
@Table("Workflows")
|
|
11
14
|
@Index({
|
|
@@ -53,6 +56,9 @@ export class WorkflowStorage {
|
|
|
53
56
|
@Column({ nullable: true })
|
|
54
57
|
public lockedTTL: DateTime;
|
|
55
58
|
|
|
59
|
+
@Column({ nullable: true })
|
|
60
|
+
public lockToken: string;
|
|
61
|
+
|
|
56
62
|
@Column({ dataType: "AsciiChar", length: 10})
|
|
57
63
|
public state: "queued" | "failed" | "done";
|
|
58
64
|
|
|
@@ -83,6 +89,8 @@ class WorkflowContext extends EntityContext {
|
|
|
83
89
|
@RegisterSingleton
|
|
84
90
|
export default class EternityStorage {
|
|
85
91
|
|
|
92
|
+
private lockQuery: RawQuery;
|
|
93
|
+
|
|
86
94
|
constructor(
|
|
87
95
|
@Inject
|
|
88
96
|
private driver: BaseDriver,
|
|
@@ -97,6 +105,10 @@ export default class EternityStorage {
|
|
|
97
105
|
const r = await db.workflows.where({ id }, (p) => (x) => x.id === p.id && x.isWorkflow === true).first();
|
|
98
106
|
if (r !== null) {
|
|
99
107
|
return {
|
|
108
|
+
id,
|
|
109
|
+
parentID: r.parentID,
|
|
110
|
+
lockTTL: r.lockedTTL,
|
|
111
|
+
lockToken: r.lockToken,
|
|
100
112
|
updated: r.updated,
|
|
101
113
|
eta: r.eta,
|
|
102
114
|
queued: r.queued,
|
|
@@ -131,7 +143,8 @@ export default class EternityStorage {
|
|
|
131
143
|
|
|
132
144
|
async save(state: Partial<WorkflowStorage>) {
|
|
133
145
|
const db = new WorkflowContext(this.driver);
|
|
134
|
-
|
|
146
|
+
const connection = db.connection;
|
|
147
|
+
await connection.runInTransaction(async () => {
|
|
135
148
|
let w = await db.workflows.where(state, (p) => (x) => x.id === p.id).first();
|
|
136
149
|
if (!w) {
|
|
137
150
|
w = db.workflows.add(state);
|
|
@@ -145,6 +158,7 @@ export default class EternityStorage {
|
|
|
145
158
|
}
|
|
146
159
|
|
|
147
160
|
w.state ||= "queued";
|
|
161
|
+
w.updated = DateTime.utcNow;
|
|
148
162
|
await db.saveChanges();
|
|
149
163
|
});
|
|
150
164
|
}
|
|
@@ -152,29 +166,75 @@ export default class EternityStorage {
|
|
|
152
166
|
async dequeue(signal?: AbortSignal) {
|
|
153
167
|
const db = new WorkflowContext(this.driver);
|
|
154
168
|
const now = this.clock.utcNow;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
.
|
|
166
|
-
|
|
167
|
-
|
|
169
|
+
|
|
170
|
+
if(!this.lockQuery) {
|
|
171
|
+
|
|
172
|
+
const type = db.model.getEntityType(WorkflowStorage);
|
|
173
|
+
|
|
174
|
+
const px = Expression.parameter("x");
|
|
175
|
+
const lockTokenField = type.getProperty("lockToken").field.columnName;
|
|
176
|
+
const lockTTLField = type.getProperty("lockedTTL").field.columnName;
|
|
177
|
+
|
|
178
|
+
const exp = UpdateStatement.create({
|
|
179
|
+
table: type.fullyQualifiedName,
|
|
180
|
+
set: [
|
|
181
|
+
Expression.assign(
|
|
182
|
+
Expression.identifier(lockTokenField),
|
|
183
|
+
Expression.member(px, "lockToken")
|
|
184
|
+
),
|
|
185
|
+
Expression.assign(
|
|
186
|
+
Expression.identifier(lockTTLField),
|
|
187
|
+
CallExpression.create({
|
|
188
|
+
callee: Expression.member(Expression.member(Expression.identifier("Sql"), "date"), "addMinutes"),
|
|
189
|
+
arguments: [CallExpression.create({
|
|
190
|
+
callee: Expression.member(Expression.member(Expression.identifier("Sql"), "date"), "now")
|
|
191
|
+
}),
|
|
192
|
+
NumberLiteral.create({ value: 5 })
|
|
193
|
+
]
|
|
194
|
+
})
|
|
195
|
+
)
|
|
196
|
+
],
|
|
197
|
+
where: Expression.logicalAnd(Expression.equal(
|
|
198
|
+
Expression.identifier("id"),
|
|
199
|
+
Expression.member(px, "id")
|
|
200
|
+
), Expression.equal(
|
|
201
|
+
Expression.identifier(lockTokenField),
|
|
202
|
+
NullExpression.create({})
|
|
203
|
+
))
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
this.lockQuery = this.driver.compiler.compileToRawQuery(null, exp, px);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const q = this.lockQuery;
|
|
210
|
+
|
|
211
|
+
const items = await db.workflows
|
|
212
|
+
.where({now}, (p) => (x) => x.eta <= p.now
|
|
213
|
+
&& (x.lockedTTL === null || x.lockedTTL <= p.now)
|
|
214
|
+
&& x.lockToken === null
|
|
215
|
+
&& x.isWorkflow === true)
|
|
216
|
+
.orderBy({}, (p) => (x) => x.eta)
|
|
217
|
+
.thenBy({}, (p) => (x) => x.priority)
|
|
218
|
+
.limit(20)
|
|
219
|
+
.withSignal(signal)
|
|
220
|
+
.toArray();
|
|
221
|
+
const list: WorkflowStorage[] = [];
|
|
222
|
+
const uuid = randomUUID();
|
|
223
|
+
for (const iterator of items) {
|
|
224
|
+
// try to acquire lock...
|
|
225
|
+
iterator.lockToken = uuid;
|
|
226
|
+
const r = await q.invoke(db.connection, iterator);
|
|
227
|
+
if (r.updated > 0) {
|
|
228
|
+
list.push(iterator);
|
|
168
229
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
});
|
|
230
|
+
}
|
|
231
|
+
return list;
|
|
172
232
|
}
|
|
173
233
|
|
|
174
234
|
async seed() {
|
|
175
235
|
const db = new WorkflowContext(this.driver);
|
|
176
|
-
await db.
|
|
177
|
-
await db.
|
|
236
|
+
await db.connection.ensureDatabase();
|
|
237
|
+
await db.connection.automaticMigrations().migrate(db);
|
|
178
238
|
}
|
|
179
239
|
|
|
180
240
|
}
|
package/src/eternity/Workflow.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { IClassOf } from "../decorators/IClassOf.js";
|
|
1
2
|
import Inject from "../di/di.js";
|
|
2
3
|
import DateTime from "../types/DateTime.js";
|
|
3
4
|
import TimeSpan from "../types/TimeSpan.js";
|
|
5
|
+
import { ActivitySuspendedError } from "./ActivitySuspendedError.js";
|
|
4
6
|
import EternityContext from "./EternityContext.js";
|
|
5
7
|
import { WorkflowRegistry } from "./WorkflowRegistry.js";
|
|
6
8
|
|
|
@@ -34,25 +36,63 @@ export default abstract class Workflow<TIn = any, TOut = any> {
|
|
|
34
36
|
|
|
35
37
|
public failedPreserveTime: TimeSpan = TimeSpan.fromDays(1);
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
constructor(
|
|
40
|
+
{
|
|
41
|
+
sequence,
|
|
42
|
+
input,
|
|
43
|
+
id,
|
|
44
|
+
eta,
|
|
45
|
+
currentTime
|
|
46
|
+
}: {
|
|
47
|
+
sequence: string,
|
|
48
|
+
input: TIn,
|
|
49
|
+
id: string,
|
|
50
|
+
eta: DateTime,
|
|
51
|
+
currentTime: DateTime
|
|
52
|
+
},
|
|
53
|
+
protected context: EternityContext
|
|
54
|
+
) {
|
|
55
|
+
this.input = input;
|
|
56
|
+
this.id = id;
|
|
57
|
+
this.eta = eta;
|
|
58
|
+
this.currentTime = currentTime;
|
|
59
|
+
this.sequence = sequence;
|
|
46
60
|
}
|
|
47
61
|
|
|
48
62
|
public abstract run(): Promise<TOut>;
|
|
49
63
|
|
|
50
|
-
|
|
64
|
+
protected delay(ts: TimeSpan) {
|
|
51
65
|
return Promise.resolve("");
|
|
52
66
|
}
|
|
53
67
|
|
|
54
|
-
|
|
68
|
+
protected waitForExternalEvent(ts: TimeSpan, ... names: string[]) {
|
|
55
69
|
// do nothing...
|
|
56
70
|
return Promise.resolve("");
|
|
57
71
|
}
|
|
72
|
+
|
|
73
|
+
protected async runChild<TChildIn, TChildOut>(type: IClassOf<Workflow<TChildIn, TChildOut>>, input: TChildIn): Promise<TChildOut> {
|
|
74
|
+
return this.context.runChild(this, type, input);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
protected async all<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }>{
|
|
78
|
+
let suspended: ActivitySuspendedError;
|
|
79
|
+
try {
|
|
80
|
+
const r = await Promise.all(values.map(async (x) => {
|
|
81
|
+
try {
|
|
82
|
+
return await x;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error instanceof ActivitySuspendedError) {
|
|
85
|
+
suspended = error;
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
return r as any;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (suspended) {
|
|
93
|
+
throw suspended;
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
58
98
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IColumn } from "../../decorators/IColumn.js";
|
|
2
2
|
import { IIndex } from "../../decorators/IIndex.js";
|
|
3
|
-
import { BaseDriver } from "../../drivers/base/BaseDriver.js";
|
|
3
|
+
import { BaseConnection, BaseDriver } from "../../drivers/base/BaseDriver.js";
|
|
4
4
|
import EntityType from "../../entity-query/EntityType.js";
|
|
5
5
|
import EntityContext from "../../model/EntityContext.js";
|
|
6
6
|
import Migrations from "../Migrations.js";
|
|
@@ -15,7 +15,7 @@ export default class PostgresAutomaticMigrations extends PostgresMigrations {
|
|
|
15
15
|
const nonKeyColumns = type.nonKeys;
|
|
16
16
|
const keys = type.keys;
|
|
17
17
|
|
|
18
|
-
const driver = context.
|
|
18
|
+
const driver = context.connection;
|
|
19
19
|
|
|
20
20
|
await this.createTable(driver, type, keys);
|
|
21
21
|
|
|
@@ -42,7 +42,7 @@ export default class PostgresAutomaticMigrations extends PostgresMigrations {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
async createColumns(driver:
|
|
45
|
+
async createColumns(driver: BaseConnection, type: EntityType, nonKeyColumns: IColumn[]) {
|
|
46
46
|
|
|
47
47
|
const name = type.schema
|
|
48
48
|
? type.schema + "." + type.name
|
|
@@ -67,7 +67,7 @@ export default class PostgresAutomaticMigrations extends PostgresMigrations {
|
|
|
67
67
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
async createTable(driver:
|
|
70
|
+
async createTable(driver: BaseConnection, type: EntityType, keys: IColumn[]) {
|
|
71
71
|
|
|
72
72
|
const name = type.schema
|
|
73
73
|
? type.schema + "." + type.name
|
|
@@ -98,7 +98,7 @@ export default class PostgresAutomaticMigrations extends PostgresMigrations {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
async migrateIndex(context: EntityContext, index: IIndex, type: EntityType) {
|
|
101
|
-
const driver = context.
|
|
101
|
+
const driver = context.connection;
|
|
102
102
|
const name = type.schema
|
|
103
103
|
? type.schema + "." + type.name
|
|
104
104
|
: type.name;
|