@ninenity/neutrun 1.0.0 → 1.9.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.
Files changed (104) hide show
  1. package/README.md +3 -2
  2. package/dist/Emojis/Pool.d.ts.map +1 -1
  3. package/dist/Emojis/Pool.js +3 -0
  4. package/dist/Emojis/Pool.js.map +1 -1
  5. package/dist/Logger/index.js +1 -1
  6. package/dist/Shared/ChannelLoader.d.ts.map +1 -1
  7. package/dist/Shared/ChannelLoader.js +22 -8
  8. package/dist/Shared/ChannelLoader.js.map +1 -1
  9. package/dist/Shared/Client.d.ts +4 -0
  10. package/dist/Shared/Client.d.ts.map +1 -1
  11. package/dist/Shared/Client.js +32 -43
  12. package/dist/Shared/Client.js.map +1 -1
  13. package/dist/Shared/ComponentRuntime.d.ts.map +1 -1
  14. package/dist/Shared/ComponentRuntime.js +14 -3
  15. package/dist/Shared/ComponentRuntime.js.map +1 -1
  16. package/dist/Shared/ConfigLoader.d.ts.map +1 -1
  17. package/dist/Shared/ConfigLoader.js +33 -8
  18. package/dist/Shared/ConfigLoader.js.map +1 -1
  19. package/dist/Shared/CooldownManager.d.ts.map +1 -1
  20. package/dist/Shared/CooldownManager.js +6 -0
  21. package/dist/Shared/CooldownManager.js.map +1 -1
  22. package/dist/Shared/Definer.d.ts.map +1 -1
  23. package/dist/Shared/Definer.js +0 -9
  24. package/dist/Shared/Definer.js.map +1 -1
  25. package/dist/Shared/Environment.d.ts +10 -0
  26. package/dist/Shared/Environment.d.ts.map +1 -0
  27. package/dist/Shared/Environment.js +18 -0
  28. package/dist/Shared/Environment.js.map +1 -0
  29. package/dist/Shared/EventBus.d.ts.map +1 -1
  30. package/dist/Shared/EventBus.js +4 -1
  31. package/dist/Shared/EventBus.js.map +1 -1
  32. package/dist/Shared/ExpressApi.d.ts +114 -0
  33. package/dist/Shared/ExpressApi.d.ts.map +1 -0
  34. package/dist/Shared/ExpressApi.js +1009 -0
  35. package/dist/Shared/ExpressApi.js.map +1 -0
  36. package/dist/Shared/InteractionManager.d.ts.map +1 -1
  37. package/dist/Shared/InteractionManager.js +28 -4
  38. package/dist/Shared/InteractionManager.js.map +1 -1
  39. package/dist/Shared/LogTransport.d.ts +3 -1
  40. package/dist/Shared/LogTransport.d.ts.map +1 -1
  41. package/dist/Shared/LogTransport.js +19 -5
  42. package/dist/Shared/LogTransport.js.map +1 -1
  43. package/dist/Shared/Neutrun.d.ts +9 -9
  44. package/dist/Shared/Neutrun.d.ts.map +1 -1
  45. package/dist/Shared/Neutrun.js +87 -37
  46. package/dist/Shared/Neutrun.js.map +1 -1
  47. package/dist/Shared/NinenityApi.d.ts +103 -0
  48. package/dist/Shared/NinenityApi.d.ts.map +1 -0
  49. package/dist/Shared/NinenityApi.js +609 -0
  50. package/dist/Shared/NinenityApi.js.map +1 -0
  51. package/dist/Shared/Observability.d.ts +59 -0
  52. package/dist/Shared/Observability.d.ts.map +1 -0
  53. package/dist/Shared/Observability.js +184 -0
  54. package/dist/Shared/Observability.js.map +1 -0
  55. package/dist/Shared/Plugin.d.ts +1 -1
  56. package/dist/Shared/Plugin.d.ts.map +1 -1
  57. package/dist/Shared/Pool.d.ts.map +1 -1
  58. package/dist/Shared/Pool.js +3 -0
  59. package/dist/Shared/Pool.js.map +1 -1
  60. package/dist/Shared/ProcessManager.d.ts +2 -1
  61. package/dist/Shared/ProcessManager.d.ts.map +1 -1
  62. package/dist/Shared/ProcessManager.js +56 -26
  63. package/dist/Shared/ProcessManager.js.map +1 -1
  64. package/dist/Shared/QueueManager.d.ts.map +1 -1
  65. package/dist/Shared/QueueManager.js +35 -13
  66. package/dist/Shared/QueueManager.js.map +1 -1
  67. package/dist/Shared/RoleLoader.d.ts.map +1 -1
  68. package/dist/Shared/RoleLoader.js +22 -8
  69. package/dist/Shared/RoleLoader.js.map +1 -1
  70. package/dist/Shared/RuntimeAliases.d.ts +10 -0
  71. package/dist/Shared/RuntimeAliases.d.ts.map +1 -0
  72. package/dist/Shared/RuntimeAliases.js +29 -0
  73. package/dist/Shared/RuntimeAliases.js.map +1 -0
  74. package/dist/Shared/Supervisor.d.ts.map +1 -1
  75. package/dist/Shared/Supervisor.js +59 -6
  76. package/dist/Shared/Supervisor.js.map +1 -1
  77. package/dist/Shared/TelemetryService.d.ts +21 -0
  78. package/dist/Shared/TelemetryService.d.ts.map +1 -1
  79. package/dist/Shared/TelemetryService.js +95 -2
  80. package/dist/Shared/TelemetryService.js.map +1 -1
  81. package/dist/Shared/WebSocket.d.ts +1 -1
  82. package/dist/Shared/WebSocket.d.ts.map +1 -1
  83. package/dist/Shared/WebSocket.js +96 -39
  84. package/dist/Shared/WebSocket.js.map +1 -1
  85. package/dist/Types/Client.Type.d.ts +3 -3
  86. package/dist/Types/Client.Type.d.ts.map +1 -1
  87. package/dist/Types/Client.Type.js.map +1 -1
  88. package/dist/api.d.ts +3 -0
  89. package/dist/api.d.ts.map +1 -0
  90. package/dist/api.js +11 -0
  91. package/dist/api.js.map +1 -0
  92. package/dist/discordjs.d.ts +8 -0
  93. package/dist/discordjs.d.ts.map +1 -0
  94. package/dist/discordjs.js +24 -0
  95. package/dist/discordjs.js.map +1 -0
  96. package/dist/express.d.ts +13 -0
  97. package/dist/express.d.ts.map +1 -0
  98. package/dist/express.js +19 -0
  99. package/dist/express.js.map +1 -0
  100. package/dist/index.d.ts +14 -3
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +29 -2
  103. package/dist/index.js.map +1 -1
  104. package/package.json +28 -4
