@entity-access/entity-access 1.0.45 → 1.0.47
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 +20 -1
- package/dist/common/Logger.d.ts +8 -1
- package/dist/common/Logger.d.ts.map +1 -1
- package/dist/common/Logger.js +18 -1
- package/dist/common/Logger.js.map +1 -1
- package/dist/common/ObjectPool.d.ts +42 -0
- package/dist/common/ObjectPool.d.ts.map +1 -0
- package/dist/common/ObjectPool.js +86 -0
- package/dist/common/ObjectPool.js.map +1 -0
- package/dist/common/sleep.d.ts +3 -0
- package/dist/common/sleep.d.ts.map +1 -0
- package/dist/common/sleep.js +17 -0
- package/dist/common/sleep.js.map +1 -0
- package/dist/decorators/Column.js +1 -1
- package/dist/decorators/Column.js.map +1 -1
- package/dist/di/di.d.ts +2 -2
- package/dist/di/di.d.ts.map +1 -1
- package/dist/di/di.js +33 -19
- package/dist/di/di.js.map +1 -1
- package/dist/drivers/base/BaseDriver.d.ts.map +1 -1
- package/dist/drivers/base/BaseDriver.js.map +1 -1
- package/dist/drivers/postgres/PostgreSqlDriver.d.ts +2 -2
- package/dist/drivers/postgres/PostgreSqlDriver.d.ts.map +1 -1
- package/dist/drivers/postgres/PostgreSqlDriver.js +54 -25
- package/dist/drivers/postgres/PostgreSqlDriver.js.map +1 -1
- package/dist/drivers/sql-server/ExpressionToSqlServer.d.ts +2 -1
- package/dist/drivers/sql-server/ExpressionToSqlServer.d.ts.map +1 -1
- package/dist/drivers/sql-server/ExpressionToSqlServer.js +3 -0
- package/dist/drivers/sql-server/ExpressionToSqlServer.js.map +1 -1
- package/dist/eternity/ActivitySuspendedError.d.ts +3 -0
- package/dist/eternity/ActivitySuspendedError.d.ts.map +1 -0
- package/dist/eternity/ActivitySuspendedError.js +3 -0
- package/dist/eternity/ActivitySuspendedError.js.map +1 -0
- package/dist/eternity/EternityContext.d.ts +27 -0
- package/dist/eternity/EternityContext.d.ts.map +1 -0
- package/dist/eternity/EternityContext.js +230 -0
- package/dist/eternity/EternityContext.js.map +1 -0
- package/dist/eternity/EternityStorage.d.ts +40 -0
- package/dist/eternity/EternityStorage.d.ts.map +1 -0
- package/dist/eternity/EternityStorage.js +217 -0
- package/dist/eternity/EternityStorage.js.map +1 -0
- package/dist/eternity/Workflow.d.ts +24 -0
- package/dist/eternity/Workflow.d.ts.map +1 -0
- package/dist/eternity/Workflow.js +49 -0
- package/dist/eternity/Workflow.js.map +1 -0
- package/dist/eternity/WorkflowClock.d.ts +5 -0
- package/dist/eternity/WorkflowClock.d.ts.map +1 -0
- package/dist/eternity/WorkflowClock.js +18 -0
- package/dist/eternity/WorkflowClock.js.map +1 -0
- package/dist/eternity/WorkflowRegistry.d.ts +13 -0
- package/dist/eternity/WorkflowRegistry.d.ts.map +1 -0
- package/dist/eternity/WorkflowRegistry.js +24 -0
- package/dist/eternity/WorkflowRegistry.js.map +1 -0
- package/dist/migrations/Migrations.d.ts.map +1 -1
- package/dist/migrations/Migrations.js +1 -0
- package/dist/migrations/Migrations.js.map +1 -1
- package/dist/model/EntityContext.d.ts +1 -1
- package/dist/model/EntityContext.d.ts.map +1 -1
- package/dist/model/EntityContext.js +2 -1
- package/dist/model/EntityContext.js.map +1 -1
- package/dist/model/EntityQuery.d.ts.map +1 -1
- package/dist/model/EntityQuery.js +1 -3
- package/dist/model/EntityQuery.js.map +1 -1
- package/dist/query/ast/DebugStringVisitor.d.ts +3 -1
- package/dist/query/ast/DebugStringVisitor.d.ts.map +1 -1
- package/dist/query/ast/DebugStringVisitor.js +6 -0
- package/dist/query/ast/DebugStringVisitor.js.map +1 -1
- package/dist/query/ast/ExpressionToSql.d.ts +3 -1
- package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
- package/dist/query/ast/ExpressionToSql.js +19 -3
- package/dist/query/ast/ExpressionToSql.js.map +1 -1
- package/dist/query/ast/Expressions.d.ts +10 -7
- package/dist/query/ast/Expressions.d.ts.map +1 -1
- package/dist/query/ast/Expressions.js +12 -4
- package/dist/query/ast/Expressions.js.map +1 -1
- package/dist/query/ast/Visitor.d.ts +3 -1
- package/dist/query/ast/Visitor.d.ts.map +1 -1
- package/dist/query/ast/Visitor.js +10 -0
- package/dist/query/ast/Visitor.js.map +1 -1
- package/dist/query/parser/ArrowToExpression.d.ts +2 -2
- package/dist/query/parser/ArrowToExpression.d.ts.map +1 -1
- package/dist/query/parser/ArrowToExpression.js +2 -2
- package/dist/query/parser/ArrowToExpression.js.map +1 -1
- package/dist/tests/eternity/eternity-tests.d.ts +3 -0
- package/dist/tests/eternity/eternity-tests.d.ts.map +1 -0
- package/dist/tests/eternity/eternity-tests.js +91 -0
- package/dist/tests/eternity/eternity-tests.js.map +1 -0
- package/dist/tests/security/tests/place-order.js +6 -2
- package/dist/tests/security/tests/place-order.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/common/Logger.ts +25 -2
- package/src/common/ObjectPool.ts +124 -0
- package/src/common/sleep.ts +16 -0
- package/src/decorators/Column.ts +1 -1
- package/src/di/di.ts +37 -20
- package/src/drivers/base/BaseDriver.ts +2 -1
- package/src/drivers/postgres/PostgreSqlDriver.ts +59 -32
- package/src/drivers/sql-server/ExpressionToSqlServer.ts +5 -1
- package/src/eternity/ActivitySuspendedError.ts +3 -0
- package/src/eternity/EternityContext.ts +254 -0
- package/src/eternity/EternityStorage.ts +180 -0
- package/src/eternity/Workflow.ts +55 -0
- package/src/eternity/WorkflowClock.ts +10 -0
- package/src/eternity/WorkflowRegistry.ts +34 -0
- package/src/migrations/Migrations.ts +2 -0
- package/src/model/EntityContext.ts +3 -2
- package/src/model/EntityQuery.ts +1 -2
- package/src/query/ast/DebugStringVisitor.ts +10 -1
- package/src/query/ast/ExpressionToSql.ts +22 -4
- package/src/query/ast/Expressions.ts +17 -5
- package/src/query/ast/Visitor.ts +11 -1
- package/src/query/parser/ArrowToExpression.ts +2 -2
- package/src/tests/eternity/eternity-tests.ts +108 -0
- package/src/tests/security/tests/place-order.ts +6 -2
- package/test.js +38 -3
package/src/di/di.ts
CHANGED
|
@@ -14,39 +14,43 @@ const registrationsSymbol = Symbol("registrations");
|
|
|
14
14
|
|
|
15
15
|
const serviceProvider = Symbol("serviceProvider");
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const globalServiceProvider = Symbol("globalInstance");
|
|
18
18
|
|
|
19
19
|
export class ServiceProvider implements IDisposable {
|
|
20
20
|
|
|
21
|
-
public static
|
|
22
|
-
return
|
|
21
|
+
public static from(owner: any) {
|
|
22
|
+
return (owner[serviceProvider]) as ServiceProvider;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
25
|
public static resolve<T>(serviceOwner: any, type: IClassOf<T>): T {
|
|
27
|
-
const sp =
|
|
26
|
+
const sp = serviceOwner[serviceProvider] as ServiceProvider;
|
|
28
27
|
return sp.resolve(type);
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
static create<T>(serviceOwner, type: IClassOf<T>): T {
|
|
32
|
-
const sp =
|
|
31
|
+
const sp = serviceOwner[serviceProvider] as ServiceProvider;
|
|
33
32
|
return sp.createFromType(type);
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
static createScope<T>(serviceOwner): ServiceProvider {
|
|
36
|
+
const sp = serviceOwner[globalServiceProvider] as ServiceProvider;
|
|
37
|
+
return sp.createScope();
|
|
38
|
+
}
|
|
37
39
|
|
|
38
40
|
private map: Map<any,any> = new Map();
|
|
39
41
|
private disposables: IDisposable[];
|
|
40
42
|
|
|
41
43
|
constructor(parent?: ServiceProvider) {
|
|
42
44
|
this[serviceProvider] = this;
|
|
43
|
-
this[
|
|
45
|
+
this[globalServiceProvider] = parent?.[globalServiceProvider] ?? this;
|
|
46
|
+
this.map.set(ServiceProvider, this);
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
add<T1, T extends T1>(type: IAbstractClassOf<T1> | IClassOf<T1>, instance: T) {
|
|
47
50
|
this.getRegistration(type, true);
|
|
48
51
|
this.map.set(type, instance);
|
|
49
52
|
instance[serviceProvider] = this;
|
|
53
|
+
instance[globalServiceProvider] = this[globalServiceProvider];
|
|
50
54
|
this.resolveProperties(instance);
|
|
51
55
|
return instance;
|
|
52
56
|
}
|
|
@@ -66,7 +70,7 @@ export class ServiceProvider implements IDisposable {
|
|
|
66
70
|
const sd = this.getRegistration(type);
|
|
67
71
|
switch(sd.kind) {
|
|
68
72
|
case "Scoped":
|
|
69
|
-
if (
|
|
73
|
+
if (this[globalServiceProvider] === this) {
|
|
70
74
|
throw new Error(`Unable to create scoped service ${type?.name ?? type} in global scope.`);
|
|
71
75
|
}
|
|
72
76
|
instance = this.map.get(type);
|
|
@@ -74,20 +78,19 @@ export class ServiceProvider implements IDisposable {
|
|
|
74
78
|
instance = this.createFromDescriptor(sd);
|
|
75
79
|
this.map.set(type, instance);
|
|
76
80
|
instance[serviceProvider] = this;
|
|
81
|
+
instance[globalServiceProvider] = this[globalServiceProvider];
|
|
77
82
|
if (instance[Symbol.disposable] || instance[Symbol.asyncDisposable]) {
|
|
78
83
|
(this.disposables ??= []).push(instance);
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
return instance;
|
|
82
87
|
case "Singleton":
|
|
83
|
-
|
|
84
|
-
while (sp[parentServiceProvider]) {
|
|
85
|
-
sp = sp[parentServiceProvider];
|
|
86
|
-
}
|
|
88
|
+
const sp = this[globalServiceProvider];
|
|
87
89
|
instance = sp.map.get(type);
|
|
88
90
|
if (!instance) {
|
|
89
91
|
instance = sp.createFromDescriptor(sd);
|
|
90
|
-
instance[serviceProvider] =
|
|
92
|
+
instance[serviceProvider] = this;
|
|
93
|
+
instance[globalServiceProvider] = sp;
|
|
91
94
|
sp.map.set(type, instance);
|
|
92
95
|
if (instance[Symbol.disposable] || instance[Symbol.asyncDisposable]) {
|
|
93
96
|
(sp.disposables ??= []).push(instance);
|
|
@@ -96,7 +99,8 @@ export class ServiceProvider implements IDisposable {
|
|
|
96
99
|
return instance;
|
|
97
100
|
case "Transient":
|
|
98
101
|
instance = sp.createFromDescriptor(sd);
|
|
99
|
-
instance[serviceProvider] =
|
|
102
|
+
instance[serviceProvider] = this;
|
|
103
|
+
instance[globalServiceProvider] = sp;
|
|
100
104
|
return instance;
|
|
101
105
|
}
|
|
102
106
|
}
|
|
@@ -120,7 +124,7 @@ export class ServiceProvider implements IDisposable {
|
|
|
120
124
|
if (!sd) {
|
|
121
125
|
|
|
122
126
|
if (add) {
|
|
123
|
-
const registration: IServiceDescriptor = { key: type, kind: this[
|
|
127
|
+
const registration: IServiceDescriptor = { key: type, kind: this[globalServiceProvider] !== this ? "Scoped" : "Singleton" };
|
|
124
128
|
registrations.set(type, registration);
|
|
125
129
|
return registration;
|
|
126
130
|
}
|
|
@@ -155,7 +159,7 @@ export class ServiceProvider implements IDisposable {
|
|
|
155
159
|
for (const key in keys) {
|
|
156
160
|
if (Object.prototype.hasOwnProperty.call(keys, key)) {
|
|
157
161
|
const element = keys[key];
|
|
158
|
-
instance[key]
|
|
162
|
+
instance[key] ??= this.resolve(element);
|
|
159
163
|
}
|
|
160
164
|
}
|
|
161
165
|
}
|
|
@@ -168,6 +172,7 @@ export class ServiceProvider implements IDisposable {
|
|
|
168
172
|
: [];
|
|
169
173
|
const instance = new type(... injectServices);
|
|
170
174
|
instance[serviceProvider] = this;
|
|
175
|
+
instance[globalServiceProvider] = this[globalServiceProvider];
|
|
171
176
|
// initialize properties...
|
|
172
177
|
this.resolveProperties(instance, type);
|
|
173
178
|
return instance;
|
|
@@ -194,9 +199,21 @@ export const ServiceCollection = {
|
|
|
194
199
|
export default function Inject(target, key, index?: number) {
|
|
195
200
|
|
|
196
201
|
if (index !== void 0) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
202
|
+
|
|
203
|
+
if (key) {
|
|
204
|
+
|
|
205
|
+
// this is parameter inside a method...
|
|
206
|
+
const plist = (Reflect as any).getMetadata("design:paramtypes", target, key);
|
|
207
|
+
const pTypes = (target[injectServiceKeysSymbol] ??= {})[key] = [];
|
|
208
|
+
pTypes[index] = plist[index];
|
|
209
|
+
|
|
210
|
+
} else {
|
|
211
|
+
|
|
212
|
+
const plist = (Reflect as any).getMetadata("design:paramtypes", target, key);
|
|
213
|
+
const serviceTypes = target[injectServiceTypesSymbol] ??= [];
|
|
214
|
+
serviceTypes[index] = plist[index];
|
|
215
|
+
}
|
|
216
|
+
|
|
200
217
|
return;
|
|
201
218
|
}
|
|
202
219
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import EntityAccessError from "../../common/EntityAccessError.js";
|
|
1
2
|
import QueryCompiler from "../../compiler/QueryCompiler.js";
|
|
2
3
|
import EntityType from "../../entity-query/EntityType.js";
|
|
3
4
|
import Migrations from "../../migrations/Migrations.js";
|
|
4
5
|
import ChangeEntry from "../../model/changes/ChangeEntry.js";
|
|
5
|
-
import { BinaryExpression, Constant, DeleteStatement, Expression, InsertStatement, QuotedLiteral, ReturnUpdated, TableLiteral, UpdateStatement, ValuesStatement } from "../../query/ast/Expressions.js";
|
|
6
|
+
import { BinaryExpression, Constant, DeleteStatement, ExistsExpression, Expression, InsertStatement, NotExits, QuotedLiteral, ReturnUpdated, SelectStatement, TableLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "../../query/ast/Expressions.js";
|
|
6
7
|
|
|
7
8
|
export const disposableSymbol: unique symbol = (Symbol as any).dispose ??= Symbol("disposable");
|
|
8
9
|
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
+
import EntityAccessError from "../../common/EntityAccessError.js";
|
|
3
|
+
import ObjectPool, { IPooledObject } from "../../common/ObjectPool.js";
|
|
4
|
+
import TimedCache from "../../common/cache/TimedCache.js";
|
|
2
5
|
import QueryCompiler from "../../compiler/QueryCompiler.js";
|
|
3
|
-
import { IColumn } from "../../decorators/IColumn.js";
|
|
4
|
-
import EntityType from "../../entity-query/EntityType.js";
|
|
5
6
|
import Migrations from "../../migrations/Migrations.js";
|
|
6
7
|
import PostgresAutomaticMigrations from "../../migrations/postgres/PostgresAutomaticMigrations.js";
|
|
7
|
-
import { Query } from "../../query/Query.js";
|
|
8
8
|
import { BaseDriver, IDbConnectionString, IDbReader, IQuery, IRecord, toQuery } from "../base/BaseDriver.js";
|
|
9
|
-
import
|
|
9
|
+
import pg from "pg";
|
|
10
10
|
import Cursor from "pg-cursor";
|
|
11
|
-
|
|
12
|
-
const { Client } = pkg;
|
|
13
|
-
|
|
14
11
|
export interface IPgSqlConnectionString extends IDbConnectionString {
|
|
15
12
|
|
|
16
13
|
user?: string, // default process.env.PGUSER || process.env.USER
|
|
@@ -30,7 +27,7 @@ export interface IPgSqlConnectionString extends IDbConnectionString {
|
|
|
30
27
|
|
|
31
28
|
class DbReader implements IDbReader {
|
|
32
29
|
|
|
33
|
-
constructor(private cursor, private client) {
|
|
30
|
+
constructor(private cursor: Cursor, private client: IPooledObject<pg.Client>) {
|
|
34
31
|
|
|
35
32
|
}
|
|
36
33
|
|
|
@@ -47,27 +44,29 @@ class DbReader implements IDbReader {
|
|
|
47
44
|
async dispose() {
|
|
48
45
|
try {
|
|
49
46
|
await this.cursor.close();
|
|
50
|
-
} catch {
|
|
51
|
-
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(error);
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
try {
|
|
55
52
|
if (this.client) {
|
|
56
|
-
await this.client.
|
|
53
|
+
await this.client[Symbol.asyncDisposable]();
|
|
57
54
|
}
|
|
58
|
-
} catch {
|
|
59
|
-
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error(error);
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
59
|
}
|
|
63
60
|
|
|
61
|
+
const poolCache = new TimedCache<string, ObjectPool<pg.Client>>();
|
|
62
|
+
|
|
64
63
|
export default class PostgreSqlDriver extends BaseDriver {
|
|
65
64
|
|
|
66
65
|
public get compiler() {
|
|
67
66
|
return this.myCompiler;
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
private transaction:
|
|
69
|
+
private transaction: IPooledObject<pg.Client>;
|
|
71
70
|
private myCompiler = new QueryCompiler();
|
|
72
71
|
|
|
73
72
|
constructor(private readonly config: IPgSqlConnectionString) {
|
|
@@ -75,6 +74,9 @@ export default class PostgreSqlDriver extends BaseDriver {
|
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
public async runInTransaction<T>(fx?: () => Promise<T>): Promise<T> {
|
|
77
|
+
if (this.transaction) {
|
|
78
|
+
throw new EntityAccessError(`Nested Transactions not supported`);
|
|
79
|
+
}
|
|
78
80
|
const connection = await this.getConnection();
|
|
79
81
|
let result: T;
|
|
80
82
|
try {
|
|
@@ -88,7 +90,7 @@ export default class PostgreSqlDriver extends BaseDriver {
|
|
|
88
90
|
throw error;
|
|
89
91
|
} finally {
|
|
90
92
|
this.transaction = void 0;
|
|
91
|
-
await connection.
|
|
93
|
+
await connection[Symbol.asyncDisposable]();
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
|
|
@@ -112,28 +114,35 @@ export default class PostgreSqlDriver extends BaseDriver {
|
|
|
112
114
|
return result;
|
|
113
115
|
} finally {
|
|
114
116
|
if (!this.transaction) {
|
|
115
|
-
await connection.
|
|
117
|
+
await connection[Symbol.asyncDisposable]();
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
public ensureDatabase() {
|
|
121
123
|
const create = async () => {
|
|
122
|
-
const defaultDb = "postgres";
|
|
123
|
-
const db = this.config.database;
|
|
124
|
-
this.config.database = defaultDb;
|
|
125
|
-
const connection = await this.getConnection();
|
|
126
|
-
// @ts-expect-error readonly
|
|
127
|
-
this.config = { ... this.config };
|
|
128
|
-
this.config.database = db;
|
|
129
124
|
try {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
125
|
+
// const defaultDb = "postgres";
|
|
126
|
+
const db = this.config.database;
|
|
127
|
+
// this.config.database = defaultDb;
|
|
128
|
+
// // const connection = await this.getConnection();
|
|
129
|
+
// // @ts-expect-error readonly
|
|
130
|
+
// this.config = { ... this.config };
|
|
131
|
+
// this.config.database = db;
|
|
132
|
+
const connection = new pg.Client({ ... this.config, database: "postgres" });
|
|
133
|
+
await connection.connect();
|
|
134
|
+
try {
|
|
135
|
+
const r = await connection.query("SELECT FROM pg_database WHERE datname = $1", [ db ]);
|
|
136
|
+
if(r.rowCount === 1) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
await connection.query("CREATE DATABASE " + JSON.stringify(db));
|
|
140
|
+
} finally {
|
|
141
|
+
await connection.end();
|
|
133
142
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error(error);
|
|
145
|
+
throw error;
|
|
137
146
|
}
|
|
138
147
|
};
|
|
139
148
|
const value = create();
|
|
@@ -153,8 +162,26 @@ export default class PostgreSqlDriver extends BaseDriver {
|
|
|
153
162
|
if (this.transaction) {
|
|
154
163
|
return this.transaction;
|
|
155
164
|
}
|
|
156
|
-
|
|
157
|
-
|
|
165
|
+
|
|
166
|
+
const key = `${this.config.host}:${this.config.port}//${this.config.database}?${this.config.user}&${this.config.password}`;
|
|
167
|
+
|
|
168
|
+
const pooledClient = poolCache.getOrCreate(key, 1, () => new ObjectPool({
|
|
169
|
+
asyncFactory: async () => {
|
|
170
|
+
const c = new pg.Client(this.config);
|
|
171
|
+
await c.connect();
|
|
172
|
+
return c;
|
|
173
|
+
},
|
|
174
|
+
destroy(item) {
|
|
175
|
+
return item.end();
|
|
176
|
+
},
|
|
177
|
+
subscribeForRemoval(po, clear) {
|
|
178
|
+
po.on("end", clear);
|
|
179
|
+
},
|
|
180
|
+
}));
|
|
181
|
+
const client = await pooledClient.acquire();
|
|
182
|
+
|
|
183
|
+
// const client = new Client(this.config);
|
|
184
|
+
// await client.connect();
|
|
158
185
|
const row = await client.query("SELECT pg_backend_pid() as id");
|
|
159
186
|
const clientId = (row.rows as any).id;
|
|
160
187
|
// there is no support to kill the query running inside
|
|
@@ -165,7 +192,7 @@ export default class PostgreSqlDriver extends BaseDriver {
|
|
|
165
192
|
}
|
|
166
193
|
|
|
167
194
|
private async kill(id) {
|
|
168
|
-
const client = new Client(this.config);
|
|
195
|
+
const client = new pg.Client(this.config);
|
|
169
196
|
try {
|
|
170
197
|
await client.connect();
|
|
171
198
|
await client.query("SELECT pg_cancel_backend($1)", [id]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ExpressionToSql from "../../query/ast/ExpressionToSql.js";
|
|
2
|
-
import { Identifier, InsertStatement, OrderByExpression, ReturnUpdated, SelectStatement, ValuesStatement } from "../../query/ast/Expressions.js";
|
|
2
|
+
import { BooleanLiteral, Identifier, InsertStatement, OrderByExpression, ReturnUpdated, SelectStatement, ValuesStatement } from "../../query/ast/Expressions.js";
|
|
3
3
|
import { ITextQuery, prepare } from "../../query/ast/IStringTransformer.js";
|
|
4
4
|
|
|
5
5
|
export default class ExpressionToSqlServer extends ExpressionToSql {
|
|
@@ -100,4 +100,8 @@ export default class ExpressionToSqlServer extends ExpressionToSql {
|
|
|
100
100
|
return prepare `(VALUES ${rows}) ${fields}`;
|
|
101
101
|
|
|
102
102
|
}
|
|
103
|
+
|
|
104
|
+
visitBooleanLiteral({ value }: BooleanLiteral): ITextQuery {
|
|
105
|
+
return value ? [ " 1 "] : [ " 0 "];
|
|
106
|
+
}
|
|
103
107
|
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import EntityAccessError from "../common/EntityAccessError.js";
|
|
4
|
+
import { IClassOf } from "../decorators/IClassOf.js";
|
|
5
|
+
import Inject, { RegisterSingleton, ServiceProvider, injectServiceKeysSymbol } from "../di/di.js";
|
|
6
|
+
import DateTime from "../types/DateTime.js";
|
|
7
|
+
import EternityStorage, { WorkflowStorage } from "./EternityStorage.js";
|
|
8
|
+
import type Workflow from "./Workflow.js";
|
|
9
|
+
import { ActivitySuspendedError } from "./ActivitySuspendedError.js";
|
|
10
|
+
import { IWorkflowSchema, WorkflowRegistry } from "./WorkflowRegistry.js";
|
|
11
|
+
import crypto from "crypto";
|
|
12
|
+
import TimeSpan from "../types/TimeSpan.js";
|
|
13
|
+
import WorkflowClock from "./WorkflowClock.js";
|
|
14
|
+
|
|
15
|
+
async function hash(text) {
|
|
16
|
+
const sha256 = crypto.createHash("sha256");
|
|
17
|
+
return sha256.update(text).digest("hex");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function bindStep(context: EternityContext, store: WorkflowStorage, name: string, old: (... a: any[]) => any, unique = false) {
|
|
21
|
+
return async function runStep(this: Workflow, ... a: any[]) {
|
|
22
|
+
const input = JSON.stringify(a);
|
|
23
|
+
const ts = unique ? "0" : Math.floor(this.currentTime.msSinceEpoch);
|
|
24
|
+
const params = input.length < 150 ? input : await hash(input);
|
|
25
|
+
const id = `${this.id}(${params},${ts})`;
|
|
26
|
+
|
|
27
|
+
const clock = context.storage.clock;
|
|
28
|
+
|
|
29
|
+
const existing = await context.storage.get(id);
|
|
30
|
+
if (existing) {
|
|
31
|
+
if (existing.state === "failed" && existing.error) {
|
|
32
|
+
throw new Error(existing.error);
|
|
33
|
+
}
|
|
34
|
+
if (existing.state === "done") {
|
|
35
|
+
(this as any).currentTime = existing.updated;
|
|
36
|
+
return JSON.parse(existing.output);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
store.lastID = id;
|
|
41
|
+
|
|
42
|
+
const step: Partial<WorkflowStorage> = {
|
|
43
|
+
id,
|
|
44
|
+
parentID: this.id,
|
|
45
|
+
eta: this.eta,
|
|
46
|
+
queued: this.eta,
|
|
47
|
+
updated: this.eta,
|
|
48
|
+
isWorkflow: false,
|
|
49
|
+
name,
|
|
50
|
+
input
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// execute...
|
|
54
|
+
const start = clock.utcNow;
|
|
55
|
+
let lastError: Error;
|
|
56
|
+
let lastResult: any;
|
|
57
|
+
|
|
58
|
+
if (name === "delay" || name === "waitForExternalEvent") {
|
|
59
|
+
|
|
60
|
+
// first parameter is the ts
|
|
61
|
+
const maxTS = a[0] as TimeSpan;
|
|
62
|
+
const eta = this.currentTime.add(maxTS);
|
|
63
|
+
step.eta = eta;
|
|
64
|
+
|
|
65
|
+
if (eta <= start) {
|
|
66
|
+
// time is up...
|
|
67
|
+
lastResult = "";
|
|
68
|
+
step.state = "done";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
} else {
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
|
|
75
|
+
const types = Object.getPrototypeOf(this)?.[injectServiceKeysSymbol]?.[name] as any[];
|
|
76
|
+
for (let index = a.length; index < types.length; index++) {
|
|
77
|
+
const element = ServiceProvider.resolve(this, types[index]);
|
|
78
|
+
a.push(element);
|
|
79
|
+
}
|
|
80
|
+
lastResult = (await old.apply(this, a)) ?? 0;
|
|
81
|
+
step.output = JSON.stringify(lastResult);
|
|
82
|
+
step.state = "done";
|
|
83
|
+
step.eta = clock.utcNow;
|
|
84
|
+
(this as any).currentTime = step.eta;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error instanceof ActivitySuspendedError) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
lastError = error;
|
|
90
|
+
step.error = error.stack ?? error.toString();
|
|
91
|
+
step.state = "failed";
|
|
92
|
+
step.eta = clock.utcNow;
|
|
93
|
+
(this as any).currentTime = step.eta;
|
|
94
|
+
}
|
|
95
|
+
step.queued = start;
|
|
96
|
+
step.updated = step.updated;
|
|
97
|
+
}
|
|
98
|
+
await this.context.storage.save(step);
|
|
99
|
+
if (lastError) {
|
|
100
|
+
throw lastError;
|
|
101
|
+
}
|
|
102
|
+
if (step.state !== "done") {
|
|
103
|
+
throw new ActivitySuspendedError();
|
|
104
|
+
}
|
|
105
|
+
return lastResult;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface IWorkflowResult<T> {
|
|
110
|
+
output: T;
|
|
111
|
+
state: "done" | "failed" | "queued";
|
|
112
|
+
error: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@RegisterSingleton
|
|
116
|
+
export default class EternityContext {
|
|
117
|
+
|
|
118
|
+
private waiter: AbortController;
|
|
119
|
+
|
|
120
|
+
private registry: Map<string, IWorkflowSchema> = new Map();
|
|
121
|
+
|
|
122
|
+
constructor(
|
|
123
|
+
@Inject
|
|
124
|
+
public storage: EternityStorage
|
|
125
|
+
) {
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public register(type: IClassOf<Workflow>) {
|
|
130
|
+
this.registry.set(type.name, WorkflowRegistry.register(type, void 0));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public async start(signal?: AbortSignal) {
|
|
134
|
+
while(!signal?.aborted) {
|
|
135
|
+
await this.processQueueOnce(signal);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public async get<T = any>(c: IClassOf<Workflow<any, T>> | string, id?: string): Promise<IWorkflowResult<T>> {
|
|
140
|
+
id ??= (c as string);
|
|
141
|
+
const s = await this.storage.get(id);
|
|
142
|
+
if (s) {
|
|
143
|
+
return {
|
|
144
|
+
state: s.state,
|
|
145
|
+
output: s.output ? JSON.parse(s.output): null,
|
|
146
|
+
error: s.error
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public async queue<T>(
|
|
153
|
+
type: IClassOf<Workflow<T>>,
|
|
154
|
+
input: Partial<T>,
|
|
155
|
+
{ id, throwIfExists, eta }: { id?: string, throwIfExists?: boolean, eta?: DateTime } = {}) {
|
|
156
|
+
const clock = this.storage.clock;
|
|
157
|
+
if (id) {
|
|
158
|
+
const r = await this.storage.get(id);
|
|
159
|
+
if (r) {
|
|
160
|
+
if (throwIfExists) {
|
|
161
|
+
throw new EntityAccessError(`Workflow with ID ${id} already exists`);
|
|
162
|
+
}
|
|
163
|
+
return id;
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
id = randomUUID();
|
|
167
|
+
while(await this.storage.get(id) !== null) {
|
|
168
|
+
console.log(`Generating UUID again ${id}`);
|
|
169
|
+
id = randomUUID();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// this will ensure even empty workflow !!
|
|
174
|
+
const schema = WorkflowRegistry.register(type, void 0);
|
|
175
|
+
|
|
176
|
+
const now = clock.utcNow;
|
|
177
|
+
eta ??= now;
|
|
178
|
+
await this.storage.save({
|
|
179
|
+
id,
|
|
180
|
+
name: schema.name,
|
|
181
|
+
input: JSON.stringify(input),
|
|
182
|
+
isWorkflow: true,
|
|
183
|
+
queued: now,
|
|
184
|
+
updated: now,
|
|
185
|
+
eta
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if(eta < clock.utcNow) {
|
|
189
|
+
this.waiter.abort();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return id;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public async processQueueOnce(signal?: AbortSignal) {
|
|
196
|
+
const pending = await this.storage.dequeue(signal);
|
|
197
|
+
// run...
|
|
198
|
+
for (const iterator of pending) {
|
|
199
|
+
try {
|
|
200
|
+
await this.run(iterator);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error(error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
private async run(workflow: WorkflowStorage) {
|
|
207
|
+
|
|
208
|
+
const clock = this.storage.clock;
|
|
209
|
+
|
|
210
|
+
if (workflow.state === "failed" || workflow.state === "done") {
|
|
211
|
+
if (workflow.eta <= clock.utcNow) {
|
|
212
|
+
// time to delete...
|
|
213
|
+
await this.storage.delete(workflow.id);
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const scope = ServiceProvider.from(this).createScope();
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
|
|
222
|
+
const schema = WorkflowRegistry.getByName(workflow.name);
|
|
223
|
+
const { input, eta, id, updated } = workflow;
|
|
224
|
+
const instance = new (schema.type)({ input, eta, id, currentTime: DateTime.from(updated) });
|
|
225
|
+
for (const iterator of schema.activities) {
|
|
226
|
+
instance[iterator] = bindStep(this, workflow, iterator, instance[iterator]);
|
|
227
|
+
}
|
|
228
|
+
for (const iterator of schema.uniqueActivities) {
|
|
229
|
+
instance[iterator] = bindStep(this, workflow, iterator, instance[iterator], true);
|
|
230
|
+
}
|
|
231
|
+
scope.add( schema.type, instance);
|
|
232
|
+
try {
|
|
233
|
+
const result = await instance.run();
|
|
234
|
+
workflow.output = JSON.stringify(result ?? 0);
|
|
235
|
+
workflow.state = "done";
|
|
236
|
+
workflow.eta = clock.utcNow.add(instance.preserveTime);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
if (error instanceof ActivitySuspendedError) {
|
|
239
|
+
// this will update last id...
|
|
240
|
+
await this.storage.save(workflow);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
workflow.error = JSON.stringify(error.stack ?? error);
|
|
244
|
+
workflow.state = "failed";
|
|
245
|
+
workflow.eta = clock.utcNow.add(instance.failedPreserveTime);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
await this.storage.save(workflow);
|
|
249
|
+
|
|
250
|
+
} finally {
|
|
251
|
+
scope.dispose();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|