apitally 0.17.3 → 0.18.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.
@@ -7,6 +7,9 @@ var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
9
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
10
+ var __esm = (fn, res) => function __init() {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ };
10
13
  var __commonJS = (cb, mod) => function __require() {
11
14
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
15
  };
@@ -11020,272 +11023,217 @@ var require_cjs = __commonJS({
11020
11023
  }
11021
11024
  });
11022
11025
 
11023
- // src/nestjs/index.ts
11024
- var nestjs_exports = {};
11025
- __export(nestjs_exports, {
11026
- setConsumer: () => setConsumer,
11027
- useApitally: () => useApitally2
11028
- });
11029
- module.exports = __toCommonJS(nestjs_exports);
11030
- var import_common = require("@nestjs/common");
11031
- var import_rxjs = __toESM(require_cjs(), 1);
11032
-
11033
- // src/express/middleware.ts
11034
- var import_perf_hooks = require("perf_hooks");
11035
-
11036
- // src/common/client.ts
11037
- var import_crypto5 = require("crypto");
11038
- var import_fetch_retry = __toESM(require("fetch-retry"), 1);
11039
-
11040
11026
  // src/common/consumerRegistry.ts
11041
- var consumerFromStringOrObject = /* @__PURE__ */ __name((consumer) => {
11042
- var _a3, _b;
11043
- if (typeof consumer === "string") {
11044
- consumer = String(consumer).trim().substring(0, 128);
11045
- return consumer ? {
11046
- identifier: consumer
11047
- } : null;
11048
- } else {
11049
- consumer.identifier = String(consumer.identifier).trim().substring(0, 128);
11050
- consumer.name = (_a3 = consumer.name) == null ? void 0 : _a3.trim().substring(0, 64);
11051
- consumer.group = (_b = consumer.group) == null ? void 0 : _b.trim().substring(0, 64);
11052
- return consumer.identifier ? consumer : null;
11053
- }
11054
- }, "consumerFromStringOrObject");
11055
- var _ConsumerRegistry = class _ConsumerRegistry {
11056
- consumers;
11057
- updated;
11058
- constructor() {
11059
- this.consumers = /* @__PURE__ */ new Map();
11060
- this.updated = /* @__PURE__ */ new Set();
11061
- }
11062
- addOrUpdateConsumer(consumer) {
11063
- if (!consumer || !consumer.name && !consumer.group) {
11064
- return;
11065
- }
11066
- const existing = this.consumers.get(consumer.identifier);
11067
- if (!existing) {
11068
- this.consumers.set(consumer.identifier, consumer);
11069
- this.updated.add(consumer.identifier);
11070
- } else {
11071
- if (consumer.name && consumer.name !== existing.name) {
11072
- existing.name = consumer.name;
11073
- this.updated.add(consumer.identifier);
11074
- }
11075
- if (consumer.group && consumer.group !== existing.group) {
11076
- existing.group = consumer.group;
11077
- this.updated.add(consumer.identifier);
11027
+ var consumerFromStringOrObject, _ConsumerRegistry, ConsumerRegistry;
11028
+ var init_consumerRegistry = __esm({
11029
+ "src/common/consumerRegistry.ts"() {
11030
+ "use strict";
11031
+ consumerFromStringOrObject = /* @__PURE__ */ __name((consumer) => {
11032
+ var _a3, _b;
11033
+ if (typeof consumer === "string") {
11034
+ consumer = String(consumer).trim().substring(0, 128);
11035
+ return consumer ? {
11036
+ identifier: consumer
11037
+ } : null;
11038
+ } else {
11039
+ consumer.identifier = String(consumer.identifier).trim().substring(0, 128);
11040
+ consumer.name = (_a3 = consumer.name) == null ? void 0 : _a3.trim().substring(0, 64);
11041
+ consumer.group = (_b = consumer.group) == null ? void 0 : _b.trim().substring(0, 64);
11042
+ return consumer.identifier ? consumer : null;
11043
+ }
11044
+ }, "consumerFromStringOrObject");
11045
+ _ConsumerRegistry = class _ConsumerRegistry {
11046
+ consumers;
11047
+ updated;
11048
+ constructor() {
11049
+ this.consumers = /* @__PURE__ */ new Map();
11050
+ this.updated = /* @__PURE__ */ new Set();
11051
+ }
11052
+ addOrUpdateConsumer(consumer) {
11053
+ if (!consumer || !consumer.name && !consumer.group) {
11054
+ return;
11055
+ }
11056
+ const existing = this.consumers.get(consumer.identifier);
11057
+ if (!existing) {
11058
+ this.consumers.set(consumer.identifier, consumer);
11059
+ this.updated.add(consumer.identifier);
11060
+ } else {
11061
+ if (consumer.name && consumer.name !== existing.name) {
11062
+ existing.name = consumer.name;
11063
+ this.updated.add(consumer.identifier);
11064
+ }
11065
+ if (consumer.group && consumer.group !== existing.group) {
11066
+ existing.group = consumer.group;
11067
+ this.updated.add(consumer.identifier);
11068
+ }
11069
+ }
11078
11070
  }
11079
- }
11080
- }
11081
- getAndResetUpdatedConsumers() {
11082
- const data = [];
11083
- this.updated.forEach((identifier) => {
11084
- const consumer = this.consumers.get(identifier);
11085
- if (consumer) {
11086
- data.push(consumer);
11071
+ getAndResetUpdatedConsumers() {
11072
+ const data = [];
11073
+ this.updated.forEach((identifier) => {
11074
+ const consumer = this.consumers.get(identifier);
11075
+ if (consumer) {
11076
+ data.push(consumer);
11077
+ }
11078
+ });
11079
+ this.updated.clear();
11080
+ return data;
11087
11081
  }
11088
- });
11089
- this.updated.clear();
11090
- return data;
11082
+ };
11083
+ __name(_ConsumerRegistry, "ConsumerRegistry");
11084
+ ConsumerRegistry = _ConsumerRegistry;
11091
11085
  }
11092
- };
11093
- __name(_ConsumerRegistry, "ConsumerRegistry");
11094
- var ConsumerRegistry = _ConsumerRegistry;
11086
+ });
11095
11087
 
11096
11088
  // src/common/logging.ts
11097
- var import_winston = require("winston");
11098
- var getLogger = /* @__PURE__ */ __name(() => {
11099
- return (0, import_winston.createLogger)({
11100
- level: process.env.APITALLY_DEBUG ? "debug" : "warn",
11101
- format: import_winston.format.combine(import_winston.format.colorize(), import_winston.format.timestamp(), import_winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`)),
11102
- transports: [
11103
- new import_winston.transports.Console()
11104
- ]
11105
- });
11106
- }, "getLogger");
11089
+ var import_winston, getLogger;
11090
+ var init_logging = __esm({
11091
+ "src/common/logging.ts"() {
11092
+ "use strict";
11093
+ import_winston = require("winston");
11094
+ getLogger = /* @__PURE__ */ __name(() => {
11095
+ return (0, import_winston.createLogger)({
11096
+ level: process.env.APITALLY_DEBUG ? "debug" : "warn",
11097
+ format: import_winston.format.combine(import_winston.format.colorize(), import_winston.format.timestamp(), import_winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`)),
11098
+ transports: [
11099
+ new import_winston.transports.Console()
11100
+ ]
11101
+ });
11102
+ }, "getLogger");
11103
+ }
11104
+ });
11107
11105
 
11108
11106
  // src/common/paramValidation.ts
11109
11107
  function isValidClientId(clientId) {
11110
11108
  const regexExp = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
11111
11109
  return regexExp.test(clientId);
11112
11110
  }
11113
- __name(isValidClientId, "isValidClientId");
11114
11111
  function isValidEnv(env) {
11115
11112
  const regexExp = /^[\w-]{1,32}$/;
11116
11113
  return regexExp.test(env);
11117
11114
  }
11118
- __name(isValidEnv, "isValidEnv");
11115
+ var init_paramValidation = __esm({
11116
+ "src/common/paramValidation.ts"() {
11117
+ "use strict";
11118
+ __name(isValidClientId, "isValidClientId");
11119
+ __name(isValidEnv, "isValidEnv");
11120
+ }
11121
+ });
11119
11122
 
11120
11123
  // src/common/requestCounter.ts
