@angelitosystems/nest-devtools 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,722 @@
1
+ // src/sdk.ts
2
+ import { hostname } from "os";
3
+ import { devtools as coreDevtools2, resolveConfig } from "@angelitosystems/devtools-core";
4
+
5
+ // src/instrumentation/http.ts
6
+ import { Redactor, randomId } from "@angelitosystems/devtools-protocol";
7
+ import { requestContext } from "@angelitosystems/devtools-core";
8
+
9
+ // src/emitter.ts
10
+ import { devtools as coreDevtools } from "@angelitosystems/devtools-core";
11
+ function emit(event, payload) {
12
+ try {
13
+ coreDevtools.send(event, payload);
14
+ } catch {
15
+ }
16
+ }
17
+
18
+ // src/instrumentation/timeline.ts
19
+ var recorders = /* @__PURE__ */ new Map();
20
+ function trackSpans(requestId, spans) {
21
+ recorders.set(requestId, spans);
22
+ return () => recorders.delete(requestId);
23
+ }
24
+
25
+ // src/instrumentation/http.ts
26
+ var INTERESTING_HEADERS = [
27
+ "content-type",
28
+ "content-length",
29
+ "accept",
30
+ "origin",
31
+ "referer",
32
+ "user-agent",
33
+ "x-forwarded-for",
34
+ "x-request-id"
35
+ ];
36
+ var WRAPPED = /* @__PURE__ */ Symbol("nest-devtools.wrapped");
37
+ var HttpInstrumentation = class {
38
+ constructor(ctx) {
39
+ this.ctx = ctx;
40
+ this.redactor = new Redactor({
41
+ redact: ctx.config.redact,
42
+ allow: ctx.config.allow,
43
+ maxBytes: ctx.config.maxPayloadBytes
44
+ });
45
+ }
46
+ ctx;
47
+ redactor;
48
+ /** Begin capturing HTTP requests. Returns a cleanup function. */
49
+ attach(app) {
50
+ try {
51
+ const adapter = app.getHttpAdapter();
52
+ if (!adapter || !["http", "express"].includes(adapter.getType())) return () => {
53
+ };
54
+ const getServer = () => adapter.getHttpServer?.();
55
+ const existing = getServer();
56
+ if (existing) {
57
+ this.wrapServer(existing);
58
+ return () => {
59
+ };
60
+ }
61
+ const appLike = app;
62
+ const originalListen = appLike.listen.bind(app);
63
+ const self = this;
64
+ const patchedListen = (...args) => {
65
+ const result = originalListen(...args);
66
+ Promise.resolve(result).then(() => {
67
+ const server = getServer();
68
+ if (server) self.wrapServer(server);
69
+ }).catch(() => {
70
+ });
71
+ return result;
72
+ };
73
+ appLike.listen = patchedListen;
74
+ return () => {
75
+ appLike.listen = originalListen;
76
+ };
77
+ } catch {
78
+ return () => {
79
+ };
80
+ }
81
+ }
82
+ /** Wrap the 'request' listeners of a server exactly once. */
83
+ wrapServer(server) {
84
+ const target = server;
85
+ if (target[WRAPPED]) return;
86
+ target[WRAPPED] = true;
87
+ const self = this;
88
+ const originalListeners = server.rawListeners("request");
89
+ server.removeAllListeners("request");
90
+ server.on("request", (req, res) => {
91
+ self.instrumentRequest(req, res, () => {
92
+ for (const listener of originalListeners) {
93
+ listener.call(server, req, res);
94
+ }
95
+ });
96
+ });
97
+ }
98
+ /** Wrap a single request lifecycle. */
99
+ instrumentRequest(req, res, next) {
100
+ if (!this.ctx.config.capture.requests) {
101
+ next();
102
+ return;
103
+ }
104
+ const startedAt = Date.now();
105
+ const requestId = randomId("req");
106
+ const method = (req.method ?? "GET").toUpperCase();
107
+ const rawUrl = req.url ?? "/";
108
+ const url = safeUrlPath(rawUrl);
109
+ const spans = [];
110
+ const unregister = trackSpans(requestId, spans);
111
+ res.once("finish", unregister);
112
+ const started = {
113
+ requestId,
114
+ projectId: this.ctx.projectInfo.projectId,
115
+ method,
116
+ url,
117
+ headers: this.collectHeaders(req),
118
+ query: parseQuery(rawUrl),
119
+ ip: clientIp(req),
120
+ userAgent: asString(req.headers["user-agent"]) || void 0,
121
+ startedAt
122
+ };
123
+ const middlewareSpan = {
124
+ layer: "middleware",
125
+ label: `${method} ${url}`,
126
+ duration: 0,
127
+ startedAt,
128
+ status: "ok"
129
+ };
130
+ spans.push(middlewareSpan);
131
+ const onFinished = () => {
132
+ const finishedAt = Date.now();
133
+ middlewareSpan.duration = Math.max(0, finishedAt - middlewareSpan.startedAt);
134
+ middlewareSpan.status = res.statusCode >= 500 ? "error" : "ok";
135
+ const responseSpan = {
136
+ layer: "response",
137
+ label: "response",
138
+ duration: 0,
139
+ startedAt: finishedAt,
140
+ status: "ok"
141
+ };
142
+ spans.push(responseSpan);
143
+ const errored = res.statusCode >= 500;
144
+ const completed = {
145
+ requestId,
146
+ projectId: this.ctx.projectInfo.projectId,
147
+ method,
148
+ url,
149
+ statusCode: res.statusCode,
150
+ duration: finishedAt - startedAt,
151
+ startedAt,
152
+ timeline: spans,
153
+ query: parseQuery(rawUrl),
154
+ errored
155
+ };
156
+ emit("request.completed", completed);
157
+ if (errored) {
158
+ emit("error.created", {
159
+ requestId,
160
+ projectId: this.ctx.projectInfo.projectId,
161
+ name: "HttpError",
162
+ message: `${method} ${url} failed with status ${res.statusCode}`,
163
+ fingerprint: httpErrorFingerprint(method, url, res.statusCode),
164
+ request: { method, url, statusCode: res.statusCode },
165
+ timestamp: finishedAt
166
+ });
167
+ }
168
+ };
169
+ const context = requestContext();
170
+ context.run({ requestId, method, url, startedAt }, () => {
171
+ emit("request.started", started);
172
+ res.once("finish", onFinished);
173
+ next();
174
+ });
175
+ }
176
+ collectHeaders(req) {
177
+ const out = {};
178
+ for (const key of INTERESTING_HEADERS) {
179
+ const value = req.headers[key];
180
+ if (value === void 0) continue;
181
+ out[key] = this.redactor.isSensitive(key) ? "[REDACTED]" : asString(value).slice(0, 200);
182
+ }
183
+ return out;
184
+ }
185
+ };
186
+ function asString(value) {
187
+ if (Array.isArray(value)) return value.join(", ");
188
+ return value ?? "";
189
+ }
190
+ function httpErrorFingerprint(method, url, statusCode) {
191
+ const route = url.replace(/\/\d+(?=\/|$)/g, "/:id");
192
+ const raw = `${method}::${route}::${statusCode}`;
193
+ let hash = 0;
194
+ for (let i = 0; i < raw.length; i++) {
195
+ hash = hash * 31 + raw.charCodeAt(i) >>> 0;
196
+ }
197
+ return hash.toString(36).padStart(7, "0");
198
+ }
199
+ function safeUrlPath(raw) {
200
+ const index = raw.indexOf("?");
201
+ return index === -1 ? raw : raw.slice(0, index);
202
+ }
203
+ function parseQuery(raw) {
204
+ const index = raw.indexOf("?");
205
+ if (index === -1) return {};
206
+ const out = {};
207
+ for (const [key, value] of new URLSearchParams(raw.slice(index + 1))) {
208
+ out[key] = value;
209
+ }
210
+ return out;
211
+ }
212
+ function clientIp(req) {
213
+ const forwarded = req.headers["x-forwarded-for"];
214
+ if (typeof forwarded === "string") return forwarded.split(",")[0]?.trim();
215
+ return req.socket?.remoteAddress ?? void 0;
216
+ }
217
+
218
+ // src/instrumentation/console.ts
219
+ import { Console } from "console";
220
+ import { Redactor as Redactor2 } from "@angelitosystems/devtools-protocol";
221
+ import { resolveSourceLocation } from "@angelitosystems/devtools-core";
222
+ import { requestContext as requestContext2 } from "@angelitosystems/devtools-core";
223
+ var METHODS = ["log", "info", "warn", "error", "debug"];
224
+ var LEVEL_BY_METHOD = {
225
+ log: "info",
226
+ info: "info",
227
+ warn: "warn",
228
+ error: "error",
229
+ debug: "debug"
230
+ };
231
+ var originalConsole = new Console({
232
+ stdout: process.stdout,
233
+ stderr: process.stderr,
234
+ colorMode: "auto"
235
+ });
236
+ var ConsoleInstrumentation = class {
237
+ constructor(ctx) {
238
+ this.ctx = ctx;
239
+ this.redactor = new Redactor2({
240
+ redact: ctx.config.redact,
241
+ allow: ctx.config.allow,
242
+ maxBytes: ctx.config.maxPayloadBytes
243
+ });
244
+ }
245
+ ctx;
246
+ redactor;
247
+ /** Patch console methods. Returns a restore function. */
248
+ attach() {
249
+ const self = this;
250
+ const restores = [];
251
+ for (const method of METHODS) {
252
+ const original = console[method];
253
+ if (typeof original !== "function") continue;
254
+ const patched = (...args) => {
255
+ try {
256
+ original.apply(console, args);
257
+ } catch {
258
+ }
259
+ self.capture(method, args);
260
+ };
261
+ console[method] = patched;
262
+ restores.push(() => {
263
+ console[method] = original;
264
+ });
265
+ }
266
+ return () => {
267
+ for (const restore of restores.splice(0)) restore();
268
+ };
269
+ }
270
+ capture(method, args) {
271
+ if (!this.ctx.config.capture.logs) return;
272
+ try {
273
+ const level = LEVEL_BY_METHOD[method];
274
+ const payload = {
275
+ projectId: this.ctx.projectInfo.projectId,
276
+ level,
277
+ message: formatMessage(args, this.redactor),
278
+ arguments: args.length > 0 ? this.redactor.redact(args) : void 0,
279
+ stack: method === "error" ? captureStack() : void 0,
280
+ source: resolveSourceLocation(captureStack() ?? "", { skip: 0 }),
281
+ context: void 0,
282
+ processId: process.pid,
283
+ requestId: requestContext2().requestId(),
284
+ timestamp: Date.now()
285
+ };
286
+ if (method === "error") payload.level = "error";
287
+ emit("log.created", payload);
288
+ } catch {
289
+ }
290
+ }
291
+ };
292
+ function formatMessage(args, redactor) {
293
+ if (args.length === 0) return "";
294
+ const first = args[0];
295
+ if (typeof first === "string") {
296
+ return args.length === 1 ? redactor.redactString(first) : `${first} ${redactor.serialize(args.slice(1))}`;
297
+ }
298
+ return redactor.serialize(args);
299
+ }
300
+ function captureStack() {
301
+ const err = new Error();
302
+ const stack = err.stack;
303
+ if (!stack) return void 0;
304
+ return stack.split("\n").slice(3).join("\n");
305
+ }
306
+
307
+ // src/instrumentation/nest-logger.ts
308
+ import { Logger } from "@nestjs/common";
309
+ import { Redactor as Redactor3 } from "@angelitosystems/devtools-protocol";
310
+ import { requestContext as requestContext3, resolveSourceLocation as resolveSourceLocation2 } from "@angelitosystems/devtools-core";
311
+ var activeRedactor = new Redactor3();
312
+ var activeConfig = null;
313
+ var activeProjectId = "";
314
+ function capture(level, message, context, stack) {
315
+ try {
316
+ if (!activeConfig?.capture.logs) return;
317
+ const text = typeof message === "string" ? message : activeRedactor.serialize(message);
318
+ const payload = {
319
+ projectId: activeProjectId,
320
+ level,
321
+ message: activeRedactor.redactString(text),
322
+ source: stack ? void 0 : resolveSdkAwareLocation(),
323
+ context,
324
+ stack,
325
+ processId: process.pid,
326
+ requestId: requestContext3().requestId(),
327
+ timestamp: Date.now()
328
+ };
329
+ emit("log.created", payload);
330
+ } catch {
331
+ }
332
+ }
333
+ var LoggerInstrumentation = class {
334
+ constructor(ctx) {
335
+ activeRedactor = new Redactor3({
336
+ redact: ctx.config.redact,
337
+ allow: ctx.config.allow,
338
+ maxBytes: ctx.config.maxPayloadBytes
339
+ });
340
+ activeConfig = ctx.config;
341
+ activeProjectId = ctx.projectInfo.projectId;
342
+ }
343
+ /** Patch Logger.prototype and the static Logger methods. Returns a restore function. */
344
+ attach() {
345
+ const restores = [];
346
+ const patch = (owner, method, level, instance) => {
347
+ const original = owner[method];
348
+ if (typeof original !== "function") return;
349
+ owner[method] = function(message, ...rest) {
350
+ const context = instance ? this?.context ?? rest[0] : rest[0];
351
+ const stack = rest[1];
352
+ try {
353
+ const result = original.apply(this, [message, ...rest]);
354
+ capture(level, message, context, stack);
355
+ return result;
356
+ } catch (err) {
357
+ capture(level, message, context, stack);
358
+ throw err;
359
+ }
360
+ };
361
+ restores.push(() => {
362
+ owner[method] = original;
363
+ });
364
+ };
365
+ const proto = Logger.prototype;
366
+ patch(proto, "log", "info", true);
367
+ patch(proto, "error", "error", true);
368
+ patch(proto, "warn", "warn", true);
369
+ patch(proto, "debug", "debug", true);
370
+ patch(proto, "verbose", "verbose", true);
371
+ const statics = Logger;
372
+ patch(statics, "log", "info", false);
373
+ patch(statics, "error", "error", false);
374
+ patch(statics, "warn", "warn", false);
375
+ patch(statics, "debug", "debug", false);
376
+ patch(statics, "verbose", "verbose", false);
377
+ return () => {
378
+ for (const restore of restores.splice(0)) restore();
379
+ };
380
+ }
381
+ };
382
+ function resolveSdkAwareLocation() {
383
+ try {
384
+ const err = new Error();
385
+ const stack = err.stack?.split("\n").slice(3).join("\n");
386
+ return resolveSourceLocation2(stack ?? "", { skip: 0 });
387
+ } catch {
388
+ return void 0;
389
+ }
390
+ }
391
+
392
+ // src/instrumentation/exceptions.ts
393
+ import { basename } from "path";
394
+ import { requestContext as requestContext4, safeStringify, resolveSourceLocation as resolveSourceLocation3 } from "@angelitosystems/devtools-core";
395
+ var ExceptionsInstrumentation = class {
396
+ /** Attach process-level hooks. Returns a detach function. */
397
+ static attach(ctx) {
398
+ const onUncaught = (error) => {
399
+ reportError(error, ctx, { source: "uncaughtException" });
400
+ };
401
+ const onRejection = (reason) => {
402
+ reportError(reason, ctx, { source: "unhandledRejection" });
403
+ };
404
+ process.on("uncaughtException", onUncaught);
405
+ process.on("unhandledRejection", onRejection);
406
+ return () => {
407
+ process.off("uncaughtException", onUncaught);
408
+ process.off("unhandledRejection", onRejection);
409
+ };
410
+ }
411
+ };
412
+ function captureError(error, options) {
413
+ reportError(error, void 0, options);
414
+ }
415
+ function reportError(error, ctx, options) {
416
+ try {
417
+ if (ctx && !ctx.config.capture.errors) return;
418
+ const normalized = normalizeError(error);
419
+ const location = resolveSourceLocation3(normalized.error ?? normalized.message, { skip: 1 });
420
+ const projectId = ctx?.projectInfo.projectId ?? basename(process.cwd());
421
+ const payload = {
422
+ requestId: requestContext4().requestId(),
423
+ projectId,
424
+ name: normalized.name,
425
+ message: normalized.message,
426
+ stack: normalized.error?.stack,
427
+ source: location,
428
+ fingerprint: fingerprint(normalized.name, normalized.message, location),
429
+ context: options?.context ?? options?.source,
430
+ request: currentRequestSummary(),
431
+ controller: options?.controller,
432
+ service: options?.service,
433
+ timestamp: Date.now()
434
+ };
435
+ emit("error.created", payload);
436
+ } catch {
437
+ }
438
+ }
439
+ function normalizeError(error) {
440
+ if (error instanceof Error) {
441
+ return { error, name: error.name, message: error.message };
442
+ }
443
+ if (typeof error === "string") {
444
+ return { error: void 0, name: "Unknown", message: error };
445
+ }
446
+ if (error !== null && typeof error === "object" && "message" in error) {
447
+ const record = error;
448
+ return {
449
+ error: void 0,
450
+ name: typeof record["name"] === "string" ? record["name"] : "Error",
451
+ message: String(record["message"])
452
+ };
453
+ }
454
+ return { error: void 0, name: "Unknown", message: safeStringify(error) };
455
+ }
456
+ function fingerprint(name, message, location) {
457
+ const raw = name + "::" + message + "::" + (location?.file ?? "") + ":" + String(location?.line ?? "");
458
+ let hash = 0;
459
+ for (let i = 0; i < raw.length; i++) {
460
+ hash = hash * 31 + raw.charCodeAt(i) >>> 0;
461
+ }
462
+ return hash.toString(36).padStart(7, "0");
463
+ }
464
+ function currentRequestSummary() {
465
+ const context = requestContext4().current();
466
+ if (!context?.method || !context?.url) return void 0;
467
+ return { method: context.method, url: context.url };
468
+ }
469
+
470
+ // src/instrumentation/performance.ts
471
+ var PerformanceInstrumentation = class {
472
+ constructor(ctx) {
473
+ this.ctx = ctx;
474
+ }
475
+ ctx;
476
+ /** No-op: sampling is owned by core. Returns a no-op cleanup. */
477
+ attach() {
478
+ void this.ctx;
479
+ return () => {
480
+ };
481
+ }
482
+ };
483
+
484
+ // src/instrumentation/app-explorer.ts
485
+ var AppExplorer = class {
486
+ constructor(ctx) {
487
+ this.ctx = ctx;
488
+ }
489
+ ctx;
490
+ /** Walk the container and emit app.snapshot. Returns a no-op cleanup. */
491
+ attach(app) {
492
+ try {
493
+ const snapshot = this.buildSnapshot(app);
494
+ emit("app.snapshot", snapshot);
495
+ } catch {
496
+ }
497
+ return () => {
498
+ };
499
+ }
500
+ buildSnapshot(app) {
501
+ const internal = app.container;
502
+ const modules = [];
503
+ const modulesMap = internal?.getModules?.();
504
+ if (modulesMap) {
505
+ for (const [id, node] of modulesMap) {
506
+ const moduleName = node.metatype?.name ?? String(id);
507
+ const members = this.extractMembers(node);
508
+ modules.push({
509
+ name: moduleName,
510
+ imports: [],
511
+ controllers: members.controllers,
512
+ providers: members.providers,
513
+ exports: []
514
+ });
515
+ }
516
+ }
517
+ return {
518
+ projectId: this.ctx.projectInfo.projectId,
519
+ projectName: this.ctx.projectInfo.projectName,
520
+ modules,
521
+ nestjsVersion: this.ctx.projectInfo.nestjsVersion,
522
+ capturedAt: Date.now()
523
+ };
524
+ }
525
+ extractMembers(node) {
526
+ const controllers = [];
527
+ const providers = [];
528
+ const providersMap = node.providers;
529
+ if (providersMap instanceof Map) {
530
+ for (const [id, wrapper] of providersMap) {
531
+ const member = this.memberFromWrapper(id, wrapper);
532
+ if (!member) continue;
533
+ if (wrapper && wrapper.subtype === "httpController") {
534
+ controllers.push(member);
535
+ } else {
536
+ providers.push(member);
537
+ }
538
+ }
539
+ }
540
+ return { controllers, providers };
541
+ }
542
+ memberFromWrapper(id, wrapper) {
543
+ const w = wrapper;
544
+ const name = w?.metatype?.name ?? (typeof id === "string" ? id.replace(/^[A-Z_0-9]+:/, "") : "unknown");
545
+ const type = classify(name, w?.instance);
546
+ return { name, type };
547
+ }
548
+ };
549
+ function classify(name, instance) {
550
+ if (name.endsWith("Gateway")) return "gateway";
551
+ if (name.endsWith("Guard")) return "guard";
552
+ if (name.endsWith("Interceptor")) return "interceptor";
553
+ if (name.endsWith("Pipe")) return "pipe";
554
+ if (name.endsWith("Filter")) return "filter";
555
+ if (instance && typeof instance.canActivate === "function") return "guard";
556
+ if (instance && typeof instance.intercept === "function") return "interceptor";
557
+ if (instance && typeof instance.transform === "function") return "pipe";
558
+ if (instance && typeof instance.catch === "function") return "filter";
559
+ return "provider";
560
+ }
561
+
562
+ // src/instrumentation/websockets.ts
563
+ var WebsocketInstrumentation = class {
564
+ constructor(ctx) {
565
+ this.ctx = ctx;
566
+ }
567
+ ctx;
568
+ /** No-op for now. Returns a no-op cleanup. */
569
+ attach() {
570
+ void this.ctx;
571
+ return () => {
572
+ };
573
+ }
574
+ };
575
+
576
+ // src/instrumentation/database.ts
577
+ var DatabaseInstrumentation = class {
578
+ constructor(ctx) {
579
+ this.ctx = ctx;
580
+ }
581
+ ctx;
582
+ /** No-op for now. Returns a no-op cleanup. */
583
+ attach() {
584
+ void this.ctx;
585
+ return () => {
586
+ };
587
+ }
588
+ };
589
+
590
+ // src/detect.ts
591
+ import { existsSync, readFileSync } from "fs";
592
+ import { resolve } from "path";
593
+ var cached;
594
+ function detectNestJsVersion() {
595
+ if (cached !== void 0) return cached;
596
+ cached = null;
597
+ try {
598
+ const candidates = [
599
+ resolve(process.cwd(), "node_modules/@nestjs/core/package.json"),
600
+ // bun/pnpm layouts: walk up a couple of levels
601
+ resolve(process.cwd(), "../../node_modules/@nestjs/core/package.json")
602
+ ];
603
+ for (const path of candidates) {
604
+ if (!existsSync(path)) continue;
605
+ const pkg = JSON.parse(readFileSync(path, "utf8"));
606
+ if (pkg.version) {
607
+ cached = pkg.version;
608
+ break;
609
+ }
610
+ }
611
+ } catch {
612
+ cached = null;
613
+ }
614
+ return cached;
615
+ }
616
+
617
+ // src/version.ts
618
+ var SDK_VERSION = "0.1.0";
619
+
620
+ // src/sdk.ts
621
+ var NestDevTools = class {
622
+ static cleanups = [];
623
+ static initialized = false;
624
+ /** Initialize DevTools for a NestJS application. One active instance per process. */
625
+ static init(app, userConfig) {
626
+ const config = resolveConfig(userConfig);
627
+ const disabled = {
628
+ enabled: false,
629
+ reason: "disabled-by-config",
630
+ projectId: config.projectId,
631
+ projectName: config.project ?? config.projectId,
632
+ server: config.server
633
+ };
634
+ if (this.initialized) return { ...disabled, reason: "already-initialized" };
635
+ if (!config.enabled) return disabled;
636
+ this.initialized = true;
637
+ const registerCleanup = (fn) => this.cleanups.push(fn);
638
+ const projectInfo = {
639
+ projectId: config.projectId,
640
+ projectName: config.project ?? config.projectId,
641
+ environment: config.environment,
642
+ hostname: safeHostname(),
643
+ port: null,
644
+ pid: process.pid,
645
+ runtime: detectRuntime(),
646
+ runtimeVersion: detectRuntimeVersion(),
647
+ nodeVersion: process.version,
648
+ nestjsVersion: detectNestJsVersion(),
649
+ sdkVersion: SDK_VERSION
650
+ };
651
+ coreDevtools2.initialize({ config, projectInfo, registerCleanup });
652
+ const adapter = app.getHttpAdapter();
653
+ const httpReady = Boolean(adapter && ["http", "express"].includes(adapter.getType()));
654
+ const http = new HttpInstrumentation({ config, projectInfo });
655
+ registerCleanup(http.attach(app));
656
+ registerCleanup(new ConsoleInstrumentation({ config, projectInfo }).attach());
657
+ registerCleanup(new LoggerInstrumentation({ config, projectInfo }).attach());
658
+ registerCleanup(ExceptionsInstrumentation.attach({ config, projectInfo }));
659
+ registerCleanup(new PerformanceInstrumentation({ config, projectInfo }).attach());
660
+ registerCleanup(new AppExplorer({ config, projectInfo }).attach(app));
661
+ if (config.capture.websockets) {
662
+ registerCleanup(new WebsocketInstrumentation({ config, projectInfo }).attach());
663
+ }
664
+ if (config.capture.database) {
665
+ registerCleanup(new DatabaseInstrumentation({ config, projectInfo }).attach());
666
+ }
667
+ return {
668
+ enabled: true,
669
+ projectId: config.projectId,
670
+ projectName: projectInfo.projectName,
671
+ server: config.server,
672
+ ...httpReady ? {} : { reason: "no-http-adapter" }
673
+ };
674
+ }
675
+ /** Teardown everything (mostly useful in tests). */
676
+ static destroy() {
677
+ for (const cleanup of this.cleanups.splice(0)) {
678
+ try {
679
+ cleanup();
680
+ } catch {
681
+ }
682
+ }
683
+ this.initialized = false;
684
+ coreDevtools2.shutdown();
685
+ }
686
+ /** Whether the SDK is currently active in this process. */
687
+ static isActive() {
688
+ return this.initialized;
689
+ }
690
+ };
691
+ var devtools = {
692
+ init: NestDevTools.init.bind(NestDevTools),
693
+ destroy: NestDevTools.destroy.bind(NestDevTools),
694
+ isActive: NestDevTools.isActive.bind(NestDevTools)
695
+ };
696
+ function safeHostname() {
697
+ try {
698
+ return hostname();
699
+ } catch {
700
+ return "unknown";
701
+ }
702
+ }
703
+ function detectRuntime() {
704
+ const versions = process.versions;
705
+ if (versions["bun"]) return "bun";
706
+ if (versions["deno"]) return "deno";
707
+ return "node";
708
+ }
709
+ function detectRuntimeVersion() {
710
+ const versions = process.versions;
711
+ return versions["bun"] ?? versions["deno"] ?? process.version;
712
+ }
713
+
714
+ // src/index.ts
715
+ import { requestContext as requestContext5 } from "@angelitosystems/devtools-core";
716
+ export {
717
+ NestDevTools,
718
+ captureError,
719
+ devtools,
720
+ requestContext5 as requestContext
721
+ };
722
+ //# sourceMappingURL=index.js.map