nongo-driver 3.3.17 → 3.3.18
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.d.ts +4 -1
- package/dist/atlas-api.js +188 -21
- package/dist/atlas-api.js.map +1 -1
- package/dist/interface/atlas/index.d.ts +1 -0
- package/package.json +2 -2
- package/src/atlas-api.ts +244 -59
- package/src/interface/atlas/index.ts +1 -0
- package/test/atlas-api.test.ts +12 -10
- package/test/database-helper.ts +3 -1
- 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/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/dist/atlas-api.d.ts
CHANGED
|
@@ -2,8 +2,11 @@ import { Analyzer, AtlasParams, AtlasSearchIndex, CreateSearchIndex } from './in
|
|
|
2
2
|
export default class AtlasApi {
|
|
3
3
|
private params;
|
|
4
4
|
private readonly ftsUrl;
|
|
5
|
-
private
|
|
5
|
+
private nonceCountByNonce;
|
|
6
|
+
private lastChallenge;
|
|
6
7
|
constructor(params: AtlasParams);
|
|
8
|
+
private digestJsonRequest;
|
|
9
|
+
private handleResponse;
|
|
7
10
|
putCustomAnalyzers(analyzers: Analyzer[]): Promise<Analyzer[]>;
|
|
8
11
|
getSearchIndexes(db: string, collection: string): Promise<AtlasSearchIndex[]>;
|
|
9
12
|
createSearchIndex(db: string, collection: string, index: CreateSearchIndex): Promise<any>;
|
package/dist/atlas-api.js
CHANGED
|
@@ -4,45 +4,213 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const deep_equal_1 = __importDefault(require("deep-equal"));
|
|
7
|
-
const
|
|
7
|
+
const undici_1 = require("undici");
|
|
8
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
8
9
|
const wbb_logger_1 = __importDefault(require("wbb-logger"));
|
|
9
10
|
const logger = new wbb_logger_1.default('atlas-api.ts');
|
|
11
|
+
function md5(input) {
|
|
12
|
+
return node_crypto_1.default.createHash('md5').update(input).digest('hex');
|
|
13
|
+
}
|
|
14
|
+
function randomHex(bytes = 16) {
|
|
15
|
+
return node_crypto_1.default.randomBytes(bytes).toString('hex');
|
|
16
|
+
}
|
|
17
|
+
function parseWwwAuthenticate(header) {
|
|
18
|
+
var _a;
|
|
19
|
+
// Expects something like: Digest realm="...", nonce="...", qop="auth", opaque="...", algorithm=MD5
|
|
20
|
+
// We’ll parse key="value" pairs.
|
|
21
|
+
const schemeMatch = header.match(/^\s*Digest\s+/i);
|
|
22
|
+
if (!schemeMatch)
|
|
23
|
+
return null;
|
|
24
|
+
const params = {};
|
|
25
|
+
const rest = header.replace(/^\s*Digest\s+/i, '');
|
|
26
|
+
// Split by commas not inside quotes
|
|
27
|
+
const parts = (_a = rest.match(/([a-z0-9_-]+)=(?:"[^"]*"|[^,]*)/gi)) !== null && _a !== void 0 ? _a : [];
|
|
28
|
+
for (const part of parts) {
|
|
29
|
+
const idx = part.indexOf('=');
|
|
30
|
+
const k = part.slice(0, idx).trim();
|
|
31
|
+
let v = part.slice(idx + 1).trim();
|
|
32
|
+
if (v.startsWith('"') && v.endsWith('"'))
|
|
33
|
+
v = v.slice(1, -1);
|
|
34
|
+
params[k] = v;
|
|
35
|
+
}
|
|
36
|
+
return params;
|
|
37
|
+
}
|
|
38
|
+
function buildDigestAuthHeader(opts) {
|
|
39
|
+
var _a, _b, _c, _d, _e;
|
|
40
|
+
const { username, password, method, uri, challenge, nc, cnonce } = opts;
|
|
41
|
+
const realm = (_a = challenge.realm) !== null && _a !== void 0 ? _a : '';
|
|
42
|
+
const nonce = (_b = challenge.nonce) !== null && _b !== void 0 ? _b : '';
|
|
43
|
+
const qopRaw = (_c = challenge.qop) !== null && _c !== void 0 ? _c : '';
|
|
44
|
+
const opaque = challenge.opaque;
|
|
45
|
+
const algorithm = ((_d = challenge.algorithm) !== null && _d !== void 0 ? _d : 'MD5').toUpperCase();
|
|
46
|
+
// We implement the common case Atlas uses: MD5 + qop=auth
|
|
47
|
+
// If qop has multiple values, pick auth if present.
|
|
48
|
+
const qop = (_e = qopRaw
|
|
49
|
+
.split(',')
|
|
50
|
+
.map((s) => s.trim())
|
|
51
|
+
.find((v) => v === 'auth')) !== null && _e !== void 0 ? _e : (qopRaw ? qopRaw.split(',')[0].trim() : undefined);
|
|
52
|
+
if (algorithm !== 'MD5') {
|
|
53
|
+
// Atlas is typically MD5; if it ever changes, you’ll want to extend this.
|
|
54
|
+
throw new Error(`Unsupported digest algorithm: ${algorithm}`);
|
|
55
|
+
}
|
|
56
|
+
const ha1 = md5(`${username}:${realm}:${password}`);
|
|
57
|
+
const ha2 = md5(`${method}:${uri}`);
|
|
58
|
+
const ncHex = nc.toString(16).padStart(8, '0');
|
|
59
|
+
let response;
|
|
60
|
+
if (qop) {
|
|
61
|
+
response = md5(`${ha1}:${nonce}:${ncHex}:${cnonce}:${qop}:${ha2}`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// RFC legacy
|
|
65
|
+
response = md5(`${ha1}:${nonce}:${ha2}`);
|
|
66
|
+
}
|
|
67
|
+
const pieces = [];
|
|
68
|
+
pieces.push(`username="${username}"`);
|
|
69
|
+
pieces.push(`realm="${realm}"`);
|
|
70
|
+
pieces.push(`nonce="${nonce}"`);
|
|
71
|
+
pieces.push(`uri="${uri}"`);
|
|
72
|
+
pieces.push(`response="${response}"`);
|
|
73
|
+
if (opaque)
|
|
74
|
+
pieces.push(`opaque="${opaque}"`);
|
|
75
|
+
if (algorithm)
|
|
76
|
+
pieces.push(`algorithm=${algorithm}`);
|
|
77
|
+
if (qop) {
|
|
78
|
+
pieces.push(`qop=${qop}`);
|
|
79
|
+
pieces.push(`nc=${ncHex}`);
|
|
80
|
+
pieces.push(`cnonce="${cnonce}"`);
|
|
81
|
+
}
|
|
82
|
+
return `Digest ${pieces.join(', ')}`;
|
|
83
|
+
}
|
|
10
84
|
class AtlasApi {
|
|
11
85
|
constructor(params) {
|
|
12
86
|
this.params = params;
|
|
13
|
-
|
|
14
|
-
this.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
pass: privateKey,
|
|
18
|
-
sendImmediately: false,
|
|
19
|
-
},
|
|
20
|
-
json: true,
|
|
21
|
-
};
|
|
87
|
+
// digest state
|
|
88
|
+
this.nonceCountByNonce = new Map();
|
|
89
|
+
this.lastChallenge = null;
|
|
90
|
+
const { baseUrl, groupId, clusterName } = params;
|
|
22
91
|
this.ftsUrl = `${baseUrl}/groups/${groupId}/clusters/${clusterName}/fts`;
|
|
23
92
|
}
|
|
93
|
+
async digestJsonRequest(method, url, body) {
|
|
94
|
+
var _a, _b, _c;
|
|
95
|
+
const { publicKey, privateKey } = this.params;
|
|
96
|
+
const headers = {
|
|
97
|
+
accept: 'application/json',
|
|
98
|
+
};
|
|
99
|
+
let requestBody;
|
|
100
|
+
if (body !== undefined) {
|
|
101
|
+
headers['content-type'] = 'application/json';
|
|
102
|
+
requestBody = JSON.stringify(body);
|
|
103
|
+
}
|
|
104
|
+
// 1) Try with cached digest challenge if we have one (avoids an extra 401 round-trip)
|
|
105
|
+
const tryOnce = async (authHeader) => {
|
|
106
|
+
var _a;
|
|
107
|
+
const h = authHeader ? Object.assign(Object.assign({}, headers), { authorization: authHeader }) : headers;
|
|
108
|
+
const res = await (0, undici_1.request)(url, { method, headers: h, body: requestBody });
|
|
109
|
+
const contentType = ((_a = res.headers['content-type']) !== null && _a !== void 0 ? _a : '').toString();
|
|
110
|
+
const raw = await res.body.text();
|
|
111
|
+
return { res, contentType, raw };
|
|
112
|
+
};
|
|
113
|
+
// If we already have a challenge cached, attempt auth immediately
|
|
114
|
+
if ((_a = this.lastChallenge) === null || _a === void 0 ? void 0 : _a.nonce) {
|
|
115
|
+
const challenge = this.lastChallenge;
|
|
116
|
+
const nonce = challenge.nonce;
|
|
117
|
+
const nc = ((_b = this.nonceCountByNonce.get(nonce)) !== null && _b !== void 0 ? _b : 0) + 1;
|
|
118
|
+
this.nonceCountByNonce.set(nonce, nc);
|
|
119
|
+
const u = new URL(url);
|
|
120
|
+
const uri = `${u.pathname}${u.search}`; // must be path+query for digest
|
|
121
|
+
const auth = buildDigestAuthHeader({
|
|
122
|
+
username: publicKey,
|
|
123
|
+
password: privateKey,
|
|
124
|
+
method,
|
|
125
|
+
uri,
|
|
126
|
+
challenge,
|
|
127
|
+
nc,
|
|
128
|
+
cnonce: randomHex(8),
|
|
129
|
+
});
|
|
130
|
+
const attempt = await tryOnce(auth);
|
|
131
|
+
if (attempt.res.statusCode !== 401) {
|
|
132
|
+
return this.handleResponse(method, url, attempt.res.statusCode, attempt.contentType, attempt.raw);
|
|
133
|
+
}
|
|
134
|
+
// fall through and re-challenge below
|
|
135
|
+
}
|
|
136
|
+
// 2) No cached challenge (or cached failed). Make an unauthenticated request to get WWW-Authenticate
|
|
137
|
+
const first = await tryOnce(undefined);
|
|
138
|
+
if (first.res.statusCode !== 401) {
|
|
139
|
+
return this.handleResponse(method, url, first.res.statusCode, first.contentType, first.raw);
|
|
140
|
+
}
|
|
141
|
+
const wwwAuth = first.res.headers['www-authenticate'];
|
|
142
|
+
const wwwAuthStr = Array.isArray(wwwAuth) ? wwwAuth.join(', ') : (wwwAuth !== null && wwwAuth !== void 0 ? wwwAuth : '').toString();
|
|
143
|
+
const challenge = parseWwwAuthenticate(wwwAuthStr);
|
|
144
|
+
if (!(challenge === null || challenge === void 0 ? void 0 : challenge.nonce)) {
|
|
145
|
+
const err = new Error(`401 from Atlas but no Digest challenge found in WWW-Authenticate header`);
|
|
146
|
+
err.statusCode = 401;
|
|
147
|
+
err.wwwAuthenticate = wwwAuthStr;
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
// cache challenge for next requests
|
|
151
|
+
this.lastChallenge = challenge;
|
|
152
|
+
const nonce = challenge.nonce;
|
|
153
|
+
const nc = ((_c = this.nonceCountByNonce.get(nonce)) !== null && _c !== void 0 ? _c : 0) + 1;
|
|
154
|
+
this.nonceCountByNonce.set(nonce, nc);
|
|
155
|
+
const u = new URL(url);
|
|
156
|
+
const uri = `${u.pathname}${u.search}`;
|
|
157
|
+
const auth = buildDigestAuthHeader({
|
|
158
|
+
username: publicKey,
|
|
159
|
+
password: privateKey,
|
|
160
|
+
method,
|
|
161
|
+
uri,
|
|
162
|
+
challenge,
|
|
163
|
+
nc,
|
|
164
|
+
cnonce: randomHex(8),
|
|
165
|
+
});
|
|
166
|
+
// 3) Retry with Digest Authorization
|
|
167
|
+
const second = await tryOnce(auth);
|
|
168
|
+
return this.handleResponse(method, url, second.res.statusCode, second.contentType, second.raw);
|
|
169
|
+
}
|
|
170
|
+
handleResponse(method, url, statusCode, contentType, raw) {
|
|
171
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
172
|
+
let parsed = raw;
|
|
173
|
+
if (contentType.includes('application/json')) {
|
|
174
|
+
try {
|
|
175
|
+
parsed = raw ? JSON.parse(raw) : null;
|
|
176
|
+
}
|
|
177
|
+
catch (_a) {
|
|
178
|
+
// keep raw
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const err = new Error(`Atlas API request failed: ${method} ${url} -> ${statusCode}`);
|
|
182
|
+
err.statusCode = statusCode;
|
|
183
|
+
err.body = parsed;
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
186
|
+
if (!raw)
|
|
187
|
+
return undefined;
|
|
188
|
+
if (contentType.includes('application/json'))
|
|
189
|
+
return JSON.parse(raw);
|
|
190
|
+
return raw;
|
|
191
|
+
}
|
|
24
192
|
async putCustomAnalyzers(analyzers) {
|
|
25
193
|
const url = `${this.ftsUrl}/analyzers`;
|
|
26
|
-
|
|
194
|
+
// Keep whatever your API expects. Many Atlas endpoints use PUT for replacement semantics.
|
|
195
|
+
return this.digestJsonRequest('PUT', url, analyzers);
|
|
27
196
|
}
|
|
28
197
|
async getSearchIndexes(db, collection) {
|
|
29
198
|
const url = `${this.ftsUrl}/indexes/${db}/${collection}`;
|
|
30
|
-
return
|
|
199
|
+
return this.digestJsonRequest('GET', url);
|
|
31
200
|
}
|
|
32
201
|
async createSearchIndex(db, collection, index) {
|
|
33
202
|
this.logIndexChange(db, collection, 'Creating', index.name);
|
|
34
203
|
const url = `${this.ftsUrl}/indexes`;
|
|
35
|
-
return
|
|
204
|
+
return this.digestJsonRequest('POST', url, Object.assign({ collectionName: collection, database: db }, index));
|
|
36
205
|
}
|
|
37
206
|
async ensureSearchIndexes(db, collection, createParams) {
|
|
38
207
|
const existingIndexes = await this.getSearchIndexes(db, collection);
|
|
39
208
|
const toRemove = new Map();
|
|
40
209
|
existingIndexes.forEach((e) => toRemove.set(e.name, e));
|
|
41
210
|
for (const cParams of createParams) {
|
|
42
|
-
const
|
|
43
|
-
const existing = toRemove.get(name);
|
|
211
|
+
const existing = toRemove.get(cParams.name);
|
|
44
212
|
if (existing) {
|
|
45
|
-
toRemove.delete(name);
|
|
213
|
+
toRemove.delete(cParams.name);
|
|
46
214
|
await this.updateSearchIndex(db, collection, existing, cParams);
|
|
47
215
|
}
|
|
48
216
|
else {
|
|
@@ -52,20 +220,19 @@ class AtlasApi {
|
|
|
52
220
|
for (const index of toRemove.values()) {
|
|
53
221
|
await this.deleteSearchIndex(db, collection, index);
|
|
54
222
|
}
|
|
55
|
-
return
|
|
223
|
+
return this.getSearchIndexes(db, collection);
|
|
56
224
|
}
|
|
57
225
|
async updateSearchIndex(db, collection, existingIndex, createParams) {
|
|
58
|
-
if ((0, deep_equal_1.default)(existingIndex.mappings, createParams.mappings))
|
|
226
|
+
if ((0, deep_equal_1.default)(existingIndex.mappings, createParams.mappings))
|
|
59
227
|
return;
|
|
60
|
-
}
|
|
61
228
|
this.logIndexChange(db, collection, 'Updating', createParams.name);
|
|
62
229
|
const url = `${this.ftsUrl}/indexes/${existingIndex.indexID}`;
|
|
63
|
-
return
|
|
230
|
+
return this.digestJsonRequest('PATCH', url, Object.assign({ collectionName: collection, database: db }, createParams));
|
|
64
231
|
}
|
|
65
232
|
async deleteSearchIndex(db, collection, existingIndex) {
|
|
66
233
|
this.logIndexChange(db, collection, 'Deleting', existingIndex.name);
|
|
67
234
|
const url = `${this.ftsUrl}/indexes/${existingIndex.indexID}`;
|
|
68
|
-
return
|
|
235
|
+
return this.digestJsonRequest('DELETE', url);
|
|
69
236
|
}
|
|
70
237
|
logIndexChange(db, collection, action, indexName) {
|
|
71
238
|
logger.info(`${action} search index "${indexName}" for collection ${db}.${collection}`);
|
package/dist/atlas-api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atlas-api.js","sourceRoot":"","sources":["../src/atlas-api.ts"],"names":[],"mappings":";;;;;AAAA,4DAAmC;AACnC,
|
|
1
|
+
{"version":3,"file":"atlas-api.js","sourceRoot":"","sources":["../src/atlas-api.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nongo-driver",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.18",
|
|
4
4
|
"description": "A promise driven, schema lead, mongo driver.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"just-diff": "^6.0.2",
|
|
29
29
|
"minimist": "^1.2.8",
|
|
30
30
|
"mongodb": "^5.7.0",
|
|
31
|
-
"request-promise": "^4.2.6",
|
|
32
31
|
"semver": "^7.5.4",
|
|
33
32
|
"tslint": "^5.8.0",
|
|
33
|
+
"undici": "^7.18.2",
|
|
34
34
|
"wbb-logger": "^1.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
package/src/atlas-api.ts
CHANGED
|
@@ -1,120 +1,305 @@
|
|
|
1
1
|
import deepEqual from 'deep-equal';
|
|
2
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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[])
|
|
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
|
|
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
|
|
275
|
+
return this.getSearchIndexes(db, collection);
|
|
83
276
|
}
|
|
84
277
|
|
|
85
|
-
public async updateSearchIndex(
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
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
|
}
|
package/test/atlas-api.test.ts
CHANGED
|
@@ -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-
|
|
7
|
+
const clusterName = 'wbb-staging';
|
|
8
8
|
const collectionName = 'Entity';
|
|
9
|
-
const database = '
|
|
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: '
|
|
15
|
-
privateKey: '
|
|
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: '
|
|
38
|
+
indexID: '6961209db2890435699787f5',
|
|
39
39
|
mappings: {dynamic: true},
|
|
40
40
|
name: 'index1',
|
|
41
|
-
status: '
|
|
41
|
+
status: 'STEADY',
|
|
42
|
+
"synonyms": [],
|
|
42
43
|
},
|
|
43
44
|
{
|
|
44
45
|
collectionName,
|
|
45
46
|
database,
|
|
46
|
-
indexID: '
|
|
47
|
+
indexID: '696120a1b289043569978f80',
|
|
47
48
|
mappings: {dynamic: true},
|
|
48
49
|
name: 'index2',
|
|
49
|
-
status: '
|
|
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);
|
package/test/database-helper.ts
CHANGED
|
@@ -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
|
};
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// This file was generated using nongo-driver's TsInterfaceGenerator.
|
|
2
|
+
export default interface DummyModelWithMaxIndexObj {
|
|
3
|
+
'clients'?: {
|
|
4
|
+
'client1'?: {
|
|
5
|
+
'favouritePet'?: string;
|
|
6
|
+
};
|
|
7
|
+
'client2'?: {
|
|
8
|
+
'favouritePet'?: string;
|
|
9
|
+
};
|
|
10
|
+
'client3'?: {
|
|
11
|
+
'favouritePet'?: string;
|
|
12
|
+
};
|
|
13
|
+
'client4'?: {
|
|
14
|
+
'favouritePet'?: string;
|
|
15
|
+
};
|
|
16
|
+
'client5'?: {
|
|
17
|
+
'favouritePet'?: string;
|
|
18
|
+
};
|
|
19
|
+
'client6'?: {
|
|
20
|
+
'favouritePet'?: string;
|
|
21
|
+
};
|
|
22
|
+
'client7'?: {
|
|
23
|
+
'favouritePet'?: string;
|
|
24
|
+
};
|
|
25
|
+
'client8'?: {
|
|
26
|
+
'favouritePet'?: string;
|
|
27
|
+
};
|
|
28
|
+
'client9'?: {
|
|
29
|
+
'favouritePet'?: string;
|
|
30
|
+
};
|
|
31
|
+
'client10'?: {
|
|
32
|
+
'favouritePet'?: string;
|
|
33
|
+
};
|
|
34
|
+
'client11'?: {
|
|
35
|
+
'favouritePet'?: string;
|
|
36
|
+
};
|
|
37
|
+
'client12'?: {
|
|
38
|
+
'favouritePet'?: string;
|
|
39
|
+
};
|
|
40
|
+
'client13'?: {
|
|
41
|
+
'favouritePet'?: string;
|
|
42
|
+
};
|
|
43
|
+
'client14'?: {
|
|
44
|
+
'favouritePet'?: string;
|
|
45
|
+
};
|
|
46
|
+
'client15'?: {
|
|
47
|
+
'favouritePet'?: string;
|
|
48
|
+
};
|
|
49
|
+
'client16'?: {
|
|
50
|
+
'favouritePet'?: string;
|
|
51
|
+
};
|
|
52
|
+
'client17'?: {
|
|
53
|
+
'favouritePet'?: string;
|
|
54
|
+
};
|
|
55
|
+
'client18'?: {
|
|
56
|
+
'favouritePet'?: string;
|
|
57
|
+
};
|
|
58
|
+
'client19'?: {
|
|
59
|
+
'favouritePet'?: string;
|
|
60
|
+
};
|
|
61
|
+
'client20'?: {
|
|
62
|
+
'favouritePet'?: string;
|
|
63
|
+
};
|
|
64
|
+
'client21'?: {
|
|
65
|
+
'favouritePet'?: string;
|
|
66
|
+
};
|
|
67
|
+
'client22'?: {
|
|
68
|
+
'favouritePet'?: string;
|
|
69
|
+
};
|
|
70
|
+
'client23'?: {
|
|
71
|
+
'favouritePet'?: string;
|
|
72
|
+
};
|
|
73
|
+
'client24'?: {
|
|
74
|
+
'favouritePet'?: string;
|
|
75
|
+
};
|
|
76
|
+
'client25'?: {
|
|
77
|
+
'favouritePet'?: string;
|
|
78
|
+
};
|
|
79
|
+
'client26'?: {
|
|
80
|
+
'favouritePet'?: string;
|
|
81
|
+
};
|
|
82
|
+
'client27'?: {
|
|
83
|
+
'favouritePet'?: string;
|
|
84
|
+
};
|
|
85
|
+
'client28'?: {
|
|
86
|
+
'favouritePet'?: string;
|
|
87
|
+
};
|
|
88
|
+
'client29'?: {
|
|
89
|
+
'favouritePet'?: string;
|
|
90
|
+
};
|
|
91
|
+
'client30'?: {
|
|
92
|
+
'favouritePet'?: string;
|
|
93
|
+
};
|
|
94
|
+
'client31'?: {
|
|
95
|
+
'favouritePet'?: string;
|
|
96
|
+
};
|
|
97
|
+
'client32'?: {
|
|
98
|
+
'favouritePet'?: string;
|
|
99
|
+
};
|
|
100
|
+
'client33'?: {
|
|
101
|
+
'favouritePet'?: string;
|
|
102
|
+
};
|
|
103
|
+
'client34'?: {
|
|
104
|
+
'favouritePet'?: string;
|
|
105
|
+
};
|
|
106
|
+
'client35'?: {
|
|
107
|
+
'favouritePet'?: string;
|
|
108
|
+
};
|
|
109
|
+
'client36'?: {
|
|
110
|
+
'favouritePet'?: string;
|
|
111
|
+
};
|
|
112
|
+
'client37'?: {
|
|
113
|
+
'favouritePet'?: string;
|
|
114
|
+
};
|
|
115
|
+
'client38'?: {
|
|
116
|
+
'favouritePet'?: string;
|
|
117
|
+
};
|
|
118
|
+
'client39'?: {
|
|
119
|
+
'favouritePet'?: string;
|
|
120
|
+
};
|
|
121
|
+
'client40'?: {
|
|
122
|
+
'favouritePet'?: string;
|
|
123
|
+
};
|
|
124
|
+
'client41'?: {
|
|
125
|
+
'favouritePet'?: string;
|
|
126
|
+
};
|
|
127
|
+
'client42'?: {
|
|
128
|
+
'favouritePet'?: string;
|
|
129
|
+
};
|
|
130
|
+
'client43'?: {
|
|
131
|
+
'favouritePet'?: string;
|
|
132
|
+
};
|
|
133
|
+
'client44'?: {
|
|
134
|
+
'favouritePet'?: string;
|
|
135
|
+
};
|
|
136
|
+
'client45'?: {
|
|
137
|
+
'favouritePet'?: string;
|
|
138
|
+
};
|
|
139
|
+
'client46'?: {
|
|
140
|
+
'favouritePet'?: string;
|
|
141
|
+
};
|
|
142
|
+
'client47'?: {
|
|
143
|
+
'favouritePet'?: string;
|
|
144
|
+
};
|
|
145
|
+
'client48'?: {
|
|
146
|
+
'favouritePet'?: string;
|
|
147
|
+
};
|
|
148
|
+
'client49'?: {
|
|
149
|
+
'favouritePet'?: string;
|
|
150
|
+
};
|
|
151
|
+
'client50'?: {
|
|
152
|
+
'favouritePet'?: string;
|
|
153
|
+
};
|
|
154
|
+
'client51'?: {
|
|
155
|
+
'favouritePet'?: string;
|
|
156
|
+
};
|
|
157
|
+
'client52'?: {
|
|
158
|
+
'favouritePet'?: string;
|
|
159
|
+
};
|
|
160
|
+
'client53'?: {
|
|
161
|
+
'favouritePet'?: string;
|
|
162
|
+
};
|
|
163
|
+
'client54'?: {
|
|
164
|
+
'favouritePet'?: string;
|
|
165
|
+
};
|
|
166
|
+
'client55'?: {
|
|
167
|
+
'favouritePet'?: string;
|
|
168
|
+
};
|
|
169
|
+
'client56'?: {
|
|
170
|
+
'favouritePet'?: string;
|
|
171
|
+
};
|
|
172
|
+
'client57'?: {
|
|
173
|
+
'favouritePet'?: string;
|
|
174
|
+
};
|
|
175
|
+
'client58'?: {
|
|
176
|
+
'favouritePet'?: string;
|
|
177
|
+
};
|
|
178
|
+
'client59'?: {
|
|
179
|
+
'favouritePet'?: string;
|
|
180
|
+
};
|
|
181
|
+
'client60'?: {
|
|
182
|
+
'favouritePet'?: string;
|
|
183
|
+
};
|
|
184
|
+
'client61'?: {
|
|
185
|
+
'favouritePet'?: string;
|
|
186
|
+
};
|
|
187
|
+
'client62'?: {
|
|
188
|
+
'favouritePet'?: string;
|
|
189
|
+
};
|
|
190
|
+
'client63'?: {
|
|
191
|
+
'favouritePet'?: string;
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
'_id'?: any;
|
|
195
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// This file was generated using nongo-driver's TsInterfaceGenerator.
|
|
2
|
+
export default interface DummyModelWithRevisionObj {
|
|
3
|
+
'revision'?: string;
|
|
4
|
+
'history'?: Array<{
|
|
5
|
+
'id'?: string;
|
|
6
|
+
'revision'?: string;
|
|
7
|
+
'created'?: Date;
|
|
8
|
+
}>;
|
|
9
|
+
'created': Date;
|
|
10
|
+
'name': string;
|
|
11
|
+
'age'?: number;
|
|
12
|
+
'_id'?: any;
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// This file was generated using nongo-driver's TsInterfaceGenerator.
|
|
2
|
+
export default interface TextIndexModelModifiedObj {
|
|
3
|
+
'name'?: string;
|
|
4
|
+
'module'?: string;
|
|
5
|
+
'description'?: {
|
|
6
|
+
'en'?: string;
|
|
7
|
+
'cy'?: string;
|
|
8
|
+
'es'?: string;
|
|
9
|
+
};
|
|
10
|
+
'summary'?: string;
|
|
11
|
+
'_id'?: any;
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// This file was generated using nongo-driver's TsInterfaceGenerator.
|
|
2
|
+
export default interface TextIndexModelTooLongObj {
|
|
3
|
+
'name'?: string;
|
|
4
|
+
'module'?: string;
|
|
5
|
+
'description'?: {
|
|
6
|
+
'en'?: string;
|
|
7
|
+
'cy'?: string;
|
|
8
|
+
};
|
|
9
|
+
'summary'?: string;
|
|
10
|
+
'_id'?: any;
|
|
11
|
+
}
|