@entity-access/entity-access 1.0.306 → 1.0.308

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 (33) hide show
  1. package/dist/common/symbols/symbols.d.ts +1 -0
  2. package/dist/common/symbols/symbols.d.ts.map +1 -1
  3. package/dist/common/symbols/symbols.js +1 -0
  4. package/dist/common/symbols/symbols.js.map +1 -1
  5. package/dist/drivers/base/BaseDriver.d.ts +18 -14
  6. package/dist/drivers/base/BaseDriver.d.ts.map +1 -1
  7. package/dist/drivers/base/BaseDriver.js +52 -25
  8. package/dist/drivers/base/BaseDriver.js.map +1 -1
  9. package/dist/drivers/postgres/PostgreSqlDriver.d.ts.map +1 -1
  10. package/dist/drivers/postgres/PostgreSqlDriver.js +23 -12
  11. package/dist/drivers/postgres/PostgreSqlDriver.js.map +1 -1
  12. package/dist/drivers/sql-server/SqlServerDriver.d.ts +1 -1
  13. package/dist/drivers/sql-server/SqlServerDriver.d.ts.map +1 -1
  14. package/dist/drivers/sql-server/SqlServerDriver.js +30 -11
  15. package/dist/drivers/sql-server/SqlServerDriver.js.map +1 -1
  16. package/dist/model/EntityContext.d.ts.map +1 -1
  17. package/dist/model/EntityContext.js +28 -10
  18. package/dist/model/EntityContext.js.map +1 -1
  19. package/dist/model/EntitySource.d.ts.map +1 -1
  20. package/dist/model/EntitySource.js +2 -1
  21. package/dist/model/EntitySource.js.map +1 -1
  22. package/dist/tsconfig.tsbuildinfo +1 -1
  23. package/dist/workflows/WorkflowStorage.d.ts.map +1 -1
  24. package/dist/workflows/WorkflowStorage.js +17 -18
  25. package/dist/workflows/WorkflowStorage.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/common/symbols/symbols.ts +1 -0
  28. package/src/drivers/base/BaseDriver.ts +69 -40
  29. package/src/drivers/postgres/PostgreSqlDriver.ts +28 -13
  30. package/src/drivers/sql-server/SqlServerDriver.ts +39 -13
  31. package/src/model/EntityContext.ts +27 -21
  32. package/src/model/EntitySource.ts +2 -1
  33. package/src/workflows/WorkflowStorage.ts +11 -26
@@ -47,41 +47,75 @@ export interface IQueryResult {
47
47
  updated?: number;
48
48
  }
49
49
 
