nongo-driver 2.12.6 → 3.1.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 (57) hide show
  1. package/dist/atlas-api.js +6 -6
  2. package/dist/atlas-api.js.map +1 -1
  3. package/dist/cursor-iterator.d.ts +1 -2
  4. package/dist/cursor-iterator.js +3 -1
  5. package/dist/cursor-iterator.js.map +1 -1
  6. package/dist/generate-interfaces.js +24 -7
  7. package/dist/generate-interfaces.js.map +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +33 -17
  10. package/dist/index.js.map +1 -1
  11. package/dist/interface/atlas/field.d.ts +1 -1
  12. package/dist/interface/atlas/field.js +1 -0
  13. package/dist/interface/atlas/field.js.map +1 -1
  14. package/dist/interface/atlas/index.js +15 -4
  15. package/dist/interface/atlas/index.js.map +1 -1
  16. package/dist/interface/index.d.ts +1 -10
  17. package/dist/interface/index.js +15 -4
  18. package/dist/interface/index.js.map +1 -1
  19. package/dist/model.d.ts +25 -13
  20. package/dist/model.js +76 -34
  21. package/dist/model.js.map +1 -1
  22. package/dist/mongo-index.d.ts +2 -3
  23. package/dist/mongo-index.js.map +1 -1
  24. package/dist/nongo.d.ts +5 -2
  25. package/dist/nongo.js +45 -21
  26. package/dist/nongo.js.map +1 -1
  27. package/dist/revision-model.d.ts +5 -0
  28. package/dist/revision-model.js +24 -0
  29. package/dist/revision-model.js.map +1 -0
  30. package/dist/schema-parser.js +10 -10
  31. package/dist/schema-parser.js.map +1 -1
  32. package/dist/ts-interface-generator.js +18 -2
  33. package/dist/ts-interface-generator.js.map +1 -1
  34. package/dist/validator.js +1 -1
  35. package/dist/validator.js.map +1 -1
  36. package/jest.config.js +0 -5
  37. package/package.json +19 -25
  38. package/src/atlas-api.ts +11 -40
  39. package/src/cursor-iterator.ts +1 -3
  40. package/src/index.ts +1 -1
  41. package/src/interface/index.ts +0 -11
  42. package/src/model.ts +92 -44
  43. package/src/mongo-index.ts +1 -3
  44. package/src/nongo.ts +34 -6
  45. package/src/revision-model.ts +22 -0
  46. package/test/database-helper.ts +26 -13
  47. package/test/model-versioning.test.ts +83 -0
  48. package/test/models/dummy-model-changed-obj.ts +12 -0
  49. package/test/models/dummy-model-obj.ts +40 -0
  50. package/test/models/dummy-model-with-max-index-obj.ts +195 -0
  51. package/test/models/dummy-model-with-revision-obj.ts +13 -0
  52. package/test/models/dummy-model-with-revision.nongo.ts +22 -0
  53. package/test/models/text-index-model-modified-obj.ts +12 -0
  54. package/test/models/text-index-model-obj.ts +11 -0
  55. package/test/models/text-index-model-too-long-obj.ts +11 -0
  56. package/test/nongo-multi.test.ts +2 -5
  57. package/test/nongo.test.ts +125 -172
package/src/atlas-api.ts CHANGED
@@ -1,16 +1,12 @@
1
1
  import deepEqual from 'deep-equal';
2
2
  import rp from 'request-promise';
3
3
  import Logger from 'wbb-logger';
4
- import {
5
- Analyzer,
6
- AtlasParams,
7
- AtlasSearchIndex,
8
- CreateSearchIndex,
9
- } from './interface/atlas';
4
+ import {Analyzer, AtlasParams, AtlasSearchIndex, CreateSearchIndex} from './interface/atlas';
10
5
 
11
6
  const logger = new Logger('atlas-api.ts');
12
7
 
13
8
  export default class AtlasApi {
9
+
14
10
  private readonly ftsUrl: string;
15
11
  private readonly defaultOptions;
16
12
 
@@ -38,10 +34,7 @@ export default class AtlasApi {
38
34
  });
