nongo-driver 3.3.17 → 3.3.19

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/src/atlas-api.ts CHANGED
@@ -1,120 +1,305 @@
1
1
  import deepEqual from 'deep-equal';
2
- import rp from 'request-promise';
2
+ import { request } from 'undici';
3
+ import crypto from 'node:crypto';
3
4
  import Logger from 'wbb-logger';
4
- import {Analyzer, AtlasParams, AtlasSearchIndex, CreateSearchIndex} from './interface/atlas';
5
+ import { Analyzer, AtlasParams, AtlasSearchIndex, CreateSearchIndex } from './interface/atlas';
5
6
 
6
7
  const logger = new Logger('atlas-api.ts');
7
8
 
8
- export default class AtlasApi {
9
+ type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
10
+
11
+ function md5(input: string) {
12
+ return crypto.createHash('md5').update(input).digest('hex');
13
+ }
14
+
15
+ function randomHex(bytes = 16) {
16
+ return crypto.randomBytes(bytes).toString('hex');
17
+ }
18
+
19
+ function parseWwwAuthenticate(header: string) {
20
+ // Expects something like: Digest realm="...", nonce="...", qop="auth", opaque="...", algorithm=MD5
21
+ // We’ll parse key="value" pairs.
22
+ const schemeMatch = header.match(/^\s*Digest\s+/i);
23
+ if (!schemeMatch) return null;
24
+
25
+ const params: Record<string, string> = {};
26
+ const rest = header.replace(/^\s*Digest\s+/i, '');
27
+
28
+ // Split by commas not inside quotes
29
+ const parts = rest.match(/([a-z0-9_-]+)=(?:"[^"]*"|[^,]*)/gi) ?? [];
30
+ for (const part of parts) {
31
+ const idx = part.indexOf('=');
32
+ const k = part.slice(0, idx).trim();
33
+ let v = part.slice(idx + 1).trim();
34
+ if (v.startsWith('"') && v.endsWith('"')) v = v.slice(1, -1);
35
+ params[k] = v;
36
+ }
37
+ return params;
38
+ }
39
+
40
+ function buildDigestAuthHeader(opts: {
41
+ username: string;
42
+ password: string;
43
+ method: string;
44
+ uri: string; // path + query, not full URL
45
+ challenge: Record<string, string>;
46
+ nc: number;
47
+ cnonce: string;
48
+ }) {
49
+ const { username, password, method, uri, challenge, nc, cnonce } = opts;
50
+
51
+ const realm = challenge.realm ?? '';
52
+ const nonce = challenge.nonce ?? '';
53
+ const qopRaw = challenge.qop ?? '';
54
+ const opaque = challenge.opaque;
55
+ const algorithm = (challenge.algorithm ?? 'MD5').toUpperCase();
9
56
 
57
+ // We implement the common case Atlas uses: MD5 + qop=auth
58
+ // If qop has multiple values, pick auth if present.
59
+ const qop = qopRaw
60
+ .split(',')
61
+ .map((s) => s.trim())
62
+ .find((v) => v === 'auth') ?? (qopRaw ? qopRaw.split(',')[0].trim() : undefined);
63
+
64
+ if (algorithm !== 'MD5') {
65
+ // Atlas is typically MD5; if it ever changes, you’ll want to extend this.
66
+ throw new Error(`Unsupported digest algorithm: ${algorithm}`);
67
+ }
68
+
69
+ const ha1 = md5(`${username}:${realm}:${password}`);
70
+ const ha2 = md5(`${method}:${uri}`);
71
+
72
+ const ncHex = nc.toString(16).padStart(8, '0');
73
+
74
+ let response: string;
75
+ if (qop) {
76
+ response = md5(`${ha1}:${nonce}:${ncHex}:${cnonce}:${qop}:${ha2}`);
77
+ } else {
78
+ // RFC legacy
79
+ response = md5(`${ha1}:${nonce}:${ha2}`);
80
+ }
81
+
82
+ const pieces: string[] = [];
83
+ pieces.push(`username="${username}"`);
84
+ pieces.push(`realm="${realm}"`);
85
+ pieces.push(`nonce="${nonce}"`);
86
+ pieces.push(`uri="${uri}"`);
87
+ pieces.push(`response="${response}"`);
88
+
89
+ if (opaque) pieces.push(`opaque="${opaque}"`);
90
+ if (algorithm) pieces.push(`algorithm=${algorithm}`);
91
+
92
+ if (qop) {
93
+ pieces.push(`qop=${qop}`);
94
+ pieces.push(`nc=${ncHex}`);
95
+ pieces.push(`cnonce="${cnonce}"`);
96
+ }
97
+
98
+ return `Digest ${pieces.join(', ')}`;
99
+ }
100
+
101
+ export default class AtlasApi {
10
102
  private readonly ftsUrl: string;
11
- private readonly defaultOptions;
103
+
104
+ // digest state
105
+ private nonceCountByNonce = new Map<string, number>();
106
+ private lastChallenge: Record<string, string> | null = null;
12
107
 
13
108
  constructor(private params: AtlasParams) {
14
- const {baseUrl, groupId, clusterName, publicKey, privateKey} = params;
15
-
16
- this.defaultOptions = {
17
- auth: {
18
- user: publicKey,
19
- pass: privateKey,
20
- sendImmediately: false,
21
- },
22
- json: true,
109
+ const { baseUrl, groupId, clusterName } = params;
110
+ this.ftsUrl = `${baseUrl}/groups/${groupId}/clusters/${clusterName}/fts`;
111
+ }
112
+
113
+ private async digestJsonRequest<T>(method: HttpMethod, url: string, body?: unknown): Promise<T> {
114
+ const { publicKey, privateKey } = this.params;
115
+
116
+ const headers: Record<string, string> = {
117
+ accept: 'application/json',
23
118
  };
24
119
 
25
- this.ftsUrl = `${baseUrl}/groups/${groupId}/clusters/${clusterName}/fts`;
120
+ let requestBody: string | undefined;
121
+ if (body !== undefined) {
122
+ headers['content-type'] = 'application/json';
123
+ requestBody = JSON.stringify(body);
124
+ }
125
+
126
+ // 1) Try with cached digest challenge if we have one (avoids an extra 401 round-trip)
127
+ const tryOnce = async (authHeader?: string) => {
128
+ const h = authHeader ? { ...headers, authorization: authHeader } : headers;
129
+
130
+ const res = await request(url, { method, headers: h, body: requestBody });
131
+ const contentType = (res.headers['content-type'] ?? '').toString();
132
+ const raw = await res.body.text();
133
+
134
+ return { res, contentType, raw };
135
+ };
136
+
137
+ // If we already have a challenge cached, attempt auth immediately
138
+ if (this.lastChallenge?.nonce) {
139
+ const challenge = this.lastChallenge;
140
+ const nonce = challenge.nonce;
141
+ const nc = (this.nonceCountByNonce.get(nonce) ?? 0) + 1;
142
+ this.nonceCountByNonce.set(nonce, nc);
143
+
144
+ const u = new URL(url);
145
+ const uri = `${u.pathname}${u.search}`; // must be path+query for digest
146
+
147
+ const auth = buildDigestAuthHeader({
148
+ username: publicKey,
149
+ password: privateKey,
150
+ method,
151
+ uri,
152
+ challenge,
153
+ nc,
154
+ cnonce: randomHex(8),
155
+ });
156
+
157
+ const attempt = await tryOnce(auth);
158
+ if (attempt.res.statusCode !== 401) {
159
+ return this.handleResponse<T>(method, url, attempt.res.statusCode, attempt.contentType, attempt.raw);
160
+ }
161
+ // fall through and re-challenge below
162
+ }
163
+
164
+ // 2) No cached challenge (or cached failed). Make an unauthenticated request to get WWW-Authenticate
165
+ const first = await tryOnce(undefined);
166
+
167
+ if (first.res.statusCode !== 401) {
168
+ return this.handleResponse<T>(method, url, first.res.statusCode, first.contentType, first.raw);
169
+ }
170
+
171
+ const wwwAuth = first.res.headers['www-authenticate'];
172
+ const wwwAuthStr = Array.isArray(wwwAuth) ? wwwAuth.join(', ') : (wwwAuth ?? '').toString();
173
+
174
+ const challenge = parseWwwAuthenticate(wwwAuthStr);
175
+ if (!challenge?.nonce) {
176
+ const err = new Error(`401 from Atlas but no Digest challenge found in WWW-Authenticate header`);
177
+ (err as any).statusCode = 401;
178
+ (err as any).wwwAuthenticate = wwwAuthStr;
179
+ throw err;
180
+ }
181
+
182
+ // cache challenge for next requests
183
+ this.lastChallenge = challenge;
184
+
185
+ const nonce = challenge.nonce;
186
+ const nc = (this.nonceCountByNonce.get(nonce) ?? 0) + 1;
187
+ this.nonceCountByNonce.set(nonce, nc);
188
+
189
+ const u = new URL(url);
190
+ const uri = `${u.pathname}${u.search}`;
191
+
192
+ const auth = buildDigestAuthHeader({
193
+ username: publicKey,
194
+ password: privateKey,
195
+ method,
196
+ uri,
197
+ challenge,
198
+ nc,
199
+ cnonce: randomHex(8),
200
+ });
201
+
202
+ // 3) Retry with Digest Authorization
203
+ const second = await tryOnce(auth);
204
+ return this.handleResponse<T>(method, url, second.res.statusCode, second.contentType, second.raw);
205
+ }
206
+
207
+ private handleResponse<T>(
208
+ method: string,
209
+ url: string,
210
+ statusCode: number,
211
+ contentType: string,
212
+ raw: string,
213
+ ): T {
214
+ if (statusCode < 200 || statusCode >= 300) {
215
+ let parsed: any = raw;
216
+ if (contentType.includes('application/json')) {
217
+ try {
218
+ parsed = raw ? JSON.parse(raw) : null;
219
+ } catch {
220
+ // keep raw
221
+ }
222
+ }
223
+
224
+ const err = new Error(`Atlas API request failed: ${method} ${url} -> ${statusCode}`) as any;
225
+ err.statusCode = statusCode;
226
+ err.body = parsed;
227
+ throw err;
228
+ }
229
+
230
+ if (!raw) return undefined as unknown as T;
231
+ if (contentType.includes('application/json')) return JSON.parse(raw) as T;
232
+ return raw as unknown as T;
26
233
  }
27
234
 
28
235
  public async putCustomAnalyzers(analyzers: Analyzer[]): Promise<Analyzer[]> {
29
236
  const url = `${this.ftsUrl}/analyzers`;
30
- return rp({
31
- ...this.defaultOptions,
32
- url,
33
- body: analyzers,
34
- });
237
+ // Keep whatever your API expects. Many Atlas endpoints use PUT for replacement semantics.
238
+ return this.digestJsonRequest<Analyzer[]>('PUT', url, analyzers);
35
239
  }
36
240
 
37
241
  public async getSearchIndexes(db: string, collection: string): Promise<AtlasSearchIndex[]> {
38
242
  const url = `${this.ftsUrl}/indexes/${db}/${collection}`;
39
- return rp({
40
- ...this.defaultOptions,
41
- url,
42
- });
243
+ return this.digestJsonRequest<AtlasSearchIndex[]>('GET', url);
43
244
  }
44
245
 
45
246
  public async createSearchIndex(db: string, collection: string, index: CreateSearchIndex): Promise<any> {
46
247
  this.logIndexChange(db, collection, 'Creating', index.name);
47
248
  const url = `${this.ftsUrl}/indexes`;
48
- return rp({
49
- ...this.defaultOptions,
50
- method: 'POST',
51
- url,
52
- body: {
53
- collectionName: collection,
54
- database: db,
55
- ...index,
56
- },
249
+ return this.digestJsonRequest<any>('POST', url, {
250
+ collectionName: collection,
251
+ database: db,
252
+ ...index,
57
253
  });
58
254
  }
59
255
 
60
- public async ensureSearchIndexes(db: string, collection: string, createParams: CreateSearchIndex[]): Promise<AtlasSearchIndex[]> {
256
+ public async ensureSearchIndexes(db: string, collection: string, createParams: CreateSearchIndex[]) {
61
257
  const existingIndexes = await this.getSearchIndexes(db, collection);
62
258
  const toRemove = new Map<string, AtlasSearchIndex>();
63
259
  existingIndexes.forEach((e) => toRemove.set(e.name, e));
64
260
 
65
261
  for (const cParams of createParams) {
66
- const {name} = cParams;
67
- const existing = toRemove.get(name);
68
-
262
+ const existing = toRemove.get(cParams.name);
69
263
  if (existing) {
70
- toRemove.delete(name);
264
+ toRemove.delete(cParams.name);
71
265
  await this.updateSearchIndex(db, collection, existing, cParams);
72
266
  } else {
73
267
  await this.createSearchIndex(db, collection, cParams);
74
268
  }
75
-
76
269
  }
77
270
 
78
271
  for (const index of toRemove.values()) {
79
272
  await this.deleteSearchIndex(db, collection, index);
80
273
  }
81
274
 
82
- return await this.getSearchIndexes(db, collection);
275
+ return this.getSearchIndexes(db, collection);
83
276
  }
84
277
 
85
- public async updateSearchIndex(db: string, collection: string, existingIndex: AtlasSearchIndex, createParams: CreateSearchIndex): Promise<any> {
86
- if (deepEqual(existingIndex.mappings, createParams.mappings)) {
87
- return;
88
- }
278
+ public async updateSearchIndex(
279
+ db: string,
280
+ collection: string,
281
+ existingIndex: AtlasSearchIndex,
282
+ createParams: CreateSearchIndex,
283
+ ): Promise<any> {
284
+ if (deepEqual(existingIndex.mappings, createParams.mappings)) return;
89
285
 
90
286
  this.logIndexChange(db, collection, 'Updating', createParams.name);
91
-
92
287
  const url = `${this.ftsUrl}/indexes/${existingIndex.indexID}`;
93
288
 
94
- return rp({
95
- ...this.defaultOptions,
96
- method: 'PATCH',
97
- url,
98
- body: {
99
- collectionName: collection,
100
- database: db,
101
- ...createParams,
102
- },
289
+ return this.digestJsonRequest<any>('PATCH', url, {
290
+ collectionName: collection,
291
+ database: db,
292
+ ...createParams,
103
293
  });
104
294
  }
105
295
 
106
296
  public async deleteSearchIndex(db: string, collection: string, existingIndex: AtlasSearchIndex): Promise<any> {
107
297
  this.logIndexChange(db, collection, 'Deleting', existingIndex.name);
108
298
  const url = `${this.ftsUrl}/indexes/${existingIndex.indexID}`;
109
- return rp({
110
- ...this.defaultOptions,
111
- method: 'DELETE',
112
- url,
113
- });
299
+ return this.digestJsonRequest<any>('DELETE', url);
114
300
  }
115
301
 
116
302
  private logIndexChange(db: string, collection: string, action: string, indexName: string) {
117
303
  logger.info(`${action} search index "${indexName}" for collection ${db}.${collection}`);
118
304
  }
119
-
120
305
  }
@@ -14,6 +14,7 @@ export interface AtlasSearchIndex {
14
14
  mappings: Mappings;
15
15
  name: string;
16
16
  status: string;
17
+ synonyms: string[];
17
18
  }
18
19
 
19
20
  export interface Analyzer {
package/src/model.ts CHANGED
@@ -22,9 +22,9 @@ import {CreateSearchIndex} from './interface';
22
22
  import MongoIndex from './mongo-index';
23
23
  import Nongo from './nongo';
24
24
  import Validator from './validator';
25
- import { ICache } from './cache';
25
+ import {ICache} from './cache';
26
26
 
27
- import crypto from "crypto";
27
+ import crypto from 'crypto';
28
28
 
29
29
  const DEFAULT_PATHS = ['_id', 'revision', 'history', 'diffs'];
30
30
 
@@ -36,7 +36,6 @@ function excludePathsFromDiff(diff, excludedPaths = DEFAULT_PATHS) {
36
36
  });
37
37
  }
38
38
 
39
-
40
39
  export default abstract class Model<
41
40
  T extends {
42
41
  _id?: ObjectId;
@@ -63,12 +62,16 @@ export default abstract class Model<
63
62
  private collectionRevision: string | null;
64
63
  private collectionEnvironmentMapping: string | null;
65
64
 
66
- constructor(public nongo: Nongo, public obj: T, public cacheObject?: ICache) {
65
+ constructor(
66
+ public nongo: Nongo,
67
+ public obj: T,
68
+ public cacheObject?: ICache,
69
+ ) {
67
70
  // Set this.db if nongo given
68
71
  if (nongo) {
69
72
  this.db = nongo.db;
70
73
  }
71
- if ( cacheObject ) {
74
+ if (cacheObject) {
72
75
  this.cache = cacheObject;
73
76
  }
74
77
 
@@ -165,9 +168,8 @@ export default abstract class Model<
165
168
  // Get id of newest revision, so we can use that to find the appropriate mapping
166
169
  // this is in case the object we're trying to save either doesn't have _id (for example if it's being imported)
167
170
  // or if the _id simply isn't correct for the environment
168
- const {revision, newestRevisionId} = await this.getLatestRevisionNumber(
169
- revisionQuery,
170
- );
171
+ const {revision, newestRevisionId} =
172
+ await this.getLatestRevisionNumber(revisionQuery);
171
173
  this.obj.previousRevision = this.obj.revision;
172
174
  this.obj.revision = revision;
173
175
  // needed to create a new environment mapping
@@ -207,7 +209,9 @@ export default abstract class Model<
207
209
  .insertOne(this.obj);
208
210
  insertedRevisionId = r.insertedId.toHexString();
209
211
  }
210
- if (this.obj.revision !== 1) {
212
+ if (this.obj.deleted) {
213
+ await this.removeEnvironmentMapping(environmentId, newestRevisionId, insertedRevisionId);
214
+ } else if (this.obj.revision !== 1) {
211
215
  // update relevant mappings
212
216
  await this.updateEnvironmentMapping(
213
217
  insertedRevisionId,
@@ -215,8 +219,7 @@ export default abstract class Model<
215
219
  environmentId,
216
220
  clientEnv.isLive,
217
221
  );
218
- }
219
- if (this.obj.revision === 1) {
222
+ } else if (this.obj.revision === 1) {
220
223
  // latest revision id, as that's what we just inserted, and need to save as baseline
221
224
  await this.createEnvironmentMapping(
222
225
  environmentId,
@@ -224,9 +227,6 @@ export default abstract class Model<
224
227
  clientEnv.isLive,
225
228
  );
226
229
  }
227
- if (this.obj.deleted) {
228
- await this.removeEnvironmentMapping(environmentId, newestRevisionId);
229
- }
230
230
  } else {
231
231
  // Upsert non revision models
232
232
  await this.db
@@ -234,7 +234,7 @@ export default abstract class Model<
234
234
  .replaceOne({_id: this.obj._id}, this.obj, {upsert: true});
235
235
  }
236
236
 
237
- this.invalidateCache({ _id : this.obj._id });
237
+ this.invalidateCache({_id: this.obj._id});
238
238
 
239
239
  // Return the saved model
240
240
  return this;
@@ -243,11 +243,15 @@ export default abstract class Model<
243
243
  private async removeEnvironmentMapping(
244
244
  environmentId: string,
245
245
  latestRevisionId: string,
246
+ newRevisionId: string,
246
247
  ): Promise<void> {
247
248
  const path = `environments.${environmentId}`;
248
249
  await this.db
249
250
  .collection(this.collectionEnvironmentMapping)
250
- .updateOne({latestRevisionId}, {$unset: {[path]: ''}});
251
+ .updateOne(
252
+ {latestRevisionId},
253
+ {$unset: {[path]: ''}, $set: {latestRevisionId: newRevisionId}},
254
+ );
251
255
  }
252
256
 
253
257
  private async createEnvironmentMapping(
@@ -491,9 +495,9 @@ export default abstract class Model<
491
495
  );
492
496
  }
493
497
 
494
- private getCacheKey(query: Filter<Document>, operation: string ): string {
498
+ private getCacheKey(query: Filter<Document>, operation: string): string {
495
499
  const q = JSON.stringify(query, Object.keys(query).sort()); // stable stringify
496
- const hash = crypto.createHash("md5").update(q).digest("hex"); // short key
500
+ const hash = crypto.createHash('md5').update(q).digest('hex'); // short key
497
501
  return `${this.db.databaseName}:${this.collection}:${operation}:${hash}`;
498
502
  }
499
503
 
@@ -530,7 +534,7 @@ export default abstract class Model<
530
534
  public async findOne(query: Filter<Document> = {}): Promise<this> {
531
535
  query = this.prepareQuery(query);
532
536
 
533
- const cacheKey = this.getCacheKey(query, "findOne");
537
+ const cacheKey = this.getCacheKey(query, 'findOne');
534
538
 
535
539
  // Check cache
536
540
  if (this.useCache && this.cache.has(cacheKey)) {
@@ -4,21 +4,21 @@ import {AtlasParams, AtlasSearchIndex, CreateSearchIndex} from '../src/interface
4
4
  import {setUpInterceptors} from './atlas-api-nock';
5
5
 
6
6
  const groupId = '5b06b6b34e658110696b1da3';
7
- const clusterName = 'wbb-stage';
7
+ const clusterName = 'wbb-staging';
8
8
  const collectionName = 'Entity';
9
- const database = 'monmouth-county-council';
9
+ const database = 'afeltham';
10
10
 
11
11
  const params: AtlasParams = {
12
12
  groupId,
13
13
  baseUrl: 'https://cloud.mongodb.com/api/atlas/v1.0',
14
- publicKey: 'aPublicKey',
15
- privateKey: 'aPrivateKey',
14
+ publicKey: 'pfkjhdxk',
15
+ privateKey: '37bed739-c794-4dec-bd52-90fb4f1bd1cd',
16
16
  clusterName,
17
17
  };
18
18
 
19
19
  it('Search index management', async () => {
20
20
 
21
- setUpInterceptors();
21
+ // setUpInterceptors();
22
22
 
23
23
  const createIndex = (name: string): CreateSearchIndex => ({
24
24
  mappings: {dynamic: true},
@@ -35,18 +35,20 @@ it('Search index management', async () => {
35
35
  {
36
36
  collectionName,
37
37
  database,
38
- indexID: '606c53edf81dfe4a33b5caeb',
38
+ indexID: '6961209db2890435699787f5',
39
39
  mappings: {dynamic: true},
40
40
  name: 'index1',
41
- status: 'IN_PROGRESS',
41
+ status: 'STEADY',
42
+ "synonyms": [],
42
43
  },
43
44
  {
44
45
  collectionName,
45
46
  database,
46
- indexID: '606c53ed4012d1669a8bbeed',
47
+ indexID: '696120a1b289043569978f80',
47
48
  mappings: {dynamic: true},
48
49
  name: 'index2',
49
- status: 'IN_PROGRESS',
50
+ status: 'STEADY',
51
+ "synonyms": [],
50
52
  },
51
53
  ];
52
54
  expect(firstEnsure).toStrictEqual(firstExpected);
@@ -59,4 +61,4 @@ it('Search index management', async () => {
59
61
  // Instead just make sure all the interceptors were used
60
62
  expect(nock.isDone());
61
63
 
62
- });
64
+ }, 10000);
@@ -11,7 +11,9 @@ let mongoMemoryServer: MongoMemoryServer;
11
11
  const getInMemoryServer = async () => {
12
12
  if (!mongoMemoryServer) {
13
13
  logger.info("Using in memory server.");
14
- mongoMemoryServer = await MongoMemoryServer.create();
14
+ mongoMemoryServer = await MongoMemoryServer.create({
15
+ binary: {version: "6.0.6"},
16
+ });
15
17
  }
16
18
  return mongoMemoryServer;
17
19
  };
@@ -69,8 +69,8 @@ describe('Testing nongo revisioning', () => {
69
69
  expect(models.length).toEqual(2);
70
70
  })
71
71
 
72
- it('Should revert to revision 0.0.1', async () => {
73
- const revision = await nongo.col(DummyModelWithRevision).getRevision({name: "John", revision: "0.0.1"});
72
+ it('Should revert to revision 1', async () => {
73
+ const revision = await nongo.col(DummyModelWithRevision).getRevision({name: "John", revision: 1});
74
74
  expect(revision.obj.revision).toEqual("0.0.1");
75
75
  const model = await nongo.col(DummyModelWithRevision).findOne({name: 'Nick'});
76
76
  const latestDoc = await nongo.col(DummyModelWithRevision).revertVersion(model.obj._id);
@@ -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
+ }
@@ -0,0 +1,40 @@
1
+ // This file was generated using nongo-driver's TsInterfaceGenerator.
2
+ export default interface DummyModelObj {
3
+ 'dontStripChildren'?: any;
4
+ 'dontStripChildren2'?: any;
5
+ 'exampleMap'?: {[key: string]: {
6
+ 'mapValueField': string;
7
+ }};
8
+ 'created'?: Date;
9
+ 'arrayOfObject'?: object[];
10
+ 'name': string;
11
+ 'age': number;
12
+ 'pets': Array<{
13
+ 'species'?: string;
14
+ 'likes'?: {
15
+ 'food'?: string[];
16
+ 'drink'?: string[];
17
+ };
18
+ }>;
19
+ 'job': {
20
+ 'role': string;
21
+ 'at'?: string;
22
+ };
23
+ 'location'?: {
24
+ 'address1'?: string;
25
+ };
26
+ 'autoInit': {
27
+ 'initArray': Array<{
28
+ 'nestedObj': any;
29
+ }>;
30
+ 'initNestedObj'?: {
31
+ 'hello'?: string;
32
+ };
33
+ 'initNestedNative'?: any;
34
+ };
35
+ 'notEmptyFields'?: {
36
+ 'aString': string;
37
+ 'anArray': string[];
38
+ };
39
+ '_id'?: any;
40
+ }