@@ -0,0 +1,1009 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Api = exports.ApiRequestError = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ const node_http_1 = require("node:http");
9
+ const node_crypto_1 = require("node:crypto");
10
+ const ConsoleLog_1 = require("./ConsoleLog");
11
+ const ProcessManager_1 = __importDefault(require("./ProcessManager"));
12
+ const TelemetryService_1 = require("./TelemetryService");
13
+ const Observability_1 = __importDefault(require("./Observability"));
14
+ class ApiRequestError extends Error {
15
+ status;
16
+ data;
17
+ response;
18
+ constructor(message, response, data) {
19
+ super(message);
20
+ this.name = 'ApiRequestError';
21
+ this.status = response.status;
22
+ this.response = response;
23
+ this.data = data;
24
+ }
25
+ }
26
+ exports.ApiRequestError = ApiRequestError;
27
+ const DEFAULT_HOST = '127.0.0.1';
28
+ const DEFAULT_PORT = 3000;
29
+ const DEFAULT_IDLE_TIMEOUT_MS = 5 * 60 * 1000;
30
+ const DEFAULT_REQUEST_TIMEOUT_MS = 30 * 1000;
31
+ const DEFAULT_RATE_LIMIT_WINDOW_MS = 60 * 1000;
32
+ const DEFAULT_RATE_LIMIT_MAX = 60;
33
+ const MAX_RATE_LIMIT_ENTRIES = 10_000;
34
+ const SERVER_CLOSE_TIMEOUT_MS = 5_000;
35
+ const MAX_INTERNAL_RESPONSE_BYTES = 2 * 1024 * 1024;
36
+ const DEFAULT_MAX_HEADER_BYTES = 16 * 1024;
37
+ const DEFAULT_MAX_QUERY_PARAMETERS = 100;
38
+ const DEFAULT_MAX_JSON_DEPTH = 16;
39
+ const DEFAULT_MAX_JSON_KEYS = 1_000;
40
+ const REQUEST_ID_PATTERN = /^[a-zA-Z0-9._:-]{8,100}$/;
41
+ class ExpressApiService {
42
+ app;
43
+ settings = normalizeSettings(undefined);
44
+ configured = false;
45
+ finalized = false;
46
+ server = null;
47
+ startPromise = null;
48
+ stopPromise = null;
49
+ idleTimer = null;
50
+ activeRequests = 0;
51
+ lastActivityAt = Date.now();
52
+ resourcesSuspended = false;
53
+ cleanupRegistered = false;
54
+ middlewareInstalled = false;
55
+ rateLimitEntries = new Map();
56
+ rateLimitLastSweepAt = 0;
57
+ resourceTransition = null;
58
+ constructor() {
59
+ this.app = (0, express_1.default)();
60
+ this.app.disable('x-powered-by');
61
+ this.app.configureNeutrun = (options) => this.configure(options);
62
+ this.app.finalize = () => this.finalize();
63
+ this.app.start = (options) => this.start(options);
64
+ this.app.ensureStarted = () => this.ensureStarted();
65
+ this.app.stop = () => this.stop();
66
+ this.app.fetch = (path, options) => this.fetch(path, options);
67
+ this.app.fetchJson = (path, options) => this.fetchJson(path, options);
68
+ this.app.protect = (options) => this.protectionMiddleware(options);
69
+ this.app.validate = (options) => this.validationMiddleware(options);
70
+ this.app.getIdentity = (response) => response.locals.neutrunIdentity;
71
+ Object.defineProperties(this.app, {
72
+ isRunning: {
73
+ enumerable: true,
74
+ get: () => this.server !== null
75
+ },
76
+ isSuspended: {
77
+ enumerable: true,
78
+ get: () => this.resourcesSuspended
79
+ },
80
+ url: {
81
+ enumerable: true,
82
+ get: () => this.url
83
+ }
84
+ });
85
+ }
86
+ configure(options) {
87
+ if (this.server) {
88
+ throw new Error('Api não pode ser reconfigurada enquanto o servidor está ativo.');
89
+ }
90
+ const nextSettings = normalizeSettings(options);
91
+ if (this.middlewareInstalled &&
92
+ String(nextSettings.security.bodyLimit ?? '1mb') !==
93
+ String(this.settings.security.bodyLimit ?? '1mb')) {
94
+ throw new Error('security.bodyLimit não pode mudar depois que os middlewares da Api foram instalados.');
95
+ }
96
+ this.settings = nextSettings;
97
+ this.configured = options !== false && options !== undefined;
98
+ this.app.set('trust proxy', this.settings.security.trustProxy ?? false);
99
+ this.app.disable('x-powered-by');
100
+ if (!this.configured || !this.settings.enabled)
101
+ return;
102
+ validateSettings(this.settings);
103
+ if (this.middlewareInstalled)
104
+ return;
105
+ this.installBuiltInMiddleware();
106
+ }
107
+ finalize() {
108
+ if (this.finalized)
109
+ return;
110
+ this.finalized = true;
111
+ this.app.use((_req, res) => {
112
+ res.status(404).json({
113
+ success: false,
114
+ error: 'Not Found'
115
+ });
116
+ });
117
+ this.app.use((error, _req, res, next) => {
118
+ const message = error instanceof Error ? error.message : String(error);
119
+ const status = isHttpStatus(error) ? error.status : 500;
120
+ (0, ConsoleLog_1.logError)(`Erro na mini API: ${message}`, 'Api');
121
+ if (res.headersSent)
122
+ return next(error);
123
+ res.status(status).json({
124
+ success: false,
125
+ error: status >= 500 ? 'Internal Server Error' : message
126
+ });
127
+ });
128
+ }
129
+ async start(options) {
130
+ if (this.stopPromise)
131
+ await this.stopPromise;
132
+ if (options !== undefined)
133
+ this.configure(options);
134
+ if (!this.configured || !this.settings.enabled)
135
+ return;
136
+ if (this.server)
137
+ return;
138
+ if (this.startPromise)
139
+ return this.startPromise;
140
+ validateSettings(this.settings);
141
+ this.finalize();
142
+ this.startPromise = new Promise((resolve, reject) => {
143
+ const server = (0, node_http_1.createServer)({
144
+ maxHeaderSize: clampInteger(this.settings.security.maxHeaderBytes, 8 * 1024, 64 * 1024, DEFAULT_MAX_HEADER_BYTES)
145
+ }, this.app);
146
+ const requestTimeout = this.settings.security.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
147
+ server.requestTimeout = requestTimeout;
148
+ server.headersTimeout = Math.max(requestTimeout + 1_000, 5_000);
149
+ server.keepAliveTimeout = Math.min(requestTimeout, 5_000);
150
+ const onError = (error) => {
151
+ server.removeListener('listening', onListening);
152
+ try {
153
+ server.close();
154
+ }
155
+ catch {
156
+ // O servidor ainda pode não ter aberto um handle.
157
+ }
158
+ reject(error);
159
+ };
160
+ const onListening = () => {
161
+ server.removeListener('error', onError);
162
+ this.server = server;
163
+ this.startPromise = null;
164
+ this.lastActivityAt = Date.now();
165
+ this.registerCleanup();
166
+ this.scheduleIdleCheck();
167
+ const address = server.address();
168
+ const actualPort = typeof address === 'object' && address ? address.port : this.settings.port;
169
+ (0, ConsoleLog_1.logSuccess)(`Mini API Express ativa em ${this.publicUrl(actualPort)}`, 'Api');
170
+ resolve();
171
+ };
172
+ server.once('error', onError);
173
+ server.once('listening', onListening);
174
+ server.listen(this.settings.port, this.settings.host);
175
+ }).catch((error) => {
176
+ this.startPromise = null;
177
+ (0, ConsoleLog_1.logError)(`Falha ao iniciar a mini API: ${error instanceof Error ? error.message : error}`, 'Api');
178
+ throw error;
179
+ });
180
+ return this.startPromise;
181
+ }
182
+ async ensureStarted() {
183
+ if (!this.configured || !this.settings.enabled) {
184
+ throw new Error('A mini API está desativada. Configure settings.api no Neutrun.init().');
185
+ }
186
+ if (!this.server)
187
+ await this.start();
188
+ await this.wakeResources();
189
+ }
190
+ async stop() {
191
+ if (this.stopPromise)
192
+ return this.stopPromise;
193
+ this.clearIdleTimer();
194
+ if (this.startPromise) {
195
+ await this.startPromise.catch(() => undefined);
196
+ }
197
+ const server = this.server;
198
+ if (!server)
199
+ return;
200
+ this.server = null;
201
+ this.stopPromise = closeHttpServer(server).finally(() => {
202
+ this.stopPromise = null;
203
+ });
204
+ await this.stopPromise;
205
+ (0, ConsoleLog_1.logInfo)('Mini API Express encerrada.', 'Api');
206
+ }
207
+ async fetch(path, options = {}) {
208
+ await this.ensureStarted();
209
+ const url = this.resolveUrl(path);
210
+ const headers = new Headers(options.headers);
211
+ const token = this.resolveCredentials()[0]?.token ?? '';
212
+ const authentication = this.settings.security.authentication;
213
+ if (token && authentication !== 'none' && !headers.has('authorization') && !headers.has('x-api-key')) {
214
+ if (authentication === 'api-key')
215
+ headers.set('x-api-key', token);
216
+ else
217
+ headers.set('authorization', `Bearer ${token}`);
218
+ }
219
+ if (!headers.has('x-request-id')) {
220
+ headers.set('x-request-id', Observability_1.default.getCorrelationId() ?? (0, node_crypto_1.randomUUID)());
221
+ }
222
+ const body = serializeBody(options.body, headers);
223
+ const { query, ...init } = options;
224
+ const requestInit = {
225
+ ...init,
226
+ method: options.method ?? (body === undefined ? 'GET' : 'POST'),
227
+ headers,
228
+ body
229
+ };
230
+ return globalThis.fetch(addQuery(url, query), requestInit);
231
+ }
232
+ async fetchJson(path, options = {}) {
233
+ const response = await this.fetch(path, options);
234
+ const data = await parseResponse(response);
235
+ if (!response.ok) {
236
+ throw new ApiRequestError(`Mini API request failed with HTTP ${response.status}.`, response, data);
237
+ }
238
+ return { data, response };
239
+ }
240
+ installBuiltInMiddleware() {
241
+ this.middlewareInstalled = true;
242
+ this.app.use(this.requestContextMiddleware());
243
+ this.app.use(this.securityHeaders());
244
+ this.app.use(this.corsMiddleware());
245
+ this.app.use(this.requestTrackingMiddleware());
246
+ this.app.use(this.requestShapeLimitsMiddleware());
247
+ this.app.use(this.requestTimeoutMiddleware());
248
+ this.app.use(this.rateLimitMiddleware());
249
+ this.app.use(this.authenticationMiddleware());
250
+ this.app.use(this.wakeResourcesMiddleware());
251
+ this.app.use(express_1.default.json({ limit: this.settings.security.bodyLimit ?? '1mb' }));
252
+ this.app.use(express_1.default.urlencoded({
253
+ extended: true,
254
+ limit: this.settings.security.bodyLimit ?? '1mb'
255
+ }));
256
+ this.app.use(this.jsonComplexityMiddleware());
257
+ }
258
+ requestContextMiddleware() {
259
+ return (req, res, next) => {
260
+ const incoming = req.get('x-request-id')?.trim() || '';
261
+ const requestId = REQUEST_ID_PATTERN.test(incoming) ? incoming : (0, node_crypto_1.randomUUID)();
262
+ res.locals.neutrunRequestId = requestId;
263
+ res.setHeader('X-Request-Id', requestId);
264
+ Observability_1.default.run({ correlationId: requestId, requestId, source: 'express-api' }, () => next());
265
+ };
266
+ }
267
+ securityHeaders() {
268
+ return (_req, res, next) => {
269
+ res.setHeader('X-Content-Type-Options', 'nosniff');
270
+ res.setHeader('X-Frame-Options', 'DENY');
271
+ res.setHeader('Referrer-Policy', 'no-referrer');
272
+ res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
273
+ res.setHeader('Content-Security-Policy', "default-src 'none'; frame-ancestors 'none'; base-uri 'none'");
274
+ res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), payment=()');
275
+ res.setHeader('Cache-Control', 'no-store');
276
+ next();
277
+ };
278
+ }
279
+ corsMiddleware() {
280
+ return (req, res, next) => {
281
+ const cors = this.settings.security.cors;
282
+ if (cors === false || cors === undefined)
283
+ return next();
284
+ const origin = req.headers.origin;
285
+ const allowed = cors === '*'
286
+ ? '*'
287
+ : origin && cors.includes(origin)
288
+ ? origin
289
+ : undefined;
290
+ if (allowed) {
291
+ res.setHeader('Access-Control-Allow-Origin', allowed);
292
+ appendVaryHeader(res, 'Origin');
293
+ res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type, X-API-Key, X-Request-Id');
294
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
295
+ }
296
+ if (req.method === 'OPTIONS')
297
+ return res.sendStatus(allowed ? 204 : 403);
298
+ next();
299
+ };
300
+ }
301
+ requestTrackingMiddleware() {
302
+ return (req, res, next) => {
303
+ const startedAt = performance.now();
304
+ this.lastActivityAt = Date.now();
305
+ this.activeRequests += 1;
306
+ let completed = false;
307
+ const complete = () => {
308
+ if (completed)
309
+ return;
310
+ completed = true;
311
+ this.activeRequests = Math.max(0, this.activeRequests - 1);
312
+ this.lastActivityAt = Date.now();
313
+ this.scheduleIdleCheck();
314
+ this.recordRequest(req, res, res.statusCode, performance.now() - startedAt, !res.writableEnded);
315
+ };
316
+ res.once('finish', complete);
317
+ res.once('close', complete);
318
+ next();
319
+ };
320
+ }
321
+ requestShapeLimitsMiddleware() {
322
+ return (req, res, next) => {
323
+ const maximum = clampInteger(this.settings.security.maxQueryParameters, 1, 1_000, DEFAULT_MAX_QUERY_PARAMETERS);
324
+ if (countObjectKeys(req.query, maximum + 1) > maximum) {
325
+ this.emitAudit(req, res, {
326
+ type: 'validation',
327
+ outcome: 'denied',
328
+ status: 400,
329
+ reason: 'query_limit'
330
+ });
331
+ return res.status(400).json({ success: false, error: 'Too many query parameters' });
332
+ }
333
+ next();
334
+ };
335
+ }
336
+ jsonComplexityMiddleware() {
337
+ return (req, res, next) => {
338
+ if (req.body === undefined || req.body === null || typeof req.body !== 'object')
339
+ return next();
340
+ const maxDepth = clampInteger(this.settings.security.maxJsonDepth, 1, 100, DEFAULT_MAX_JSON_DEPTH);
341
+ const maxKeys = clampInteger(this.settings.security.maxJsonKeys, 1, 100_000, DEFAULT_MAX_JSON_KEYS);
342
+ const complexity = inspectObjectComplexity(req.body, maxDepth, maxKeys);
343
+ if (complexity.valid)
344
+ return next();
345
+ this.emitAudit(req, res, {
346
+ type: 'validation',
347
+ outcome: 'denied',
348
+ status: 400,
349
+ reason: complexity.reason
350
+ });
351
+ return res.status(400).json({
352
+ success: false,
353
+ error: 'Request body is too complex'
354
+ });
355
+ };
356
+ }
357
+ requestTimeoutMiddleware() {
358
+ return (_req, res, next) => {
359
+ const timeoutMs = this.settings.security.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
360
+ res.setTimeout(timeoutMs, () => {
361
+ if (!res.headersSent) {
362
+ res.status(504).json({ success: false, error: 'Gateway Timeout' });
363
+ }
364
+ else {
365
+ res.destroy();
366
+ }
367
+ });
368
+ next();
369
+ };
370
+ }
371
+ wakeResourcesMiddleware() {
372
+ return (_req, _res, next) => {
373
+ void this.wakeResources().then(() => next()).catch(next);
374
+ };
375
+ }
376
+ rateLimitMiddleware() {
377
+ return (req, res, next) => {
378
+ const options = this.settings.security.rateLimit;
379
+ if (options === false || options === undefined)
380
+ return next();
381
+ const now = Date.now();
382
+ const key = req.ip || req.socket.remoteAddress || 'unknown';
383
+ const windowMs = Math.max(1_000, options.windowMs ?? DEFAULT_RATE_LIMIT_WINDOW_MS);
384
+ const max = Math.max(1, options.max ?? DEFAULT_RATE_LIMIT_MAX);
385
+ this.pruneRateLimits(now, windowMs);
386
+ const current = this.rateLimitEntries.get(key);
387
+ if (!current || now - current.startedAt >= windowMs) {
388
+ this.rateLimitEntries.set(key, { startedAt: now, count: 1 });
389
+ setRateLimitHeaders(res, max, max - 1, windowMs);
390
+ return next();
391
+ }
392
+ current.count = Math.min(Number.MAX_SAFE_INTEGER, current.count + 1);
393
+ const remaining = Math.max(0, max - current.count);
394
+ const resetMs = Math.max(0, windowMs - (now - current.startedAt));
395
+ setRateLimitHeaders(res, max, remaining, resetMs);
396
+ if (current.count > max) {
397
+ res.setHeader('Retry-After', Math.ceil(resetMs / 1_000));
398
+ Observability_1.default.increment('neutrun_api_rate_limit_total');
399
+ this.emitAudit(req, res, {
400
+ type: 'rate-limit',
401
+ outcome: 'denied',
402
+ status: 429
403
+ });
404
+ return res.status(429).json({
405
+ success: false,
406
+ error: 'Too Many Requests'
407
+ });
408
+ }
409
+ next();
410
+ };
411
+ }
412
+ authenticationMiddleware() {
413
+ return (req, res, next) => {
414
+ const authentication = this.settings.security.authentication;
415
+ if (authentication === 'none') {
416
+ res.locals.neutrunIdentity = anonymousIdentity();
417
+ return next();
418
+ }
419
+ if (isPublicPath(req.path, this.settings.security.publicPaths)) {
420
+ res.locals.neutrunIdentity = anonymousIdentity();
421
+ return next();
422
+ }
423
+ const received = authentication === 'api-key'
424
+ ? req.get('x-api-key')
425
+ : extractBearerToken(req.get('authorization'));
426
+ const credential = received
427
+ ? this.resolveCredentials().find((entry) => timingSafeEqual(received, entry.token))
428
+ : undefined;
429
+ if (!credential) {
430
+ Observability_1.default.increment('neutrun_api_authentication_total', 1, { outcome: 'denied' });
431
+ this.emitAudit(req, res, {
432
+ type: 'authentication',
433
+ outcome: 'denied',
434
+ status: 401,
435
+ reason: received ? 'invalid_credential' : 'missing_credential'
436
+ });
437
+ return res.status(401).json({
438
+ success: false,
439
+ error: 'Unauthorized'
440
+ });
441
+ }
442
+ const identity = {
443
+ authentication,
444
+ credentialId: credential.id,
445
+ roles: credential.roles,
446
+ scopes: credential.scopes
447
+ };
448
+ res.locals.neutrunIdentity = identity;
449
+ Observability_1.default.increment('neutrun_api_authentication_total', 1, { outcome: 'allowed' });
450
+ this.emitAudit(req, res, {
451
+ type: 'authentication',
452
+ outcome: 'allowed',
453
+ credentialId: credential.id
454
+ });
455
+ next();
456
+ };
457
+ }
458
+ protectionMiddleware(options = {}) {
459
+ return (req, res, next) => {
460
+ const run = async () => {
461
+ const identity = res.locals.neutrunIdentity;
462
+ const roles = new Set(identity?.roles ?? []);
463
+ const scopes = new Set(identity?.scopes ?? []);
464
+ const hasRoles = (options.roles ?? []).every((role) => roles.has(role));
465
+ const hasScopes = (options.scopes ?? []).every((scope) => scopes.has(scope));
466
+ const policyEligible = Boolean(identity && identity.authentication !== 'none' && hasRoles && hasScopes);
467
+ const customAllowed = policyEligible && options.authorize
468
+ ? await options.authorize(req, identity)
469
+ : policyEligible;
470
+ if (policyEligible && identity && customAllowed) {
471
+ this.emitAudit(req, res, {
472
+ type: 'authorization',
473
+ outcome: 'allowed',
474
+ credentialId: identity.credentialId
475
+ });
476
+ next();
477
+ return;
478
+ }
479
+ Observability_1.default.increment('neutrun_api_authorization_total', 1, { outcome: 'denied' });
480
+ this.emitAudit(req, res, {
481
+ type: 'authorization',
482
+ outcome: 'denied',
483
+ status: identity?.authentication === 'none' ? 401 : 403,
484
+ credentialId: identity?.credentialId,
485
+ reason: 'policy_denied'
486
+ });
487
+ res.status(identity?.authentication === 'none' ? 401 : 403).json({
488
+ success: false,
489
+ error: identity?.authentication === 'none' ? 'Unauthorized' : 'Forbidden'
490
+ });
491
+ };
492
+ void run().catch(next);
493
+ };
494
+ }
495
+ validationMiddleware(options) {
496
+ return (req, res, next) => {
497
+ const run = async () => {
498
+ const checks = [
499
+ ['body', options.body, req.body],
500
+ ['query', options.query, req.query],
501
+ ['params', options.params, req.params]
502
+ ];
503
+ for (const [part, validator, value] of checks) {
504
+ if (!validator)
505
+ continue;
506
+ const result = await validator(value, req);
507
+ if (result === false || typeof result === 'string') {
508
+ const reason = typeof result === 'string' ? result.slice(0, 200) : `Invalid ${part}`;
509
+ Observability_1.default.increment('neutrun_api_validation_total', 1, { outcome: 'denied', part });
510
+ this.emitAudit(req, res, {
511
+ type: 'validation',
512
+ outcome: 'denied',
513
+ status: 400,
514
+ reason: part
515
+ });
516
+ res.status(400).json({ success: false, error: reason });
517
+ return;
518
+ }
519
+ }
520
+ next();
521
+ };
522
+ void run().catch(next);
523
+ };
524
+ }
525
+ resolveCredentials() {
526
+ return resolveCredentials(this.settings.security);
527
+ }
528
+ scheduleIdleCheck() {
529
+ this.clearIdleTimer();
530
+ const lifecycle = this.settings.lifecycle;
531
+ if (!this.server || lifecycle.mode === 'always')
532
+ return;
533
+ const idleTimeoutMs = lifecycle.idleTimeoutMs;
534
+ this.idleTimer = setTimeout(() => {
535
+ void this.handleIdle().catch((error) => {
536
+ (0, ConsoleLog_1.logWarn)(`Falha no ciclo de ociosidade da mini API: ${error instanceof Error ? error.message : error}`, 'Api');
537
+ this.lastActivityAt = Date.now();
538
+ this.scheduleIdleCheck();
539
+ });
540
+ }, Math.max(1_000, idleTimeoutMs - (Date.now() - this.lastActivityAt)));
541
+ this.idleTimer.unref?.();
542
+ }
543
+ async handleIdle() {
544
+ if (!this.server || this.activeRequests > 0) {
545
+ this.scheduleIdleCheck();
546
+ return;
547
+ }
548
+ if (Date.now() - this.lastActivityAt < this.settings.lifecycle.idleTimeoutMs) {
549
+ this.scheduleIdleCheck();
550
+ return;
551
+ }
552
+ await this.suspendResources();
553
+ if (this.settings.lifecycle.mode === 'idle') {
554
+ await this.stop();
555
+ }
556
+ }
557
+ async suspendResources() {
558
+ if (this.resourceTransition)
559
+ await this.resourceTransition;
560
+ if (this.resourcesSuspended)
561
+ return;
562
+ const transition = (async () => {
563
+ await this.settings.lifecycle.onIdle?.();
564
+ this.resourcesSuspended = true;
565
+ TelemetryService_1.Telemetry.registerEvent('api:idle');
566
+ })();
567
+ this.resourceTransition = transition;
568
+ try {
569
+ await transition;
570
+ }
571
+ finally {
572
+ if (this.resourceTransition === transition)
573
+ this.resourceTransition = null;
574
+ }
575
+ }
576
+ async wakeResources() {
577
+ if (this.resourceTransition)
578
+ await this.resourceTransition;
579
+ if (!this.resourcesSuspended)
580
+ return;
581
+ const transition = (async () => {
582
+ await this.settings.lifecycle.onWake?.();
583
+ this.resourcesSuspended = false;
584
+ TelemetryService_1.Telemetry.registerEvent('api:wake');
585
+ })();
586
+ this.resourceTransition = transition;
587
+ try {
588
+ await transition;
589
+ }
590
+ finally {
591
+ if (this.resourceTransition === transition)
592
+ this.resourceTransition = null;
593
+ }
594
+ }
595
+ registerCleanup() {
596
+ if (this.cleanupRegistered)
597
+ return;
598
+ this.cleanupRegistered = true;
599
+ ProcessManager_1.default.registerCleanupTask(() => this.stop(), 'mini API Express');
600
+ }
601
+ recordRequest(req, res, status, durationMs, error = false) {
602
+ const path = normalizeTelemetryPath(req.route?.path ?? req.path ?? '/');
603
+ TelemetryService_1.Telemetry.registerCounter('apiRequests');
604
+ TelemetryService_1.Telemetry.registerEvent(`api:${req.method}:${path}`.slice(0, 120), {
605
+ error: error || status >= 400,
606
+ durationMs
607
+ });
608
+ Observability_1.default.increment('neutrun_api_requests_total', 1, {
609
+ method: req.method,
610
+ outcome: status >= 400 || error ? 'error' : 'success',
611
+ status: `${Math.floor(status / 100)}xx`
612
+ });
613
+ Observability_1.default.observe('neutrun_api_request_duration_ms', durationMs, {
614
+ method: req.method
615
+ });
616
+ this.emitAudit(req, res, {
617
+ type: 'request',
618
+ outcome: status >= 400 || error ? 'error' : 'success',
619
+ status,
620
+ durationMs
621
+ });
622
+ }
623
+ emitAudit(req, res, event) {
624
+ const audit = this.settings.security.audit;
625
+ if (!audit)
626
+ return;
627
+ const payload = {
628
+ ...event,
629
+ at: new Date().toISOString(),
630
+ requestId: String(res.locals.neutrunRequestId || (0, node_crypto_1.randomUUID)()),
631
+ method: req.method.slice(0, 16),
632
+ path: normalizeTelemetryPath(req.route?.path ?? req.path ?? '/')
633
+ };
634
+ void Promise.resolve(audit(payload)).catch((error) => {
635
+ (0, ConsoleLog_1.logWarn)(`Falha no destino de auditoria da mini API: ${error instanceof Error ? error.message : error}`, 'Api');
636
+ });
637
+ }
638
+ resolveUrl(path) {
639
+ const base = this.url;
640
+ if (!base)
641
+ throw new Error('A mini API ainda não está ativa.');
642
+ const resolved = /^https?:\/\//i.test(path)
643
+ ? new URL(path)
644
+ : new URL(path.replace(/^\/+/, ''), `${base}/`);
645
+ if (resolved.origin !== new URL(base).origin) {
646
+ throw new Error('Api.fetch aceita apenas URLs da própria mini API.');
647
+ }
648
+ return resolved.toString();
649
+ }
650
+ publicUrl(port) {
651
+ const host = this.settings.host === '0.0.0.0' || this.settings.host === '::'
652
+ ? '127.0.0.1'
653
+ : this.settings.host;
654
+ return `http://${formatHost(host)}:${port}`;
655
+ }
656
+ get url() {
657
+ if (!this.server)
658
+ return null;
659
+ const address = this.server.address();
660
+ const port = typeof address === 'object' && address ? address.port : this.settings.port;
661
+ return this.publicUrl(port);
662
+ }
663
+ clearIdleTimer() {
664
+ if (!this.idleTimer)
665
+ return;
666
+ clearTimeout(this.idleTimer);
667
+ this.idleTimer = null;
668
+ }
669
+ pruneRateLimits(now, windowMs) {
670
+ if (this.rateLimitEntries.size < MAX_RATE_LIMIT_ENTRIES &&
671
+ now - this.rateLimitLastSweepAt < windowMs)
672
+ return;
673
+ this.rateLimitLastSweepAt = now;
674
+ for (const [key, entry] of this.rateLimitEntries) {
675
+ if (now - entry.startedAt >= windowMs)
676
+ this.rateLimitEntries.delete(key);
677
+ }
678
+ while (this.rateLimitEntries.size >= MAX_RATE_LIMIT_ENTRIES) {
679
+ const oldest = this.rateLimitEntries.keys().next().value;
680
+ if (!oldest)
681
+ break;
682
+ this.rateLimitEntries.delete(oldest);
683
+ }
684
+ }
685
+ }
686
+ const service = new ExpressApiService();
687
+ exports.Api = service.app;
688
+ function normalizeSettings(options) {
689
+ const api = options === false ? {} : options ?? {};
690
+ const host = api.host?.trim() || process.env.NEUTRUN_API_HOST?.trim() || DEFAULT_HOST;
691
+ const serverless = api.serverless;
692
+ const lifecycle = api.lifecycle ?? {};
693
+ const serverlessOptions = typeof serverless === 'object' ? serverless : {};
694
+ const serverlessEnabled = serverless === true || serverlessOptions.enabled === true;
695
+ const lifecycleMode = serverlessEnabled
696
+ ? lifecycle.mode ?? 'idle'
697
+ : lifecycle.mode ?? 'always';
698
+ const idleTimeoutMs = parseDuration(lifecycle.idleTimeoutMs ?? serverlessOptions.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS);
699
+ return {
700
+ enabled: api.enabled ?? (options !== undefined && options !== false),
701
+ host,
702
+ port: normalizePort(api.port ?? process.env.NEUTRUN_API_PORT),
703
+ lifecycle: {
704
+ mode: lifecycleMode,
705
+ idleTimeoutMs,
706
+ onIdle: lifecycle.onIdle ?? serverlessOptions.onIdle,
707
+ onWake: lifecycle.onWake ?? serverlessOptions.onWake
708
+ },
709
+ security: {
710
+ authentication: api.security?.authentication ?? (resolveCredentials(api.security).length > 0 || isPublicHost(host) ? 'bearer' : 'none'),
711
+ token: api.security?.token,
712
+ tokenEnv: api.security?.tokenEnv ?? 'NEUTRUN_API_TOKEN',
713
+ tokens: api.security?.tokens,
714
+ tokensEnv: api.security?.tokensEnv ?? 'NEUTRUN_API_TOKENS',
715
+ credentials: api.security?.credentials,
716
+ allowInsecurePublic: api.security?.allowInsecurePublic,
717
+ publicPaths: api.security?.publicPaths ?? [],
718
+ cors: api.security?.cors,
719
+ bodyLimit: api.security?.bodyLimit,
720
+ rateLimit: api.security?.rateLimit,
721
+ requestTimeoutMs: api.security?.requestTimeoutMs,
722
+ trustProxy: api.security?.trustProxy,
723
+ maxHeaderBytes: api.security?.maxHeaderBytes,
724
+ maxQueryParameters: api.security?.maxQueryParameters,
725
+ maxJsonDepth: api.security?.maxJsonDepth,
726
+ maxJsonKeys: api.security?.maxJsonKeys,
727
+ audit: api.security?.audit
728
+ }
729
+ };
730
+ }
731
+ function validateSettings(settings) {
732
+ const credentials = resolveCredentials(settings.security);
733
+ if (settings.security.authentication !== 'none' && credentials.length === 0) {
734
+ throw new Error('A mini API autenticada precisa de security.token ou NEUTRUN_API_TOKEN configurado.');
735
+ }
736
+ if (settings.security.authentication === 'none' &&
737
+ isPublicHost(settings.host) &&
738
+ settings.security.allowInsecurePublic !== true) {
739
+ throw new Error('A mini API pública não pode desativar autenticação sem security.allowInsecurePublic=true.');
740
+ }
741
+ if (settings.security.cors === '*' &&
742
+ settings.security.authentication !== 'none') {
743
+ throw new Error('A mini API autenticada exige uma lista explícita de origens CORS.');
744
+ }
745
+ if (isPublicHost(settings.host) &&
746
+ credentials.some((credential) => Buffer.byteLength(credential.token, 'utf8') < 16)) {
747
+ throw new Error('Credenciais da mini API precisam ter pelo menos 16 bytes.');
748
+ }
749
+ }
750
+ function isPublicHost(host) {
751
+ return !['127.0.0.1', 'localhost', '::1'].includes(host);
752
+ }
753
+ function resolveCredentials(security) {
754
+ const values = [];
755
+ for (const credential of security?.credentials ?? [])
756
+ values.push(credential);
757
+ for (const token of security?.tokens ?? [])
758
+ values.push({ token });
759
+ const tokensEnvironment = process.env[security?.tokensEnv ?? 'NEUTRUN_API_TOKENS'];
760
+ for (const token of tokensEnvironment?.split(/[\r\n,]+/) ?? [])
761
+ values.push({ token });
762
+ const directToken = security?.token?.trim();
763
+ if (directToken)
764
+ values.push({ token: directToken });
765
+ const environmentToken = process.env[security?.tokenEnv ?? 'NEUTRUN_API_TOKEN']?.trim();
766
+ if (environmentToken)
767
+ values.push({ token: environmentToken });
768
+ const seen = new Set();
769
+ const output = [];
770
+ for (const value of values) {
771
+ const token = value.token?.trim();
772
+ if (!token || seen.has(token))
773
+ continue;
774
+ seen.add(token);
775
+ output.push({
776
+ token,
777
+ id: sanitizeCredentialId(value.id) ?? createCredentialId(token),
778
+ roles: sanitizePolicyValues(value.roles),
779
+ scopes: sanitizePolicyValues(value.scopes)
780
+ });
781
+ }
782
+ return output;
783
+ }
784
+ function anonymousIdentity() {
785
+ return { authentication: 'none', roles: [], scopes: [] };
786
+ }
787
+ function sanitizeCredentialId(value) {
788
+ const normalized = value?.trim();
789
+ if (!normalized)
790
+ return undefined;
791
+ return normalized.replace(/[^a-zA-Z0-9._:-]+/g, '_').slice(0, 100);
792
+ }
793
+ function createCredentialId(token) {
794
+ return `key_${(0, node_crypto_1.createHash)('sha256').update(token).digest('hex').slice(0, 12)}`;
795
+ }
796
+ function sanitizePolicyValues(values) {
797
+ return Array.from(new Set((values ?? [])
798
+ .map((value) => String(value).trim().slice(0, 100))
799
+ .filter(Boolean)))
800
+ .slice(0, 100);
801
+ }
802
+ function appendVaryHeader(response, value) {
803
+ const current = response.getHeader('Vary');
804
+ const entries = String(current ?? '')
805
+ .split(',')
806
+ .map((entry) => entry.trim())
807
+ .filter(Boolean);
808
+ if (!entries.some((entry) => entry.toLowerCase() === value.toLowerCase())) {
809
+ entries.push(value);
810
+ }
811
+ response.setHeader('Vary', entries.join(', '));
812
+ }
813
+ function setRateLimitHeaders(response, limit, remaining, resetMs) {
814
+ response.setHeader('RateLimit-Limit', limit);
815
+ response.setHeader('RateLimit-Remaining', remaining);
816
+ response.setHeader('RateLimit-Reset', Math.ceil(resetMs / 1_000));
817
+ }
818
+ function countObjectKeys(value, stopAfter) {
819
+ if (!value || typeof value !== 'object')
820
+ return 0;
821
+ let count = 0;
822
+ const stack = [value];
823
+ const seen = new WeakSet();
824
+ while (stack.length > 0 && count < stopAfter) {
825
+ const current = stack.pop();
826
+ if (seen.has(current))
827
+ continue;
828
+ seen.add(current);
829
+ for (const entry of Object.values(current)) {
830
+ count += 1;
831
+ if (entry && typeof entry === 'object')
832
+ stack.push(entry);
833
+ if (count >= stopAfter)
834
+ break;
835
+ }
836
+ }
837
+ return count;
838
+ }
839
+ function inspectObjectComplexity(value, maxDepth, maxKeys) {
840
+ const stack = [{ value, depth: 1 }];
841
+ const seen = new WeakSet();
842
+ let keys = 0;
843
+ while (stack.length > 0) {
844
+ const current = stack.pop();
845
+ if (seen.has(current.value))
846
+ continue;
847
+ seen.add(current.value);
848
+ if (current.depth > maxDepth)
849
+ return { valid: false, reason: 'json_depth' };
850
+ for (const entry of Object.values(current.value)) {
851
+ keys += 1;
852
+ if (keys > maxKeys)
853
+ return { valid: false, reason: 'json_keys' };
854
+ if (entry && typeof entry === 'object') {
855
+ stack.push({ value: entry, depth: current.depth + 1 });
856
+ }
857
+ }
858
+ }
859
+ return { valid: true };
860
+ }
861
+ function normalizeTelemetryPath(value) {
862
+ const raw = String(value || '/').split('?')[0] || '/';
863
+ return raw
864
+ .replace(/\b\d{6,}\b/g, ':id')
865
+ .replace(/\b[0-9a-f]{8}-[0-9a-f-]{27,}\b/gi, ':id')
866
+ .slice(0, 120);
867
+ }
868
+ function clampInteger(value, minimum, maximum, fallback) {
869
+ const parsed = Number(value);
870
+ if (!Number.isFinite(parsed))
871
+ return fallback;
872
+ return Math.max(minimum, Math.min(maximum, Math.floor(parsed)));
873
+ }
874
+ function normalizePort(value) {
875
+ const parsed = Number(value ?? DEFAULT_PORT);
876
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65_535) {
877
+ throw new TypeError('A porta da mini API deve ser um inteiro entre 0 e 65535.');
878
+ }
879
+ return parsed;
880
+ }
881
+ function parseDuration(value) {
882
+ if (typeof value === 'number')
883
+ return Math.max(1_000, value);
884
+ const normalized = value.trim().toLowerCase();
885
+ const match = normalized.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h)?$/);
886
+ if (!match)
887
+ throw new TypeError(`Duração inválida para a mini API: ${value}`);
888
+ const amount = Number(match[1]);
889
+ const multiplier = match[2] === 'h'
890
+ ? 60 * 60 * 1_000
891
+ : match[2] === 'm'
892
+ ? 60 * 1_000
893
+ : match[2] === 's'
894
+ ? 1_000
895
+ : 1;
896
+ return Math.max(1_000, Math.round(amount * multiplier));
897
+ }
898
+ function isPublicPath(path, paths) {
899
+ return (paths ?? []).some((entry) => {
900
+ const normalized = entry.trim();
901
+ if (!normalized)
902
+ return false;
903
+ if (normalized.endsWith('*'))
904
+ return path.startsWith(normalized.slice(0, -1));
905
+ return path === normalized;
906
+ });
907
+ }
908
+ function extractBearerToken(header) {
909
+ const match = header?.match(/^Bearer\s+(.+)$/i);
910
+ return match?.[1]?.trim() || '';
911
+ }
912
+ function timingSafeEqual(left, right) {
913
+ const leftBuffer = Buffer.from(left);
914
+ const rightBuffer = Buffer.from(right);
915
+ if (leftBuffer.length !== rightBuffer.length)
916
+ return false;
917
+ return (0, node_crypto_1.timingSafeEqual)(leftBuffer, rightBuffer);
918
+ }
919
+ function serializeBody(body, headers) {
920
+ if (body === undefined)
921
+ return undefined;
922
+ if (typeof body === 'string')
923
+ return body;
924
+ if (body instanceof URLSearchParams || body instanceof FormData || body instanceof Blob) {
925
+ return body;
926
+ }
927
+ if (body instanceof ArrayBuffer || ArrayBuffer.isView(body))
928
+ return body;
929
+ if (!headers.has('Content-Type'))
930
+ headers.set('Content-Type', 'application/json');
931
+ return JSON.stringify(body);
932
+ }
933
+ function addQuery(url, query) {
934
+ if (!query)
935
+ return url;
936
+ const parsed = new URL(url);
937
+ for (const [key, value] of Object.entries(query)) {
938
+ if (value !== undefined && value !== null)
939
+ parsed.searchParams.set(key, String(value));
940
+ }
941
+ return parsed.toString();
942
+ }
943
+ async function parseResponse(response) {
944
+ const text = await readResponseText(response, MAX_INTERNAL_RESPONSE_BYTES);
945
+ if (!text)
946
+ return undefined;
947
+ try {
948
+ return JSON.parse(text);
949
+ }
950
+ catch {
951
+ return text;
952
+ }
953
+ }
954
+ async function readResponseText(response, maxBytes) {
955
+ const contentLength = Number(response.headers.get('content-length'));
956
+ if (Number.isFinite(contentLength) && contentLength > maxBytes) {
957
+ throw new RangeError(`Resposta da mini API excedeu ${maxBytes} bytes.`);
958
+ }
959
+ if (!response.body)
960
+ return '';
961
+ const reader = response.body.getReader();
962
+ const chunks = [];
963
+ let total = 0;
964
+ try {
965
+ while (true) {
966
+ const { done, value } = await reader.read();
967
+ if (done)
968
+ break;
969
+ total += value.byteLength;
970
+ if (total > maxBytes) {
971
+ await reader.cancel();
972
+ throw new RangeError(`Resposta da mini API excedeu ${maxBytes} bytes.`);
973
+ }
974
+ chunks.push(value);
975
+ }
976
+ }
977
+ finally {
978
+ reader.releaseLock();
979
+ }
980
+ return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))).toString('utf8');
981
+ }
982
+ async function closeHttpServer(server) {
983
+ await new Promise((resolve) => {
984
+ let finished = false;
985
+ const finish = () => {
986
+ if (finished)
987
+ return;
988
+ finished = true;
989
+ clearTimeout(timeout);
990
+ resolve();
991
+ };
992
+ const timeout = setTimeout(() => {
993
+ server.closeAllConnections?.();
994
+ (0, ConsoleLog_1.logWarn)(`Encerramento da mini API excedeu ${SERVER_CLOSE_TIMEOUT_MS}ms; conexões restantes foram finalizadas.`, 'Api');
995
+ finish();
996
+ }, SERVER_CLOSE_TIMEOUT_MS);
997
+ server.close(() => finish());
998
+ server.closeIdleConnections?.();
999
+ });
1000
+ }
1001
+ function isHttpStatus(error) {
1002
+ return typeof error === 'object' && error !== null &&
1003
+ 'status' in error && typeof error.status === 'number' &&
1004
+ error.status >= 400 && error.status <= 599;
1005
+ }
1006
+ function formatHost(host) {
1007
+ return host.includes(':') && !host.startsWith('[') ? `[${host}]` : host;
1008
+ }
1009
+ //# sourceMappingURL=ExpressApi.js.map