@entity-access/entity-access 1.0.287 → 1.0.289
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/decorators/CheckConstraint.d.ts +4 -0
- package/dist/decorators/CheckConstraint.d.ts.map +1 -0
- package/dist/decorators/CheckConstraint.js +8 -0
- package/dist/decorators/CheckConstraint.js.map +1 -0
- package/dist/decorators/ICheckConstraint.d.ts +5 -0
- package/dist/decorators/ICheckConstraint.d.ts.map +1 -0
- package/dist/decorators/ICheckConstraint.js +3 -0
- package/dist/decorators/ICheckConstraint.js.map +1 -0
- package/dist/entity-query/EntityType.d.ts +2 -0
- package/dist/entity-query/EntityType.d.ts.map +1 -1
- package/dist/entity-query/EntityType.js +1 -0
- package/dist/entity-query/EntityType.js.map +1 -1
- package/dist/migrations/Migrations.d.ts +2 -0
- package/dist/migrations/Migrations.d.ts.map +1 -1
- package/dist/migrations/Migrations.js +8 -0
- package/dist/migrations/Migrations.js.map +1 -1
- package/dist/migrations/postgres/PostgresAutomaticMigrations.d.ts +3 -0
- package/dist/migrations/postgres/PostgresAutomaticMigrations.d.ts.map +1 -1
- package/dist/migrations/postgres/PostgresAutomaticMigrations.js +34 -9
- package/dist/migrations/postgres/PostgresAutomaticMigrations.js.map +1 -1
- package/dist/migrations/sql-server/SqlServerAutomaticMigrations.d.ts +3 -0
- package/dist/migrations/sql-server/SqlServerAutomaticMigrations.d.ts.map +1 -1
- package/dist/migrations/sql-server/SqlServerAutomaticMigrations.js +33 -9
- package/dist/migrations/sql-server/SqlServerAutomaticMigrations.js.map +1 -1
- package/dist/model/EntityModel.d.ts.map +1 -1
- package/dist/model/EntityModel.js +1 -0
- package/dist/model/EntityModel.js.map +1 -1
- package/dist/model/EntitySource.js +1 -1
- package/dist/model/EntitySource.js.map +1 -1
- package/dist/model/verification/VerificationSession.js +1 -1
- package/dist/model/verification/VerificationSession.js.map +1 -1
- package/dist/tests/db-tests/tests/check-constraint-test.d.ts +3 -0
- package/dist/tests/db-tests/tests/check-constraint-test.d.ts.map +1 -0
- package/dist/tests/db-tests/tests/check-constraint-test.js +28 -0
- package/dist/tests/db-tests/tests/check-constraint-test.js.map +1 -0
- package/dist/tests/eternity/throttle-tests.d.ts +3 -0
- package/dist/tests/eternity/throttle-tests.d.ts.map +1 -0
- package/dist/tests/eternity/throttle-tests.js +86 -0
- package/dist/tests/eternity/throttle-tests.js.map +1 -0
- package/dist/tests/model/ShoppingContext.d.ts.map +1 -1
- package/dist/tests/model/ShoppingContext.js +6 -1
- package/dist/tests/model/ShoppingContext.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/workflows/WorkflowContext.d.ts +5 -1
- package/dist/workflows/WorkflowContext.d.ts.map +1 -1
- package/dist/workflows/WorkflowContext.js +11 -3
- package/dist/workflows/WorkflowContext.js.map +1 -1
- package/dist/workflows/WorkflowStorage.d.ts +5 -0
- package/dist/workflows/WorkflowStorage.d.ts.map +1 -1
- package/dist/workflows/WorkflowStorage.js +28 -0
- package/dist/workflows/WorkflowStorage.js.map +1 -1
- package/package.json +1 -1
- package/src/decorators/CheckConstraint.ts +10 -0
- package/src/decorators/ICheckConstraint.ts +4 -0
- package/src/entity-query/EntityType.ts +3 -0
- package/src/migrations/Migrations.ts +11 -0
- package/src/migrations/postgres/PostgresAutomaticMigrations.ts +43 -9
- package/src/migrations/sql-server/SqlServerAutomaticMigrations.ts +43 -10
- package/src/model/EntityModel.ts +1 -0
- package/src/model/EntitySource.ts +1 -1
- package/src/model/verification/VerificationSession.ts +1 -1
- package/src/tests/db-tests/tests/check-constraint-test.ts +35 -0
- package/src/tests/eternity/throttle-tests.ts +100 -0
- package/src/tests/model/ShoppingContext.ts +5 -0
- package/src/workflows/WorkflowContext.ts +20 -3
- package/src/workflows/WorkflowStorage.ts +31 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
import Inject, { Register, RegisterSingleton, ServiceProvider } from "../../di/di.js";
|
|
3
|
+
import WorkflowContext from "../../workflows/WorkflowContext.js";
|
|
4
|
+
import Workflow, { Activity } from "../../workflows/Workflow.js";
|
|
5
|
+
import WorkflowClock from "../../workflows/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 WorkflowStorage from "../../workflows/WorkflowStorage.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.now;
|
|
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 WorkflowStorage(this.driver, mockClock);
|
|
68
|
+
scope.add(Mailer, mailer);
|
|
69
|
+
scope.add(WorkflowStorage, storage);
|
|
70
|
+
|
|
71
|
+
const c = new WorkflowContext(storage);
|
|
72
|
+
scope.add(WorkflowContext, c);
|
|
73
|
+
|
|
74
|
+
// this is an important step
|
|
75
|
+
c.register(SendWorkflow);
|
|
76
|
+
|
|
77
|
+
await storage.seed();
|
|
78
|
+
|
|
79
|
+
const id = await c.queue(SendWorkflow, "a", {
|
|
80
|
+
throttle: {
|
|
81
|
+
group: "a",
|
|
82
|
+
maxPerSecond: 1/15
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const result = await storage.getWorkflow(id);
|
|
87
|
+
|
|
88
|
+
const next = await c.queue(SendWorkflow, "b", {
|
|
89
|
+
throttle: {
|
|
90
|
+
group: "a",
|
|
91
|
+
maxPerSecond: 1/15
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const resultNext = await storage.getWorkflow(next);
|
|
96
|
+
const span = DateTime.from(resultNext.eta).diff(DateTime.from(result.eta));
|
|
97
|
+
assert(span.totalSeconds > 14);
|
|
98
|
+
|
|
99
|
+
// throw new Error("Preserve");
|
|
100
|
+
}
|
|
@@ -7,6 +7,7 @@ import DateTime from "../../types/DateTime.js";
|
|
|
7
7
|
import { UserFile } from "./UseFile.js";
|
|
8
8
|
import Sql from "../../sql/Sql.js";
|
|
9
9
|
import MultiForeignKeys from "../../decorators/ForeignKey.js";
|
|
10
|
+
import CheckConstraint from "../../decorators/CheckConstraint.js";
|
|
10
11
|
|
|
11
12
|
export const statusPublished = "published";
|
|
12
13
|
|
|
@@ -285,6 +286,10 @@ export class ProductCategory {
|
|
|
285
286
|
}
|
|
286
287
|
|
|
287
288
|
@Table("ProductPrices")
|
|
289
|
+
@CheckConstraint({
|
|
290
|
+
name: "CX_ProductPrices_PositivePrice",
|
|
291
|
+
filter: (x) => x.amount > 0
|
|
292
|
+
})
|
|
288
293
|
export class ProductPrice {
|
|
289
294
|
|
|
290
295
|
@Column({ key: true, generated: "identity", dataType: "BigInt"})
|
|
@@ -138,6 +138,10 @@ export interface IWorkflowQueueParameter {
|
|
|
138
138
|
eta?: DateTime;
|
|
139
139
|
parentID?: string;
|
|
140
140
|
taskGroup?: string;
|
|
141
|
+
throttle?: {
|
|
142
|
+
group: string;
|
|
143
|
+
maxPerSecond: number;
|
|
144
|
+
}
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
export interface IWorkflowStartParams {
|
|
@@ -189,7 +193,8 @@ export default class WorkflowContext {
|
|
|
189
193
|
throwIfExists,
|
|
190
194
|
eta,
|
|
191
195
|
parentID,
|
|
192
|
-
taskGroup = "default"
|
|
196
|
+
taskGroup = "default",
|
|
197
|
+
throttle
|
|
193
198
|
}: IWorkflowQueueParameter = {}) {
|
|
194
199
|
const clock = this.storage.clock;
|
|
195
200
|
let tries = 1;
|
|
@@ -207,10 +212,21 @@ export default class WorkflowContext {
|
|
|
207
212
|
throw new EntityAccessError(`ID cannot be more than 400 characters`);
|
|
208
213
|
}
|
|
209
214
|
|
|
215
|
+
const now = align(clock.utcNow);
|
|
216
|
+
|
|
217
|
+
let queued = now;
|
|
218
|
+
|
|
219
|
+
let throttleGroup = null;
|
|
220
|
+
|
|
221
|
+
if (throttle) {
|
|
222
|
+
eta = await this.storage.getNextEta(throttle);
|
|
223
|
+
queued = eta;
|
|
224
|
+
throttleGroup = throttle.group;
|
|
225
|
+
}
|
|
226
|
+
|
|
210
227
|
// this will ensure even empty workflow !!
|
|
211
228
|
const schema = WorkflowRegistry.register(type, void 0);
|
|
212
229
|
|
|
213
|
-
const now = align(clock.utcNow);
|
|
214
230
|
|
|
215
231
|
let lastError = null;
|
|
216
232
|
while(tries--) {
|
|
@@ -229,10 +245,11 @@ export default class WorkflowContext {
|
|
|
229
245
|
await this.storage.save({
|
|
230
246
|
id,
|
|
231
247
|
taskGroup,
|
|
248
|
+
throttleGroup,
|
|
232
249
|
name: schema.name,
|
|
233
250
|
input: JSON.stringify(input),
|
|
234
251
|
isWorkflow: true,
|
|
235
|
-
queued
|
|
252
|
+
queued,
|
|
236
253
|
updated: now,
|
|
237
254
|
parentID,
|
|
238
255
|
eta
|
|
@@ -27,6 +27,14 @@ const loadedFromDb = Symbol("loadedFromDB");
|
|
|
27
27
|
],
|
|
28
28
|
filter: (x) => x.isWorkflow === true
|
|
29
29
|
})
|
|
30
|
+
@Index({
|
|
31
|
+
name: "IX_Workflows_Throttle_Group",
|
|
32
|
+
columns: [
|
|
33
|
+
{ name: (x) => x.throttleGroup, descending: false },
|
|
34
|
+
{ name: (x) => x.queued, descending: false }
|
|
35
|
+
],
|
|
36
|
+
filter: (x) => x.isWorkflow === true && x.throttleGroup !== null
|
|
37
|
+
})
|
|
30
38
|
export class WorkflowItem {
|
|
31
39
|
|
|
32
40
|
@Column({ dataType: "Char", length: 400, key: true })
|
|
@@ -62,6 +70,11 @@ export class WorkflowItem {
|
|
|
62
70
|
})
|
|
63
71
|
public taskGroup: string;
|
|
64
72
|
|
|
73
|
+
@Column({
|
|
74
|
+
dataType: "Char", length: 200, nullable: true
|
|
75
|
+
})
|
|
76
|
+
public throttleGroup: string;
|
|
77
|
+
|
|
65
78
|
@Column({ dataType: "Int", default: () => 0})
|
|
66
79
|
public priority: number;
|
|
67
80
|
|
|
@@ -112,6 +125,24 @@ export default class WorkflowStorage {
|
|
|
112
125
|
|
|
113
126
|
}
|
|
114
127
|
|
|
128
|
+
async getNextEta(throttle: { group: string, maxPerSecond: number }) {
|
|
129
|
+
|
|
130
|
+
const db = new WorkflowContext(this.driver);
|
|
131
|
+
const last = await db.workflows.where(throttle, (p) => (x) => x.throttleGroup === p.group
|
|
132
|
+
&& x.isWorkflow === true)
|
|
133
|
+
.orderByDescending(void 0, (p) => (x) => x.queued)
|
|
134
|
+
.first();
|
|
135
|
+
|
|
136
|
+
if (last) {
|
|
137
|
+
if (throttle.maxPerSecond <= 0) {
|
|
138
|
+
throttle.maxPerSecond = 1;
|
|
139
|
+
}
|
|
140
|
+
return DateTime.from(last.queued).addSeconds(1 / throttle.maxPerSecond);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return DateTime.now;
|
|
144
|
+
}
|
|
145
|
+
|
|
115
146
|
async getWorkflow(id: string) {
|
|
116
147
|
const db = new WorkflowContext(this.driver);
|
|
117
148
|
const r = await db.workflows.where({ id }, (p) => (x) => x.id === p.id && x.isWorkflow === true).first();
|