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