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.
@@ -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 readonly defaultOptions;
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 request_promise_1 = __importDefault(require("request-promise"));
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
- const { baseUrl, groupId, clusterName, publicKey, privateKey } = params;
14
- this.defaultOptions = {
15
- auth: {
16
- user: publicKey,
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
- return (0, request_promise_1.default)(Object.assign(Object.assign({}, this.defaultOptions), { url, body: analyzers }));
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 (0, request_promise_1.default)(Object.assign(Object.assign({}, this.defaultOptions), { url }));
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 (0, request_promise_1.default)(Object.assign(Object.assign({}, this.defaultOptions), { method: 'POST', url, body: Object.assign({ collectionName: collection, database: db }, index) }));
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 { name } = cParams;
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 await this.getSearchIndexes(db, collection);
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 (0, request_promise_1.default)(Object.assign(Object.assign({}, this.defaultOptions), { method: 'PATCH', url, body: Object.assign({ collectionName: collection, database: db }, createParams) }));
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 (0, request_promise_1.default)(Object.assign(Object.assign({}, this.defaultOptions), { method: 'DELETE', url }));
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}`);
@@ -1 +1 @@
1
- {"version":3,"file":"atlas-api.js","sourceRoot":"","sources":["../src/atlas-api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"atlas-api.js","sourceRoot":"","sources":["../src/atlas-api.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,7 @@ export interface AtlasSearchIndex {
11
11
  mappings: Mappings;
12
12
  name: string;
13
13
  status: string;
14
+ synonyms: string[];
14
15
  }
15
16
  export interface Analyzer {
16
17
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nongo-driver",
3
- "version": "3.3.17",
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 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 {
@@ -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
  };
@@ -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 TextIndexModelObj {
3
+ 'name'?: string;
4
+ 'module'?: string;
5
+ 'description'?: {
6
+ 'en'?: string;
7
+ 'cy'?: string;
8
+ };
9
+ 'summary'?: string;
10
+ '_id'?: any;
11
+ }
@@ -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
+ }