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.
- package/dist/atlas-api.js +6 -6
- package/dist/atlas-api.js.map +1 -1
- package/dist/cursor-iterator.d.ts +1 -2
- package/dist/cursor-iterator.js +3 -1
- package/dist/cursor-iterator.js.map +1 -1
- package/dist/generate-interfaces.js +24 -7
- package/dist/generate-interfaces.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +33 -17
- package/dist/index.js.map +1 -1
- package/dist/interface/atlas/field.d.ts +1 -1
- package/dist/interface/atlas/field.js +1 -0
- package/dist/interface/atlas/field.js.map +1 -1
- package/dist/interface/atlas/index.js +15 -4
- package/dist/interface/atlas/index.js.map +1 -1
- package/dist/interface/index.d.ts +1 -10
- package/dist/interface/index.js +15 -4
- package/dist/interface/index.js.map +1 -1
- package/dist/model.d.ts +25 -13
- package/dist/model.js +76 -34
- package/dist/model.js.map +1 -1
- package/dist/mongo-index.d.ts +2 -3
- package/dist/mongo-index.js.map +1 -1
- package/dist/nongo.d.ts +5 -2
- package/dist/nongo.js +45 -21
- package/dist/nongo.js.map +1 -1
- package/dist/revision-model.d.ts +5 -0
- package/dist/revision-model.js +24 -0
- package/dist/revision-model.js.map +1 -0
- package/dist/schema-parser.js +10 -10
- package/dist/schema-parser.js.map +1 -1
- package/dist/ts-interface-generator.js +18 -2
- package/dist/ts-interface-generator.js.map +1 -1
- package/dist/validator.js +1 -1
- package/dist/validator.js.map +1 -1
- package/jest.config.js +0 -5
- package/package.json +19 -25
- package/src/atlas-api.ts +11 -40
- package/src/cursor-iterator.ts +1 -3
- package/src/index.ts +1 -1
- package/src/interface/index.ts +0 -11
- package/src/model.ts +92 -44
- package/src/mongo-index.ts +1 -3
- package/src/nongo.ts +34 -6
- package/src/revision-model.ts +22 -0
- package/test/database-helper.ts +26 -13
- package/test/model-versioning.test.ts +83 -0
- package/test/models/dummy-model-changed-obj.ts +12 -0
- package/test/models/dummy-model-obj.ts +40 -0
- package/test/models/dummy-model-with-max-index-obj.ts +195 -0
- package/test/models/dummy-model-with-revision-obj.ts +13 -0
- package/test/models/dummy-model-with-revision.nongo.ts +22 -0
- package/test/models/text-index-model-modified-obj.ts +12 -0
- package/test/models/text-index-model-obj.ts +11 -0
- package/test/models/text-index-model-too-long-obj.ts +11 -0
- package/test/nongo-multi.test.ts +2 -5
- 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
|
|
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
|
}
|
package/src/cursor-iterator.ts
CHANGED
|
@@ -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
|
|
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';
|
package/src/interface/index.ts
CHANGED
|
@@ -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
|
|
3
|
-
import {
|
|
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
|
|
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
|
|
11
|
+
const DEFAULT_PATHS = ['_id', 'revision', 'history', 'diffs']
|
|
13
12
|
|
|
14
|
-
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
181
|
-
options?:
|
|
182
|
-
): Promise<
|
|
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
|
|
192
|
-
query:
|
|
193
|
-
options?:
|
|
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(
|
|
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:
|
|
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:
|
|
272
|
+
public async remove(query: Filter<Document>, options?: DeleteOptions): Promise<DeleteResult> {
|
|
223
273
|
query = this.prepareQuery(query);
|
|
224
|
-
|
|
225
|
-
return res.result;
|
|
274
|
+
return this.db.collection(this.collection).deleteMany(query, options);
|
|
226
275
|
}
|
|
227
276
|
|
|
228
|
-
public async aggregate(pipeline:
|
|
229
|
-
return
|
|
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:
|
|
234
|
-
options?:
|
|
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
|
|
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:
|
|
300
|
+
public async update(query: Filter<Document>, updates: UpdateFilter<Document>, options?: UpdateOptions): Promise<UpdateResult<Document>> {
|
|
252
301
|
query = this.prepareQuery(query);
|
|
253
|
-
|
|
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
|
|
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
|
/*
|
package/src/mongo-index.ts
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
+
}
|
package/test/database-helper.ts
CHANGED
|
@@ -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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
useUnifiedTopology: true,
|
|
27
|
-
});
|
|
39
|
+
const mongod = await getInMemoryServer();
|
|
40
|
+
return MongoClient.connect(await mongod.getUri());
|
|
28
41
|
}
|
|
29
42
|
|
|
30
|
-
public static
|
|
31
|
-
return
|
|
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(
|
|
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
|
+
}
|