@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.
Files changed (116) hide show
  1. package/.vscode/launch.json +20 -1
  2. package/dist/common/Logger.d.ts +8 -1
  3. package/dist/common/Logger.d.ts.map +1 -1
  4. package/dist/common/Logger.js +18 -1
  5. package/dist/common/Logger.js.map +1 -1
  6. package/dist/common/ObjectPool.d.ts +42 -0
  7. package/dist/common/ObjectPool.d.ts.map +1 -0
  8. package/dist/common/ObjectPool.js +86 -0
  9. package/dist/common/ObjectPool.js.map +1 -0
  10. package/dist/common/sleep.d.ts +3 -0
  11. package/dist/common/sleep.d.ts.map +1 -0
  12. package/dist/common/sleep.js +17 -0
  13. package/dist/common/sleep.js.map +1 -0
  14. package/dist/decorators/Column.js +1 -1
  15. package/dist/decorators/Column.js.map +1 -1
  16. package/dist/di/di.d.ts +2 -2
  17. package/dist/di/di.d.ts.map +1 -1
  18. package/dist/di/di.js +33 -19
  19. package/dist/di/di.js.map +1 -1
  20. package/dist/drivers/base/BaseDriver.d.ts.map +1 -1
  21. package/dist/drivers/base/BaseDriver.js.map +1 -1
  22. package/dist/drivers/postgres/PostgreSqlDriver.d.ts +2 -2
  23. package/dist/drivers/postgres/PostgreSqlDriver.d.ts.map +1 -1
  24. package/dist/drivers/postgres/PostgreSqlDriver.js +54 -25
  25. package/dist/drivers/postgres/PostgreSqlDriver.js.map +1 -1
  26. package/dist/drivers/sql-server/ExpressionToSqlServer.d.ts +2 -1
  27. package/dist/drivers/sql-server/ExpressionToSqlServer.d.ts.map +1 -1
  28. package/dist/drivers/sql-server/ExpressionToSqlServer.js +3 -0
  29. package/dist/drivers/sql-server/ExpressionToSqlServer.js.map +1 -1
  30. package/dist/eternity/ActivitySuspendedError.d.ts +3 -0
  31. package/dist/eternity/ActivitySuspendedError.d.ts.map +1 -0
  32. package/dist/eternity/ActivitySuspendedError.js +3 -0
  33. package/dist/eternity/ActivitySuspendedError.js.map +1 -0
  34. package/dist/eternity/EternityContext.d.ts +27 -0
  35. package/dist/eternity/EternityContext.d.ts.map +1 -0
  36. package/dist/eternity/EternityContext.js +230 -0
  37. package/dist/eternity/EternityContext.js.map +1 -0
  38. package/dist/eternity/EternityStorage.d.ts +40 -0
  39. package/dist/eternity/EternityStorage.d.ts.map +1 -0
  40. package/dist/eternity/EternityStorage.js +217 -0
  41. package/dist/eternity/EternityStorage.js.map +1 -0
  42. package/dist/eternity/Workflow.d.ts +24 -0
  43. package/dist/eternity/Workflow.d.ts.map +1 -0
  44. package/dist/eternity/Workflow.js +49 -0
  45. package/dist/eternity/Workflow.js.map +1 -0
  46. package/dist/eternity/WorkflowClock.d.ts +5 -0
  47. package/dist/eternity/WorkflowClock.d.ts.map +1 -0
  48. package/dist/eternity/WorkflowClock.js +18 -0
  49. package/dist/eternity/WorkflowClock.js.map +1 -0
  50. package/dist/eternity/WorkflowRegistry.d.ts +13 -0
  51. package/dist/eternity/WorkflowRegistry.d.ts.map +1 -0
  52. package/dist/eternity/WorkflowRegistry.js +24 -0
  53. package/dist/eternity/WorkflowRegistry.js.map +1 -0
  54. package/dist/migrations/Migrations.d.ts.map +1 -1
  55. package/dist/migrations/Migrations.js +1 -0
  56. package/dist/migrations/Migrations.js.map +1 -1
  57. package/dist/model/EntityContext.d.ts +1 -1
  58. package/dist/model/EntityContext.d.ts.map +1 -1
  59. package/dist/model/EntityContext.js +2 -1
  60. package/dist/model/EntityContext.js.map +1 -1
  61. package/dist/model/EntityQuery.d.ts.map +1 -1
  62. package/dist/model/EntityQuery.js +1 -3
  63. package/dist/model/EntityQuery.js.map +1 -1
  64. package/dist/query/ast/DebugStringVisitor.d.ts +3 -1
  65. package/dist/query/ast/DebugStringVisitor.d.ts.map +1 -1
  66. package/dist/query/ast/DebugStringVisitor.js +6 -0
  67. package/dist/query/ast/DebugStringVisitor.js.map +1 -1
  68. package/dist/query/ast/ExpressionToSql.d.ts +3 -1
  69. package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
  70. package/dist/query/ast/ExpressionToSql.js +19 -3
  71. package/dist/query/ast/ExpressionToSql.js.map +1 -1
  72. package/dist/query/ast/Expressions.d.ts +10 -7
  73. package/dist/query/ast/Expressions.d.ts.map +1 -1
  74. package/dist/query/ast/Expressions.js +12 -4
  75. package/dist/query/ast/Expressions.js.map +1 -1
  76. package/dist/query/ast/Visitor.d.ts +3 -1
  77. package/dist/query/ast/Visitor.d.ts.map +1 -1
  78. package/dist/query/ast/Visitor.js +10 -0
  79. package/dist/query/ast/Visitor.js.map +1 -1
  80. package/dist/query/parser/ArrowToExpression.d.ts +2 -2
  81. package/dist/query/parser/ArrowToExpression.d.ts.map +1 -1
  82. package/dist/query/parser/ArrowToExpression.js +2 -2
  83. package/dist/query/parser/ArrowToExpression.js.map +1 -1
  84. package/dist/tests/eternity/eternity-tests.d.ts +3 -0
  85. package/dist/tests/eternity/eternity-tests.d.ts.map +1 -0
  86. package/dist/tests/eternity/eternity-tests.js +91 -0
  87. package/dist/tests/eternity/eternity-tests.js.map +1 -0
  88. package/dist/tests/security/tests/place-order.js +6 -2
  89. package/dist/tests/security/tests/place-order.js.map +1 -1
  90. package/dist/tsconfig.tsbuildinfo +1 -1
  91. package/package.json +1 -1
  92. package/src/common/Logger.ts +25 -2
  93. package/src/common/ObjectPool.ts +124 -0
  94. package/src/common/sleep.ts +16 -0
  95. package/src/decorators/Column.ts +1 -1
  96. package/src/di/di.ts +37 -20
  97. package/src/drivers/base/BaseDriver.ts +2 -1
  98. package/src/drivers/postgres/PostgreSqlDriver.ts +59 -32
  99. package/src/drivers/sql-server/ExpressionToSqlServer.ts +5 -1
  100. package/src/eternity/ActivitySuspendedError.ts +3 -0
  101. package/src/eternity/EternityContext.ts +254 -0
  102. package/src/eternity/EternityStorage.ts +180 -0
  103. package/src/eternity/Workflow.ts +55 -0
  104. package/src/eternity/WorkflowClock.ts +10 -0
  105. package/src/eternity/WorkflowRegistry.ts +34 -0
  106. package/src/migrations/Migrations.ts +2 -0
  107. package/src/model/EntityContext.ts +3 -2
  108. package/src/model/EntityQuery.ts +1 -2
  109. package/src/query/ast/DebugStringVisitor.ts +10 -1
  110. package/src/query/ast/ExpressionToSql.ts +22 -4
  111. package/src/query/ast/Expressions.ts +17 -5
  112. package/src/query/ast/Visitor.ts +11 -1
  113. package/src/query/parser/ArrowToExpression.ts +2 -2
  114. package/src/tests/eternity/eternity-tests.ts +108 -0
  115. package/src/tests/security/tests/place-order.ts +6 -2
  116. package/test.js +38 -3