11121
- var _RequestCounter = class _RequestCounter {
11122
- requestCounts;
11123
- requestSizeSums;
11124
- responseSizeSums;
11125
- responseTimes;
11126
- requestSizes;
11127
- responseSizes;
11128
- constructor() {
11129
- this.requestCounts = /* @__PURE__ */ new Map();
11130
- this.requestSizeSums = /* @__PURE__ */ new Map();
11131
- this.responseSizeSums = /* @__PURE__ */ new Map();
11132
- this.responseTimes = /* @__PURE__ */ new Map();
11133
- this.requestSizes = /* @__PURE__ */ new Map();
11134
- this.responseSizes = /* @__PURE__ */ new Map();
11135
- }
11136
- getKey(requestInfo) {
11137
- return [
11138
- requestInfo.consumer || "",
11139
- requestInfo.method.toUpperCase(),
11140
- requestInfo.path,
11141
- requestInfo.statusCode
11142
- ].join("|");
11143
- }
11144
- addRequest(requestInfo) {
11145
- const key = this.getKey(requestInfo);
11146
- this.requestCounts.set(key, (this.requestCounts.get(key) || 0) + 1);
11147
- if (!this.responseTimes.has(key)) {
11148
- this.responseTimes.set(key, /* @__PURE__ */ new Map());
11149
- }
11150
- const responseTimeMap = this.responseTimes.get(key);
11151
- const responseTimeMsBin = Math.floor(requestInfo.responseTime / 10) * 10;
11152
- responseTimeMap.set(responseTimeMsBin, (responseTimeMap.get(responseTimeMsBin) || 0) + 1);
11153
- if (requestInfo.requestSize !== void 0) {
11154
- requestInfo.requestSize = Number(requestInfo.requestSize);
11155
- this.requestSizeSums.set(key, (this.requestSizeSums.get(key) || 0) + requestInfo.requestSize);
11156
- if (!this.requestSizes.has(key)) {
11157
- this.requestSizes.set(key, /* @__PURE__ */ new Map());
11158
- }
11159
- const requestSizeMap = this.requestSizes.get(key);
11160
- const requestSizeKbBin = Math.floor(requestInfo.requestSize / 1e3);
11161
- requestSizeMap.set(requestSizeKbBin, (requestSizeMap.get(requestSizeKbBin) || 0) + 1);
11162
- }
11163
- if (requestInfo.responseSize !== void 0) {
11164
- requestInfo.responseSize = Number(requestInfo.responseSize);
11165
- this.responseSizeSums.set(key, (this.responseSizeSums.get(key) || 0) + requestInfo.responseSize);
11166
- if (!this.responseSizes.has(key)) {
11167
- this.responseSizes.set(key, /* @__PURE__ */ new Map());
11168
- }
11169
- const responseSizeMap = this.responseSizes.get(key);
11170
- const responseSizeKbBin = Math.floor(requestInfo.responseSize / 1e3);
11171
- responseSizeMap.set(responseSizeKbBin, (responseSizeMap.get(responseSizeKbBin) || 0) + 1);
11172
- }
11173
- }
11174
- getAndResetRequests() {
11175
- const data = [];
11176
- this.requestCounts.forEach((count, key) => {
11177
- const [consumer, method, path, statusCodeStr] = key.split("|");
11178
- const responseTimes = this.responseTimes.get(key) || /* @__PURE__ */ new Map();
11179
- const requestSizes = this.requestSizes.get(key) || /* @__PURE__ */ new Map();
11180
- const responseSizes = this.responseSizes.get(key) || /* @__PURE__ */ new Map();
11181
- data.push({
11182
- consumer: consumer || null,
11183
- method,
11184
- path,
11185
- status_code: parseInt(statusCodeStr),
11186
- request_count: count,
11187
- request_size_sum: this.requestSizeSums.get(key) || 0,
11188
- response_size_sum: this.responseSizeSums.get(key) || 0,
11189
- response_times: Object.fromEntries(responseTimes),
11190
- request_sizes: Object.fromEntries(requestSizes),
11191
- response_sizes: Object.fromEntries(responseSizes)
11192
- });
11193
- });
11194
- this.requestCounts.clear();
11195
- this.requestSizeSums.clear();
11196
- this.responseSizeSums.clear();
11197
- this.responseTimes.clear();
11198
- this.requestSizes.clear();
11199
- this.responseSizes.clear();
11200
- return data;
11124
+ var _RequestCounter, RequestCounter;
11125
+ var init_requestCounter = __esm({
11126
+ "src/common/requestCounter.ts"() {
11127
+ "use strict";
11128
+ _RequestCounter = class _RequestCounter {
11129
+ requestCounts;
11130
+ requestSizeSums;
11131
+ responseSizeSums;
11132
+ responseTimes;
11133
+ requestSizes;
11134
+ responseSizes;
11135
+ constructor() {
11136
+ this.requestCounts = /* @__PURE__ */ new Map();
11137
+ this.requestSizeSums = /* @__PURE__ */ new Map();
11138
+ this.responseSizeSums = /* @__PURE__ */ new Map();
11139
+ this.responseTimes = /* @__PURE__ */ new Map();
11140
+ this.requestSizes = /* @__PURE__ */ new Map();
11141
+ this.responseSizes = /* @__PURE__ */ new Map();
11142
+ }
11143
+ getKey(requestInfo) {
11144
+ return [
11145
+ requestInfo.consumer || "",
11146
+ requestInfo.method.toUpperCase(),
11147
+ requestInfo.path,
11148
+ requestInfo.statusCode
11149
+ ].join("|");
11150
+ }
11151
+ addRequest(requestInfo) {
11152
+ const key = this.getKey(requestInfo);
11153
+ this.requestCounts.set(key, (this.requestCounts.get(key) || 0) + 1);
11154
+ if (!this.responseTimes.has(key)) {
11155
+ this.responseTimes.set(key, /* @__PURE__ */ new Map());
11156
+ }
11157
+ const responseTimeMap = this.responseTimes.get(key);
11158
+ const responseTimeMsBin = Math.floor(requestInfo.responseTime / 10) * 10;
11159
+ responseTimeMap.set(responseTimeMsBin, (responseTimeMap.get(responseTimeMsBin) || 0) + 1);
11160
+ if (requestInfo.requestSize !== void 0) {
11161
+ requestInfo.requestSize = Number(requestInfo.requestSize);
11162
+ this.requestSizeSums.set(key, (this.requestSizeSums.get(key) || 0) + requestInfo.requestSize);
11163
+ if (!this.requestSizes.has(key)) {
11164
+ this.requestSizes.set(key, /* @__PURE__ */ new Map());
11165
+ }
11166
+ const requestSizeMap = this.requestSizes.get(key);
11167
+ const requestSizeKbBin = Math.floor(requestInfo.requestSize / 1e3);
11168
+ requestSizeMap.set(requestSizeKbBin, (requestSizeMap.get(requestSizeKbBin) || 0) + 1);
11169
+ }
11170
+ if (requestInfo.responseSize !== void 0) {
11171
+ requestInfo.responseSize = Number(requestInfo.responseSize);
11172
+ this.responseSizeSums.set(key, (this.responseSizeSums.get(key) || 0) + requestInfo.responseSize);
11173
+ if (!this.responseSizes.has(key)) {
11174
+ this.responseSizes.set(key, /* @__PURE__ */ new Map());
11175
+ }
11176
+ const responseSizeMap = this.responseSizes.get(key);
11177
+ const responseSizeKbBin = Math.floor(requestInfo.responseSize / 1e3);
11178
+ responseSizeMap.set(responseSizeKbBin, (responseSizeMap.get(responseSizeKbBin) || 0) + 1);
11179
+ }
11180
+ }
11181
+ getAndResetRequests() {
11182
+ const data = [];
11183
+ this.requestCounts.forEach((count, key) => {
11184
+ const [consumer, method, path, statusCodeStr] = key.split("|");
11185
+ const responseTimes = this.responseTimes.get(key) || /* @__PURE__ */ new Map();
11186
+ const requestSizes = this.requestSizes.get(key) || /* @__PURE__ */ new Map();
11187
+ const responseSizes = this.responseSizes.get(key) || /* @__PURE__ */ new Map();
11188
+ data.push({
11189
+ consumer: consumer || null,
11190
+ method,
11191
+ path,
11192
+ status_code: parseInt(statusCodeStr),
11193
+ request_count: count,
11194
+ request_size_sum: this.requestSizeSums.get(key) || 0,
11195
+ response_size_sum: this.responseSizeSums.get(key) || 0,
11196
+ response_times: Object.fromEntries(responseTimes),
11197
+ request_sizes: Object.fromEntries(requestSizes),
11198
+ response_sizes: Object.fromEntries(responseSizes)
11199
+ });
11200
+ });
11201
+ this.requestCounts.clear();
11202
+ this.requestSizeSums.clear();
11203
+ this.responseSizeSums.clear();
11204
+ this.responseTimes.clear();
11205
+ this.requestSizes.clear();
11206
+ this.responseSizes.clear();
11207
+ return data;
11208
+ }
11209
+ };
11210
+ __name(_RequestCounter, "RequestCounter");
11211
+ RequestCounter = _RequestCounter;
11201
11212
  }
11202
- };
11203
- __name(_RequestCounter, "RequestCounter");
11204
- var RequestCounter = _RequestCounter;
11205
-
11206
- // src/common/requestLogger.ts
11207
- var import_async_lock = __toESM(require("async-lock"), 1);
11208
- var import_buffer2 = require("buffer");
11209
- var import_crypto3 = require("crypto");
11210
- var import_fs2 = require("fs");
11211
- var import_os2 = require("os");
11212
- var import_path2 = require("path");
11213
+ });
11213
11214
 
11214
11215
  // src/common/sentry.ts
11215
- var sentry;
11216
- (async () => {
11217
- try {
11218
- sentry = await import("@sentry/node");
11219
- } catch (e) {
11220
- }
11221
- })();
11222
11216
  function getSentryEventId() {
11223
11217
  if (sentry && sentry.lastEventId) {
11224
11218
  return sentry.lastEventId();
11225
11219
  }
11226
11220
  return void 0;
11227
11221
  }
11228
- __name(getSentryEventId, "getSentryEventId");
11229
-
11230
- // src/common/serverErrorCounter.ts
11231
- var import_crypto = require("crypto");
11232
- var MAX_MSG_LENGTH = 2048;
11233
- var MAX_STACKTRACE_LENGTH = 65536;
11234
- var _ServerErrorCounter = class _ServerErrorCounter {
11235
- errorCounts;
11236
- errorDetails;
11237
- sentryEventIds;
11238
- constructor() {
11239
- this.errorCounts = /* @__PURE__ */ new Map();
11240
- this.errorDetails = /* @__PURE__ */ new Map();
11241
- this.sentryEventIds = /* @__PURE__ */ new Map();
11242
- }
11243
- addServerError(serverError) {
11244
- const key = this.getKey(serverError);
11245
- if (!this.errorDetails.has(key)) {
11246
- this.errorDetails.set(key, serverError);
11247
- }
11248
- this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
11249
- const sentryEventId = getSentryEventId();
11250
- if (sentryEventId) {
11251
- this.sentryEventIds.set(key, sentryEventId);
11252
- }
11253
- }
11254
- getAndResetServerErrors() {
11255
- const data = [];
11256
- this.errorCounts.forEach((count, key) => {
11257
- const serverError = this.errorDetails.get(key);
11258
- if (serverError) {
11259
- data.push({
11260
- consumer: serverError.consumer || null,
11261
- method: serverError.method,
11262
- path: serverError.path,
11263
- type: serverError.type,
11264
- msg: truncateExceptionMessage(serverError.msg),
11265
- traceback: truncateExceptionStackTrace(serverError.traceback),
11266
- sentry_event_id: this.sentryEventIds.get(key) || null,
11267
- error_count: count
11268
- });
11222
+ var sentry;
11223
+ var init_sentry = __esm({
11224
+ "src/common/sentry.ts"() {
11225
+ "use strict";
11226
+ (async () => {
11227
+ try {
11228
+ sentry = await import("@sentry/node");
11229
+ } catch (e) {
11269
11230
  }
11270
- });
11271
- this.errorCounts.clear();
11272
- this.errorDetails.clear();
11273
- return data;
11231
+ })();
11232
+ __name(getSentryEventId, "getSentryEventId");
11274
11233
  }
11275
- getKey(serverError) {
11276
- const hashInput = [
11277
- serverError.consumer || "",
11278
- serverError.method.toUpperCase(),
11279
- serverError.path,
11280
- serverError.type,
11281
- serverError.msg.trim(),
11282
- serverError.traceback.trim()
11283
- ].join("|");
11284
- return (0, import_crypto.createHash)("md5").update(hashInput).digest("hex");
11285
- }
11286
- };
11287
- __name(_ServerErrorCounter, "ServerErrorCounter");
11288
- var ServerErrorCounter = _ServerErrorCounter;
11234
+ });
11235
+
11236
+ // src/common/serverErrorCounter.ts
11289
11237
  function truncateExceptionMessage(msg) {
11290
11238
  if (msg.length <= MAX_MSG_LENGTH) {
11291
11239
  return msg;
@@ -11294,7 +11242,6 @@ function truncateExceptionMessage(msg) {
11294
11242
  const cutoff = MAX_MSG_LENGTH - suffix.length;
11295
11243
  return msg.substring(0, cutoff) + suffix;
11296
11244
  }
11297
- __name(truncateExceptionMessage, "truncateExceptionMessage");
11298
11245
  function truncateExceptionStackTrace(stack) {
11299
11246
  const suffix = "... (truncated) ...";
11300
11247
  const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
@@ -11311,413 +11258,155 @@ function truncateExceptionStackTrace(stack) {
11311
11258
  }
11312
11259
  return truncatedLines.join("\n");
11313
11260
  }
11314
- __name(truncateExceptionStackTrace, "truncateExceptionStackTrace");
11315
-
11316
- // src/common/tempGzipFile.ts
11317
- var import_buffer = require("buffer");
11318
- var import_crypto2 = require("crypto");
11319
- var import_fs = require("fs");
11320
- var import_os = require("os");
11321
- var import_path = require("path");
11322
- var import_zlib = require("zlib");
11323
- var _TempGzipFile = class _TempGzipFile {
11324
- uuid;
11325
- filePath;
11326
- gzip;
11327
- writeStream;
11328
- readyPromise;
11329
- closedPromise;
11330
- constructor() {
11331
- this.uuid = (0, import_crypto2.randomUUID)();
11332
- this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
11333
- this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
11334
- this.readyPromise = new Promise((resolve, reject) => {
11335
- this.writeStream.once("ready", resolve);
11336
- this.writeStream.once("error", reject);
11337
- });
11338
- this.closedPromise = new Promise((resolve, reject) => {
11339
- this.writeStream.once("close", resolve);
11340
- this.writeStream.once("error", reject);
11341
- });
11342
- this.gzip = (0, import_zlib.createGzip)();
11343
- this.gzip.pipe(this.writeStream);
11344
- }
11345
- get size() {
11346
- return this.writeStream.bytesWritten;
11347
- }
11348
- async writeLine(data) {
11349
- await this.readyPromise;
11350
- return new Promise((resolve, reject) => {
11351
- this.gzip.write(import_buffer.Buffer.concat([
11352
- data,
11353
- import_buffer.Buffer.from("\n")
11354
- ]), (error) => {
11355
- if (error) {
11356
- reject(error);
11357
- } else {
11358
- resolve();
11359
- }
11360
- });
11361
- });
11362
- }
11363
- async getContent() {
11364
- return new Promise((resolve, reject) => {
11365
- (0, import_fs.readFile)(this.filePath, (error, data) => {
11366
- if (error) {
11367
- reject(error);
11368
- } else {
11369
- resolve(data);
11370
- }
11371
- });
11372
- });
11373
- }
11374
- async close() {
11375
- await new Promise((resolve) => {
11376
- this.gzip.end(() => {
11377
- resolve();
11378
- });
11379
- });
11380
- await this.closedPromise;
11381
- }
11382
- async delete() {
11383
- await this.close();
11384
- (0, import_fs.unlinkSync)(this.filePath);
11385
- }
11386
- };
11387
- __name(_TempGzipFile, "TempGzipFile");
11388
- var TempGzipFile = _TempGzipFile;
11389
-
11390
- // src/common/requestLogger.ts
11391
- var MAX_BODY_SIZE = 5e4;
11392
- var MAX_FILE_SIZE = 1e6;
11393
- var MAX_FILES = 50;
11394
- var MAX_PENDING_WRITES = 100;
11395
- var BODY_TOO_LARGE = import_buffer2.Buffer.from("<body too large>");
11396
- var BODY_MASKED = import_buffer2.Buffer.from("<masked>");
11397
- var MASKED = "******";
11398
- var ALLOWED_CONTENT_TYPES = [
11399
- "application/json",
11400
- "application/problem+json",
11401
- "application/vnd.api+json",
11402
- "text/plain"
11403
- ];
11404
- var EXCLUDE_PATH_PATTERNS = [
11405
- /\/_?healthz?$/i,
11406
- /\/_?health[_-]?checks?$/i,
11407
- /\/_?heart[_-]?beats?$/i,
11408
- /\/ping$/i,
11409
- /\/ready$/i,
11410
- /\/live$/i
11411
- ];
11412
- var EXCLUDE_USER_AGENT_PATTERNS = [
11413
- /health[-_ ]?check/i,
11414
- /microsoft-azure-application-lb/i,
11415
- /googlehc/i,
11416
- /kube-probe/i
11417
- ];
11418
- var MASK_QUERY_PARAM_PATTERNS = [
11419
- /auth/i,
11420
- /api-?key/i,
11421
- /secret/i,
11422
- /token/i,
11423
- /password/i,
11424
- /pwd/i
11425
- ];
11426
- var MASK_HEADER_PATTERNS = [
11427
- /auth/i,
11428
- /api-?key/i,
11429
- /secret/i,
11430
- /token/i,
11431
- /cookie/i
11432
- ];
11433
- var MASK_BODY_FIELD_PATTERNS = [
11434
- /password/i,
11435
- /pwd/i,
11436
- /token/i,
11437
- /secret/i,
11438
- /auth/i,
11439
- /card[-_ ]?number/i,
11440
- /ccv/i,
11441
- /ssn/i
11442
- ];
11443
- var DEFAULT_CONFIG = {
11444
- enabled: false,
11445
- logQueryParams: true,
11446
- logRequestHeaders: false,
11447
- logRequestBody: false,
11448
- logResponseHeaders: true,
11449
- logResponseBody: false,
11450
- logException: true,
11451
- maskQueryParams: [],
11452
- maskHeaders: [],
11453
- maskBodyFields: [],
11454
- excludePaths: []
11455
- };
11456
- var _RequestLogger = class _RequestLogger {
11457
- config;
11458
- enabled;
11459
- suspendUntil = null;
11460
- pendingWrites = [];
11461
- currentFile = null;
11462
- files = [];
11463
- maintainIntervalId;
11464
- lock = new import_async_lock.default();
11465
- constructor(config) {
11466
- this.config = {
11467
- ...DEFAULT_CONFIG,
11468
- ...config
11469
- };
11470
- this.enabled = this.config.enabled && checkWritableFs();
11471
- if (this.enabled) {
11472
- this.maintainIntervalId = setInterval(() => {
11473
- this.maintain();
11474
- }, 1e3);
11475
- }
11476
- }
11477
- get maxBodySize() {
11478
- return MAX_BODY_SIZE;
11479
- }
11480
- shouldExcludePath(urlPath) {
11481
- const patterns = [
11482
- ...this.config.excludePaths,
11483
- ...EXCLUDE_PATH_PATTERNS
11484
- ];
11485
- return matchPatterns(urlPath, patterns);
11486
- }
11487
- shouldExcludeUserAgent(userAgent) {
11488
- return userAgent ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS) : false;
11489
- }
11490
- shouldMaskQueryParam(name) {
11491
- const patterns = [
11492
- ...this.config.maskQueryParams,
11493
- ...MASK_QUERY_PARAM_PATTERNS
11494
- ];
11495
- return matchPatterns(name, patterns);
11496
- }
11497
- shouldMaskHeader(name) {
11498
- const patterns = [
11499
- ...this.config.maskHeaders,
11500
- ...MASK_HEADER_PATTERNS
11501
- ];
11502
- return matchPatterns(name, patterns);
11503
- }
11504
- shouldMaskBodyField(name) {
11505
- const patterns = [
11506
- ...this.config.maskBodyFields,
11507
- ...MASK_BODY_FIELD_PATTERNS
11508
- ];
11509
- return matchPatterns(name, patterns);
11510
- }
11511
- hasSupportedContentType(headers) {
11512
- var _a3;
11513
- const contentType = (_a3 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a3[1];
11514
- return this.isSupportedContentType(contentType);
11515
- }
11516
- hasJsonContentType(headers) {
11517
- var _a3;
11518
- const contentType = (_a3 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a3[1];
11519
- return contentType ? /\bjson\b/i.test(contentType) : null;
11520
- }
11521
- isSupportedContentType(contentType) {
11522
- return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
11523
- }
11524
- maskQueryParams(search) {
11525
- const params = new URLSearchParams(search);
11526
- for (const [key] of params) {
11527
- if (this.shouldMaskQueryParam(key)) {
11528
- params.set(key, MASKED);
11529
- }
11530
- }
11531
- return params.toString();
11532
- }
11533
- maskHeaders(headers) {
11534
- return headers.map(([k, v]) => [
11535
- k,
11536
- this.shouldMaskHeader(k) ? MASKED : v
11537
- ]);
11538
- }
11539
- maskBody(data) {
11540
- if (typeof data === "object" && data !== null && !Array.isArray(data)) {
11541
- const result = {};
11542
- for (const [key, value] of Object.entries(data)) {
11543
- if (typeof value === "string" && this.shouldMaskBodyField(key)) {
11544
- result[key] = MASKED;
11545
- } else {
11546
- result[key] = this.maskBody(value);
11547
- }
11548
- }
11549
- return result;
11550
- }
11551
- if (Array.isArray(data)) {
11552
- return data.map((item) => this.maskBody(item));
11553
- }
11554
- return data;
11555
- }
11556
- applyMasking(item) {
11557
- if (this.config.maskRequestBodyCallback && item.request.body && item.request.body !== BODY_TOO_LARGE) {
11558
- try {
11559
- const maskedBody = this.config.maskRequestBodyCallback(item.request);
11560
- item.request.body = maskedBody ?? BODY_MASKED;
11561
- } catch {
11562
- item.request.body = void 0;
11563
- }
11564
- }
11565
- if (this.config.maskResponseBodyCallback && item.response.body && item.response.body !== BODY_TOO_LARGE) {
11566
- try {
11567
- const maskedBody = this.config.maskResponseBodyCallback(item.request, item.response);
11568
- item.response.body = maskedBody ?? BODY_MASKED;
11569
- } catch {
11570
- item.response.body = void 0;
11571
- }
11572
- }
11573
- if (item.request.body && item.request.body.length > MAX_BODY_SIZE) {
11574
- item.request.body = BODY_TOO_LARGE;
11575
- }
11576
- if (item.response.body && item.response.body.length > MAX_BODY_SIZE) {
11577
- item.response.body = BODY_TOO_LARGE;
11578
- }
11579
- for (const key of [
11580
- "request",
11581
- "response"
11582
- ]) {
11583
- const bodyData = item[key].body;
11584
- if (!bodyData || bodyData === BODY_TOO_LARGE || bodyData === BODY_MASKED) {
11585
- continue;
11261
+ var import_crypto, MAX_MSG_LENGTH, MAX_STACKTRACE_LENGTH, _ServerErrorCounter, ServerErrorCounter;
11262
+ var init_serverErrorCounter = __esm({
11263
+ "src/common/serverErrorCounter.ts"() {
11264
+ "use strict";
11265
+ import_crypto = require("crypto");
11266
+ init_sentry();
11267
+ MAX_MSG_LENGTH = 2048;
11268
+ MAX_STACKTRACE_LENGTH = 65536;
11269
+ _ServerErrorCounter = class _ServerErrorCounter {
11270
+ errorCounts;
11271
+ errorDetails;
11272
+ sentryEventIds;
11273
+ constructor() {
11274
+ this.errorCounts = /* @__PURE__ */ new Map();
11275
+ this.errorDetails = /* @__PURE__ */ new Map();
11276
+ this.sentryEventIds = /* @__PURE__ */ new Map();
11277
+ }
11278
+ addServerError(serverError) {
11279
+ const key = this.getKey(serverError);
11280
+ if (!this.errorDetails.has(key)) {
11281
+ this.errorDetails.set(key, serverError);
11282
+ }
11283
+ this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
11284
+ const sentryEventId = getSentryEventId();
11285
+ if (sentryEventId) {
11286
+ this.sentryEventIds.set(key, sentryEventId);
11287
+ }
11288
+ }
11289
+ getAndResetServerErrors() {
11290
+ const data = [];
11291
+ this.errorCounts.forEach((count, key) => {
11292
+ const serverError = this.errorDetails.get(key);
11293
+ if (serverError) {
11294
+ data.push({
11295
+ consumer: serverError.consumer || null,
11296
+ method: serverError.method,
11297
+ path: serverError.path,
11298
+ type: serverError.type,
11299
+ msg: truncateExceptionMessage(serverError.msg),
11300
+ traceback: truncateExceptionStackTrace(serverError.traceback),
11301
+ sentry_event_id: this.sentryEventIds.get(key) || null,
11302
+ error_count: count
11303
+ });
11304
+ }
11305
+ });
11306
+ this.errorCounts.clear();
11307
+ this.errorDetails.clear();
11308
+ return data;
11309
+ }
11310
+ getKey(serverError) {
11311
+ const hashInput = [
11312
+ serverError.consumer || "",
11313
+ serverError.method.toUpperCase(),
11314
+ serverError.path,
11315
+ serverError.type,
11316
+ serverError.msg.trim(),
11317
+ serverError.traceback.trim()
11318
+ ].join("|");
11319
+ return (0, import_crypto.createHash)("md5").update(hashInput).digest("hex");
11586
11320
  }
11587
- const headers = item[key].headers;
11588
- const hasJsonContent = this.hasJsonContentType(headers);
11589
- if (hasJsonContent === null || hasJsonContent) {
11590
- try {
11591
- const parsedBody = JSON.parse(bodyData.toString());
11592
- const maskedBody = this.maskBody(parsedBody);
11593
- item[key].body = import_buffer2.Buffer.from(JSON.stringify(maskedBody));
11594
- } catch {
11595
- }
11596
- }
11597
- }
11598
- item.request.headers = this.config.logRequestHeaders ? this.maskHeaders(item.request.headers) : [];
11599
- item.response.headers = this.config.logResponseHeaders ? this.maskHeaders(item.response.headers) : [];
11600
- const url = new URL(item.request.url);
11601
- url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
11602
- item.request.url = url.toString();
11603
- return item;
11604
- }
11605
- logRequest(request, response, error) {
11606
- var _a3, _b, _c;
11607
- if (!this.enabled || this.suspendUntil !== null) return;
11608
- const url = new URL(request.url);
11609
- const path = request.path ?? url.pathname;
11610
- const userAgent = (_a3 = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a3[1];
11611
- if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
11612
- return;
11613
- }
11614
- if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
11615
- request.body = void 0;
11616
- }
11617
- if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
11618
- response.body = void 0;
11619
- }
11620
- if (request.size !== void 0 && request.size < 0) {
11621
- request.size = void 0;
11622
- }
11623
- if (response.size !== void 0 && response.size < 0) {
11624
- response.size = void 0;
11625
- }
11626
- const item = {
11627
- uuid: (0, import_crypto3.randomUUID)(),
11628
- request,
11629
- response,
11630
- exception: error && this.config.logException ? {
11631
- type: error.name,
11632
- message: truncateExceptionMessage(error.message),
11633
- stacktrace: truncateExceptionStackTrace(error.stack || ""),
11634
- sentryEventId: getSentryEventId()
11635
- } : void 0
11636
11321
  };
11637
- this.pendingWrites.push(item);
11638
- if (this.pendingWrites.length > MAX_PENDING_WRITES) {
11639
- this.pendingWrites.shift();
11640
- }
11322
+ __name(_ServerErrorCounter, "ServerErrorCounter");
11323
+ ServerErrorCounter = _ServerErrorCounter;
11324
+ __name(truncateExceptionMessage, "truncateExceptionMessage");
11325
+ __name(truncateExceptionStackTrace, "truncateExceptionStackTrace");
11641
11326
  }
11642
- async writeToFile() {
11643
- if (!this.enabled || this.pendingWrites.length === 0) {
11644
- return;
11645
- }
11646
- return this.lock.acquire("file", async () => {
11647
- if (!this.currentFile) {
11648
- this.currentFile = new TempGzipFile();
11649
- }
11650
- while (this.pendingWrites.length > 0) {
11651
- let item = this.pendingWrites.shift();
11652
- if (item) {
11653
- item = this.applyMasking(item);
11654
- const finalItem = {
11655
- uuid: item.uuid,
11656
- request: skipEmptyValues(item.request),
11657
- response: skipEmptyValues(item.response),
11658
- exception: item.exception
11659
- };
11660
- [
11661
- finalItem.request.body,
11662
- finalItem.response.body
11663
- ].forEach((body) => {
11664
- if (body) {
11665
- body.toJSON = function() {
11666
- return this.toString("base64");
11667
- };
11327
+ });
11328
+
11329
+ // src/common/tempGzipFile.ts
11330
+ var import_buffer, import_crypto2, import_fs, import_os, import_path, import_zlib, _TempGzipFile, TempGzipFile;
11331
+ var init_tempGzipFile = __esm({
11332
+ "src/common/tempGzipFile.ts"() {
11333
+ "use strict";
11334
+ import_buffer = require("buffer");
11335
+ import_crypto2 = require("crypto");
11336
+ import_fs = require("fs");
11337
+ import_os = require("os");
11338
+ import_path = require("path");
11339
+ import_zlib = require("zlib");
11340
+ _TempGzipFile = class _TempGzipFile {
11341
+ uuid;
11342
+ filePath;
11343
+ gzip;
11344
+ writeStream;
11345
+ readyPromise;
11346
+ closedPromise;
11347
+ constructor() {
11348
+ this.uuid = (0, import_crypto2.randomUUID)();
11349
+ this.filePath = (0, import_path.join)((0, import_os.tmpdir)(), `apitally-${this.uuid}.gz`);
11350
+ this.writeStream = (0, import_fs.createWriteStream)(this.filePath);
11351
+ this.readyPromise = new Promise((resolve, reject) => {
11352
+ this.writeStream.once("ready", resolve);
11353
+ this.writeStream.once("error", reject);
11354
+ });
11355
+ this.closedPromise = new Promise((resolve, reject) => {
11356
+ this.writeStream.once("close", resolve);
11357
+ this.writeStream.once("error", reject);
11358
+ });
11359
+ this.gzip = (0, import_zlib.createGzip)();
11360
+ this.gzip.pipe(this.writeStream);
11361
+ }
11362
+ get size() {
11363
+ return this.writeStream.bytesWritten;
11364
+ }
11365
+ async writeLine(data) {
11366
+ await this.readyPromise;
11367
+ return new Promise((resolve, reject) => {
11368
+ this.gzip.write(import_buffer.Buffer.concat([
11369
+ data,
11370
+ import_buffer.Buffer.from("\n")
11371
+ ]), (error) => {
11372
+ if (error) {
11373
+ reject(error);
11374
+ } else {
11375
+ resolve();
11668
11376
  }
11669
11377
  });
11670
- await this.currentFile.writeLine(import_buffer2.Buffer.from(JSON.stringify(finalItem)));
11671
- }
11378
+ });
11672
11379
  }
11673
- });
11674
- }
11675
- getFile() {
11676
- return this.files.shift();
11677
- }
11678
- retryFileLater(file) {
11679
- this.files.unshift(file);
11680
- }
11681
- async rotateFile() {
11682
- return this.lock.acquire("file", async () => {
11683
- if (this.currentFile) {
11684
- await this.currentFile.close();
11685
- this.files.push(this.currentFile);
11686
- this.currentFile = null;
11380
+ async getContent() {
11381
+ return new Promise((resolve, reject) => {
11382
+ (0, import_fs.readFile)(this.filePath, (error, data) => {
11383
+ if (error) {
11384
+ reject(error);
11385
+ } else {
11386
+ resolve(data);
11387
+ }
11388
+ });
11389
+ });
11687
11390
  }
11688
- });
11689
- }
11690
- async maintain() {
11691
- await this.writeToFile();
11692
- if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
11693
- await this.rotateFile();
11694
- }
11695
- while (this.files.length > MAX_FILES) {
11696
- const file = this.files.shift();
11697
- file == null ? void 0 : file.delete();
11698
- }
11699
- if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
11700
- this.suspendUntil = null;
11701
- }
11702
- }
11703
- async clear() {
11704
- this.pendingWrites = [];
11705
- await this.rotateFile();
11706
- this.files.forEach((file) => {
11707
- file.delete();
11708
- });
11709
- this.files = [];
11710
- }
11711
- async close() {
11712
- this.enabled = false;
11713
- await this.clear();
11714
- if (this.maintainIntervalId) {
11715
- clearInterval(this.maintainIntervalId);
11716
- }
11391
+ async close() {
11392
+ await new Promise((resolve) => {
11393
+ this.gzip.end(() => {
11394
+ resolve();
11395
+ });
11396
+ });
11397
+ await this.closedPromise;
11398
+ }
11399
+ async delete() {
11400
+ await this.close();
11401
+ (0, import_fs.unlinkSync)(this.filePath);
11402
+ }
11403
+ };
11404
+ __name(_TempGzipFile, "TempGzipFile");
11405
+ TempGzipFile = _TempGzipFile;
11717
11406
  }
11718
- };
11719
- __name(_RequestLogger, "RequestLogger");
11720
- var RequestLogger = _RequestLogger;
11407
+ });
11408
+
11409
+ // src/common/requestLogger.ts
11721
11410
  function convertHeaders(headers) {
11722
11411
  if (headers instanceof Headers) {
11723
11412
  return Array.from(headers.entries());
@@ -11740,7 +11429,6 @@ function convertHeaders(headers) {
11740
11429
  ];
11741
11430
  });
11742
11431
  }
11743
- __name(convertHeaders, "convertHeaders");
11744
11432
  function convertBody(body, contentType) {
11745
11433
  if (!body || !contentType) {
11746
11434
  return;
@@ -11760,7 +11448,6 @@ function convertBody(body, contentType) {
11760
11448
  return;
11761
11449
  }
11762
11450
  }
11763
- __name(convertBody, "convertBody");
11764
11451
  function isValidJsonString(body) {
11765
11452
  if (typeof body !== "string") {
11766
11453
  return false;
@@ -11772,13 +11459,11 @@ function isValidJsonString(body) {
11772
11459
  return false;
11773
11460
  }
11774
11461
  }
11775
- __name(isValidJsonString, "isValidJsonString");
11776
11462
  function matchPatterns(value, patterns) {
11777
11463
  return patterns.some((pattern) => {
11778
11464
  return pattern.test(value);
11779
11465
  });
11780
11466
  }
11781
- __name(matchPatterns, "matchPatterns");
11782
11467
  function skipEmptyValues(data) {
11783
11468
  return Object.fromEntries(Object.entries(data).filter(([_, v]) => {
11784
11469
  if (v == null || Number.isNaN(v)) return false;
@@ -11788,7 +11473,6 @@ function skipEmptyValues(data) {
11788
11473
  return true;
11789
11474
  }));
11790
11475
  }
11791
- __name(skipEmptyValues, "skipEmptyValues");
11792
11476
  function checkWritableFs() {
11793
11477
  try {
11794
11478
  const testPath = (0, import_path2.join)((0, import_os2.tmpdir)(), `apitally-${(0, import_crypto3.randomUUID)()}`);
@@ -11799,333 +11483,703 @@ function checkWritableFs() {
11799
11483
  return false;
11800
11484
  }
11801
11485
  }
11802
- __name(checkWritableFs, "checkWritableFs");
11803
-
11804
- // src/common/validationErrorCounter.ts
11805
- var import_crypto4 = require("crypto");
11806
- var _ValidationErrorCounter = class _ValidationErrorCounter {
11807
- errorCounts;
11808
- errorDetails;
11809
- constructor() {
11810
- this.errorCounts = /* @__PURE__ */ new Map();
11811
- this.errorDetails = /* @__PURE__ */ new Map();
11812
- }
11813
- addValidationError(validationError) {
11814
- const key = this.getKey(validationError);
11815
- if (!this.errorDetails.has(key)) {
11816
- this.errorDetails.set(key, validationError);
11817
- }
11818
- this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
11819
- }
11820
- getAndResetValidationErrors() {
11821
- const data = [];
11822
- this.errorCounts.forEach((count, key) => {
11823
- const validationError = this.errorDetails.get(key);
11824
- if (validationError) {
11825
- data.push({
11826
- consumer: validationError.consumer || null,
11827
- method: validationError.method,
11828
- path: validationError.path,
11829
- loc: validationError.loc.split("."),
11830
- msg: validationError.msg,
11831
- type: validationError.type,
11832
- error_count: count
11833
- });
11486
+ var import_async_lock, import_buffer2, import_crypto3, import_fs2, import_os2, import_path2, MAX_BODY_SIZE, MAX_FILE_SIZE, MAX_FILES, MAX_PENDING_WRITES, BODY_TOO_LARGE, BODY_MASKED, MASKED, ALLOWED_CONTENT_TYPES, EXCLUDE_PATH_PATTERNS, EXCLUDE_USER_AGENT_PATTERNS, MASK_QUERY_PARAM_PATTERNS, MASK_HEADER_PATTERNS, MASK_BODY_FIELD_PATTERNS, DEFAULT_CONFIG, _RequestLogger, RequestLogger;
11487
+ var init_requestLogger = __esm({
11488
+ "src/common/requestLogger.ts"() {
11489
+ "use strict";
11490
+ import_async_lock = __toESM(require("async-lock"), 1);
11491
+ import_buffer2 = require("buffer");
11492
+ import_crypto3 = require("crypto");
11493
+ import_fs2 = require("fs");
11494
+ import_os2 = require("os");
11495
+ import_path2 = require("path");
11496
+ init_sentry();
11497
+ init_serverErrorCounter();
11498
+ init_tempGzipFile();
11499
+ MAX_BODY_SIZE = 5e4;
11500
+ MAX_FILE_SIZE = 1e6;
11501
+ MAX_FILES = 50;
11502
+ MAX_PENDING_WRITES = 100;
11503
+ BODY_TOO_LARGE = import_buffer2.Buffer.from("<body too large>");
11504
+ BODY_MASKED = import_buffer2.Buffer.from("<masked>");
11505
+ MASKED = "******";
11506
+ ALLOWED_CONTENT_TYPES = [
11507
+ "application/json",
11508
+ "application/problem+json",
11509
+ "application/vnd.api+json",
11510
+ "text/plain"
11511
+ ];
11512
+ EXCLUDE_PATH_PATTERNS = [
11513
+ /\/_?healthz?$/i,
11514
+ /\/_?health[_-]?checks?$/i,
11515
+ /\/_?heart[_-]?beats?$/i,
11516
+ /\/ping$/i,
11517
+ /\/ready$/i,
11518
+ /\/live$/i
11519
+ ];
11520
+ EXCLUDE_USER_AGENT_PATTERNS = [
11521
+ /health[-_ ]?check/i,
11522
+ /microsoft-azure-application-lb/i,
11523
+ /googlehc/i,
11524
+ /kube-probe/i
11525
+ ];
11526
+ MASK_QUERY_PARAM_PATTERNS = [
11527
+ /auth/i,
11528
+ /api-?key/i,
11529
+ /secret/i,
11530
+ /token/i,
11531
+ /password/i,
11532
+ /pwd/i
11533
+ ];
11534
+ MASK_HEADER_PATTERNS = [
11535
+ /auth/i,
11536
+ /api-?key/i,
11537
+ /secret/i,
11538
+ /token/i,
11539
+ /cookie/i
11540
+ ];
11541
+ MASK_BODY_FIELD_PATTERNS = [
11542
+ /password/i,
11543
+ /pwd/i,
11544
+ /token/i,
11545
+ /secret/i,
11546
+ /auth/i,
11547
+ /card[-_ ]?number/i,
11548
+ /ccv/i,
11549
+ /ssn/i
11550
+ ];
11551
+ DEFAULT_CONFIG = {
11552
+ enabled: false,
11553
+ logQueryParams: true,
11554
+ logRequestHeaders: false,
11555
+ logRequestBody: false,
11556
+ logResponseHeaders: true,
11557
+ logResponseBody: false,
11558
+ logException: true,
11559
+ maskQueryParams: [],
11560
+ maskHeaders: [],
11561
+ maskBodyFields: [],
11562
+ excludePaths: []
11563
+ };
11564
+ _RequestLogger = class _RequestLogger {
11565
+ config;
11566
+ enabled;
11567
+ suspendUntil = null;
11568
+ pendingWrites = [];
11569
+ currentFile = null;
11570
+ files = [];
11571
+ maintainIntervalId;
11572
+ lock = new import_async_lock.default();
11573
+ constructor(config) {
11574
+ this.config = {
11575
+ ...DEFAULT_CONFIG,
11576
+ ...config
11577
+ };
11578
+ this.enabled = this.config.enabled && checkWritableFs();
11579
+ if (this.enabled) {
11580
+ this.maintainIntervalId = setInterval(() => {
11581
+ this.maintain();
11582
+ }, 1e3);
11583
+ }
11834
11584
  }
11835
- });
11836
- this.errorCounts.clear();
11837
- this.errorDetails.clear();
11838
- return data;
11839
- }
11840
- getKey(validationError) {
11841
- const hashInput = [
11842
- validationError.consumer || "",
11843
- validationError.method.toUpperCase(),
11844
- validationError.path,
11845
- validationError.loc,
11846
- validationError.msg.trim(),
11847
- validationError.type
11848
- ].join("|");
11849
- return (0, import_crypto4.createHash)("md5").update(hashInput).digest("hex");
11850
- }
11851
- };
11852
- __name(_ValidationErrorCounter, "ValidationErrorCounter");
11853
- var ValidationErrorCounter = _ValidationErrorCounter;
11854
-
11855
- // src/common/client.ts
11856
- var SYNC_INTERVAL = 6e4;
11857
- var INITIAL_SYNC_INTERVAL = 1e4;
11858
- var INITIAL_SYNC_INTERVAL_DURATION = 36e5;
11859
- var MAX_QUEUE_TIME = 36e5;
11860
- var _a;
11861
- var HTTPError = (_a = class extends Error {
11862
- response;
11863
- constructor(response) {
11864
- const reason = response.status ? `status code ${response.status}` : "an unknown error";
11865
- super(`Request failed with ${reason}`);
11866
- this.response = response;
11867
- }
11868
- }, __name(_a, "HTTPError"), _a);
11869
- var _ApitallyClient = class _ApitallyClient {
11870
- clientId;
11871
- env;
11872
- instanceUuid;
11873
- syncDataQueue;
11874
- syncIntervalId;
11875
- startupData;
11876
- startupDataSent = false;
11877
- enabled = true;
11878
- requestCounter;
11879
- requestLogger;
11880
- validationErrorCounter;
11881
- serverErrorCounter;
11882
- consumerRegistry;
11883
- logger;
11884
- constructor({ clientId, env = "dev", requestLogging, requestLoggingConfig, logger }) {
11885
- if (_ApitallyClient.instance) {
11886
- throw new Error("Apitally client is already initialized");
11887
- }
11888
- if (!isValidClientId(clientId)) {
11889
- throw new Error(`Invalid Apitally client ID '${clientId}' (expecting hexadecimal UUID format)`);
11890
- }
11891
- if (!isValidEnv(env)) {
11892
- throw new Error(`Invalid env '${env}' (expecting 1-32 alphanumeric lowercase characters and hyphens only)`);
11893
- }
11894
- if (requestLoggingConfig && !requestLogging) {
11895
- console.warn("requestLoggingConfig is deprecated, use requestLogging instead.");
11896
- }
11897
- _ApitallyClient.instance = this;
11898
- this.clientId = clientId;
11899
- this.env = env;
11900
- this.instanceUuid = (0, import_crypto5.randomUUID)();
11901
- this.syncDataQueue = [];
11902
- this.requestCounter = new RequestCounter();
11903
- this.requestLogger = new RequestLogger(requestLogging ?? requestLoggingConfig);
11904
- this.validationErrorCounter = new ValidationErrorCounter();
11905
- this.serverErrorCounter = new ServerErrorCounter();
11906
- this.consumerRegistry = new ConsumerRegistry();
11907
- this.logger = logger ?? getLogger();
11908
- this.startSync();
11909
- this.handleShutdown = this.handleShutdown.bind(this);
11910
- }
11911
- static getInstance() {
11912
- if (!_ApitallyClient.instance) {
11913
- throw new Error("Apitally client is not initialized");
11914
- }
11915
- return _ApitallyClient.instance;
11916
- }
11917
- isEnabled() {
11918
- return this.enabled;
11919
- }
11920
- static async shutdown() {
11921
- if (_ApitallyClient.instance) {
11922
- await _ApitallyClient.instance.handleShutdown();
11923
- }
11924
- }
11925
- async handleShutdown() {
11926
- this.enabled = false;
11927
- this.stopSync();
11928
- await this.sendSyncData();
11929
- await this.sendLogData();
11930
- await this.requestLogger.close();
11931
- _ApitallyClient.instance = void 0;
11932
- }
11933
- getHubUrlPrefix() {
11934
- const baseURL = process.env.APITALLY_HUB_BASE_URL || "https://hub.apitally.io";
11935
- const version = "v2";
11936
- return `${baseURL}/${version}/${this.clientId}/${this.env}/`;
11937
- }
11938
- async sendData(url, payload) {
11939
- const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
11940
- retries: 3,
11941
- retryDelay: 1e3,
11942
- retryOn: [
11943
- 408,
11944
- 429,
11945
- 500,
11946
- 502,
11947
- 503,
11948
- 504
11949
- ]
11950
- });
11951
- const response = await fetchWithRetry(this.getHubUrlPrefix() + url, {
11952
- method: "POST",
11953
- body: JSON.stringify(payload),
11954
- headers: {
11955
- "Content-Type": "application/json"
11585
+ get maxBodySize() {
11586
+ return MAX_BODY_SIZE;
11956
11587
  }
11957
- });
11958
- if (!response.ok) {
11959
- throw new HTTPError(response);
11960
- }
11961
- }
11962
- startSync() {
11963
- this.sync();
11964
- this.syncIntervalId = setInterval(() => {
11965
- this.sync();
11966
- }, INITIAL_SYNC_INTERVAL);
11967
- setTimeout(() => {
11968
- clearInterval(this.syncIntervalId);
11969
- this.syncIntervalId = setInterval(() => {
11970
- this.sync();
11971
- }, SYNC_INTERVAL);
11972
- }, INITIAL_SYNC_INTERVAL_DURATION);
11973
- }
11974
- async sync() {
11975
- try {
11976
- const promises = [
11977
- this.sendSyncData(),
11978
- this.sendLogData()
11979
- ];
11980
- if (!this.startupDataSent) {
11981
- promises.push(this.sendStartupData());
11588
+ shouldExcludePath(urlPath) {
11589
+ const patterns = [
11590
+ ...this.config.excludePaths,
11591
+ ...EXCLUDE_PATH_PATTERNS
11592
+ ];
11593
+ return matchPatterns(urlPath, patterns);
11982
11594
  }
11983
- await Promise.all(promises);
11984
- } catch (error) {
11985
- this.logger.error("Error while syncing with Apitally Hub", {
11986
- error
11987
- });
11988
- }
11989
- }
11990
- stopSync() {
11991
- if (this.syncIntervalId) {
11992
- clearInterval(this.syncIntervalId);
11993
- this.syncIntervalId = void 0;
11994
- }
11995
- }
11996
- setStartupData(data) {
11997
- this.startupData = data;
11998
- this.startupDataSent = false;
11999
- this.sendStartupData();
12000
- }
12001
- async sendStartupData() {
12002
- if (this.startupData) {
12003
- this.logger.debug("Sending startup data to Apitally Hub");
12004
- const payload = {
12005
- instance_uuid: this.instanceUuid,
12006
- message_uuid: (0, import_crypto5.randomUUID)(),
12007
- ...this.startupData
12008
- };
12009
- try {
12010
- await this.sendData("startup", payload);
12011
- this.startupDataSent = true;
12012
- } catch (error) {
12013
- const handled = this.handleHubError(error);
12014
- if (!handled) {
12015
- this.logger.error(error.message);
12016
- this.logger.debug("Error while sending startup data to Apitally Hub (will retry)", {
12017
- error
12018
- });
12019
- }
11595
+ shouldExcludeUserAgent(userAgent) {
11596
+ return userAgent ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS) : false;
12020
11597
  }
12021
- }
12022
- }
12023
- async sendSyncData() {
12024
- this.logger.debug("Synchronizing data with Apitally Hub");
12025
- const newPayload = {
12026
- timestamp: Date.now() / 1e3,
12027
- instance_uuid: this.instanceUuid,
12028
- message_uuid: (0, import_crypto5.randomUUID)(),
12029
- requests: this.requestCounter.getAndResetRequests(),
12030
- validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
12031
- server_errors: this.serverErrorCounter.getAndResetServerErrors(),
12032
- consumers: this.consumerRegistry.getAndResetUpdatedConsumers()
12033
- };
12034
- this.syncDataQueue.push(newPayload);
12035
- let i = 0;
12036
- while (this.syncDataQueue.length > 0) {
12037
- const payload = this.syncDataQueue.shift();
12038
- if (payload) {
12039
- try {
12040
- if (Date.now() - payload.timestamp * 1e3 <= MAX_QUEUE_TIME) {
12041
- if (i > 0) {
12042
- await this.randomDelay();
12043
- }
12044
- await this.sendData("sync", payload);
12045
- i += 1;
12046
- }
12047
- } catch (error) {
12048
- const handled = this.handleHubError(error);
12049
- if (!handled) {
12050
- this.logger.debug("Error while synchronizing data with Apitally Hub (will retry)", {
12051
- error
12052
- });
12053
- this.syncDataQueue.push(payload);
12054
- break;
11598
+ shouldMaskQueryParam(name) {
11599
+ const patterns = [
11600
+ ...this.config.maskQueryParams,
11601
+ ...MASK_QUERY_PARAM_PATTERNS
11602
+ ];
11603
+ return matchPatterns(name, patterns);
11604
+ }
11605
+ shouldMaskHeader(name) {
11606
+ const patterns = [
11607
+ ...this.config.maskHeaders,
11608
+ ...MASK_HEADER_PATTERNS
11609
+ ];
11610
+ return matchPatterns(name, patterns);
11611
+ }
11612
+ shouldMaskBodyField(name) {
11613
+ const patterns = [
11614
+ ...this.config.maskBodyFields,
11615
+ ...MASK_BODY_FIELD_PATTERNS
11616
+ ];
11617
+ return matchPatterns(name, patterns);
11618
+ }
11619
+ hasSupportedContentType(headers) {
11620
+ var _a3;
11621
+ const contentType = (_a3 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a3[1];
11622
+ return this.isSupportedContentType(contentType);
11623
+ }
11624
+ hasJsonContentType(headers) {
11625
+ var _a3;
11626
+ const contentType = (_a3 = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a3[1];
11627
+ return contentType ? /\bjson\b/i.test(contentType) : null;
11628
+ }
11629
+ isSupportedContentType(contentType) {
11630
+ return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
11631
+ }
11632
+ maskQueryParams(search) {
11633
+ const params = new URLSearchParams(search);
11634
+ for (const [key] of params) {
11635
+ if (this.shouldMaskQueryParam(key)) {
11636
+ params.set(key, MASKED);
12055
11637
  }
12056
11638
  }
11639
+ return params.toString();
12057
11640
  }
12058
- }
12059
- }
12060
- async sendLogData() {
12061
- this.logger.debug("Sending request log data to Apitally Hub");
12062
- await this.requestLogger.rotateFile();
12063
- const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
12064
- retries: 3,
12065
- retryDelay: 1e3,
12066
- retryOn: [
12067
- 408,
12068
- 429,
12069
- 500,
12070
- 502,
12071
- 503,
12072
- 504
12073
- ]
12074
- });
12075
- let i = 0;
12076
- let logFile;
12077
- while (logFile = this.requestLogger.getFile()) {
12078
- if (i > 0) {
12079
- await this.randomDelay();
11641
+ maskHeaders(headers) {
11642
+ return headers.map(([k, v]) => [
11643
+ k,
11644
+ this.shouldMaskHeader(k) ? MASKED : v
11645
+ ]);
12080
11646
  }
12081
- try {
12082
- const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
12083
- method: "POST",
12084
- body: await logFile.getContent()
12085
- });
12086
- if (response.status === 402 && response.headers.has("Retry-After")) {
12087
- const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
12088
- if (retryAfter > 0) {
12089
- this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
12090
- this.requestLogger.clear();
12091
- return;
11647
+ maskBody(data) {
11648
+ if (typeof data === "object" && data !== null && !Array.isArray(data)) {
11649
+ const result = {};
11650
+ for (const [key, value] of Object.entries(data)) {
11651
+ if (typeof value === "string" && this.shouldMaskBodyField(key)) {
11652
+ result[key] = MASKED;
11653
+ } else {
11654
+ result[key] = this.maskBody(value);
11655
+ }
12092
11656
  }
11657
+ return result;
12093
11658
  }
12094
- if (!response.ok) {
12095
- throw new HTTPError(response);
11659
+ if (Array.isArray(data)) {
11660
+ return data.map((item) => this.maskBody(item));
12096
11661
  }
12097
- logFile.delete();
12098
- } catch (error) {
12099
- this.requestLogger.retryFileLater(logFile);
12100
- break;
11662
+ return data;
12101
11663
  }
12102
- i++;
12103
- if (i >= 10) break;
12104
- }
11664
+ applyMasking(item) {
11665
+ if (this.config.maskRequestBodyCallback && item.request.body && item.request.body !== BODY_TOO_LARGE) {
11666
+ try {
11667
+ const maskedBody = this.config.maskRequestBodyCallback(item.request);
11668
+ item.request.body = maskedBody ?? BODY_MASKED;
11669
+ } catch {
11670
+ item.request.body = void 0;
11671
+ }
11672
+ }
11673
+ if (this.config.maskResponseBodyCallback && item.response.body && item.response.body !== BODY_TOO_LARGE) {
11674
+ try {
11675
+ const maskedBody = this.config.maskResponseBodyCallback(item.request, item.response);
11676
+ item.response.body = maskedBody ?? BODY_MASKED;
11677
+ } catch {
11678
+ item.response.body = void 0;
11679
+ }
11680
+ }
11681
+ if (item.request.body && item.request.body.length > MAX_BODY_SIZE) {
11682
+ item.request.body = BODY_TOO_LARGE;
11683
+ }
11684
+ if (item.response.body && item.response.body.length > MAX_BODY_SIZE) {
11685
+ item.response.body = BODY_TOO_LARGE;
11686
+ }
11687
+ for (const key of [
11688
+ "request",
11689
+ "response"
11690
+ ]) {
11691
+ const bodyData = item[key].body;
11692
+ if (!bodyData || bodyData === BODY_TOO_LARGE || bodyData === BODY_MASKED) {
11693
+ continue;
11694
+ }
11695
+ const headers = item[key].headers;
11696
+ const hasJsonContent = this.hasJsonContentType(headers);
11697
+ if (hasJsonContent === null || hasJsonContent) {
11698
+ try {
11699
+ const parsedBody = JSON.parse(bodyData.toString());
11700
+ const maskedBody = this.maskBody(parsedBody);
11701
+ item[key].body = import_buffer2.Buffer.from(JSON.stringify(maskedBody));
11702
+ } catch {
11703
+ }
11704
+ }
11705
+ }
11706
+ item.request.headers = this.config.logRequestHeaders ? this.maskHeaders(item.request.headers) : [];
11707
+ item.response.headers = this.config.logResponseHeaders ? this.maskHeaders(item.response.headers) : [];
11708
+ const url = new URL(item.request.url);
11709
+ url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
11710
+ item.request.url = url.toString();
11711
+ return item;
11712
+ }
11713
+ logRequest(request, response, error) {
11714
+ var _a3, _b, _c;
11715
+ if (!this.enabled || this.suspendUntil !== null) return;
11716
+ const url = new URL(request.url);
11717
+ const path = request.path ?? url.pathname;
11718
+ const userAgent = (_a3 = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a3[1];
11719
+ if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
11720
+ return;
11721
+ }
11722
+ if (!this.config.logRequestBody || !this.hasSupportedContentType(request.headers)) {
11723
+ request.body = void 0;
11724
+ }
11725
+ if (!this.config.logResponseBody || !this.hasSupportedContentType(response.headers)) {
11726
+ response.body = void 0;
11727
+ }
11728
+ if (request.size !== void 0 && request.size < 0) {
11729
+ request.size = void 0;
11730
+ }
11731
+ if (response.size !== void 0 && response.size < 0) {
11732
+ response.size = void 0;
11733
+ }
11734
+ const item = {
11735
+ uuid: (0, import_crypto3.randomUUID)(),
11736
+ request,
11737
+ response,
11738
+ exception: error && this.config.logException ? {
11739
+ type: error.name,
11740
+ message: truncateExceptionMessage(error.message),
11741
+ stacktrace: truncateExceptionStackTrace(error.stack || ""),
11742
+ sentryEventId: getSentryEventId()
11743
+ } : void 0
11744
+ };
11745
+ this.pendingWrites.push(item);
11746
+ if (this.pendingWrites.length > MAX_PENDING_WRITES) {
11747
+ this.pendingWrites.shift();
11748
+ }
11749
+ }
11750
+ async writeToFile() {
11751
+ if (!this.enabled || this.pendingWrites.length === 0) {
11752
+ return;
11753
+ }
11754
+ return this.lock.acquire("file", async () => {
11755
+ if (!this.currentFile) {
11756
+ this.currentFile = new TempGzipFile();
11757
+ }
11758
+ while (this.pendingWrites.length > 0) {
11759
+ let item = this.pendingWrites.shift();
11760
+ if (item) {
11761
+ item = this.applyMasking(item);
11762
+ const finalItem = {
11763
+ uuid: item.uuid,
11764
+ request: skipEmptyValues(item.request),
11765
+ response: skipEmptyValues(item.response),
11766
+ exception: item.exception
11767
+ };
11768
+ [
11769
+ finalItem.request.body,
11770
+ finalItem.response.body
11771
+ ].forEach((body) => {
11772
+ if (body) {
11773
+ body.toJSON = function() {
11774
+ return this.toString("base64");
11775
+ };
11776
+ }
11777
+ });
11778
+ await this.currentFile.writeLine(import_buffer2.Buffer.from(JSON.stringify(finalItem)));
11779
+ }
11780
+ }
11781
+ });
11782
+ }
11783
+ getFile() {
11784
+ return this.files.shift();
11785
+ }
11786
+ retryFileLater(file) {
11787
+ this.files.unshift(file);
11788
+ }
11789
+ async rotateFile() {
11790
+ return this.lock.acquire("file", async () => {
11791
+ if (this.currentFile) {
11792
+ await this.currentFile.close();
11793
+ this.files.push(this.currentFile);
11794
+ this.currentFile = null;
11795
+ }
11796
+ });
11797
+ }
11798
+ async maintain() {
11799
+ await this.writeToFile();
11800
+ if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {
11801
+ await this.rotateFile();
11802
+ }
11803
+ while (this.files.length > MAX_FILES) {
11804
+ const file = this.files.shift();
11805
+ file == null ? void 0 : file.delete();
11806
+ }
11807
+ if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {
11808
+ this.suspendUntil = null;
11809
+ }
11810
+ }
11811
+ async clear() {
11812
+ this.pendingWrites = [];
11813
+ await this.rotateFile();
11814
+ this.files.forEach((file) => {
11815
+ file.delete();
11816
+ });
11817
+ this.files = [];
11818
+ }
11819
+ async close() {
11820
+ this.enabled = false;
11821
+ await this.clear();
11822
+ if (this.maintainIntervalId) {
11823
+ clearInterval(this.maintainIntervalId);
11824
+ }
11825
+ }
11826
+ };
11827
+ __name(_RequestLogger, "RequestLogger");
11828
+ RequestLogger = _RequestLogger;
11829
+ __name(convertHeaders, "convertHeaders");
11830
+ __name(convertBody, "convertBody");
11831
+ __name(isValidJsonString, "isValidJsonString");
11832
+ __name(matchPatterns, "matchPatterns");
11833
+ __name(skipEmptyValues, "skipEmptyValues");
11834
+ __name(checkWritableFs, "checkWritableFs");
11835
+ }
11836
+ });
11837
+
11838
+ // src/common/validationErrorCounter.ts
11839
+ var import_crypto4, _ValidationErrorCounter, ValidationErrorCounter;
11840
+ var init_validationErrorCounter = __esm({
11841
+ "src/common/validationErrorCounter.ts"() {
11842
+ "use strict";
11843
+ import_crypto4 = require("crypto");
11844
+ _ValidationErrorCounter = class _ValidationErrorCounter {
11845
+ errorCounts;
11846
+ errorDetails;
11847
+ constructor() {
11848
+ this.errorCounts = /* @__PURE__ */ new Map();
11849
+ this.errorDetails = /* @__PURE__ */ new Map();
11850
+ }
11851
+ addValidationError(validationError) {
11852
+ const key = this.getKey(validationError);
11853
+ if (!this.errorDetails.has(key)) {
11854
+ this.errorDetails.set(key, validationError);
11855
+ }
11856
+ this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
11857
+ }
11858
+ getAndResetValidationErrors() {
11859
+ const data = [];
11860
+ this.errorCounts.forEach((count, key) => {
11861
+ const validationError = this.errorDetails.get(key);
11862
+ if (validationError) {
11863
+ data.push({
11864
+ consumer: validationError.consumer || null,
11865
+ method: validationError.method,
11866
+ path: validationError.path,
11867
+ loc: validationError.loc.split("."),
11868
+ msg: validationError.msg,
11869
+ type: validationError.type,
11870
+ error_count: count
11871
+ });
11872
+ }
11873
+ });
11874
+ this.errorCounts.clear();
11875
+ this.errorDetails.clear();
11876
+ return data;
11877
+ }
11878
+ getKey(validationError) {
11879
+ const hashInput = [
11880
+ validationError.consumer || "",
11881
+ validationError.method.toUpperCase(),
11882
+ validationError.path,
11883
+ validationError.loc,
11884
+ validationError.msg.trim(),
11885
+ validationError.type
11886
+ ].join("|");
11887
+ return (0, import_crypto4.createHash)("md5").update(hashInput).digest("hex");
11888
+ }
11889
+ };
11890
+ __name(_ValidationErrorCounter, "ValidationErrorCounter");
11891
+ ValidationErrorCounter = _ValidationErrorCounter;
12105
11892
  }
12106
- handleHubError(error) {
12107
- if (error instanceof HTTPError) {
12108
- if (error.response.status === 404) {
12109
- this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);
11893
+ });
11894
+
11895
+ // src/common/client.ts
11896
+ var import_crypto5, import_fetch_retry, SYNC_INTERVAL, INITIAL_SYNC_INTERVAL, INITIAL_SYNC_INTERVAL_DURATION, MAX_QUEUE_TIME, _a, HTTPError, _ApitallyClient, ApitallyClient;
11897
+ var init_client = __esm({
11898
+ "src/common/client.ts"() {
11899
+ "use strict";
11900
+ import_crypto5 = require("crypto");
11901
+ import_fetch_retry = __toESM(require("fetch-retry"), 1);
11902
+ init_consumerRegistry();
11903
+ init_logging();
11904
+ init_paramValidation();
11905
+ init_requestCounter();
11906
+ init_requestLogger();
11907
+ init_serverErrorCounter();
11908
+ init_validationErrorCounter();
11909
+ SYNC_INTERVAL = 6e4;
11910
+ INITIAL_SYNC_INTERVAL = 1e4;
11911
+ INITIAL_SYNC_INTERVAL_DURATION = 36e5;
11912
+ MAX_QUEUE_TIME = 36e5;
11913
+ HTTPError = (_a = class extends Error {
11914
+ response;
11915
+ constructor(response) {
11916
+ const reason = response.status ? `status code ${response.status}` : "an unknown error";
11917
+ super(`Request failed with ${reason}`);
11918
+ this.response = response;
11919
+ }
11920
+ }, __name(_a, "HTTPError"), _a);
11921
+ _ApitallyClient = class _ApitallyClient {
11922
+ clientId;
11923
+ env;
11924
+ instanceUuid;
11925
+ syncDataQueue;
11926
+ syncIntervalId;
11927
+ startupData;
11928
+ startupDataSent = false;
11929
+ enabled = true;
11930
+ requestCounter;
11931
+ requestLogger;
11932
+ validationErrorCounter;
11933
+ serverErrorCounter;
11934
+ consumerRegistry;
11935
+ logger;
11936
+ constructor({ clientId, env = "dev", requestLogging, requestLoggingConfig, logger }) {
11937
+ if (_ApitallyClient.instance) {
11938
+ throw new Error("Apitally client is already initialized");
11939
+ }
11940
+ if (!isValidClientId(clientId)) {
11941
+ throw new Error(`Invalid Apitally client ID '${clientId}' (expecting hexadecimal UUID format)`);
11942
+ }
11943
+ if (!isValidEnv(env)) {
11944
+ throw new Error(`Invalid env '${env}' (expecting 1-32 alphanumeric lowercase characters and hyphens only)`);
11945
+ }
11946
+ if (requestLoggingConfig && !requestLogging) {
11947
+ console.warn("requestLoggingConfig is deprecated, use requestLogging instead.");
11948
+ }
11949
+ _ApitallyClient.instance = this;
11950
+ this.clientId = clientId;
11951
+ this.env = env;
11952
+ this.instanceUuid = (0, import_crypto5.randomUUID)();
11953
+ this.syncDataQueue = [];
11954
+ this.requestCounter = new RequestCounter();
11955
+ this.requestLogger = new RequestLogger(requestLogging ?? requestLoggingConfig);
11956
+ this.validationErrorCounter = new ValidationErrorCounter();
11957
+ this.serverErrorCounter = new ServerErrorCounter();
11958
+ this.consumerRegistry = new ConsumerRegistry();
11959
+ this.logger = logger ?? getLogger();
11960
+ this.startSync();
11961
+ this.handleShutdown = this.handleShutdown.bind(this);
11962
+ }
11963
+ static getInstance() {
11964
+ if (!_ApitallyClient.instance) {
11965
+ throw new Error("Apitally client is not initialized");
11966
+ }
11967
+ return _ApitallyClient.instance;
11968
+ }
11969
+ isEnabled() {
11970
+ return this.enabled;
11971
+ }
11972
+ static async shutdown() {
11973
+ if (_ApitallyClient.instance) {
11974
+ await _ApitallyClient.instance.handleShutdown();
11975
+ }
11976
+ }
11977
+ async handleShutdown() {
12110
11978
  this.enabled = false;
12111
11979
  this.stopSync();
12112
- return true;
11980
+ await this.sendSyncData();
11981
+ await this.sendLogData();
11982
+ await this.requestLogger.close();
11983
+ _ApitallyClient.instance = void 0;
11984
+ }
11985
+ getHubUrlPrefix() {
11986
+ const baseURL = process.env.APITALLY_HUB_BASE_URL || "https://hub.apitally.io";
11987
+ const version = "v2";
11988
+ return `${baseURL}/${version}/${this.clientId}/${this.env}/`;
11989
+ }
11990
+ async sendData(url, payload) {
11991
+ const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
11992
+ retries: 3,
11993
+ retryDelay: 1e3,
11994
+ retryOn: [
11995
+ 408,
11996
+ 429,
11997
+ 500,
11998
+ 502,
11999
+ 503,
12000
+ 504
12001
+ ]
12002
+ });
12003
+ const response = await fetchWithRetry(this.getHubUrlPrefix() + url, {
12004
+ method: "POST",
12005
+ body: JSON.stringify(payload),
12006
+ headers: {
12007
+ "Content-Type": "application/json"
12008
+ }
12009
+ });
12010
+ if (!response.ok) {
12011
+ throw new HTTPError(response);
12012
+ }
12113
12013
  }
12114
- if (error.response.status === 422) {
12115
- this.logger.error("Received validation error from Apitally Hub");
12116
- return true;
12014
+ startSync() {
12015
+ this.sync();
12016
+ this.syncIntervalId = setInterval(() => {
12017
+ this.sync();
12018
+ }, INITIAL_SYNC_INTERVAL);
12019
+ setTimeout(() => {
12020
+ clearInterval(this.syncIntervalId);
12021
+ this.syncIntervalId = setInterval(() => {
12022
+ this.sync();
12023
+ }, SYNC_INTERVAL);
12024
+ }, INITIAL_SYNC_INTERVAL_DURATION);
12025
+ }
12026
+ async sync() {
12027
+ try {
12028
+ const promises = [
12029
+ this.sendSyncData(),
12030
+ this.sendLogData()
12031
+ ];
12032
+ if (!this.startupDataSent) {
12033
+ promises.push(this.sendStartupData());
12034
+ }
12035
+ await Promise.all(promises);
12036
+ } catch (error) {
12037
+ this.logger.error("Error while syncing with Apitally Hub", {
12038
+ error
12039
+ });
12040
+ }
12117
12041
  }
12118
- }
12119
- return false;
12120
- }
12121
- async randomDelay() {
12122
- const delay = 100 + Math.random() * 400;
12123
- await new Promise((resolve) => setTimeout(resolve, delay));
12042
+ stopSync() {
12043
+ if (this.syncIntervalId) {
12044
+ clearInterval(this.syncIntervalId);
12045
+ this.syncIntervalId = void 0;
12046
+ }
12047
+ }
12048
+ setStartupData(data) {
12049
+ this.startupData = data;
12050
+ this.startupDataSent = false;
12051
+ this.sendStartupData();
12052
+ }
12053
+ async sendStartupData() {
12054
+ if (this.startupData) {
12055
+ this.logger.debug("Sending startup data to Apitally Hub");
12056
+ const payload = {
12057
+ instance_uuid: this.instanceUuid,
12058
+ message_uuid: (0, import_crypto5.randomUUID)(),
12059
+ ...this.startupData
12060
+ };
12061
+ try {
12062
+ await this.sendData("startup", payload);
12063
+ this.startupDataSent = true;
12064
+ } catch (error) {
12065
+ const handled = this.handleHubError(error);
12066
+ if (!handled) {
12067
+ this.logger.error(error.message);
12068
+ this.logger.debug("Error while sending startup data to Apitally Hub (will retry)", {
12069
+ error
12070
+ });
12071
+ }
12072
+ }
12073
+ }
12074
+ }
12075
+ async sendSyncData() {
12076
+ this.logger.debug("Synchronizing data with Apitally Hub");
12077
+ const newPayload = {
12078
+ timestamp: Date.now() / 1e3,
12079
+ instance_uuid: this.instanceUuid,
12080
+ message_uuid: (0, import_crypto5.randomUUID)(),
12081
+ requests: this.requestCounter.getAndResetRequests(),
12082
+ validation_errors: this.validationErrorCounter.getAndResetValidationErrors(),
12083
+ server_errors: this.serverErrorCounter.getAndResetServerErrors(),
12084
+ consumers: this.consumerRegistry.getAndResetUpdatedConsumers()
12085
+ };
12086
+ this.syncDataQueue.push(newPayload);
12087
+ let i = 0;
12088
+ while (this.syncDataQueue.length > 0) {
12089
+ const payload = this.syncDataQueue.shift();
12090
+ if (payload) {
12091
+ try {
12092
+ if (Date.now() - payload.timestamp * 1e3 <= MAX_QUEUE_TIME) {
12093
+ if (i > 0) {
12094
+ await this.randomDelay();
12095
+ }
12096
+ await this.sendData("sync", payload);
12097
+ i += 1;
12098
+ }
12099
+ } catch (error) {
12100
+ const handled = this.handleHubError(error);
12101
+ if (!handled) {
12102
+ this.logger.debug("Error while synchronizing data with Apitally Hub (will retry)", {
12103
+ error
12104
+ });
12105
+ this.syncDataQueue.push(payload);
12106
+ break;
12107
+ }
12108
+ }
12109
+ }
12110
+ }
12111
+ }
12112
+ async sendLogData() {
12113
+ this.logger.debug("Sending request log data to Apitally Hub");
12114
+ await this.requestLogger.rotateFile();
12115
+ const fetchWithRetry = (0, import_fetch_retry.default)(fetch, {
12116
+ retries: 3,
12117
+ retryDelay: 1e3,
12118
+ retryOn: [
12119
+ 408,
12120
+ 429,
12121
+ 500,
12122
+ 502,
12123
+ 503,
12124
+ 504
12125
+ ]
12126
+ });
12127
+ let i = 0;
12128
+ let logFile;
12129
+ while (logFile = this.requestLogger.getFile()) {
12130
+ if (i > 0) {
12131
+ await this.randomDelay();
12132
+ }
12133
+ try {
12134
+ const response = await fetchWithRetry(`${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`, {
12135
+ method: "POST",
12136
+ body: await logFile.getContent()
12137
+ });
12138
+ if (response.status === 402 && response.headers.has("Retry-After")) {
12139
+ const retryAfter = parseInt(response.headers.get("Retry-After") ?? "0");
12140
+ if (retryAfter > 0) {
12141
+ this.requestLogger.suspendUntil = Date.now() + retryAfter * 1e3;
12142
+ this.requestLogger.clear();
12143
+ return;
12144
+ }
12145
+ }
12146
+ if (!response.ok) {
12147
+ throw new HTTPError(response);
12148
+ }
12149
+ logFile.delete();
12150
+ } catch (error) {
12151
+ this.requestLogger.retryFileLater(logFile);
12152
+ break;
12153
+ }
12154
+ i++;
12155
+ if (i >= 10) break;
12156
+ }
12157
+ }
12158
+ handleHubError(error) {
12159
+ if (error instanceof HTTPError) {
12160
+ if (error.response.status === 404) {
12161
+ this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);
12162
+ this.enabled = false;
12163
+ this.stopSync();
12164
+ return true;
12165
+ }
12166
+ if (error.response.status === 422) {
12167
+ this.logger.error("Received validation error from Apitally Hub");
12168
+ return true;
12169
+ }
12170
+ }
12171
+ return false;
12172
+ }
12173
+ async randomDelay() {
12174
+ const delay = 100 + Math.random() * 400;
12175
+ await new Promise((resolve) => setTimeout(resolve, delay));
12176
+ }
12177
+ };
12178
+ __name(_ApitallyClient, "ApitallyClient");
12179
+ __publicField(_ApitallyClient, "instance");
12180
+ ApitallyClient = _ApitallyClient;
12124
12181
  }
12125
- };
12126
- __name(_ApitallyClient, "ApitallyClient");
12127
- __publicField(_ApitallyClient, "instance");
12128
- var ApitallyClient = _ApitallyClient;
12182
+ });
12129
12183
 
12130
12184
  // src/common/headers.ts
12131
12185
  function parseContentLength(contentLength) {
@@ -12144,11 +12198,14 @@ function parseContentLength(contentLength) {
12144
12198
  }
12145
12199
  return void 0;
12146
12200
  }
12147
- __name(parseContentLength, "parseContentLength");
12201
+ var init_headers = __esm({
12202
+ "src/common/headers.ts"() {
12203
+ "use strict";
12204
+ __name(parseContentLength, "parseContentLength");
12205
+ }
12206
+ });
12148
12207
 
12149
12208
  // src/common/packageVersions.ts
12150
- var import_module = require("module");
12151
- var import_meta = {};
12152
12209
  function getPackageVersion(name) {
12153
12210
  const packageJsonPath = `${name}/package.json`;
12154
12211
  try {
@@ -12162,176 +12219,190 @@ function getPackageVersion(name) {
12162
12219
  }
12163
12220
  }
12164
12221
  }
12165
- __name(getPackageVersion, "getPackageVersion");
12222
+ var import_module, import_meta;
12223
+ var init_packageVersions = __esm({
12224
+ "src/common/packageVersions.ts"() {
12225
+ "use strict";
12226
+ import_module = require("module");
12227
+ import_meta = {};
12228
+ __name(getPackageVersion, "getPackageVersion");
12229
+ }
12230
+ });
12166
12231
 
12167
12232
  // src/express/utils.js
12168
- var regExpToParseExpressPathRegExp = /^\/\^\\?\/?(?:(:?[\w\\.-]*(?:\\\/:?[\w\\.-]*)*)|(\(\?:\\?\/?\([^)]+\)\)))\\\/.*/;
12169
- var regExpToReplaceExpressPathRegExpParams = /\(\?:\\?\/?\([^)]+\)\)/;
12170
- var regexpExpressParamRegexp = /\(\?:\\?\\?\/?\([^)]+\)\)/g;
12171
- var regexpExpressPathParamRegexp = /(:[^)]+)\([^)]+\)/g;
12172
- var EXPRESS_ROOT_PATH_REGEXP_VALUE = "/^\\/?(?=\\/|$)/i";
12173
- var STACK_ITEM_VALID_NAMES = [
12174
- "router",
12175
- "bound dispatch",
12176
- "mounted_app"
12177
- ];
12178
- var getRouterInfo = /* @__PURE__ */ __name(function(app) {
12179
- var _a3, _b;
12180
- if (app.stack) {
12181
- return {
12182
- stack: app.stack,
12183
- version: "v4"
12184
- };
12185
- } else if ((_a3 = app._router) == null ? void 0 : _a3.stack) {
12186
- return {
12187
- stack: app._router.stack,
12188
- version: "v4"
12189
- };
12190
- } else if ((_b = app.router) == null ? void 0 : _b.stack) {
12191
- return {
12192
- stack: app.router.stack,
12193
- version: "v5"
12194
- };
12195
- }
12196
- return {
12197
- stack: null,
12198
- version: "v4"
12199
- };
12200
- }, "getRouterInfo");
12201
- var getRouteMethods = /* @__PURE__ */ __name(function(route) {
12202
- let methods = Object.keys(route.methods);
12203
- methods = methods.filter((method) => method !== "_all");
12204
- methods = methods.map((method) => method.toUpperCase());
12205
- return methods;
12206
- }, "getRouteMethods");
12207
- var getRouteMiddlewares = /* @__PURE__ */ __name(function(route) {
12208
- return route.stack.map((item) => {
12209
- return item.handle.name || "anonymous";
12210
- });
12211
- }, "getRouteMiddlewares");
12212
- var hasParams = /* @__PURE__ */ __name(function(expressPathRegExp) {
12213
- return regexpExpressParamRegexp.test(expressPathRegExp);
12214
- }, "hasParams");
12215
- var parseExpressRoute = /* @__PURE__ */ __name(function(route, basePath) {
12216
- const paths = [];
12217
- if (Array.isArray(route.path)) {
12218
- paths.push(...route.path);
12219
- } else {
12220
- paths.push(route.path);
12221
- }
12222
- const endpoints = paths.map((path) => {
12223
- const completePath = basePath && path === "/" ? basePath : `${basePath}${path}`;
12224
- const endpoint = {
12225
- path: completePath.replace(regexpExpressPathParamRegexp, "$1"),
12226
- methods: getRouteMethods(route),
12227
- middlewares: getRouteMiddlewares(route)
12228
- };
12229
- return endpoint;
12230
- });
12231
- return endpoints;
12232
- }, "parseExpressRoute");
12233
- var parseExpressPathRegExp = /* @__PURE__ */ __name(function(expressPathRegExp, keys) {
12234
- let parsedRegExp = expressPathRegExp.toString();
12235
- let expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);
12236
- let paramIndex = 0;
12237
- while (hasParams(parsedRegExp)) {
12238
- const paramName = keys[paramIndex].name;
12239
- const paramId = `:${paramName}`;
12240
- parsedRegExp = parsedRegExp.replace(regExpToReplaceExpressPathRegExpParams, (str) => {
12241
- if (str.startsWith("(?:\\/")) {
12242
- return `\\/${paramId}`;
12243
- }
12244
- return paramId;
12245
- });
12246
- paramIndex++;
12247
- }
12248
- if (parsedRegExp !== expressPathRegExp.toString()) {
12249
- expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);
12250
- }
12251
- return expressPathRegExpExec[1].replace(/\\\//g, "/");
12252
- }, "parseExpressPathRegExp");
12253
- var parseExpressPath = /* @__PURE__ */ __name(function(expressPath, params) {
12254
- let result = expressPath;
12255
- for (const [paramName, paramValue] of Object.entries(params)) {
12256
- result = result.replace(paramValue, `:${paramName}`);
12257
- }
12258
- return result;
12259
- }, "parseExpressPath");
12260
- var parseEndpoints = /* @__PURE__ */ __name(function(app, basePath, endpoints) {
12261
- const routerInfo = getRouterInfo(app);
12262
- const stack = routerInfo.stack;
12263
- const version = routerInfo.version;
12264
- endpoints = endpoints || [];
12265
- basePath = basePath || "";
12266
- if (!stack) {
12267
- if (endpoints.length) {
12268
- endpoints = addEndpoints(endpoints, [
12269
- {
12270
- path: basePath,
12271
- methods: [],
12272
- middlewares: []
12233
+ var regExpToParseExpressPathRegExp, regExpToReplaceExpressPathRegExpParams, regexpExpressParamRegexp, regexpExpressPathParamRegexp, EXPRESS_ROOT_PATH_REGEXP_VALUE, STACK_ITEM_VALID_NAMES, getRouterInfo, getRouteMethods, getRouteMiddlewares, hasParams, parseExpressRoute, parseExpressPathRegExp, parseExpressPath, parseEndpoints, addEndpoints, parseStack, getEndpoints;
12234
+ var init_utils = __esm({
12235
+ "src/express/utils.js"() {
12236
+ "use strict";
12237
+ regExpToParseExpressPathRegExp = /^\/\^\\?\/?(?:(:?[\w\\.-]*(?:\\\/:?[\w\\.-]*)*)|(\(\?:\\?\/?\([^)]+\)\)))\\\/.*/;
12238
+ regExpToReplaceExpressPathRegExpParams = /\(\?:\\?\/?\([^)]+\)\)/;
12239
+ regexpExpressParamRegexp = /\(\?:\\?\\?\/?\([^)]+\)\)/g;
12240
+ regexpExpressPathParamRegexp = /(:[^)]+)\([^)]+\)/g;
12241
+ EXPRESS_ROOT_PATH_REGEXP_VALUE = "/^\\/?(?=\\/|$)/i";
12242
+ STACK_ITEM_VALID_NAMES = [
12243
+ "router",
12244
+ "bound dispatch",
12245
+ "mounted_app"
12246
+ ];
12247
+ getRouterInfo = /* @__PURE__ */ __name(function(app) {
12248
+ var _a3, _b;
12249
+ if (app.stack) {
12250
+ return {
12251
+ stack: app.stack,
12252
+ version: "v4"
12253
+ };
12254
+ } else if ((_a3 = app._router) == null ? void 0 : _a3.stack) {
12255
+ return {
12256
+ stack: app._router.stack,
12257
+ version: "v4"
12258
+ };
12259
+ } else if ((_b = app.router) == null ? void 0 : _b.stack) {
12260
+ return {
12261
+ stack: app.router.stack,
12262
+ version: "v5"
12263
+ };
12264
+ }
12265
+ return {
12266
+ stack: null,
12267
+ version: "v4"
12268
+ };
12269
+ }, "getRouterInfo");
12270
+ getRouteMethods = /* @__PURE__ */ __name(function(route) {
12271
+ let methods = Object.keys(route.methods);
12272
+ methods = methods.filter((method) => method !== "_all");
12273
+ methods = methods.map((method) => method.toUpperCase());
12274
+ return methods;
12275
+ }, "getRouteMethods");
12276
+ getRouteMiddlewares = /* @__PURE__ */ __name(function(route) {
12277
+ return route.stack.map((item) => {
12278
+ return item.handle.name || "anonymous";
12279
+ });
12280
+ }, "getRouteMiddlewares");
12281
+ hasParams = /* @__PURE__ */ __name(function(expressPathRegExp) {
12282
+ return regexpExpressParamRegexp.test(expressPathRegExp);
12283
+ }, "hasParams");
12284
+ parseExpressRoute = /* @__PURE__ */ __name(function(route, basePath) {
12285
+ const paths = [];
12286
+ if (Array.isArray(route.path)) {
12287
+ paths.push(...route.path);
12288
+ } else {
12289
+ paths.push(route.path);
12290
+ }
12291
+ const endpoints = paths.map((path) => {
12292
+ const completePath = basePath && path === "/" ? basePath : `${basePath}${path}`;
12293
+ const endpoint = {
12294
+ path: completePath.replace(regexpExpressPathParamRegexp, "$1"),
12295
+ methods: getRouteMethods(route),
12296
+ middlewares: getRouteMiddlewares(route)
12297
+ };
12298
+ return endpoint;
12299
+ });
12300
+ return endpoints;
12301
+ }, "parseExpressRoute");
12302
+ parseExpressPathRegExp = /* @__PURE__ */ __name(function(expressPathRegExp, keys) {
12303
+ let parsedRegExp = expressPathRegExp.toString();
12304
+ let expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);
12305
+ let paramIndex = 0;
12306
+ while (hasParams(parsedRegExp)) {
12307
+ const paramName = keys[paramIndex].name;
12308
+ const paramId = `:${paramName}`;
12309
+ parsedRegExp = parsedRegExp.replace(regExpToReplaceExpressPathRegExpParams, (str) => {
12310
+ if (str.startsWith("(?:\\/")) {
12311
+ return `\\/${paramId}`;
12312
+ }
12313
+ return paramId;
12314
+ });
12315
+ paramIndex++;
12316
+ }
12317
+ if (parsedRegExp !== expressPathRegExp.toString()) {
12318
+ expressPathRegExpExec = regExpToParseExpressPathRegExp.exec(parsedRegExp);
12319
+ }
12320
+ return expressPathRegExpExec[1].replace(/\\\//g, "/");
12321
+ }, "parseExpressPathRegExp");
12322
+ parseExpressPath = /* @__PURE__ */ __name(function(expressPath, params) {
12323
+ let result = expressPath;
12324
+ for (const [paramName, paramValue] of Object.entries(params)) {
12325
+ result = result.replace(paramValue, `:${paramName}`);
12326
+ }
12327
+ return result;
12328
+ }, "parseExpressPath");
12329
+ parseEndpoints = /* @__PURE__ */ __name(function(app, basePath, endpoints) {
12330
+ const routerInfo = getRouterInfo(app);
12331
+ const stack = routerInfo.stack;
12332
+ const version = routerInfo.version;
12333
+ endpoints = endpoints || [];
12334
+ basePath = basePath || "";
12335
+ if (!stack) {
12336
+ if (endpoints.length) {
12337
+ endpoints = addEndpoints(endpoints, [
12338
+ {
12339
+ path: basePath,
12340
+ methods: [],
12341
+ middlewares: []
12342
+ }
12343
+ ]);
12273
12344
  }
12274
- ]);
12275
- }
12276
- } else {
12277
- endpoints = parseStack(stack, basePath, endpoints, version);
12278
- }
12279
- return endpoints;
12280
- }, "parseEndpoints");
12281
- var addEndpoints = /* @__PURE__ */ __name(function(currentEndpoints, endpointsToAdd) {
12282
- endpointsToAdd.forEach((newEndpoint) => {
12283
- const existingEndpoint = currentEndpoints.find((endpoint) => endpoint.path === newEndpoint.path);
12284
- if (existingEndpoint !== void 0) {
12285
- const newMethods = newEndpoint.methods.filter((method) => !existingEndpoint.methods.includes(method));
12286
- existingEndpoint.methods = existingEndpoint.methods.concat(newMethods);
12287
- } else {
12288
- currentEndpoints.push(newEndpoint);
12289
- }
12290
- });
12291
- return currentEndpoints;
12292
- }, "addEndpoints");
12293
- var parseStack = /* @__PURE__ */ __name(function(stack, basePath, endpoints, version) {
12294
- stack.forEach((stackItem) => {
12295
- if (stackItem.route) {
12296
- const newEndpoints = parseExpressRoute(stackItem.route, basePath);
12297
- endpoints = addEndpoints(endpoints, newEndpoints);
12298
- } else if (STACK_ITEM_VALID_NAMES.includes(stackItem.name)) {
12299
- let newBasePath = basePath;
12300
- if (version === "v4") {
12301
- const isExpressPathRegExp = regExpToParseExpressPathRegExp.test(stackItem.regexp);
12302
- if (isExpressPathRegExp) {
12303
- const parsedPath = parseExpressPathRegExp(stackItem.regexp, stackItem.keys);
12304
- newBasePath += `/${parsedPath}`;
12305
- } else if (!stackItem.path && stackItem.regexp && stackItem.regexp.toString() !== EXPRESS_ROOT_PATH_REGEXP_VALUE) {
12306
- const regExpPath = `RegExp(${stackItem.regexp})`;
12307
- newBasePath += `/${regExpPath}`;
12308
- }
12309
- } else if (version === "v5") {
12310
- if (!stackItem.path) {
12311
- return;
12312
- } else if (stackItem.path !== "/") {
12313
- newBasePath += stackItem.path.startsWith("/") ? stackItem.path : `/${stackItem.path}`;
12345
+ } else {
12346
+ endpoints = parseStack(stack, basePath, endpoints, version);
12347
+ }
12348
+ return endpoints;
12349
+ }, "parseEndpoints");
12350
+ addEndpoints = /* @__PURE__ */ __name(function(currentEndpoints, endpointsToAdd) {
12351
+ endpointsToAdd.forEach((newEndpoint) => {
12352
+ const existingEndpoint = currentEndpoints.find((endpoint) => endpoint.path === newEndpoint.path);
12353
+ if (existingEndpoint !== void 0) {
12354
+ const newMethods = newEndpoint.methods.filter((method) => !existingEndpoint.methods.includes(method));
12355
+ existingEndpoint.methods = existingEndpoint.methods.concat(newMethods);
12356
+ } else {
12357
+ currentEndpoints.push(newEndpoint);
12314
12358
  }
12315
- }
12316
- endpoints = parseEndpoints(stackItem.handle, newBasePath, endpoints);
12317
- }
12318
- });
12319
- return endpoints;
12320
- }, "parseStack");
12321
- var getEndpoints = /* @__PURE__ */ __name(function(app, basePath) {
12322
- const endpoints = parseEndpoints(app);
12323
- const standardHttpMethods = [
12324
- "GET",
12325
- "POST",
12326
- "PUT",
12327
- "DELETE",
12328
- "PATCH"
12329
- ];
12330
- return endpoints.flatMap((route) => route.methods.filter((method) => standardHttpMethods.includes(method.toUpperCase())).map((method) => ({
12331
- method,
12332
- path: (basePath + route.path).replace(/\/\//g, "/")
12333
- })));
12334
- }, "getEndpoints");
12359
+ });
12360
+ return currentEndpoints;
12361
+ }, "addEndpoints");
12362
+ parseStack = /* @__PURE__ */ __name(function(stack, basePath, endpoints, version) {
12363
+ stack.forEach((stackItem) => {
12364
+ if (stackItem.route) {
12365
+ const newEndpoints = parseExpressRoute(stackItem.route, basePath);
12366
+ endpoints = addEndpoints(endpoints, newEndpoints);
12367
+ } else if (STACK_ITEM_VALID_NAMES.includes(stackItem.name)) {
12368
+ let newBasePath = basePath;
12369
+ if (version === "v4") {
12370
+ const isExpressPathRegExp = regExpToParseExpressPathRegExp.test(stackItem.regexp);
12371
+ if (isExpressPathRegExp) {
12372
+ const parsedPath = parseExpressPathRegExp(stackItem.regexp, stackItem.keys);
12373
+ newBasePath += `/${parsedPath}`;
12374
+ } else if (!stackItem.path && stackItem.regexp && stackItem.regexp.toString() !== EXPRESS_ROOT_PATH_REGEXP_VALUE) {
12375
+ const regExpPath = `RegExp(${stackItem.regexp})`;
12376
+ newBasePath += `/${regExpPath}`;
12377
+ }
12378
+ } else if (version === "v5") {
12379
+ if (!stackItem.path) {
12380
+ return;
12381
+ } else if (stackItem.path !== "/") {
12382
+ newBasePath += stackItem.path.startsWith("/") ? stackItem.path : `/${stackItem.path}`;
12383
+ }
12384
+ }
12385
+ endpoints = parseEndpoints(stackItem.handle, newBasePath, endpoints);
12386
+ }
12387
+ });
12388
+ return endpoints;
12389
+ }, "parseStack");
12390
+ getEndpoints = /* @__PURE__ */ __name(function(app, basePath) {
12391
+ const endpoints = parseEndpoints(app);
12392
+ const standardHttpMethods = [
12393
+ "GET",
12394
+ "POST",
12395
+ "PUT",
12396
+ "DELETE",
12397
+ "PATCH"
12398
+ ];
12399
+ return endpoints.flatMap((route) => route.methods.filter((method) => standardHttpMethods.includes(method.toUpperCase())).map((method) => ({
12400
+ method,
12401
+ path: (basePath + route.path).replace(/\/\//g, "/")
12402
+ })));
12403
+ }, "getEndpoints");
12404
+ }
12405
+ });
12335
12406
 
12336
12407
  // src/express/middleware.ts
12337
12408
  function useApitally(app, config) {
@@ -12348,7 +12419,6 @@ function useApitally(app, config) {
12348
12419
  }, "setStartupData");
12349
12420
  setTimeout(() => setStartupData(), 500);
12350
12421
  }
12351
- __name(useApitally, "useApitally");
12352
12422
  function getMiddleware(app, client) {
12353
12423
  let errorHandlerConfigured = false;
12354
12424
  return (req, res, next) => {
@@ -12467,7 +12537,6 @@ function getMiddleware(app, client) {
12467
12537
  }
12468
12538
  };
12469
12539
  }
12470
- __name(getMiddleware, "getMiddleware");
12471
12540
  function getRoutePath(req) {
12472
12541
  if (!req.route) {
12473
12542
  return;
@@ -12481,7 +12550,6 @@ function getRoutePath(req) {
12481
12550
  }
12482
12551
  return req.route.path;
12483
12552
  }
12484
- __name(getRoutePath, "getRoutePath");
12485
12553
  function getRouterPath(stack, baseUrl) {
12486
12554
  var _a3;
12487
12555
  const routerPaths = [];
@@ -12508,11 +12576,9 @@ function getRouterPath(stack, baseUrl) {
12508
12576
  }
12509
12577
  return routerPaths.filter((path) => path !== "/").join("");
12510
12578
  }
12511
- __name(getRouterPath, "getRouterPath");
12512
12579
  function setConsumer(req, consumer) {
12513
12580
  req.apitallyConsumer = consumer || void 0;
12514
12581
  }
12515
- __name(setConsumer, "setConsumer");
12516
12582
  function getConsumer(req) {
12517
12583
  if (req.apitallyConsumer) {
12518
12584
  return consumerFromStringOrObject(req.apitallyConsumer);
@@ -12522,7 +12588,6 @@ function getConsumer(req) {
12522
12588
  }
12523
12589
  return null;
12524
12590
  }
12525
- __name(getConsumer, "getConsumer");
12526
12591
  function extractExpressValidatorErrors(responseBody) {
12527
12592
  try {
12528
12593
  const errors = [];
@@ -12542,7 +12607,6 @@ function extractExpressValidatorErrors(responseBody) {
12542
12607
  return [];
12543
12608
  }
12544
12609
  }
12545
- __name(extractExpressValidatorErrors, "extractExpressValidatorErrors");
12546
12610
  function extractCelebrateErrors(responseBody) {
12547
12611
  try {
12548
12612
  const errors = [];
@@ -12564,7 +12628,6 @@ function extractCelebrateErrors(responseBody) {
12564
12628
  return [];
12565
12629
  }
12566
12630
  }
12567
- __name(extractCelebrateErrors, "extractCelebrateErrors");
12568
12631
  function extractNestValidationErrors(responseBody) {
12569
12632
  try {
12570
12633
  const errors = [];
@@ -12582,12 +12645,10 @@ function extractNestValidationErrors(responseBody) {
12582
12645
  return [];
12583
12646
  }
12584
12647
  }
12585
- __name(extractNestValidationErrors, "extractNestValidationErrors");
12586
12648
  function subsetJoiMessage(message, key) {
12587
12649
  const messageWithKey = message.split(". ").find((message2) => message2.includes(`"${key}"`));
12588
12650
  return messageWithKey ? messageWithKey : message;
12589
12651
  }
12590
- __name(subsetJoiMessage, "subsetJoiMessage");
12591
12652
  function getAppInfo(app, basePath, appVersion) {
12592
12653
  const versions = [
12593
12654
  [
@@ -12628,9 +12689,298 @@ function getAppInfo(app, basePath, appVersion) {
12628
12689
  client: "js:express"
12629
12690
  };
12630
12691
  }
12631
- __name(getAppInfo, "getAppInfo");
12692
+ var import_perf_hooks;
12693
+ var init_middleware = __esm({
12694
+ "src/express/middleware.ts"() {
12695
+ "use strict";
12696
+ import_perf_hooks = require("perf_hooks");
12697
+ init_client();
12698
+ init_consumerRegistry();
12699
+ init_headers();
12700
+ init_packageVersions();
12701
+ init_requestLogger();
12702
+ init_utils();
12703
+ __name(useApitally, "useApitally");
12704
+ __name(getMiddleware, "getMiddleware");
12705
+ __name(getRoutePath, "getRoutePath");
12706
+ __name(getRouterPath, "getRouterPath");
12707
+ __name(setConsumer, "setConsumer");
12708
+ __name(getConsumer, "getConsumer");
12709
+ __name(extractExpressValidatorErrors, "extractExpressValidatorErrors");
12710
+ __name(extractCelebrateErrors, "extractCelebrateErrors");
12711
+ __name(extractNestValidationErrors, "extractNestValidationErrors");
12712
+ __name(subsetJoiMessage, "subsetJoiMessage");
12713
+ __name(getAppInfo, "getAppInfo");
12714
+ }
12715
+ });
12716
+
12717
+ // src/express/index.ts
12718
+ var express_exports = {};
12719
+ __export(express_exports, {
12720
+ setConsumer: () => setConsumer,
12721
+ useApitally: () => useApitally
12722
+ });
12723
+ var init_express = __esm({
12724
+ "src/express/index.ts"() {
12725
+ "use strict";
12726
+ init_middleware();
12727
+ }
12728
+ });
12729
+
12730
+ // src/fastify/plugin.ts
12731
+ function getAppInfo2(routes, appVersion) {
12732
+ const versions = [
12733
+ [
12734
+ "nodejs",
12735
+ process.version.replace(/^v/, "")
12736
+ ]
12737
+ ];
12738
+ const fastifyVersion = getPackageVersion("fastify");
12739
+ const nestjsVersion = getPackageVersion("@nestjs/core");
12740
+ const apitallyVersion = getPackageVersion("../..");
12741
+ if (fastifyVersion) {
12742
+ versions.push([
12743
+ "fastify",
12744
+ fastifyVersion
12745
+ ]);
12746
+ }
12747
+ if (nestjsVersion) {
12748
+ versions.push([
12749
+ "nestjs",
12750
+ nestjsVersion
12751
+ ]);
12752
+ }
12753
+ if (apitallyVersion) {
12754
+ versions.push([
12755
+ "apitally",
12756
+ apitallyVersion
12757
+ ]);
12758
+ }
12759
+ if (appVersion) {
12760
+ versions.push([
12761
+ "app",
12762
+ appVersion
12763
+ ]);
12764
+ }
12765
+ return {
12766
+ paths: routes,
12767
+ versions: Object.fromEntries(versions),
12768
+ client: "js:fastify"
12769
+ };
12770
+ }
12771
+ function setConsumer2(request, consumer) {
12772
+ request.apitallyConsumer = consumer || void 0;
12773
+ }
12774
+ function getConsumer2(request) {
12775
+ if (request.apitallyConsumer) {
12776
+ return consumerFromStringOrObject(request.apitallyConsumer);
12777
+ } else if (request.consumerIdentifier) {
12778
+ process.emitWarning("The consumerIdentifier property on the request object is deprecated. Use apitallyConsumer instead.", "DeprecationWarning");
12779
+ return consumerFromStringOrObject(request.consumerIdentifier);
12780
+ }
12781
+ return null;
12782
+ }
12783
+ function getResponseTime(reply) {
12784
+ if (reply.elapsedTime !== void 0) {
12785
+ return reply.elapsedTime;
12786
+ } else if (reply.getResponseTime !== void 0) {
12787
+ return reply.getResponseTime();
12788
+ }
12789
+ return 0;
12790
+ }
12791
+ function extractAjvErrors(message) {
12792
+ try {
12793
+ const regex = /(?<=^|, )((?:headers|params|query|querystring|body)[/.][^ ]+)(?= )/g;
12794
+ const matches = [];
12795
+ let match;
12796
+ while ((match = regex.exec(message)) !== null) {
12797
+ matches.push({
12798
+ match: match[0],
12799
+ index: match.index
12800
+ });
12801
+ }
12802
+ return matches.map((m, i) => {
12803
+ const endIndex = i + 1 < matches.length ? matches[i + 1].index - 2 : message.length;
12804
+ const matchSplit = m.match.split(/[/.]/);
12805
+ if (matchSplit[0] === "querystring") {
12806
+ matchSplit[0] = "query";
12807
+ }
12808
+ return {
12809
+ loc: matchSplit.join("."),
12810
+ msg: message.substring(m.index, endIndex),
12811
+ type: ""
12812
+ };
12813
+ });
12814
+ } catch (error) {
12815
+ return [];
12816
+ }
12817
+ }
12818
+ function extractNestValidationErrors2(message) {
12819
+ try {
12820
+ return message.filter((msg) => typeof msg === "string").map((msg) => ({
12821
+ loc: "",
12822
+ msg,
12823
+ type: ""
12824
+ }));
12825
+ } catch (error) {
12826
+ return [];
12827
+ }
12828
+ }
12829
+ var import_fastify_plugin, apitallyPlugin, plugin_default;
12830
+ var init_plugin = __esm({
12831
+ "src/fastify/plugin.ts"() {
12832
+ "use strict";
12833
+ import_fastify_plugin = __toESM(require("fastify-plugin"), 1);
12834
+ init_client();
12835
+ init_consumerRegistry();
12836
+ init_headers();
12837
+ init_packageVersions();
12838
+ init_requestLogger();
12839
+ apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
12840
+ const client = new ApitallyClient(config);
12841
+ const routes = [];
12842
+ fastify.decorateRequest("apitallyConsumer", null);
12843
+ fastify.decorateRequest("consumerIdentifier", null);
12844
+ fastify.decorateReply("payload", null);
12845
+ fastify.addHook("onRoute", (routeOptions) => {
12846
+ const methods = Array.isArray(routeOptions.method) ? routeOptions.method : [
12847
+ routeOptions.method
12848
+ ];
12849
+ methods.forEach((method) => {
12850
+ if (![
12851
+ "HEAD",
12852
+ "OPTIONS"
12853
+ ].includes(method.toUpperCase())) {
12854
+ routes.push({
12855
+ method: method.toUpperCase(),
12856
+ path: routeOptions.url
12857
+ });
12858
+ }
12859
+ });
12860
+ });
12861
+ fastify.addHook("onReady", () => {
12862
+ client.setStartupData(getAppInfo2(routes, config.appVersion));
12863
+ });
12864
+ fastify.addHook("onClose", async () => {
12865
+ await client.handleShutdown();
12866
+ });
12867
+ fastify.addHook("onSend", (request, reply, payload, done) => {
12868
+ const contentType = reply.getHeader("content-type");
12869
+ if (client.requestLogger.isSupportedContentType(contentType)) {
12870
+ reply.payload = payload;
12871
+ }
12872
+ done();
12873
+ });
12874
+ fastify.addHook("onError", (request, reply, error, done) => {
12875
+ if (!error.statusCode || error.statusCode === 500) {
12876
+ reply.serverError = error;
12877
+ }
12878
+ done();
12879
+ });
12880
+ fastify.addHook("onResponse", (request, reply, done) => {
12881
+ var _a3;
12882
+ if (client.isEnabled() && request.method.toUpperCase() !== "OPTIONS") {
12883
+ const consumer = getConsumer2(request);
12884
+ const path = "routeOptions" in request ? request.routeOptions.url : request.routerPath;
12885
+ const requestSize = parseContentLength(request.headers["content-length"]);
12886
+ const responseSize = parseContentLength(reply.getHeader("content-length"));
12887
+ const responseTime = getResponseTime(reply);
12888
+ client.consumerRegistry.addOrUpdateConsumer(consumer);
12889
+ client.requestCounter.addRequest({
12890
+ consumer: consumer == null ? void 0 : consumer.identifier,
12891
+ method: request.method,
12892
+ path,
12893
+ statusCode: reply.statusCode,
12894
+ responseTime,
12895
+ requestSize,
12896
+ responseSize
12897
+ });
12898
+ if ((reply.statusCode === 400 || reply.statusCode === 422) && reply.payload) {
12899
+ try {
12900
+ const parsedPayload = JSON.parse(reply.payload);
12901
+ const validationErrors = [];
12902
+ if ((!parsedPayload.code || parsedPayload.code === "FST_ERR_VALIDATION") && typeof parsedPayload.message === "string") {
12903
+ validationErrors.push(...extractAjvErrors(parsedPayload.message));
12904
+ } else if (Array.isArray(parsedPayload.message)) {
12905
+ validationErrors.push(...extractNestValidationErrors2(parsedPayload.message));
12906
+ }
12907
+ validationErrors.forEach((error) => {
12908
+ client.validationErrorCounter.addValidationError({
12909
+ consumer: consumer == null ? void 0 : consumer.identifier,
12910
+ method: request.method,
12911
+ path,
12912
+ ...error
12913
+ });
12914
+ });
12915
+ } catch (error) {
12916
+ }
12917
+ }
12918
+ if (reply.statusCode === 500 && reply.serverError) {
12919
+ client.serverErrorCounter.addServerError({
12920
+ consumer: consumer == null ? void 0 : consumer.identifier,
12921
+ method: request.method,
12922
+ path,
12923
+ type: reply.serverError.name,
12924
+ msg: reply.serverError.message,
12925
+ traceback: reply.serverError.stack || ""
12926
+ });
12927
+ }
12928
+ if (client.requestLogger.enabled) {
12929
+ client.requestLogger.logRequest({
12930
+ timestamp: Date.now() / 1e3,
12931
+ method: request.method,
12932
+ path,
12933
+ url: `${request.protocol}://${request.host ?? request.hostname}${request.originalUrl ?? request.url}`,
12934
+ headers: convertHeaders(request.headers),
12935
+ size: Number(requestSize),
12936
+ consumer: consumer == null ? void 0 : consumer.identifier,
12937
+ body: convertBody(request.body, request.headers["content-type"])
12938
+ }, {
12939
+ statusCode: reply.statusCode,
12940
+ responseTime: responseTime / 1e3,
12941
+ headers: convertHeaders(reply.getHeaders()),
12942
+ size: Number(responseSize),
12943
+ body: convertBody(reply.payload, (_a3 = reply.getHeader("content-type")) == null ? void 0 : _a3.toString())
12944
+ }, reply.serverError);
12945
+ }
12946
+ }
12947
+ done();
12948
+ });
12949
+ }, "apitallyPlugin");
12950
+ __name(getAppInfo2, "getAppInfo");
12951
+ __name(setConsumer2, "setConsumer");
12952
+ __name(getConsumer2, "getConsumer");
12953
+ __name(getResponseTime, "getResponseTime");
12954
+ __name(extractAjvErrors, "extractAjvErrors");
12955
+ __name(extractNestValidationErrors2, "extractNestValidationErrors");
12956
+ plugin_default = (0, import_fastify_plugin.default)(apitallyPlugin, {
12957
+ name: "apitally"
12958
+ });
12959
+ }
12960
+ });
12961
+
12962
+ // src/fastify/index.ts
12963
+ var fastify_exports = {};
12964
+ __export(fastify_exports, {
12965
+ apitallyPlugin: () => plugin_default,
12966
+ setConsumer: () => setConsumer2
12967
+ });
12968
+ var init_fastify = __esm({
12969
+ "src/fastify/index.ts"() {
12970
+ "use strict";
12971
+ init_plugin();
12972
+ }
12973
+ });
12632
12974
 
12633
12975
  // src/nestjs/index.ts
12976
+ var nestjs_exports = {};
12977
+ __export(nestjs_exports, {
12978
+ setConsumer: () => setConsumer3,
12979
+ useApitally: () => useApitally2
12980
+ });
12981
+ module.exports = __toCommonJS(nestjs_exports);
12982
+ var import_common = require("@nestjs/common");
12983
+ var import_rxjs = __toESM(require_cjs(), 1);
12634
12984
  function _ts_decorate(decorators, target, key, desc) {
12635
12985
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
12636
12986
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -12638,22 +12988,48 @@ function _ts_decorate(decorators, target, key, desc) {
12638
12988
  return c > 3 && r && Object.defineProperty(target, key, r), r;
12639
12989
  }
12640
12990
  __name(_ts_decorate, "_ts_decorate");
12641
- function useApitally2(app, config) {
12991
+ function _ts_metadata(k, v) {
12992
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
12993
+ }
12994
+ __name(_ts_metadata, "_ts_metadata");
12995
+ var setConsumerFn = null;
12996
+ async function useApitally2(app, config) {
12642
12997
  const httpAdapter = app.getHttpAdapter();
12643
- const expressInstance = httpAdapter.getInstance();
12644
- useApitally(expressInstance, config);
12645
- app.useGlobalInterceptors(new ApitallyInterceptor());
12998
+ const instance = httpAdapter.getInstance();
12999
+ const platform = instance.use === void 0 && typeof instance.register === "function" ? "fastify" : "express";
13000
+ if (platform === "express") {
13001
+ const { useApitally: useApitally3, setConsumer: setConsumer4 } = await Promise.resolve().then(() => (init_express(), express_exports));
13002
+ setConsumerFn = setConsumer4;
13003
+ useApitally3(instance, config);
13004
+ } else if (platform === "fastify") {
13005
+ const { apitallyPlugin: apitallyPlugin2, setConsumer: setConsumer4 } = await Promise.resolve().then(() => (init_fastify(), fastify_exports));
13006
+ setConsumerFn = setConsumer4;
13007
+ await instance.register(apitallyPlugin2, config);
13008
+ }
13009
+ app.useGlobalInterceptors(new ApitallyInterceptor(platform));
12646
13010
  }
12647
13011
  __name(useApitally2, "useApitally");
13012
+ function setConsumer3(request, consumer) {
13013
+ if (setConsumerFn) {
13014
+ setConsumerFn(request, consumer);
13015
+ }
13016
+ }
13017
+ __name(setConsumer3, "setConsumer");
12648
13018
  var _a2;
12649
13019
  var ApitallyInterceptor = (_a2 = class {
13020
+ platform;
13021
+ constructor(platform) {
13022
+ this.platform = platform;
13023
+ }
12650
13024
  intercept(context, next) {
12651
13025
  return next.handle().pipe((0, import_rxjs.catchError)((exception) => {
12652
13026
  if (context.getType() === "http") {
12653
13027
  const ctx = context.switchToHttp();
12654
13028
  const res = ctx.getResponse();
12655
- if (res.locals) {
13029
+ if (this.platform === "express" && res.locals) {
12656
13030
  res.locals.serverError = exception;
13031
+ } else if (this.platform === "fastify" && (!exception.statusCode || exception.statusCode === 500)) {
13032
+ res.serverError = exception;
12657
13033
  }
12658
13034
  }
12659
13035
  return (0, import_rxjs.throwError)(() => exception);
@@ -12661,7 +13037,11 @@ var ApitallyInterceptor = (_a2 = class {
12661
13037
  }
12662
13038
  }, __name(_a2, "ApitallyInterceptor"), _a2);
12663
13039
  ApitallyInterceptor = _ts_decorate([
12664
- (0, import_common.Injectable)()
13040
+ (0, import_common.Injectable)(),
13041
+ _ts_metadata("design:type", Function),
13042
+ _ts_metadata("design:paramtypes", [
13043
+ String
13044
+ ])
12665
13045
  ], ApitallyInterceptor);
12666
13046
  // Annotate the CommonJS export names for ESM import in node:
12667
13047
  0 && (module.exports = {