@zakodium/adonis-mongodb 0.18.1 → 0.20.0

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 (41) hide show
  1. package/README.md +2 -2
  2. package/lib/adonis-typings/container.d.ts +3 -3
  3. package/lib/adonis-typings/database.d.ts +5 -5
  4. package/lib/adonis-typings/index.d.ts +0 -6
  5. package/lib/adonis-typings/migration.d.ts +8 -1
  6. package/lib/adonis-typings/odm.d.ts +4 -8
  7. package/lib/adonis-typings/transaction.d.ts +39 -0
  8. package/lib/adonis-typings/transaction.js +2 -0
  9. package/lib/commands/MongodbListMigrations.js +6 -6
  10. package/lib/commands/MongodbMakeMigration.js +8 -6
  11. package/lib/commands/MongodbMigrate.js +23 -13
  12. package/lib/commands/util/MigrationCommand.js +24 -13
  13. package/lib/commands/util/transformMigrations.d.ts +1 -1
  14. package/lib/commands/util/transformMigrations.js +2 -2
  15. package/lib/providers/MongodbProvider.d.ts +2 -2
  16. package/lib/providers/MongodbProvider.js +11 -1
  17. package/lib/src/Auth/MongodbModelAuthProvider.d.ts +2 -4
  18. package/lib/src/Auth/MongodbModelAuthProvider.js +12 -6
  19. package/lib/src/Database/Connection.d.ts +5 -5
  20. package/lib/src/Database/Connection.js +34 -9
  21. package/lib/src/Database/ConnectionManager.d.ts +1 -2
  22. package/lib/src/Database/ConnectionManager.js +5 -2
  23. package/lib/src/Database/Database.d.ts +2 -3
  24. package/lib/src/Database/Database.js +5 -1
  25. package/lib/src/Database/TransactionEventEmitter.d.ts +8 -0
  26. package/lib/src/Database/TransactionEventEmitter.js +8 -0
  27. package/lib/src/Migration.js +24 -3
  28. package/lib/src/Model/Model.d.ts +3 -3
  29. package/lib/src/Model/Model.js +35 -16
  30. package/lib/src/Model/proxyHandler.js +1 -1
  31. package/lib/src/Odm/decorators.d.ts +1 -1
  32. package/package.json +11 -11
  33. package/src/Auth/MongodbModelAuthProvider.ts +2 -2
  34. package/src/Database/Connection.ts +49 -13
  35. package/src/Database/ConnectionManager.ts +3 -1
  36. package/src/Database/Database.ts +2 -2
  37. package/src/Database/TransactionEventEmitter.ts +10 -0
  38. package/src/Migration.ts +33 -4
  39. package/src/Model/Model.ts +15 -17
  40. package/src/Model/proxyHandler.ts +1 -1
  41. package/src/Odm/decorators.ts +1 -1
@@ -1,21 +1,23 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
 
3
3
  import { Exception } from '@poppinss/utils';