50
- export interface IBaseTransaction {
51
- commit(): Promise<any>;
52
- rollback(): Promise<any>;
53
- dispose(): Promise<void>;
54
- }
50
+ const currentTransaction = Symbol("currentTrnsaction");
51
+
52
+ export abstract class EntityTransaction {
55
53
 
56
- export class EntityTransaction {
54
+ private committedOrRolledBack = false;
57
55
 
58
- committedOrRolledBack = false;
56
+ private disposed = false;
57
+
58
+ constructor(protected conn: BaseConnection) {
59
+ conn[currentTransaction] = this;
60
+ }
59
61
 
60
- constructor(private tx: IBaseTransaction) {}
62
+ begin() {
63
+ return this.beginTransaction();
64
+ }
61
65
 
62
66
  commit() {
63
67
  this.committedOrRolledBack = true;
64
- return this.tx.commit();
68
+ return this.commitTransaction();
65
69
  }
66
70
 
67
71
  rollback() {
68
72
  this.committedOrRolledBack = true;
69
- return this.tx.rollback();
73
+ return this.rollbackTransaction();
70
74
  }
71
75
 
72
76
  async dispose() {
77
+ if (this.disposed) {
78
+ return;
79
+ }
80
+ this.disposed = true;
73
81
  if(!this.committedOrRolledBack) {
74
- await this.tx.rollback();
82
+ await this.rollbackTransaction();
75
83
  }
76
- await this.tx.dispose();
84
+ await this.disposeTransaction();
77
85
  }
78
86
 
79
- async [Symbol.asyncDispose]() {
80
- if(!this.committedOrRolledBack) {
81
- await this.tx.rollback();
82
- }
83
- await this.tx.dispose();
87
+ [Symbol.asyncDispose]() {
88
+ return this.dispose();
89
+ }
90
+
91
+ protected abstract disposeTransaction(): Promise<void>;
92
+
93
+ protected abstract commitTransaction(): Promise<void>;
94
+
95
+ protected abstract rollbackTransaction(): Promise<void>;
96
+
97
+ protected abstract beginTransaction(): Promise<void>;
98
+
99
+ }
100
+
101
+ const emptyResolve = Promise.resolve();
102
+
103
+ class EmptyTransaction extends EntityTransaction {
104
+
105
+ protected disposeTransaction() {
106
+ return emptyResolve;
107
+ }
108
+ protected commitTransaction() {
109
+ return emptyResolve;
110
+ }
111
+ protected rollbackTransaction() {
112
+ return emptyResolve;
84
113
  }
114
+
115
+ protected beginTransaction() {
116
+ return emptyResolve;
117
+ }
118
+
85
119
  }
86
120
 
87
121
  export abstract class BaseConnection {
@@ -90,7 +124,7 @@ export abstract class BaseConnection {
90
124
 
91
125
  protected connectionString: IDbConnectionString;
92
126
 
93
- private currentTransaction: EntityTransaction;
127
+ private [currentTransaction]: EntityTransaction;
94
128
 
95
129
 
96
130
  constructor(public driver: BaseDriver) {
@@ -106,35 +140,30 @@ export abstract class BaseConnection {
106
140
  */
107
141
  public abstract automaticMigrations(): Migrations;
108
142
 
143
+ public async runInTransaction<T = any>(fx?: () => Promise<T>) {
144
+ await using tx = await this.createTransaction();
145
+ const result = await fx();
146
+ await tx.commit();
147
+ return result;
148
+ }
149
+
109
150
 
110
151
  public abstract executeReader(command: IQuery, signal?: AbortSignal): Promise<IDbReader>;
111
152
 
112
153
  public abstract executeQuery(command: IQuery, signal?: AbortSignal): Promise<IQueryResult>;
113
154
 
114
- public abstract createTransaction(): Promise<EntityTransaction>;
115
-
116
- public async runInTransaction<T = any>(fx?: () => Promise<T>) {
117
- if(this.currentTransaction) {
118
- // nested transactions... do not worry
119
- // just pass through
120
- return await fx();
121
- }
122
- let failed = true;
123
- let tx: EntityTransaction;
124
- try {
125
- tx = this.currentTransaction = await this.createTransaction();
126
- const result = await fx();
127
- await tx.commit();
128
- failed = false;
129
- return result;
130
- } finally {
131
- if (failed) {
132
- await tx?.rollback();
133
- }
134
- await tx?.[Symbol.asyncDispose]();
135
- this.currentTransaction = null;
155
+ public async createTransaction() {
156
+ if (this[currentTransaction]) {
157
+ // return fake one...
158
+ return new EmptyTransaction(this);
136
159
  }
160
+ const tx = this[currentTransaction] = await this.createDbTransaction();
161
+ await tx.begin();
162
+ return tx;
137
163
  }
164
+
165
+ protected abstract createDbTransaction(): Promise<EntityTransaction>;
166
+
138
167
  }
139
168
 
140
169
  export type DirectSaveType =
@@ -3,7 +3,7 @@ import ObjectPool, { IPooledObject } from "../../common/ObjectPool.js";
3
3
  import QueryCompiler from "../../compiler/QueryCompiler.js";
4
4
  import Migrations from "../../migrations/Migrations.js";
5
5
  import PostgresAutomaticMigrations from "../../migrations/postgres/PostgresAutomaticMigrations.js";
6
- import { BaseConnection, BaseDriver, EntityTransaction, IBaseTransaction, IDbConnectionString, IDbReader, IQuery, IRecord, toQuery } from "../base/BaseDriver.js";
6
+ import { BaseConnection, BaseDriver, EntityTransaction, IDbConnectionString, IDbReader, IQuery, toQuery } from "../base/BaseDriver.js";
7
7
  import pg from "pg";
8
8
  import Cursor from "pg-cursor";
9
9
  export interface IPgSqlConnectionString extends IDbConnectionString {
@@ -118,6 +118,28 @@ export default class PostgreSqlDriver extends BaseDriver {
118
118
  }
119
119
  }
120
120
 
121
+ class PostgresTransaction extends EntityTransaction {
122
+
123
+ constructor(conn: PostgreSqlConnection, private tx: IPooledObject<pg.Client>) {
124
+ super(conn);
125
+ }
126
+
127
+ protected disposeTransaction() {
128
+ (this.conn as any).transaction = null;
129
+ return this.tx[Symbol.asyncDispose]();
130
+ }
131
+ protected commitTransaction() {
132
+ return this.tx.query("COMMIT") as any;
133
+ }
134
+ protected rollbackTransaction() {
135
+ return this.tx.query("ROLLBACK") as any;
136
+ }
137
+ protected beginTransaction() {
138
+ return this.tx.query("BEGIN") as any;
139
+ }
140
+
141
+ }
142
+
121
143
  class PostgreSqlConnection extends BaseConnection {
122
144
 
123
145
  public get isInTransaction() {
@@ -134,18 +156,6 @@ class PostgreSqlConnection extends BaseConnection {
134
156
  super(driver);
135
157
  }
136
158
 
137
- public async createTransaction(): Promise<EntityTransaction> {
138
- const tx = await this.getConnection();
139
- await tx.query("BEGIN");
140
- return new EntityTransaction({
141
- commit: () => tx.query("COMMIT"),
142
- rollback: () => tx.query("ROLLBACK"),
143
- dispose: () => {
144
- this.transaction = null;
145
- return tx[Symbol.asyncDispose]();
146
- }
147
- });
148
- }
149
159
 
150
160
  public automaticMigrations(): Migrations {
151
161
  return new PostgresAutomaticMigrations(this.compiler);
@@ -226,6 +236,11 @@ class PostgreSqlConnection extends BaseConnection {
226
236
  return client;
227
237
  }
228
238
 
239
+ protected async createDbTransaction(): Promise<EntityTransaction> {
240
+ const tx = this.transaction = await this.getConnection();
241
+ return new PostgresTransaction(this, tx);
242
+ }
243
+
229
244
  private async kill(id) {
230
245
  const client = new pg.Client(this.config);
231
246
  try {
@@ -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 { BaseConnection, BaseDriver, EntityTransaction, IBaseTransaction, IDbConnectionString, IDbReader, IQuery, IRecord, toQuery } from "../base/BaseDriver.js";
4
+ import { BaseConnection, BaseDriver, EntityTransaction, IDbConnectionString, IDbReader, IQuery, 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";
@@ -34,6 +34,39 @@ export default class SqlServerDriver extends BaseDriver {
34
34
  }
35
35
  }
36
36
 
37
+ const emptyResolve = Promise.resolve();
38
+
39
+ class SqlEntityTransaction extends EntityTransaction {
40
+
41
+ rolledBack: any;
42
+
43
+ constructor(conn: SqlServerConnection, private tx: sql.Transaction) {
44
+ super(conn);
45
+ tx.on("rollback", (aborted) => {
46
+ this.rolledBack = aborted;
47
+ });
48
+ }
49
+
50
+ protected async beginTransaction() {
51
+ this.tx = await this.tx.begin();
52
+ }
53
+
54
+ protected disposeTransaction(): Promise<void> {
55
+ (this.conn as any).transaction = null;
56
+ return emptyResolve;
57
+ }
58
+ protected async commitTransaction() {
59
+ return this.tx.commit();
60
+ }
61
+ protected async rollbackTransaction() {
62
+ if(this.rolledBack) {
63
+ return;
64
+ }
65
+ await this.tx.rollback();
66
+ }
67
+
68
+ }
69
+
37
70
  export class SqlServerConnection extends BaseConnection {
38
71
 
39
72
  private transaction: sql.Transaction;
@@ -114,22 +147,15 @@ export class SqlServerConnection extends BaseConnection {
114
147
  return value;
115
148
  }
116
149
 
117
- public async createTransaction(): Promise<EntityTransaction> {
118
- this.transaction = new sql.Transaction(await this.newConnection());
119
- let rolledBack = false;
120
- this.transaction.on("rollback", (aborted) => rolledBack = aborted);
121
- await this.transaction.begin();
122
- return new EntityTransaction({
123
- commit: () => this.transaction.commit(),
124
- rollback: async () => !rolledBack && await this.transaction.rollback(),
125
- dispose: () => this.transaction = void 0
126
- });
127
- }
128
-
129
150
  public automaticMigrations(): Migrations {
130
151
  return new SqlServerAutomaticMigrations(this.sqlQueryCompiler);
131
152
  }
132
153
 
154
+ protected async createDbTransaction(): Promise<EntityTransaction> {
155
+ const tx = this.transaction = new sql.Transaction(await this.newConnection());
156
+ return new SqlEntityTransaction(this, tx);
157
+ }
158
+
133
159
  protected async newRequest(signal: AbortSignal) {
134
160
  let request: sql.Request;
135
161
  if (this.transaction) {
@@ -12,6 +12,7 @@ import Inject, { ServiceProvider } from "../di/di.js";
12
12
  import EntityAccessError from "../common/EntityAccessError.js";
13
13
  import Logger from "../common/Logger.js";
14
14
  import { FilteredExpression } from "./events/FilteredExpression.js";
15
+ import { traceSymbol } from "../common/symbols/symbols.js";
15
16
 
16
17
  const isChanging = Symbol("isChanging");
17
18
 
@@ -121,32 +122,37 @@ export default class EntityContext {
121
122
  return 0;
122
123
  }
123
124
 
124
- return this.connection.runInTransaction(async () => {
125
- try {
125
+ await using tx = await this.connection.createTransaction();
126
+ const oldTraceSymbol = this[traceSymbol];
127
+ this[traceSymbol] = options?.trace;
128
+ try {
126
129
 
127
- if(!this.raiseEvents) {
128
- return await this.saveChangesInternalWithoutEvents(options);
129
- }
130
+ if(!this.raiseEvents) {
131
+ const rx = await this.saveChangesInternalWithoutEvents(options);
132
+ await tx.commit();
133
+ return rx;
134
+ }
130
135
 
131
- this[isChanging] = true;
132
- const r = await this.saveChangesInternal(options);
133
- const postSaveChanges = this.postSaveChangesQueue;
134
- this.postSaveChangesQueue = void 0;
135
- this[isChanging] = false;
136
- if (postSaveChanges?.length) {
137
- postSaveChanges.sort((a, b) => a.order - b.order);
138
- for (const { task } of postSaveChanges) {
139
- const p = task();
140
- if (p?.then) {
141
- await p;
142
- }
136
+ this[isChanging] = true;
137
+ const r = await this.saveChangesInternal(options);
138
+ const postSaveChanges = this.postSaveChangesQueue;
139
+ this.postSaveChangesQueue = void 0;
140
+ this[isChanging] = false;
141
+ if (postSaveChanges?.length) {
142
+ postSaveChanges.sort((a, b) => a.order - b.order);
143
+ for (const { task } of postSaveChanges) {
144
+ const p = task();
145
+ if (p?.then) {
146
+ await p;
143
147
  }
144
148
  }
145
- return r;
146
- } finally {
147
- this[isChanging] = false;
148
149
  }
149
- });
150
+ await tx.commit();
151
+ return r;
152
+ } finally {
153
+ this[isChanging] = false;
154
+ this[traceSymbol] = oldTraceSymbol;
155
+ }
150
156
  }
151
157
 
152
158
  public queuePostSaveTask(task: () => any, order = Number.MAX_SAFE_INTEGER) {
@@ -2,7 +2,7 @@ import type EntityContext from "./EntityContext.js";
2
2
  import type EntityType from "../entity-query/EntityType.js";
3
3
  import type { IBaseQuery, IEntityQuery, IFilterExpression } from "./IFilterWithParameter.js";
4
4
  import EntityQuery from "./EntityQuery.js";
5
- import { contextSymbol, modelSymbol } from "../common/symbols/symbols.js";
5
+ import { contextSymbol, modelSymbol, traceSymbol } from "../common/symbols/symbols.js";
6
6
  import { Expression, ExpressionAs, Identifier, InsertStatement, TableLiteral } from "../query/ast/Expressions.js";
7
7
  import { DirectSaveType } from "../drivers/base/BaseDriver.js";
8
8
  import IdentityService from "./identity/IdentityService.js";
@@ -233,6 +233,7 @@ export class EntitySource<T = any> {
233
233
  selectStatement.model = model;
234
234
  return new EntityQuery<T>({
235
235
  context,
236
+ trace: context[traceSymbol],
236
237
  type: model,
237
238
  selectStatement
238
239
  }) as any as IEntityQuery<T>;
@@ -224,32 +224,17 @@ export default class WorkflowStorage {
224
224
  async save(state: Partial<WorkflowItem>) {
225
225
  const db = new WorkflowContext(this.driver);
226
226
  const connection = db.connection;
227
- await connection.runInTransaction(async () => {
228
- // let w = await db.workflows.where(state, (p) => (x) => x.id === p.id).first();
229
- // if (!w) {
230
- // w = db.workflows.add(state);
231
- // w.taskGroup ||= "default";
232
- // }
233
-
234
- // for (const key in state) {
235
- // if (Object.prototype.hasOwnProperty.call(state, key)) {
236
- // const element = state[key];
237
- // w[key] = element;
238
- // }
239
- // }
240
-
241
- // w.state ||= "queued";
242
- // w.updated ??= DateTime.now;
243
- state.state ||= "queued";
244
- state.updated ??= DateTime.now;
245
- state.taskGroup ||= "default";
246
- // await db.saveChanges();
247
- if(state[loadedFromDb]) {
248
- await db.workflows.saveDirect({ mode: "update", changes: state });
249
- } else {
250
- await db.workflows.saveDirect({ mode: "upsert", changes: state });
251
- }
252
- });
227
+ await using tx = await connection.createTransaction();
228
+ state.state ||= "queued";
229
+ state.updated ??= DateTime.now;
230
+ state.taskGroup ||= "default";
231
+ // await db.saveChanges();
232
+ if(state[loadedFromDb]) {
233
+ await db.workflows.saveDirect({ mode: "update", changes: state });
234
+ } else {
235
+ await db.workflows.saveDirect({ mode: "upsert", changes: state });
236
+ }
237
+ await tx.commit();
253
238
  }
254
239
 
255
240
  async dequeue(taskGroup: string, signal?: AbortSignal) {