39
35
  }
40
36
 
41
- public async getSearchIndexes(
42
- db: string,
43
- collection: string,
44
- ): Promise<AtlasSearchIndex[]> {
37
+ public async getSearchIndexes(db: string, collection: string): Promise<AtlasSearchIndex[]> {
45
38
  const url = `${this.ftsUrl}/indexes/${db}/${collection}`;
46
39
  return rp({
47
40
  ...this.defaultOptions,
@@ -49,11 +42,7 @@ export default class AtlasApi {
49
42
  });
50
43
  }
51
44
 
52
- public async createSearchIndex(
53
- db: string,
54
- collection: string,
55
- index: CreateSearchIndex,
56
- ): Promise<any> {
45
+ public async createSearchIndex(db: string, collection: string, index: CreateSearchIndex): Promise<any> {
57
46
  this.logIndexChange(db, collection, 'Creating', index.name);
58
47
  const url = `${this.ftsUrl}/indexes`;
59
48
  return rp({
@@ -68,11 +57,7 @@ export default class AtlasApi {
68
57
  });
69
58
  }
70
59
 
71
- public async ensureSearchIndexes(
72
- db: string,
73
- collection: string,
74
- createParams: CreateSearchIndex[],
75
- ): Promise<AtlasSearchIndex[]> {
60
+ public async ensureSearchIndexes(db: string, collection: string, createParams: CreateSearchIndex[]): Promise<AtlasSearchIndex[]> {
76
61
  const existingIndexes = await this.getSearchIndexes(db, collection);
77
62
  const toRemove = new Map<string, AtlasSearchIndex>();
78
63
  existingIndexes.forEach((e) => toRemove.set(e.name, e));
@@ -87,6 +72,7 @@ export default class AtlasApi {
87
72
  } else {
88
73
  await this.createSearchIndex(db, collection, cParams);
89
74
  }
75
+
90
76
  }
91
77
 
92
78
  for (const index of toRemove.values()) {
@@ -96,12 +82,7 @@ export default class AtlasApi {
96
82
  return await this.getSearchIndexes(db, collection);
97
83
  }
98
84
 
99
- public async updateSearchIndex(
100
- db: string,
101
- collection: string,
102
- existingIndex: AtlasSearchIndex,
103
- createParams: CreateSearchIndex,
104
- ): Promise<any> {
85
+ public async updateSearchIndex(db: string, collection: string, existingIndex: AtlasSearchIndex, createParams: CreateSearchIndex): Promise<any> {
105
86
  if (deepEqual(existingIndex.mappings, createParams.mappings)) {
106
87
  return;
107
88
  }
@@ -122,11 +103,7 @@ export default class AtlasApi {
122
103
  });
123
104
  }
124
105
 
125
- public async deleteSearchIndex(
126
- db: string,
127
- collection: string,
128
- existingIndex: AtlasSearchIndex,
129
- ): Promise<any> {
106
+ public async deleteSearchIndex(db: string, collection: string, existingIndex: AtlasSearchIndex): Promise<any> {
130
107
  this.logIndexChange(db, collection, 'Deleting', existingIndex.name);
131
108
  const url = `${this.ftsUrl}/indexes/${existingIndex.indexID}`;
132
109
  return rp({
@@ -136,14 +113,8 @@ export default class AtlasApi {
136
113
  });
137
114
  }
138
115
 
139
- private logIndexChange(
140
- db: string,
141
- collection: string,
142
- action: string,
143
- indexName: string,
144
- ) {
145
- logger.info(
146
- `${action} search index "${indexName}" for collection ${db}.${collection}`,
147
- );
116
+ private logIndexChange(db: string, collection: string, action: string, indexName: string) {
117
+ logger.info(`${action} search index "${indexName}" for collection ${db}.${collection}`);
148
118
  }
119
+
149
120
  }
@@ -1,8 +1,6 @@
1
- import {Cursor} from 'mongodb';
2
-
3
1
  export default class CursorIterator<T = any> implements AsyncIterator<T> {
4
2
 
5
- constructor(private cursor: Cursor, private map = (next) => next) {
3
+ constructor(private cursor, private map = (next) => next) {
6
4
  }
7
5
 
8
6
  public [Symbol.asyncIterator] = () => this;
package/src/index.ts CHANGED
@@ -11,4 +11,4 @@ export {default as generateInterfaces} from './generate-interfaces';
11
11
  export {default as NongoMap} from './nongo-map';
12
12
  export * from './interface';
13
13
  export {default as AtlasApi} from './atlas-api';
14
- export {ObjectId, MongoClient} from 'mongodb';
14
+ export {ObjectId, MongoClient, SortDirection, CollectionInfo, Db, DeleteResult, FindOptions, Filter, Document, UpdateFilter, UpdateOptions} from 'mongodb';
@@ -4,14 +4,3 @@ export * from './atlas';
4
4
  * Defines an object that can be created via the 'new' keyword
5
5
  */
6
6
  export type Newable<T = any> = new(...args: any[]) => T;
7
-
8
- export interface NongoWriteResult {
9
- nModified: number;
10
- ok: number;
11
- n: number;
12
- }
13
-
14
- export interface NongoDeleteResult {
15
- ok?: number;
16
- n?: number;
17
- }
package/src/model.ts CHANGED
@@ -1,17 +1,30 @@
1
1
  import clone from 'clone-deep';
2
- import cmd from 'cmd-promise';
3
- import {BulkWriteOpResultObject, CollectionBulkWriteOptions, Db, ObjectId} from 'mongodb';
4
- import Logger from 'wbb-logger';
2
+ import {AggregateOptions, AnyBulkWriteOperation, BulkWriteOptions, BulkWriteResult, CountDocumentsOptions, Db, DeleteOptions, DeleteResult, Document, Filter, FindOptions, ObjectId, UpdateFilter, UpdateOptions, UpdateResult} from 'mongodb';
3
+ import {diff} from 'just-diff';
5
4
 
6
5
  import CursorIterator from './cursor-iterator';
7
- import {CreateSearchIndex, NongoDeleteResult, NongoWriteResult} from './interface';
6
+ import {CreateSearchIndex} from './interface';
8
7
  import MongoIndex from './mongo-index';
9
8
  import Nongo from './nongo';
10
9
  import Validator from './validator';
11
10
 
12
- const logger = new Logger('schema-parser.ts');
11
+ const DEFAULT_PATHS = ['_id', 'revision', 'history', 'diffs']
13
12
 
14
- export default abstract class Model<T extends {_id?: ObjectId} = any> {
13
+ function excludePathsFromDiff(diff, excludedPaths = DEFAULT_PATHS) {
14
+ return diff.filter((d) => {
15
+ if (!excludedPaths.includes(d.path[0])) {
16
+ return true;
17
+ }
18
+ })
19
+ }
20
+
21
+ export interface RevisionHistory {
22
+ id: string;
23
+ revision: string;
24
+ created: Date;
25
+ }
26
+
27
+ export default abstract class Model<T extends {_id?: ObjectId, revision?: string, history?: RevisionHistory[], diffs?: any} = any> {
15
28
  // {@code instanceof} can be a bit funny when importing classes across
16
29
  // modules so this is an explicit type guard we can use
17
30
  public isNongoModel: true = true;
@@ -19,6 +32,7 @@ export default abstract class Model<T extends {_id?: ObjectId} = any> {
19
32
  public db: Db;
20
33
  public name: string;
21
34
  public dynamic: boolean;
35
+ private collectionRevision: string | null;
22
36
 
23
37
  constructor(public nongo: Nongo, public obj: T) {
24
38
  // Set this.db if nongo given
@@ -32,11 +46,21 @@ export default abstract class Model<T extends {_id?: ObjectId} = any> {
32
46
  }
33
47
 
34
48
  this.collection = this.defineCollection();
49
+ this.collectionRevision =
50
+ this.schemaVersioningEnabled() ? `${this.defineCollection()}${Nongo.REVISION}` : null;
35
51
  this.name = this.constructor.name;
36
52
  this.dynamic = false;
37
53
  this.initStructure();
38
54
  }
39
55
 
56
+ public getCollectionRevision (): string {
57
+ return this.collectionRevision;
58
+ }
59
+
60
+ protected schemaVersioningEnabled(): boolean {
61
+ return false;
62
+ }
63
+
40
64
  public getMongoIndexes(): MongoIndex[] {
41
65
  return [];
42
66
  }
@@ -47,18 +71,7 @@ export default abstract class Model<T extends {_id?: ObjectId} = any> {
47
71
 
48
72
  public async validate() {
49
73
  const schema = this.nongo.schema[this.name];
50
- return await new Validator(schema, this).validate();
51
- }
52
-
53
- public async dumpCollection(dir: string, gzip = false): Promise<void> {
54
- logger.info(`Taking dump of ${this.name}...`);
55
-
56
- const script =
57
- `mkdir -p ${dir};` +
58
- `mongodump --uri "${this.nongo.uri()}" -c ${this.name} ${gzip ? '--gzip' : ''} --out ${dir};`;
59
-
60
- await cmd(script);
61
- logger.info(`Dump written to ${dir}...`);
74
+ return new Validator(schema, this).validate();
62
75
  }
63
76
 
64
77
  public idString(): string {
@@ -85,11 +98,30 @@ export default abstract class Model<T extends {_id?: ObjectId} = any> {
85
98
  }
86
99
  }
87
100
 
101
+ let revisionObj;
102
+ const revisionObjId = new ObjectId();
103
+ if (this.collectionRevision) {
104
+ revisionObj = await this.db.collection(this.collection).findOne({_id: this.obj._id});
105
+ this.obj.revision = Nongo.revisionNumber(revisionObj?.revision);
106
+ this.obj.history = Nongo.revisionHistory(revisionObj, revisionObjId);
107
+
108
+ // get diffs
109
+ if (revisionObj) {
110
+ delete this.obj.diffs;
111
+ const diffs = diff(revisionObj, this.obj);
112
+ this.obj.diffs = excludePathsFromDiff(diffs);
113
+ }
114
+ }
88
115
  // Upsert
89
- await this.db
116
+ const UpsertResult = await this.db
90
117
  .collection(this.collection)
91
118
  .replaceOne({_id: this.obj._id}, this.obj, {upsert: true});
92
119
 
120
+ if (UpsertResult.modifiedCount === 1 && this.collectionRevision) {
121
+ revisionObj._id = revisionObjId;
122
+ await this.db.collection(this.collectionRevision).insertOne(revisionObj);
123
+ }
124
+
93
125
  // Return the saved model
94
126
  return this;
95
127
  }
@@ -160,7 +192,24 @@ export default abstract class Model<T extends {_id?: ObjectId} = any> {
160
192
  }
161
193
 
162
194
  public async byId(id) {
163
- return await this.findOne({_id: id});
195
+ return this.findOne({_id: id});
196
+ }
197
+
198
+ public async getRevisions () {
199
+ return this.toArray(await this.findIterator({}, {sort: {revision: 'descending'}}, this.collectionRevision));
200
+ }
201
+
202
+ public async getRevision (query: Filter<Document>) {
203
+ const doc = await this.db.collection(this.collectionRevision).findOne(query);
204
+
205
+ // Return null if no doc was found
206
+ if (doc == null) {
207
+ return null;
208
+ }
209
+
210
+ // Otherwise return the doc as a model
211
+ // @ts-ignore
212
+ return new this.constructor(this.nongo, doc);
164
213
  }
165
214
 
166
215
  /*
@@ -168,7 +217,7 @@ export default abstract class Model<T extends {_id?: ObjectId} = any> {
168
217
  * @param options: the query options e.g. sorting, limiting results etc.
169
218
  * @return the results of the query as an array of models.
170
219
  */
171
- public async find(query: any = {}, options?: any): Promise<this[]> {
220
+ public async find(query: Filter<Document> = {}, options?: FindOptions<Document>): Promise<this[]> {
172
221
  return this.toArray(await this.findIterator(query, options));
173
222
  }
174
223
 
@@ -177,9 +226,9 @@ export default abstract class Model<T extends {_id?: ObjectId} = any> {
177
226
  * @param options?: CollectionBulkWriteOptions
178
227
  */
179
228
  public async bulkWrite(
180
- operations: object[],
181
- options?: CollectionBulkWriteOptions,
182
- ): Promise<BulkWriteOpResultObject> {
229
+ operations: AnyBulkWriteOperation<Document>[],
230
+ options?: BulkWriteOptions,
231
+ ): Promise<BulkWriteResult> {
183
232
  return this.db.collection(this.collection).bulkWrite(operations, options);
184
233
  }
185
234
 
@@ -188,12 +237,13 @@ export default abstract class Model<T extends {_id?: ObjectId} = any> {
188
237
  * @param options: the query options e.g. sorting, limiting results etc.
189
238
  * @return the results of the query as an {@link AsyncIterator}.
190
239
  */
191
- public async findIterator(
192
- query: any = {},
193
- options?: any,
240
+ public async findIterator(
241
+ query: Filter<Document> = {},
242
+ options?: FindOptions<Document>,
243
+ collection = this.collection,
194
244
  ): Promise<CursorIterator<this>> {
195
245
  query = this.prepareQuery(query);
196
- const cursor = this.db.collection(this.collection).find(query, options);
246
+ const cursor = this.db.collection(collection).find(query, options);
197
247
  return new CursorIterator<this>(
198
248
  cursor,
199
249
  // @ts-ignore
@@ -205,7 +255,7 @@ export default abstract class Model<T extends {_id?: ObjectId} = any> {
205
255
  * @param query: A mongo db query.
206
256
  * @return the result of the query as a model.
207
257
  */
208
- public async findOne(query: any = {}): Promise<this> {
258
+ public async findOne(query: Filter<Document> = {}): Promise<this> {
209
259
  query = this.prepareQuery(query);
210
260
  const doc = await this.db.collection(this.collection).findOne(query);
211
261
 
@@ -219,19 +269,18 @@ export default abstract class Model<T extends {_id?: ObjectId} = any> {
219
269
  return new this.constructor(this.nongo, doc);
220
270
  }
221
271
 
222
- public async remove(query: any): Promise<NongoDeleteResult> {
272
+ public async remove(query: Filter<Document>, options?: DeleteOptions): Promise<DeleteResult> {
223
273
  query = this.prepareQuery(query);
224
- const res = await this.db.collection(this.collection).deleteMany(query);
225
- return res.result;
274
+ return this.db.collection(this.collection).deleteMany(query, options);
226
275
  }
227
276
 
228
- public async aggregate(pipeline: any, options?: any): Promise<any[]> {
229
- return await this.toArray(await this.aggregateIterator(pipeline, options));
277
+ public async aggregate(pipeline: Document[], options?: AggregateOptions): Promise<any[]> {
278
+ return this.toArray(await this.aggregateIterator(pipeline, options));
230
279
  }
231
280
 
232
281
  public async aggregateIterator(
233
- pipeline: any,
234
- options?: any,
282
+ pipeline: Document[],
283
+ options?: AggregateOptions,
235
284
  ): Promise<CursorIterator<any>> {
236
285
  options = options || {};
237
286
  if (options.allowDiskUse == null) {
@@ -244,20 +293,19 @@ export default abstract class Model<T extends {_id?: ObjectId} = any> {
244
293
  return new CursorIterator(cursor);
245
294
  }
246
295
 
247
- public async distinct(field: string) {
248
- return await this.db.collection(this.collection).distinct(field, {});
296
+ public async distinct(field: string, filter: Filter<Document> = {}) {
297
+ return this.db.collection(this.collection).distinct(field, filter);
249
298
  }
250
299
 
251
- public async update(query: any, updates: any): Promise<NongoWriteResult> {
300
+ public async update(query: Filter<Document>, updates: UpdateFilter<Document>, options?: UpdateOptions): Promise<UpdateResult<Document>> {
252
301
  query = this.prepareQuery(query);
253
- const res = await this.db
302
+ return this.db
254
303
  .collection(this.collection)
255
- .updateMany(query, updates);
256
- return res.result;
304
+ .updateMany(query, updates, options);
257
305
  }
258
306
 
259
- public async count(query = {}): Promise<number> {
260
- return await this.db.collection(this.collection).countDocuments(query);
307
+ public async count(query: Document = {}, options?: CountDocumentsOptions): Promise<number> {
308
+ return this.db.collection(this.collection).countDocuments(query, options);
261
309
  }
262
310
 
263
311
  /*
@@ -1,5 +1,3 @@
1
- import {IndexOptions} from 'mongodb';
2
-
3
1
  /**
4
2
  * Very basic model used to hold information on a mongo index.
5
3
  */
@@ -13,7 +11,7 @@ export default class MongoIndex {
13
11
  * @param options IndexOptions - options for the index these match the options defined here:
14
12
  * https://mongodb.github.io/node-mongodb-native/api-generated/collection.html#ensureindex
15
13
  */
16
- constructor(index: {[key: string]: 1 | -1 | 'text'}, public readonly options: IndexOptions = {}, validateName = true) {
14
+ constructor(index: {[key: string]: 1 | -1 | 'text'}, public readonly options: any = {}, validateName = true) {
17
15
  // Delete any keys from options that have an undefined value. We do this to standardise the options so they can
18
16
  // easily be compared
19
17
  for (const key of Object.keys(options)) {
package/src/nongo.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import deepEqual from 'deep-equal';
2
2
  import {Collection, Db, MongoClient, ObjectId} from 'mongodb';
3
3
  import Logger from 'wbb-logger';
4
+ import incVersion from 'semver/functions/inc';
4
5
  import AtlasApi from './atlas-api';
5
6
  import {Newable} from './interface';
6
- import Model from './model';
7
+ import Model, { RevisionHistory } from './model';
7
8
  import MongoIndex from './mongo-index';
8
9
  import MongoIndexSet from './mongo-index-set';
9
10
  import NongoParams from './nongo-params';
@@ -28,11 +29,36 @@ export default class Nongo {
28
29
  return id;
29
30
  }
30
31
 
32
+ public static revisionNumber(revision: string): string {
33
+ if (!revision) {
34
+ return '0.0.1';
35
+ }
36
+ return incVersion(revision, 'patch');
37
+ }
38
+
39
+ public static revisionHistory(model, revisionId): RevisionHistory[] {
40
+ if (!model?.history) {
41
+ return [];
42
+ } else {
43
+ // we don't want to mutate the original array
44
+ const array = [...model.history];
45
+ array.push(
46
+ {
47
+ id: revisionId.toString(),
48
+ revision: model.revision,
49
+ created: new Date(),
50
+ }
51
+ )
52
+ return array;
53
+ }
54
+ }
55
+
31
56
  public schema: any = {};
32
57
  public validKeys: any = {};
33
58
  public noStripKeys: any = {};
34
59
  public db: Db;
35
60
  public dbName: string;
61
+ public static REVISION = "Revision";
36
62
 
37
63
  // Parallel to {@code this.modelClasses} and {@code this.schemaParsers}
38
64
  private instances: Model[];
@@ -60,11 +86,7 @@ export default class Nongo {
60
86
 
61
87
  public async connect() {
62
88
  // Connect to the database
63
- this.mongoClient = this.mongoClient ?? await MongoClient.connect(this.uri(), {
64
- useNewUrlParser: true,
65
- useUnifiedTopology: true,
66
- poolSize: this.poolSize,
67
- });
89
+ this.mongoClient = this.mongoClient ?? await new MongoClient(this.uri(), {minPoolSize: this.poolSize}).connect();
68
90
  this.db = this.mongoClient.db(this.params.db);
69
91
  this.dbName = this.params.db;
70
92
 
@@ -106,6 +128,12 @@ export default class Nongo {
106
128
  await this.db.createCollection(collName);
107
129
  }
108
130
 
131
+ // Create the revision collection if it doesn't exist
132
+ const revisionCollName = instance.getCollectionRevision();
133
+ if (revisionCollName && !collectionNames.includes(revisionCollName)) {
134
+ await this.db.createCollection(revisionCollName);
135
+ }
136
+
109
137
  // Add field to {@code this.newGenerator} with a function as a value that can be
110
138
  // used to init a new model
111
139
  this.newGeneratorMap[instance.name] = (obj) => {
@@ -0,0 +1,22 @@
1
+ import { Model } from ".";
2
+
3
+ export default abstract class RevisionModel<T = any> extends Model<T> {
4
+ public defineSchema(): any {
5
+ return {
6
+ revision: {
7
+ type: "string",
8
+ },
9
+ history: {
10
+ type: [{
11
+ id: {type: "string"},
12
+ revision: {type: "string"},
13
+ created: {type: "date"},
14
+ }]
15
+ }
16
+ };
17
+ }
18
+
19
+ public schemaVersioningEnabled(): boolean {
20
+ return true;
21
+ }
22
+ }
@@ -2,9 +2,22 @@ import {MongoClient} from 'mongodb';
2
2
  import {MongoMemoryServer} from 'mongodb-memory-server';
3
3
  import {Newable, NongoParams} from '../src';
4
4
  import Nongo from '../src/nongo';
5
+ import Logger from 'wbb-logger';
6
+
7
+ const logger = new Logger("database-helper.ts");
8
+
9
+ let mongoMemoryServer: MongoMemoryServer;
10
+
11
+ const getInMemoryServer = async () => {
12
+ if (!mongoMemoryServer) {
13
+ logger.info("Using in memory server.");
14
+ mongoMemoryServer = await MongoMemoryServer.create();
15
+ }
16
+ return mongoMemoryServer;
17
+ };
5
18
 
6
- const mongoMemoryServer = new MongoMemoryServer();
7
19
  export default class DatabaseHelper {
20
+ public static DB_NAME = 'testing';
8
21
  public static async getNongoInstance(models: Newable[]): Promise<Nongo> {
9
22
  return await new Nongo(
10
23
  await DatabaseHelper.getNongoParams(),
@@ -13,27 +26,27 @@ export default class DatabaseHelper {
13
26
  }
14
27
 
15
28
  public static async getNongoParams(): Promise<NongoParams> {
16
- return {
17
- host: 'localhost',
18
- port: await mongoMemoryServer.getPort(),
19
- db: await mongoMemoryServer.getDbName(),
20
- };
29
+ logger.info("Fetching in memory server.");
30
+ const mongod = await getInMemoryServer();
31
+ return {
32
+ protocol: null,
33
+ uri: mongod.getUri(),
34
+ db: DatabaseHelper.DB_NAME,
35
+ };
21
36
  }
22
37
 
23
38
  public static async getClient(): Promise<MongoClient> {
24
- return MongoClient.connect(await mongoMemoryServer.getUri(), {
25
- useNewUrlParser: true,
26
- useUnifiedTopology: true,
27
- });
39
+ const mongod = await getInMemoryServer();
40
+ return MongoClient.connect(await mongod.getUri());
28
41
  }
29
42
 
30
- public static async getDbName(): Promise<string> {
31
- return await mongoMemoryServer.getDbName();
43
+ public static getDbName(): string {
44
+ return DatabaseHelper.DB_NAME;
32
45
  }
33
46
 
34
47
  public static async drop(): Promise<void> {
35
48
  const client = await DatabaseHelper.getClient();
36
- const db = client.db(await DatabaseHelper.getDbName());
49
+ const db = client.db(DatabaseHelper.getDbName());
37
50
  await db.dropDatabase();
38
51
  await client.close();
39
52
  }
@@ -0,0 +1,83 @@
1
+ import Logger from 'wbb-logger';
2
+ import { Nongo } from '../src';
3
+ import DatabaseHelper from './database-helper';
4
+ import DummyModelWithRevisionObj from './models/dummy-model-with-revision-obj';
5
+ import DummyModelWithRevision from './models/dummy-model-with-revision.nongo';
6
+ import DummyModel from './models/dummy-model.nongo';
7
+
8
+ const logger = new Logger('model-versioning.test.ts');
9
+
10
+ const models = [DummyModel, DummyModelWithRevision];
11
+
12
+ let nongo: Nongo;
13
+
14
+ const print = (model) => {
15
+ model.forEach((m) => console.log(JSON.stringify(m.obj, null, 2)))
16
+ }
17
+
18
+ // Catch unhanded promise rejections and log them
19
+ process.on('unhandledRejection', (reason) => {
20
+ logger.error(reason);
21
+ });
22
+
23
+ describe('Testing nongo revisioning', () => {
24
+
25
+ beforeAll(async () => {
26
+ nongo = await DatabaseHelper.getNongoInstance(models);
27
+ })
28
+
29
+ afterAll(async () => {
30
+ // remove everything
31
+ })
32
+
33
+ it('Should create revision collection on DummyModelWithRevision', async () => {
34
+ const collections = await nongo.db.collections();
35
+ const collectionsNames = collections.map(coll => coll.collectionName);
36
+ expect(collectionsNames.length).toEqual(3);
37
+ expect(collectionsNames.includes('DummyModelWithRevisionRevision')).toEqual(true)
38
+ expect(collectionsNames.includes('DummyModelRevision')).toEqual(false)
39
+ })
40
+
41
+ it('Should have revision 0.0.1', async () => {
42
+ const obj: DummyModelWithRevisionObj = {
43
+ name: 'John',
44
+ created: new Date(),
45
+ };
46
+ const obj2: DummyModelWithRevisionObj = {
47
+ name: 'Leo',
48
+ age: 35,
49
+ created: new Date(),
50
+ };
51
+ const model = await nongo.new(DummyModelWithRevision, obj).save();
52
+ const model2 = await nongo.new(DummyModelWithRevision, obj2).save();
53
+ expect(model.obj.revision).toEqual('0.0.1');
54
+ expect(model2.obj.revision).toEqual('0.0.1');
55
+ })
56
+
57
+ it('Should save revision doc in revision collection', async () => {
58
+ const model = await nongo.col(DummyModelWithRevision).findOne({name: 'John'})
59
+ model.obj.age = 30;
60
+ await model.save();
61
+ expect(model.obj.revision).toEqual('0.0.2');
62
+ const revisions = await nongo.col(DummyModelWithRevision).getRevisions();
63
+ expect(revisions.length).toEqual(1);
64
+ })
65
+
66
+ it('Should save 2 revision docs in revision collection', async () => {
67
+ const model = await nongo.col(DummyModelWithRevision).findOne({name: 'John'});
68
+ model.obj.name = 'Nick';
69
+ await model.save();
70
+ const revisions = await nongo.col(DummyModelWithRevision).getRevisions();
71
+ // print(revisions);
72
+ console.log("MODEKS")
73
+ expect(revisions.length).toEqual(2);
74
+ const models = await nongo.col(DummyModelWithRevision).find();
75
+ // print(models);
76
+ expect(models.length).toEqual(2);
77
+ })
78
+
79
+ it('Should return revision doc', async () => {
80
+ const revision = await nongo.col(DummyModelWithRevision).getRevision({name: "John", revision: "0.0.1"});
81
+ expect(revision.obj.revision).toEqual("0.0.1");
82
+ })
83
+ })
@@ -0,0 +1,12 @@
1
+ // This file was generated using nongo-driver's TsInterfaceGenerator.
2
+ export default interface DummyModelChangedObj {
3
+ 'name': string;
4
+ 'pets': Array<{
5
+ 'species'?: string;
6
+ 'likes'?: {
7
+ 'food'?: string[];
8
+ 'drink'?: string[];
9
+ };
10
+ }>;
11
+ '_id'?: any;
12
+ }