@@ -0,0 +1,180 @@
1
+ import Column from "../decorators/Column.js";
2
+ import Index from "../decorators/Index.js";
3
+ import Table from "../decorators/Table.js";
4
+ import Inject, { RegisterScoped, RegisterSingleton, ServiceProvider } from "../di/di.js";
5
+ import { BaseDriver } from "../drivers/base/BaseDriver.js";
6
+ import EntityContext from "../model/EntityContext.js";
7
+ import DateTime from "../types/DateTime.js";
8
+ import WorkflowClock from "./WorkflowClock.js";
9
+
10
+ @Table("Workflows")
11
+ @Index({
12
+ name: "IX_Workflows_Group",
13
+ columns: [{ name: (x) => x.group, descending: false }],
14
+ filter: (x) => x.group !== null
15
+ })
16
+ @Index({
17
+ name: "IX_Workflows_ETA",
18
+ columns: [{ name: (x) => x.eta, descending: false }],
19
+ filter: (x) => x.isWorkflow === true
20
+ })
21
+ export class WorkflowStorage {
22
+
23
+ @Column({ dataType: "Char", length: 400, key: true })
24
+ public id: string;
25
+
26
+ @Column({ dataType: "Boolean" })
27
+ public isWorkflow: boolean;
28
+
29
+ @Column({ dataType: "Char", nullable: true })
30
+ public name: string;
31
+
32
+ @Column({ dataType: "Char", length: 200, nullable: true })
33
+ public group: string;
34
+
35
+ @Column({ dataType: "Char"})
36
+ public input: string;
37
+
38
+ @Column({ dataType: "Char", nullable: true})
39
+ public output: string;
40
+
41
+ @Column({ })
42
+ public eta: DateTime;
43
+
44
+ @Column({ })
45
+ public queued: DateTime;
46
+
47
+ @Column({ })
48
+ public updated: DateTime;
49
+
50
+ @Column({ dataType: "Int", default: "0"})
51
+ public priority: number;
52
+
53
+ @Column({ nullable: true })
54
+ public lockedTTL: DateTime;
55
+
56
+ @Column({ dataType: "AsciiChar", length: 10})
57
+ public state: "queued" | "failed" | "done";
58
+
59
+ @Column({ dataType: "Char", nullable: true})
60
+ public error: string;
61
+
62
+ @Column({ dataType: "Char", nullable: true})
63
+ public extra: string;
64
+
65
+ @Column({ dataType: "Char", length: 200 , nullable: true})
66
+ public parentID: string;
67
+
68
+ @Column({ dataType: "Char", length: 200 , nullable: true})
69
+ public lastID: string;
70
+ }
71
+
72
+ @RegisterScoped
73
+ class WorkflowContext extends EntityContext {
74
+
75
+ public workflows = this.model.register(WorkflowStorage);
76
+
77
+ verifyFilters: boolean = false;
78
+
79
+ raiseEvents: boolean = false;
80
+
81
+ }
82
+
83
+ @RegisterSingleton
84
+ export default class EternityStorage {
85
+
86
+ constructor(
87
+ @Inject
88
+ private driver: BaseDriver,
89
+ @Inject
90
+ public readonly clock: WorkflowClock
91
+ ) {
92
+
93
+ }
94
+
95
+ async get(id: string, input?) {
96
+ const db = new WorkflowContext(this.driver);
97
+ const r = await db.workflows.where({ id }, (p) => (x) => x.id === p.id && x.isWorkflow === true).first();
98
+ if (r !== null) {
99
+ return {
100
+ updated: r.updated,
101
+ eta: r.eta,
102
+ queued: r.queued,
103
+ state: r.state,
104
+ output: r.output,
105
+ error: r.error
106
+ };
107
+ }
108
+ return null;
109
+ }
110
+
111
+ async delete(id) {
112
+ const db = new WorkflowContext(this.driver);
113
+ const children = await db.workflows.where({ id}, (p) => (x) => x.parentID === p.id)
114
+ .limit(100)
115
+ .toArray();
116
+ for (const iterator of children) {
117
+ db.workflows.delete(iterator);
118
+ }
119
+ await db.saveChanges();
120
+ if (children.length === 100) {
121
+ return;
122
+ }
123
+
124
+ const w = await db.workflows.where({ id}, (p) => (x) => x.id === p.id).first();
125
+ if (!w) {
126
+ return;
127
+ }
128
+ db.workflows.delete(w);
129
+ await db.saveChanges();
130
+ }
131
+
132
+ async save(state: Partial<WorkflowStorage>) {
133
+ const db = new WorkflowContext(this.driver);
134
+ await this.driver.runInTransaction(async () => {
135
+ let w = await db.workflows.where(state, (p) => (x) => x.id === p.id).first();
136
+ if (!w) {
137
+ w = db.workflows.add(state);
138
+ }
139
+
140
+ for (const key in state) {
141
+ if (Object.prototype.hasOwnProperty.call(state, key)) {
142
+ const element = state[key];
143
+ w[key] = element;
144
+ }
145
+ }
146
+
147
+ w.state ||= "queued";
148
+ await db.saveChanges();
149
+ });
150
+ }
151
+
152
+ async dequeue(signal?: AbortSignal) {
153
+ const db = new WorkflowContext(this.driver);
154
+ const now = this.clock.utcNow;
155
+ const lockedTTL = now.addMinutes(1);
156
+ return this.driver.runInTransaction(async () => {
157
+ const list = await db.workflows
158
+ .where({now}, (p) => (x) => x.eta <= p.now
159
+ && (x.lockedTTL === null || x.lockedTTL <= p.now)
160
+ && x.isWorkflow === true)
161
+ .orderBy({}, (p) => (x) => x.eta)
162
+ .thenBy({}, (p) => (x) => x.priority)
163
+ .limit(20)
164
+ .withSignal(signal)
165
+ .toArray();
166
+ for (const iterator of list) {
167
+ iterator.lockedTTL = lockedTTL;
168
+ }
169
+ await db.saveChanges(signal);
170
+ return list;
171
+ });
172
+ }
173
+
174
+ async seed() {
175
+ const db = new WorkflowContext(this.driver);
176
+ await db.driver.ensureDatabase();
177
+ await db.driver.automaticMigrations().migrate(db);
178
+ }
179
+
180
+ }
@@ -0,0 +1,55 @@
1
+ import Inject from "../di/di.js";
2
+ import DateTime from "../types/DateTime.js";
3
+ import TimeSpan from "../types/TimeSpan.js";
4
+ import EternityContext from "./EternityContext.js";
5
+ import { WorkflowRegistry } from "./WorkflowRegistry.js";
6
+
7
+
8
+ export function Activity(target, key) {
9
+ WorkflowRegistry.register(target.constructor, key);
10
+ }
11
+
12
+ export function UniqueActivity(target, key) {
13
+ WorkflowRegistry.register(target.constructor, key, true);
14
+ }
15
+
16
+
17
+ export default abstract class Workflow<TIn = any, TOut = any> {
18
+
19
+ /**
20
+ * If specified, all workflows in same sequence
21
+ * will executed sequentially in a single worker node
22
+ */
23
+ public readonly sequence: string;
24
+
25
+ public readonly input: TIn;
26
+
27
+ public readonly id: string;
28
+
29
+ public readonly eta: DateTime;
30
+
31
+ public readonly currentTime: DateTime;
32
+
33
+ public preserveTime: TimeSpan = TimeSpan.fromMinutes(5);
34
+
35
+ public failedPreserveTime: TimeSpan = TimeSpan.fromDays(1);
36
+
37
+ @Inject
38
+ protected context: EternityContext;
39
+
40
+ constructor(p: Partial<Workflow>) {
41
+ Object.setPrototypeOf(p, new.target.prototype);
42
+ return p as Workflow;
43
+ }
44
+
45
+ public abstract run(): Promise<TOut>;
46
+
47
+ public delay(ts: TimeSpan) {
48
+ return Promise.resolve("");
49
+ }
50
+
51
+ public waitForExternalEvent(ts: TimeSpan, ... names: string[]) {
52
+ // do nothing...
53
+ return Promise.resolve("");
54
+ }
55
+ }
@@ -0,0 +1,10 @@
1
+ import { RegisterSingleton } from "../di/di.js";
2
+ import DateTime from "../types/DateTime.js";
3
+
4
+ @RegisterSingleton
5
+ export default class WorkflowClock {
6
+
7
+ public get utcNow() {
8
+ return DateTime.utcNow;
9
+ }
10
+ }
@@ -0,0 +1,34 @@
1
+ import { IClassOf } from "../decorators/IClassOf.js";
2
+ import type Workflow from "./Workflow.js";
3
+
4
+ export interface IWorkflowSchema {
5
+ name: string;
6
+ type: IClassOf<Workflow>;
7
+ activities: string[];
8
+ uniqueActivities: string[];
9
+ }
10
+
11
+ // const schema: Map<any,IWorkflowSchema> = new Map();
12
+ const schemaByName: Map<string,IWorkflowSchema> = new Map();
13
+
14
+ export const WorkflowRegistry = {
15
+ register(target: IClassOf<any>, key, unique = false) {
16
+ let methods = schemaByName.get(target.name);
17
+ if (!methods) {
18
+ methods = { name: target.name, type: target, activities: ["delay", "waitForExternalEvent"], uniqueActivities: [] };
19
+ schemaByName.set(target.name, methods);
20
+ }
21
+ if (key) {
22
+ if (unique) {
23
+ methods.uniqueActivities.push(key);
24
+ } else {
25
+ methods.activities.push(key);
26
+ }
27
+ }
28
+ return methods;
29
+ },
30
+
31
+ getByName(name: string) {
32
+ return schemaByName.get(name);
33
+ }
34
+ };
@@ -24,6 +24,8 @@ export default abstract class Migrations {
24
24
  async migrateIndexInternal(context: EntityContext, index: IIndex, type: EntityType) {
25
25
  // parse filter... pending...
26
26
 
27
+ index = { ... index };
28
+
27
29
  if (index.filter && typeof index.filter !== "string") {
28
30
  // parse..
29
31
  const source = context.query(type.typeClass) as EntityQuery<any>;
@@ -35,7 +35,7 @@ export default class EntityContext {
35
35
  @Inject
36
36
  private events?: ContextEvents,
37
37
  @Inject
38
- private logger?: Logger
38
+ public readonly logger?: Logger
39
39
  ) {
40
40
  this.raiseEvents = !!events;
41
41
  }
@@ -159,7 +159,8 @@ export default class EntityContext {
159
159
  }
160
160
 
161
161
  protected async saveChangesWithoutEvents(signal: AbortSignal) {
162
- for (const iterator of this.changeSet.entries) {
162
+ const copy = [].concat(this.changeSet.entries);
163
+ for (const iterator of copy) {
163
164
  switch (iterator.status) {
164
165
  case "inserted":
165
166
  const insert = this.driver.createInsertExpression(iterator.type, iterator.entity);
@@ -69,9 +69,8 @@ export default class EntityQuery<T = any>
69
69
  }
70
70
 
71
71
  async *enumerate(): AsyncGenerator<T, any, unknown> {
72
- const logger = ServiceProvider.resolve(this.context, Logger);
73
72
  const scope = new DisposableScope();
74
- const session = logger.newSession();
73
+ const session = this.context.logger?.newSession() ?? Logger.nullLogger;
75
74
  let query: { text: string, values: any[]};
76
75
  try {
77
76
  scope.register(session);
@@ -1,4 +1,4 @@
1
- import { ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateElement, TemplateLiteral, UpdateStatement, ValuesStatement } from "./Expressions.js";
1
+ import { ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NotExits, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateElement, TemplateLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "./Expressions.js";
2
2
  import Visitor from "./Visitor.js";
3
3
 
4
4
  const isBinary = (type) => /^(BinaryExpression|CoalesceExpression)$/.test(type);
@@ -169,7 +169,16 @@ export default class DebugStringVisitor extends Visitor<string> {
169
169
  return "UPDATE";
170
170
  }
171
171
 
172
+ visitNotExists(e: NotExits): string {
173
+ return ` NOT EXISTS ${this.visit(e.target)}`;
174
+ }
175
+
176
+ visitUnionAllStatement(e: UnionAllStatement): string {
177
+ return e.queries.map((x) => this.visit(x)).join("\nUNION ALL\n");
178
+ }
179
+
172
180
  private visitArray(e: Expression[], separator = ", ") {
173
181
  return e.map((x) => this.visit(x)).join(separator);
174
182
  }
183
+
175
184
  }
@@ -2,7 +2,7 @@ import QueryCompiler from "../../compiler/QueryCompiler.js";
2
2
  import EntityType from "../../entity-query/EntityType.js";
3
3
  import EntityQuery from "../../model/EntityQuery.js";
4
4
  import { filteredSymbol } from "../../model/events/EntityEvents.js";
5
- import { BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateLiteral, UpdateStatement, ValuesStatement } from "./Expressions.js";
5
+ import { BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NotExits, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "./Expressions.js";
6
6
  import { ITextQuery, QueryParameter, prepare, prepareJoin } from "./IStringTransformer.js";
7
7
  import ParameterScope from "./ParameterScope.js";
8
8
  import Visitor from "./Visitor.js";
@@ -126,7 +126,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
126
126
  }
127
127
 
128
128
  visitBooleanLiteral( { value }: BooleanLiteral): ITextQuery {
129
- return [ () => value ? "1" : "0" ];
129
+ return [ value ? " true ": " false "];
130
130
  }
131
131
 
132
132
  visitTemplateLiteral(e: TemplateLiteral): ITextQuery {
@@ -284,6 +284,10 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
284
284
  return prepare `${this.visit(target)}.${this.visit(property)}`;
285
285
  }
286
286
 
287
+ visitNotExists(e: NotExits): ITextQuery {
288
+ return [" NOT EXISTS ", this.visit(e.target) ];
289
+ }
290
+
287
291
  visitNullExpression(e: NullExpression): ITextQuery {
288
292
  return ["NULL"];
289
293
  }
@@ -298,7 +302,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
298
302
  : this.visit(e.right);
299
303
 
300
304
  if ((e.right as ExpressionType).type === "NullExpression") {
301
- if (e.operator === "===" || e.operator === "==") {
305
+ if (e.operator === "===" || e.operator === "==" || e.operator === "=") {
302
306
  return prepare `${left} IS NULL`;
303
307
  }
304
308
  if (e.operator === "!==" || e.operator === "!=" || e.operator === "<>") {
@@ -306,7 +310,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
306
310
  }
307
311
  }
308
312
  if ((e.left as ExpressionType).type === "NullExpression") {
309
- if (e.operator === "===" || e.operator === "==") {
313
+ if (e.operator === "===" || e.operator === "==" || e.operator === "=") {
310
314
  return prepare `${right} IS NULL`;
311
315
  }
312
316
  if (e.operator === "!==" || e.operator === "!=" || e.operator === "<>") {
@@ -411,6 +415,20 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
411
415
  return prepare `EXISTS (${this.visit(e.target)})`;
412
416
  }
413
417
 
418
+ visitUnionAllStatement(e: UnionAllStatement): ITextQuery {
419
+ const all: ITextQuery = [];
420
+ let first = true;
421
+ for (const iterator of e.queries) {
422
+ all.push(this.visit(iterator));
423
+ if (first) {
424
+ first = false;
425
+ continue;
426
+ }
427
+ all.push(" UNION ALL ");
428
+ }
429
+ return all;
430
+ }
431
+
414
432
  /**
415
433
  * This will also create and replace joins if query is provided.
416
434
  * @param x MemberExpression
@@ -124,7 +124,7 @@ export abstract class Expression {
124
124
  return copy;
125
125
  }
126
126
 
127
- readonly type: never | string;
127
+ readonly type: ExpressionType["type"];
128
128
 
129
129
  }
130
130
 
@@ -133,10 +133,10 @@ export class ArrayExpression extends Expression {
133
133
  elements: Expression[];
134
134
  }
135
135
 
136
- export class PartialExpression extends Expression {
137
- readonly type = "PartialExpression";
138
- query: ITextQuery;
139
- }
136
+ // export class PartialExpression extends Expression {
137
+ // readonly type = "PartialExpression";
138
+ // query: ITextQuery;
139
+ // }
140
140
 
141
141
  export class BinaryExpression extends Expression {
142
142
 
@@ -166,6 +166,11 @@ export class OrderByExpression extends Expression {
166
166
  descending: boolean;
167
167
  }
168
168
 
169
+ export class NotExits extends Expression {
170
+ readonly type = "NotExists";
171
+ target: Expression;
172
+ }
173
+
169
174
  export class ExistsExpression extends Expression {
170
175
  readonly type = "ExistsExpression";
171
176
  target: Expression;
@@ -371,6 +376,11 @@ export class UpdateStatement extends Expression {
371
376
 
372
377
  }
373
378
 
379
+ export class UnionAllStatement extends Expression {
380
+ readonly type = "UnionAllStatement";
381
+ queries: Expression[];
382
+ }
383
+
374
384
  export class DeleteStatement extends Expression {
375
385
  readonly type = "DeleteStatement";
376
386
  table: TableLiteral | QuotedLiteral;
@@ -407,5 +417,7 @@ export type ExpressionType =
407
417
  NewObjectExpression |
408
418
  ParameterExpression |
409
419
  ArrayExpression |
420
+ NotExits |
421
+ UnionAllStatement |
410
422
  TemplateElement
411
423
  ;
@@ -1,5 +1,5 @@
1
1
  import { NotSupportedError } from "../parser/NotSupportedError.js";
2
- import { ArrayExpression, ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateElement, TemplateLiteral, UpdateStatement, ValuesStatement } from "./Expressions.js";
2
+ import { ArrayExpression, ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NotExits, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateElement, TemplateLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "./Expressions.js";
3
3
 
4
4
 
5
5
  export default abstract class Visitor<T = any> {
@@ -69,10 +69,20 @@ export default abstract class Visitor<T = any> {
69
69
  return this.visitParameterExpression(e);
70
70
  case "ArrayExpression":
71
71
  return this.visitArrayExpression(e);
72
+ case "NotExists":
73
+ return this.visitNotExists(e);
74
+ case "UnionAllStatement":
75
+ return this.visitUnionAllStatement(e);
72
76
  }
73
77
  const c: never = e;
74
78
  throw new Error(`${e1.type} Not implemented`);
75
79
  }
80
+ visitUnionAllStatement(e: UnionAllStatement): T {
81
+ throw new NotSupportedError("Union All");
82
+ }
83
+ visitNotExists(e: NotExits): T {
84
+ throw new NotSupportedError("Not Exists");
85
+ }
76
86
  visitArrayExpression(e: ArrayExpression): T {
77
87
  throw new NotSupportedError("Array Expression");
78
88
  }
@@ -1,5 +1,5 @@
1
1
  import { parseExpression } from "@babel/parser";
2
- import { ArrowFunctionExpression, BinaryExpression, CallExpression, CoalesceExpression, ConditionalExpression, Constant, Expression, ExpressionAs, Identifier, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, ParameterExpression, QuotedLiteral, StringLiteral, TemplateLiteral } from "../ast/Expressions.js";
2
+ import { ArrowFunctionExpression, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, Expression, ExpressionAs, Identifier, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, ParameterExpression, QuotedLiteral, StringLiteral, TemplateLiteral } from "../ast/Expressions.js";
3
3
  import { BabelVisitor } from "./BabelVisitor.js";
4
4
  import * as bpe from "@babel/types";
5
5
  import Restructure from "./Restructure.js";
@@ -85,7 +85,7 @@ export default class ArrowToExpression extends BabelVisitor<Expression> {
85
85
  }
86
86
 
87
87
  visitBooleanLiteral({ value }: bpe.BooleanLiteral) {
88
- return Constant.create({ value });
88
+ return BooleanLiteral.create({ value });
89
89
  }
90
90
 
91
91
  visitDecimalLiteral( { value }: bpe.DecimalLiteral) {
@@ -0,0 +1,108 @@
1
+ import assert from "assert";
2
+ import Inject, { Register, RegisterSingleton, ServiceProvider } from "../../di/di.js";
3
+ import EternityContext from "../../eternity/EternityContext.js";
4
+ import Workflow, { Activity } from "../../eternity/Workflow.js";
5
+ import WorkflowClock from "../../eternity/WorkflowClock.js";
6
+ import DateTime from "../../types/DateTime.js";
7
+ import { TestConfig } from "../TestConfig.js";
8
+ import { BaseDriver } from "../../drivers/base/BaseDriver.js";
9
+ import EternityStorage from "../../eternity/EternityStorage.js";
10
+ import TimeSpan from "../../types/TimeSpan.js";
11
+ import sleep from "../../common/sleep.js";
12
+
13
+ class MockClock extends WorkflowClock {
14
+
15
+ public get utcNow(): DateTime {
16
+ return this.time;
17
+ }
18
+
19
+ public set utcNow(v: DateTime) {
20
+ this.time = v;
21
+ }
22
+
23
+ private time: DateTime = DateTime.utcNow;
24
+
25
+ public add(ts: TimeSpan) {
26
+ this.time = this.time.add(ts);
27
+ return this;
28
+ }
29
+ }
30
+
31
+ @RegisterSingleton
32
+ class Mailer {
33
+
34
+ public items: any[] = [];
35
+ }
36
+
37
+ class SendWorkflow extends Workflow<string, string> {
38
+
39
+ public async run(): Promise<any> {
40
+
41
+ await this.delay(TimeSpan.fromHours(1));
42
+
43
+ await this.sendMail("a", "b", "c");
44
+ return "1";
45
+ }
46
+
47
+ @Activity
48
+ public async sendMail(
49
+ from: string,
50
+ to: string,
51
+ message: string,
52
+ @Inject logger?: Mailer) {
53
+ await sleep(10);
54
+ logger.items.push({ from, to, message });
55
+ }
56
+
57
+ }
58
+
59
+ export default async function (this: TestConfig) {
60
+
61
+ const mockClock = new MockClock();
62
+ const mailer = new Mailer();
63
+
64
+ const scope = new ServiceProvider();
65
+ scope.add(WorkflowClock, mockClock);
66
+ scope.add(BaseDriver, this.driver);
67
+ const storage = new EternityStorage(this.driver, mockClock);
68
+ await storage.seed();
69
+ scope.add(Mailer, mailer);
70
+ scope.add(EternityStorage, storage);
71
+
72
+ const c = new EternityContext(storage);
73
+ scope.add(EternityContext, c);
74
+
75
+ // this is an important step
76
+ c.register(SendWorkflow);
77
+
78
+ const id = await c.queue(SendWorkflow, "a");
79
+
80
+ mockClock.add(TimeSpan.fromSeconds(15));
81
+
82
+ await c.processQueueOnce();
83
+
84
+ mockClock.add(TimeSpan.fromSeconds(15));
85
+
86
+ await c.processQueueOnce();
87
+
88
+ assert.equal(0, mailer.items.length);
89
+
90
+ mockClock.add(TimeSpan.fromHours(1));
91
+
92
+ await c.processQueueOnce();
93
+
94
+ assert.equal(1, mailer.items.length);
95
+
96
+ let r = await c.get(SendWorkflow, id);
97
+ assert.equal("1", r.output);
98
+
99
+ mockClock.add(TimeSpan.fromDays(2));
100
+
101
+ await c.processQueueOnce();
102
+
103
+ r = await c.get(SendWorkflow, id);
104
+ assert.strictEqual(null, r);
105
+
106
+ // throw new Error("Preserve");
107
+
108
+ }
@@ -27,7 +27,9 @@ export default async function(this: TestConfig) {
27
27
  }
28
28
 
29
29
  async function getNewOrders(this: TestConfig) {
30
- const scope = ServiceProvider.global.createScope();
30
+ const global = new ServiceProvider();
31
+ global.add(BaseDriver, this.driver);
32
+ const scope = global.createScope();
31
33
  try {
32
34
  const user = new UserInfo();
33
35
  user.userID = 2;
@@ -49,7 +51,9 @@ async function getNewOrders(this: TestConfig) {
49
51
  }
50
52
 
51
53
  async function addNewOrder(this: TestConfig, customer: User, userID?) {
52
- const scope = ServiceProvider.global.createScope();
54
+ const global = new ServiceProvider();
55
+ global.add(BaseDriver, this.driver);
56
+ const scope = global.createScope();
53
57
  try {
54
58
  const user = new UserInfo();
55
59
  user.userID = userID ?? customer.userID;