4
- import {
5
- MongoClient,
4
+ import type {
6
5
  Db,
7
6
  Collection,
8
7
  ClientSession,
9
8
  Document,
10
9
  TransactionOptions,
11
10
  } from 'mongodb';
11
+ import { MongoClient } from 'mongodb';
12
12
 
13
- import { LoggerContract } from '@ioc:Adonis/Core/Logger';
13
+ import type { LoggerContract } from '@ioc:Adonis/Core/Logger';
14
14
  import type {
15
15
  MongodbConnectionConfig,
16
16
  ConnectionContract,
17
17
  } from '@ioc:Zakodium/Mongodb/Database';
18
18
 
19
+ import { TransactionEventEmitter } from './TransactionEventEmitter';
20
+
19
21
  enum ConnectionStatus {
20
22
  CONNECTED = 'CONNECTED',
21
23
  DISCONNECTED = 'DISCONNECTED',
@@ -45,7 +47,7 @@ export declare interface Connection {
45
47
  ): this;
46
48
  }
47
49
 
48
- // eslint-disable-next-line unicorn/prefer-event-target, @typescript-eslint/no-unsafe-declaration-merging
50
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
49
51
  export class Connection extends EventEmitter implements ConnectionContract {
50
52
  public readonly client: MongoClient;
51
53
  public readonly name: string;
@@ -131,17 +133,51 @@ export class Connection extends EventEmitter implements ConnectionContract {
131
133
  }
132
134
 
133
135
  public async transaction<TResult>(
134
- handler: (session: ClientSession, db: Db) => Promise<TResult>,
136
+ handler: (
137
+ session: ClientSession,
138
+ db: Db,
139
+ transactionEventEmitter: TransactionEventEmitter,
140
+ ) => Promise<TResult>,
135
141
  options?: TransactionOptions,
136
142
  ): Promise<TResult> {
137
143
  const db = await this._ensureDb();
138
- let result: TResult;
139
- await this.client.withSession(async (session) => {
140
- return session.withTransaction(async (session) => {
141
- result = await handler(session, db);
142
- }, options);
143
- });
144
- // @ts-expect-error The `await` ensures `result` has a value.
145
- return result;
144
+
145
+ let session: ClientSession;
146
+ const emitter = new TransactionEventEmitter();
147
+
148
+ return this.client
149
+ .withSession((_session) =>
150
+ _session.withTransaction(async (_session) => {
151
+ session = _session;
152
+ return handler(session, db, emitter);
153
+ }, options),
154
+ )
155
+ .then(
156
+ (result) => {
157
+ // https://github.com/mongodb/node-mongodb-native/blob/v6.7.0/src/transactions.ts#L147
158
+ // https://github.com/mongodb/node-mongodb-native/blob/v6.7.0/src/transactions.ts#L54
159
+ // session.transaction.isCommitted is not a sufficient indicator,
160
+ // because it's true if transaction commits or aborts.
161
+ const isCommitted = session.transaction.isCommitted;
162
+ const isAborted =
163
+ // https://github.com/mongodb/node-mongodb-native/blob/v6.7.0/src/transactions.ts#L11
164
+ Reflect.get(session.transaction, 'state') === 'TRANSACTION_ABORTED';
165
+
166
+ emitter.emit(
167
+ isCommitted && isAborted ? 'abort' : 'commit',
168
+ session,
169
+ db,
170
+ );
171
+
172
+ return result;
173
+ // If an error occurs in this scope,
174
+ // it will not be caught by this then's error handler, but by the caller's catch.
175
+ // This is what we want, as an error in this scope should not trigger the abort event.
176
+ },
177
+ (error) => {
178
+ emitter.emit('abort', session, db, error);
179
+ throw error;
180
+ },
181
+ );
146
182
  }
147
183
  }
@@ -1,6 +1,6 @@
1
1
  import { Exception } from '@poppinss/utils';
2
2
 
3
- import { LoggerContract } from '@ioc:Adonis/Core/Logger';
3
+ import type { LoggerContract } from '@ioc:Adonis/Core/Logger';
4
4
  import type {
5
5
  ConnectionContract,
6
6
  ConnectionManagerContract,
@@ -78,6 +78,8 @@ export class ConnectionManager implements ConnectionManagerContract {
78
78
 
79
79
  public connect(connectionName: string): void {
80
80
  const connection = this.validateConnection(connectionName);
81
+ // Connection error is handled by the `error` event listener.
82
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
81
83
  connection.connection.connect();
82
84
  }
83
85
 
@@ -1,6 +1,6 @@
1
- import { ClientSession, Db, TransactionOptions } from 'mongodb';
1
+ import type { ClientSession, Db, TransactionOptions } from 'mongodb';
2
2
 
3
- import { LoggerContract } from '@ioc:Adonis/Core/Logger';
3
+ import type { LoggerContract } from '@ioc:Adonis/Core/Logger';
4
4
  import type {
5
5
  ConnectionContract,
6
6
  ConnectionManagerContract,
@@ -0,0 +1,10 @@
1
+ import { EventEmitter } from 'node:events';
2
+
3
+ import type { ClientSession, Db } from 'mongodb';
4
+
5
+ export interface TransactionEvents {
6
+ commit: [session: ClientSession, db: Db];
7
+ abort: [session: ClientSession, db: Db, error?: Error];
8
+ }
9
+
10
+ export class TransactionEventEmitter extends EventEmitter<TransactionEvents> {}
package/src/Migration.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { Logger } from '@poppinss/cliui/build/src/Logger';
2
- import {
3
- CreateIndexesOptions,
1
+ import type { Logger } from '@poppinss/cliui/build/src/Logger';
2
+ import type {
4
3
  ClientSession,
4
+ CreateIndexesOptions,
5
5
  Db,
6
- IndexSpecification,
7
6
  DropIndexesOptions,
7
+ IndexSpecification,
8
8
  } from 'mongodb';
9
9
 
10
10
  import type {
@@ -13,12 +13,18 @@ import type {
13
13
  } from '@ioc:Zakodium/Mongodb/Database';
14
14
 
15
15
  enum MigrationType {
16
+ DropCollection = 'DropCollection',
16
17
  CreateCollection = 'CreateCollection',
17
18
  DropIndex = 'DropIndex',
18
19
  CreateIndex = 'CreateIndex',
19
20
  Custom = 'Custom',
20
21
  }
21
22
 
23
+ interface DropCollectionOperation {
24
+ type: MigrationType.DropCollection;
25
+ collectionName: string;
26
+ }
27
+
22
28
  interface CreateCollectionOperation {
23
29
  type: MigrationType.CreateCollection;
24
30
  collectionName: string;
@@ -44,6 +50,7 @@ interface CustomOperation {
44
50
  }
45
51
 
46
52
  type MigrationOperation =
53
+ | DropCollectionOperation
47
54
  | CreateCollectionOperation
48
55
  | DropIndexOperation
49
56
  | CreateIndexOperation
@@ -68,6 +75,13 @@ export default function createMigration(Database: DatabaseContract): any {
68
75
  }
69
76
  }
70
77
 
78
+ public dropCollection(collectionName: string): void {
79
+ this.$operations.push({
80
+ type: MigrationType.DropCollection,
81
+ collectionName,
82
+ });
83
+ }
84
+
71
85
  public createCollection(collectionName: string): void {
72
86
  this.$operations.push({
73
87
  type: MigrationType.CreateCollection,
@@ -114,6 +128,7 @@ export default function createMigration(Database: DatabaseContract): any {
114
128
  await this._dropIndexes(session);
115
129
  await this._createIndexes(session);
116
130
  await this._executeDeferred(session);
131
+ await this._dropCollections();
117
132
  }
118
133
 
119
134
  private async _listCollections() {
@@ -128,6 +143,14 @@ export default function createMigration(Database: DatabaseContract): any {
128
143
  return this.$collectionList;
129
144
  }
130
145
 
146
+ private async _dropCollections(): Promise<void> {
147
+ const db = await this.$connection.database();
148
+ for (const op of this.$operations.filter(isDropCollection)) {
149
+ this.$logger.info(`Dropping collection ${op.collectionName}`);
150
+ await db.dropCollection(op.collectionName);
151
+ }
152
+ }
153
+
131
154
  private async _createCollections(session: ClientSession): Promise<void> {
132
155
  const db = await this.$connection.database();
133
156
  for (const op of this.$operations.filter(isCreateCollection)) {
@@ -179,6 +202,12 @@ export default function createMigration(Database: DatabaseContract): any {
179
202
  return Migration;
180
203
  }
181
204
 
205
+ function isDropCollection(
206
+ op: MigrationOperation,
207
+ ): op is DropCollectionOperation {
208
+ return op.type === MigrationType.DropCollection;
209
+ }
210
+
182
211
  function isCreateCollection(
183
212
  op: MigrationOperation,
184
213
  ): op is CreateCollectionOperation {
@@ -2,7 +2,7 @@ import assert from 'node:assert';
2
2
 
3
3
  import { defineStaticProperty, Exception } from '@poppinss/utils';
4
4
  import { cloneDeep, isEqual, pickBy, snakeCase } from 'lodash';
5
- import {
5
+ import type {
6
6
  BulkWriteOptions,
7
7
  ClientSession,
8
8
  Collection,
@@ -19,18 +19,18 @@ import {
19
19
  } from 'mongodb';
20
20
  import pluralize from 'pluralize';
21
21
 
22
- import { DatabaseContract } from '@ioc:Zakodium/Mongodb/Database';
23
- import {
24
- MongodbDocument,
25
- QueryContract,
26
- NoExtraProperties,
27
- ModelAttributes,
22
+ import type { DatabaseContract } from '@ioc:Zakodium/Mongodb/Database';
23
+ import type {
24
+ ComputedOptions,
25
+ FieldOptions,
26
+ ForbiddenQueryOptions,
28
27
  ModelAdapterOptions,
28
+ ModelAttributes,
29
29
  ModelDocumentOptions,
30
- FieldOptions,
30
+ MongodbDocument,
31
+ NoExtraProperties,
32
+ QueryContract,
31
33
  QuerySortObject,
32
- ForbiddenQueryOptions,
33
- ComputedOptions,
34
34
  } from '@ioc:Zakodium/Mongodb/Odm';
35
35
 
36
36
  import { proxyHandler } from './proxyHandler';
@@ -200,7 +200,9 @@ class Query<ModelType extends typeof BaseModel>
200
200
  public async explain(verbosity?: ExplainVerbosityLike): Promise<Document> {
201
201
  const collection = await this.ModelConstructor.getCollection();
202
202
  const driverOptions = this.getDriverOptions();
203
- return collection.find(this.filter, driverOptions).explain(verbosity);
203
+ return collection
204
+ .find(this.filter, driverOptions)
205
+ .explain(verbosity as ExplainVerbosityLike);
204
206
  }
205
207
 
206
208
  public async *[Symbol.asyncIterator](): AsyncIterableIterator<
@@ -239,10 +241,6 @@ function ensureSort(options?: FindOptions): void {
239
241
  };
240
242
  }
241
243
 
242
- function hasOwn(object: unknown, key: string): boolean {
243
- return Object.prototype.hasOwnProperty.call(object, key);
244
- }
245
-
246
244
  interface DataToSet {
247
245
  [key: string]: unknown;
248
246
  createdAt: Date;
@@ -358,7 +356,7 @@ export class BaseModel {
358
356
  * Define the property when not defined on self. This makes sure that all
359
357
  * subclasses boot on their own.
360
358
  */
361
- if (!hasOwn(this, 'booted')) {
359
+ if (!Object.hasOwn(this, 'booted')) {
362
360
  this.booted = false;
363
361
  }
364
362
 
@@ -775,7 +773,7 @@ export class BaseModel {
775
773
  }
776
774
 
777
775
  export class BaseAutoIncrementModel extends BaseModel {
778
- public readonly _id: number;
776
+ declare public readonly _id: number;
779
777
 
780
778
  public async save(
781
779
  options?: ModelDocumentOptions<InsertOneOptions>,
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { BaseModel } from './Model';
2
+ import type { BaseModel } from './Model';
3
3
 
4
4
  export const proxyHandler: ProxyHandler<any> = {
5
5
  get(target: any, prop: string | symbol, receiver: any) {
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  ComputedDecorator,
3
3
  ComputedOptions,
4
4
  FieldDecorator,