@resolveio/server-lib 20.11.15 → 20.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/managers/mongo.manager.js +53 -8
- package/managers/mongo.manager.js.map +1 -1
- package/methods/pdf.js +10 -5
- package/methods/pdf.js.map +1 -1
- package/models/slow-query-report.model.d.ts +22 -0
- package/models/slow-query-report.model.js +4 -0
- package/models/slow-query-report.model.js.map +1 -0
- package/package.json +1 -1
- package/util/slow-query-reporter.d.ts +27 -0
- package/util/slow-query-reporter.js +293 -0
- package/util/slow-query-reporter.js.map +1 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.SlowQueryReporter = void 0;
|
|
40
|
+
var crypto_1 = require("crypto");
|
|
41
|
+
var NodeCache = require("node-cache");
|
|
42
|
+
var os_1 = require("os");
|
|
43
|
+
var resolveio_server_app_1 = require("../resolveio-server-app");
|
|
44
|
+
var common_1 = require("./common");
|
|
45
|
+
var SLOW_QUERY_ENDPOINT = 'https://backend.resolveio.com/api/slow-queries/report';
|
|
46
|
+
var DEFAULT_SLOW_QUERY_THRESHOLD_MS = 2000;
|
|
47
|
+
var EXPLAIN_THROTTLE_SECONDS = 5 * 60;
|
|
48
|
+
var SlowQueryReporter = /** @class */ (function () {
|
|
49
|
+
function SlowQueryReporter() {
|
|
50
|
+
}
|
|
51
|
+
SlowQueryReporter.reportSlowQuery = function (params) {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
53
|
+
var thresholdMs, queryHash, apiKey, payload, explain, err_1;
|
|
54
|
+
return __generator(this, function (_a) {
|
|
55
|
+
switch (_a.label) {
|
|
56
|
+
case 0:
|
|
57
|
+
thresholdMs = this.getThresholdMs();
|
|
58
|
+
if (thresholdMs <= 0 || params.durationMs < thresholdMs) {
|
|
59
|
+
return [2 /*return*/];
|
|
60
|
+
}
|
|
61
|
+
queryHash = params.queryHash || this.generateQueryHash(params);
|
|
62
|
+
apiKey = this.getApiKey();
|
|
63
|
+
payload = {
|
|
64
|
+
queryHash: queryHash,
|
|
65
|
+
collection: params.collection,
|
|
66
|
+
pipeline: params.pipeline ? (0, common_1.deepCopy)(params.pipeline) : undefined,
|
|
67
|
+
filter: params.filter ? (0, common_1.deepCopy)(params.filter) : undefined,
|
|
68
|
+
options: params.options ? (0, common_1.deepCopy)(params.options) : undefined,
|
|
69
|
+
durationMs: params.durationMs,
|
|
70
|
+
thresholdMs: thresholdMs,
|
|
71
|
+
clientSlug: resolveio_server_app_1.ResolveIOServer.getClientName(),
|
|
72
|
+
clientName: this.getClientName(),
|
|
73
|
+
sourceApp: params.sourceApp || 'mongo-manager',
|
|
74
|
+
environment: this.getEnvironment(),
|
|
75
|
+
nodeHostname: (0, os_1.hostname)(),
|
|
76
|
+
notes: params.notes,
|
|
77
|
+
apiKey: apiKey
|
|
78
|
+
};
|
|
79
|
+
_a.label = 1;
|
|
80
|
+
case 1:
|
|
81
|
+
_a.trys.push([1, 4, , 5]);
|
|
82
|
+
return [4 /*yield*/, this.captureExplain(params, queryHash)];
|
|
83
|
+
case 2:
|
|
84
|
+
explain = _a.sent();
|
|
85
|
+
if (explain) {
|
|
86
|
+
payload.explainPlan = (0, common_1.deepCopy)(explain.queryPlanner || explain);
|
|
87
|
+
if (explain.executionStats) {
|
|
88
|
+
payload.explainExecutionStats = (0, common_1.deepCopy)(explain.executionStats);
|
|
89
|
+
}
|
|
90
|
+
payload.explainGeneratedAt = new Date();
|
|
91
|
+
}
|
|
92
|
+
return [4 /*yield*/, this.sendPayload(payload, apiKey)];
|
|
93
|
+
case 3:
|
|
94
|
+
_a.sent();
|
|
95
|
+
return [3 /*break*/, 5];
|
|
96
|
+
case 4:
|
|
97
|
+
err_1 = _a.sent();
|
|
98
|
+
console.error('SlowQueryReporter failed', err_1);
|
|
99
|
+
return [3 /*break*/, 5];
|
|
100
|
+
case 5: return [2 /*return*/];
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
SlowQueryReporter.reportSlowQueryFireAndForget = function (params) {
|
|
106
|
+
// eslint-disable-next-line no-restricted-syntax, promise/prefer-await-to-then
|
|
107
|
+
void this.reportSlowQuery(params).catch(function (err) {
|
|
108
|
+
console.error('SlowQueryReporter fire-and-forget error', err);
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
SlowQueryReporter.getThresholdMs = function () {
|
|
112
|
+
var config = resolveio_server_app_1.ResolveIOServer.getServerConfig();
|
|
113
|
+
var envValue = process.env.SLOW_QUERY_THRESHOLD_MS;
|
|
114
|
+
if (envValue && !Number.isNaN(Number(envValue))) {
|
|
115
|
+
return Number.parseInt(envValue, 10);
|
|
116
|
+
}
|
|
117
|
+
var configValue = config === null || config === void 0 ? void 0 : config.SLOW_QUERY_THRESHOLD_MS;
|
|
118
|
+
if (typeof configValue === 'number') {
|
|
119
|
+
return configValue;
|
|
120
|
+
}
|
|
121
|
+
if (typeof configValue === 'string' && configValue.length && !Number.isNaN(Number(configValue))) {
|
|
122
|
+
return Number.parseInt(configValue, 10);
|
|
123
|
+
}
|
|
124
|
+
return DEFAULT_SLOW_QUERY_THRESHOLD_MS;
|
|
125
|
+
};
|
|
126
|
+
SlowQueryReporter.getEnvironment = function () {
|
|
127
|
+
var config = resolveio_server_app_1.ResolveIOServer.getServerConfig();
|
|
128
|
+
return ((config === null || config === void 0 ? void 0 : config.RESOLVEIO_ENV) ||
|
|
129
|
+
(config === null || config === void 0 ? void 0 : config.ENVIRONMENT) ||
|
|
130
|
+
(config === null || config === void 0 ? void 0 : config.ROOT_URL) ||
|
|
131
|
+
process.env.RESOLVEIO_ENV ||
|
|
132
|
+
process.env.NODE_ENV ||
|
|
133
|
+
'unknown');
|
|
134
|
+
};
|
|
135
|
+
SlowQueryReporter.getClientName = function () {
|
|
136
|
+
var config = resolveio_server_app_1.ResolveIOServer.getServerConfig();
|
|
137
|
+
return (config === null || config === void 0 ? void 0 : config.CLIENT_NAME) || resolveio_server_app_1.ResolveIOServer.getClientName();
|
|
138
|
+
};
|
|
139
|
+
SlowQueryReporter.captureExplain = function (params, queryHash) {
|
|
140
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
141
|
+
var collection, err_2;
|
|
142
|
+
return __generator(this, function (_a) {
|
|
143
|
+
switch (_a.label) {
|
|
144
|
+
case 0:
|
|
145
|
+
if (params.queryType === 'countDocuments') {
|
|
146
|
+
return [2 /*return*/, undefined];
|
|
147
|
+
}
|
|
148
|
+
if (!this.shouldExplain(queryHash)) {
|
|
149
|
+
return [2 /*return*/, undefined];
|
|
150
|
+
}
|
|
151
|
+
collection = resolveio_server_app_1.ResolveIOServer.getMainDB().collection(params.collection);
|
|
152
|
+
_a.label = 1;
|
|
153
|
+
case 1:
|
|
154
|
+
_a.trys.push([1, 8, , 9]);
|
|
155
|
+
if (!(params.queryType === 'aggregate')) return [3 /*break*/, 3];
|
|
156
|
+
return [4 /*yield*/, collection.aggregate(params.pipeline || [], params.options).explain('executionStats')];
|
|
157
|
+
case 2: return [2 /*return*/, _a.sent()];
|
|
158
|
+
case 3:
|
|
159
|
+
if (!(params.queryType === 'find')) return [3 /*break*/, 5];
|
|
160
|
+
return [4 /*yield*/, collection.find(params.filter || {}, params.options).explain('executionStats')];
|
|
161
|
+
case 4: return [2 /*return*/, _a.sent()];
|
|
162
|
+
case 5:
|
|
163
|
+
if (!(params.queryType === 'findOne')) return [3 /*break*/, 7];
|
|
164
|
+
return [4 /*yield*/, collection.find(params.filter || {}, params.options).limit(1).explain('executionStats')];
|
|
165
|
+
case 6: return [2 /*return*/, _a.sent()];
|
|
166
|
+
case 7: return [3 /*break*/, 9];
|
|
167
|
+
case 8:
|
|
168
|
+
err_2 = _a.sent();
|
|
169
|
+
console.error('SlowQueryReporter explain failed', err_2);
|
|
170
|
+
return [3 /*break*/, 9];
|
|
171
|
+
case 9: return [2 /*return*/, undefined];
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
SlowQueryReporter.shouldExplain = function (queryHash) {
|
|
177
|
+
if (!queryHash) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
if (this._explainCache.has(queryHash)) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
this._explainCache.set(queryHash, true);
|
|
184
|
+
return true;
|
|
185
|
+
};
|
|
186
|
+
SlowQueryReporter.sendPayload = function (payload, apiKey) {
|
|
187
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
188
|
+
var transport, key, headers, response, err_3;
|
|
189
|
+
return __generator(this, function (_a) {
|
|
190
|
+
switch (_a.label) {
|
|
191
|
+
case 0:
|
|
192
|
+
transport = globalThis.fetch;
|
|
193
|
+
if (typeof transport !== 'function') {
|
|
194
|
+
console.error('SlowQueryReporter cannot send payload (fetch missing)', payload);
|
|
195
|
+
return [2 /*return*/];
|
|
196
|
+
}
|
|
197
|
+
key = apiKey || this.getApiKey();
|
|
198
|
+
headers = removeEmptyFields({
|
|
199
|
+
'Content-Type': 'application/json',
|
|
200
|
+
'X-ResolveIO-Slow-Query-Key': key,
|
|
201
|
+
'X-Slow-Query-Key': key,
|
|
202
|
+
'X-API-Key': key
|
|
203
|
+
});
|
|
204
|
+
_a.label = 1;
|
|
205
|
+
case 1:
|
|
206
|
+
_a.trys.push([1, 3, , 4]);
|
|
207
|
+
return [4 /*yield*/, transport(SLOW_QUERY_ENDPOINT, {
|
|
208
|
+
method: 'POST',
|
|
209
|
+
headers: headers,
|
|
210
|
+
body: JSON.stringify(payload)
|
|
211
|
+
})];
|
|
212
|
+
case 2:
|
|
213
|
+
response = _a.sent();
|
|
214
|
+
if (!response.ok) {
|
|
215
|
+
console.error('SlowQueryReporter payload rejected', {
|
|
216
|
+
status: response.status,
|
|
217
|
+
statusText: response.statusText,
|
|
218
|
+
queryHash: payload.queryHash
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return [3 /*break*/, 4];
|
|
222
|
+
case 3:
|
|
223
|
+
err_3 = _a.sent();
|
|
224
|
+
console.error('SlowQueryReporter network error', err_3);
|
|
225
|
+
return [3 /*break*/, 4];
|
|
226
|
+
case 4: return [2 /*return*/];
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
SlowQueryReporter.getApiKey = function () {
|
|
232
|
+
var config = resolveio_server_app_1.ResolveIOServer.getServerConfig();
|
|
233
|
+
return (process.env.SLOW_QUERY_API_KEY ||
|
|
234
|
+
process.env.SLOW_QUERY_INGEST_KEY ||
|
|
235
|
+
(config === null || config === void 0 ? void 0 : config.SLOW_QUERY_API_KEY) ||
|
|
236
|
+
(config === null || config === void 0 ? void 0 : config.SLOW_QUERY_INGEST_KEY) ||
|
|
237
|
+
resolveio_server_app_1.ResolveIOServer.getClientName());
|
|
238
|
+
};
|
|
239
|
+
SlowQueryReporter.generateQueryHash = function (params) {
|
|
240
|
+
try {
|
|
241
|
+
var normalized = this.normalizeForHash({
|
|
242
|
+
collection: params.collection,
|
|
243
|
+
filter: params.filter || null,
|
|
244
|
+
pipeline: params.pipeline || null,
|
|
245
|
+
options: params.options || null,
|
|
246
|
+
queryType: params.queryType
|
|
247
|
+
});
|
|
248
|
+
var digest = (0, crypto_1.createHash)('sha256').update(JSON.stringify(normalized)).digest('hex').slice(0, 16);
|
|
249
|
+
return "".concat(params.collection, "-").concat(digest);
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
console.error('SlowQueryReporter hash generation failed', err);
|
|
253
|
+
return "".concat(params.collection, "-").concat(Date.now());
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
SlowQueryReporter.normalizeForHash = function (value) {
|
|
257
|
+
var _this = this;
|
|
258
|
+
if (value === null || value === undefined) {
|
|
259
|
+
return value;
|
|
260
|
+
}
|
|
261
|
+
if (Array.isArray(value)) {
|
|
262
|
+
return value.map(function (item) { return _this.normalizeForHash(item); });
|
|
263
|
+
}
|
|
264
|
+
if (value instanceof RegExp) {
|
|
265
|
+
return value.toString();
|
|
266
|
+
}
|
|
267
|
+
if (typeof value === 'object') {
|
|
268
|
+
if (typeof value.toHexString === 'function') {
|
|
269
|
+
return value.toHexString();
|
|
270
|
+
}
|
|
271
|
+
var ordered_1 = {};
|
|
272
|
+
Object.keys(value).sort().forEach(function (key) {
|
|
273
|
+
ordered_1[key] = _this.normalizeForHash(value[key]);
|
|
274
|
+
});
|
|
275
|
+
return ordered_1;
|
|
276
|
+
}
|
|
277
|
+
return value;
|
|
278
|
+
};
|
|
279
|
+
SlowQueryReporter._explainCache = new NodeCache({ stdTTL: EXPLAIN_THROTTLE_SECONDS, checkperiod: 0 });
|
|
280
|
+
return SlowQueryReporter;
|
|
281
|
+
}());
|
|
282
|
+
exports.SlowQueryReporter = SlowQueryReporter;
|
|
283
|
+
function removeEmptyFields(obj) {
|
|
284
|
+
return Object.keys(obj).reduce(function (acc, key) {
|
|
285
|
+
var value = obj[key];
|
|
286
|
+
if (value !== undefined && value !== null) {
|
|
287
|
+
acc[key] = value;
|
|
288
|
+
}
|
|
289
|
+
return acc;
|
|
290
|
+
}, {});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
//# sourceMappingURL=slow-query-reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/util/slow-query-reporter.ts"],"names":[],"mappings":"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","file":"slow-query-reporter.js","sourcesContent":["import { createHash } from 'crypto';\nimport { Document } from 'mongodb';\nimport * as NodeCache from 'node-cache';\nimport { hostname } from 'os';\nimport { SlowQueryReportPayload } from '../models/slow-query-report.model';\nimport { ResolveIOServer } from '../resolveio-server-app';\nimport { deepCopy } from './common';\n\nconst SLOW_QUERY_ENDPOINT = 'https://backend.resolveio.com/api/slow-queries/report';\nconst DEFAULT_SLOW_QUERY_THRESHOLD_MS = 2000;\nconst EXPLAIN_THROTTLE_SECONDS = 5 * 60;\n\ninterface SlowQueryReportParams {\n\tcollection: string;\n\tfilter?: Document;\n\tpipeline?: any[];\n\toptions?: Document;\n\tdurationMs: number;\n\tqueryType: 'find' | 'findOne' | 'aggregate' | 'countDocuments';\n\tnotes?: string;\n\tqueryHash?: string;\n\tsourceApp?: string;\n}\n\nexport class SlowQueryReporter {\n\tprivate static _explainCache = new NodeCache({ stdTTL: EXPLAIN_THROTTLE_SECONDS, checkperiod: 0 });\n\n\tpublic static async reportSlowQuery(params: SlowQueryReportParams): Promise<void> {\n\t\tconst thresholdMs = this.getThresholdMs();\n\t\tif (thresholdMs <= 0 || params.durationMs < thresholdMs) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst queryHash = params.queryHash || this.generateQueryHash(params);\n\t\tconst apiKey = this.getApiKey();\n\t\tconst payload: SlowQueryReportPayload = {\n\t\t\tqueryHash,\n\t\t\tcollection: params.collection,\n\t\t\tpipeline: params.pipeline ? deepCopy(params.pipeline) : undefined,\n\t\t\tfilter: params.filter ? deepCopy(params.filter) : undefined,\n\t\t\toptions: params.options ? deepCopy(params.options) : undefined,\n\t\t\tdurationMs: params.durationMs,\n\t\t\tthresholdMs,\n\t\t\tclientSlug: ResolveIOServer.getClientName(),\n\t\t\tclientName: this.getClientName(),\n\t\t\tsourceApp: params.sourceApp || 'mongo-manager',\n\t\t\tenvironment: this.getEnvironment(),\n\t\t\tnodeHostname: hostname(),\n\t\t\tnotes: params.notes,\n\t\t\tapiKey\n\t\t};\n\n\t\ttry {\n\t\t\tconst explain = await this.captureExplain(params, queryHash);\n\t\t\tif (explain) {\n\t\t\t\tpayload.explainPlan = deepCopy(explain.queryPlanner || explain);\n\t\t\t\tif (explain.executionStats) {\n\t\t\t\t\tpayload.explainExecutionStats = deepCopy(explain.executionStats);\n\t\t\t\t}\n\t\t\t\tpayload.explainGeneratedAt = new Date();\n\t\t\t}\n\n\t\t\tawait this.sendPayload(payload, apiKey);\n\t\t}\n\t\tcatch (err) {\n\t\t\tconsole.error('SlowQueryReporter failed', err);\n\t\t}\n\t}\n\n\tpublic static reportSlowQueryFireAndForget(params: SlowQueryReportParams): void {\n\t\t// eslint-disable-next-line no-restricted-syntax, promise/prefer-await-to-then\n\t\tvoid this.reportSlowQuery(params).catch(err => {\n\t\t\tconsole.error('SlowQueryReporter fire-and-forget error', err);\n\t\t});\n\t}\n\n\tprivate static getThresholdMs(): number {\n\t\tconst config = ResolveIOServer.getServerConfig();\n\t\tconst envValue = process.env.SLOW_QUERY_THRESHOLD_MS;\n\t\tif (envValue && !Number.isNaN(Number(envValue))) {\n\t\t\treturn Number.parseInt(envValue, 10);\n\t\t}\n\n\t\tconst configValue = config?.SLOW_QUERY_THRESHOLD_MS;\n\t\tif (typeof configValue === 'number') {\n\t\t\treturn configValue;\n\t\t}\n\n\t\tif (typeof configValue === 'string' && configValue.length && !Number.isNaN(Number(configValue))) {\n\t\t\treturn Number.parseInt(configValue, 10);\n\t\t}\n\n\t\treturn DEFAULT_SLOW_QUERY_THRESHOLD_MS;\n\t}\n\n\tprivate static getEnvironment(): string {\n\t\tconst config = ResolveIOServer.getServerConfig();\n\t\treturn (\n\t\t\tconfig?.RESOLVEIO_ENV ||\n\t\t\tconfig?.ENVIRONMENT ||\n\t\t\tconfig?.ROOT_URL ||\n\t\t\tprocess.env.RESOLVEIO_ENV ||\n\t\t\tprocess.env.NODE_ENV ||\n\t\t\t'unknown'\n\t\t);\n\t}\n\n\tprivate static getClientName(): string | undefined {\n\t\tconst config = ResolveIOServer.getServerConfig();\n\t\treturn config?.CLIENT_NAME || ResolveIOServer.getClientName();\n\t}\n\n\tprivate static async captureExplain(params: SlowQueryReportParams, queryHash: string): Promise<Document | undefined> {\n\t\tif (params.queryType === 'countDocuments') {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (!this.shouldExplain(queryHash)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst collection = ResolveIOServer.getMainDB().collection(params.collection);\n\t\ttry {\n\t\t\tif (params.queryType === 'aggregate') {\n\t\t\t\treturn await collection.aggregate(params.pipeline || [], params.options).explain('executionStats');\n\t\t\t}\n\t\t\telse if (params.queryType === 'find') {\n\t\t\t\treturn await collection.find(params.filter || {}, params.options).explain('executionStats');\n\t\t\t}\n\t\t\telse if (params.queryType === 'findOne') {\n\t\t\t\treturn await collection.find(params.filter || {}, params.options).limit(1).explain('executionStats');\n\t\t\t}\n\t\t}\n\t\tcatch (err) {\n\t\t\tconsole.error('SlowQueryReporter explain failed', err);\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate static shouldExplain(queryHash: string): boolean {\n\t\tif (!queryHash) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (this._explainCache.has(queryHash)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis._explainCache.set(queryHash, true);\n\t\treturn true;\n\t}\n\n\tprivate static async sendPayload(payload: SlowQueryReportPayload, apiKey?: string): Promise<void> {\n\t\tconst transport = globalThis.fetch;\n\t\tif (typeof transport !== 'function') {\n\t\t\tconsole.error('SlowQueryReporter cannot send payload (fetch missing)', payload);\n\t\t\treturn;\n\t\t}\n\n\t\tconst key = apiKey || this.getApiKey();\n\t\tconst headers = removeEmptyFields({\n\t\t\t'Content-Type': 'application/json',\n\t\t\t'X-ResolveIO-Slow-Query-Key': key,\n\t\t\t'X-Slow-Query-Key': key,\n\t\t\t'X-API-Key': key\n\t\t});\n\n\t\ttry {\n\t\t\tconst response = await transport(SLOW_QUERY_ENDPOINT, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders,\n\t\t\t\tbody: JSON.stringify(payload)\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconsole.error('SlowQueryReporter payload rejected', {\n\t\t\t\t\tstatus: response.status,\n\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\tqueryHash: payload.queryHash\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tcatch (err) {\n\t\t\tconsole.error('SlowQueryReporter network error', err);\n\t\t}\n\t}\n\n\tprivate static getApiKey(): string | undefined {\n\t\tconst config = ResolveIOServer.getServerConfig();\n\t\treturn (\n\t\t\tprocess.env.SLOW_QUERY_API_KEY ||\n\t\t\tprocess.env.SLOW_QUERY_INGEST_KEY ||\n\t\t\tconfig?.SLOW_QUERY_API_KEY ||\n\t\t\tconfig?.SLOW_QUERY_INGEST_KEY ||\n\t\t\tResolveIOServer.getClientName()\n\t\t);\n\t}\n\n\tprivate static generateQueryHash(params: SlowQueryReportParams): string {\n\t\ttry {\n\t\t\tconst normalized = this.normalizeForHash({\n\t\t\t\tcollection: params.collection,\n\t\t\t\tfilter: params.filter || null,\n\t\t\t\tpipeline: params.pipeline || null,\n\t\t\t\toptions: params.options || null,\n\t\t\t\tqueryType: params.queryType\n\t\t\t});\n\t\t\tconst digest = createHash('sha256').update(JSON.stringify(normalized)).digest('hex').slice(0, 16);\n\t\t\treturn `${params.collection}-${digest}`;\n\t\t}\n\t\tcatch (err) {\n\t\t\tconsole.error('SlowQueryReporter hash generation failed', err);\n\t\t\treturn `${params.collection}-${Date.now()}`;\n\t\t}\n\t}\n\n\tprivate static normalizeForHash(value: any): any {\n\t\tif (value === null || value === undefined) {\n\t\t\treturn value;\n\t\t}\n\n\t\tif (Array.isArray(value)) {\n\t\t\treturn value.map(item => this.normalizeForHash(item));\n\t\t}\n\n\t\tif (value instanceof RegExp) {\n\t\t\treturn value.toString();\n\t\t}\n\n\t\tif (typeof value === 'object') {\n\t\t\tif (typeof (value as any).toHexString === 'function') {\n\t\t\t\treturn (value as any).toHexString();\n\t\t\t}\n\n\t\t\tconst ordered: Record<string, any> = {};\n\t\t\tObject.keys(value).sort().forEach(key => {\n\t\t\t\tordered[key] = this.normalizeForHash(value[key]);\n\t\t\t});\n\t\t\treturn ordered;\n\t\t}\n\n\t\treturn value;\n\t}\n}\n\n\tfunction removeEmptyFields<T extends Record<string, any>>(obj: T): T {\n\t\treturn (Object.keys(obj) as Array<keyof T>).reduce((acc, key) => {\n\t\t\tconst value = obj[key];\n\t\t\tif (value !== undefined && value !== null) {\n\t\t\t\tacc[key] = value;\n\t\t\t}\n\t\t\treturn acc;\n\t\t}, {} as T);\n\t}\n"]}
|