@angelitosystems/devtools-protocol 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.cjs +282 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +345 -0
- package/dist/index.d.ts +345 -0
- package/dist/index.js +239 -0
- package/dist/index.js.map +1 -0
- package/package.json +29 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_REDACT_KEYS: () => DEFAULT_REDACT_KEYS,
|
|
24
|
+
DEVTOOLS_EVENTS: () => DEVTOOLS_EVENTS,
|
|
25
|
+
MAX_CAPTURE_BYTES: () => MAX_CAPTURE_BYTES,
|
|
26
|
+
MAX_DEPTH: () => MAX_DEPTH,
|
|
27
|
+
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
28
|
+
Redactor: () => Redactor,
|
|
29
|
+
buildEditorUrl: () => buildEditorUrl,
|
|
30
|
+
createMessage: () => createMessage,
|
|
31
|
+
cursorUrl: () => cursorUrl,
|
|
32
|
+
defaultRedactor: () => defaultRedactor,
|
|
33
|
+
detectPlatform: () => detectPlatform,
|
|
34
|
+
isDevToolsEventName: () => isDevToolsEventName,
|
|
35
|
+
parseMessage: () => parseMessage,
|
|
36
|
+
randomId: () => randomId,
|
|
37
|
+
redactText: () => redactText,
|
|
38
|
+
redactValue: () => redactValue,
|
|
39
|
+
vscodeUrl: () => vscodeUrl
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(index_exports);
|
|
42
|
+
|
|
43
|
+
// src/types.ts
|
|
44
|
+
var PROTOCOL_VERSION = 1;
|
|
45
|
+
|
|
46
|
+
// src/events.ts
|
|
47
|
+
var DEVTOOLS_EVENTS = [
|
|
48
|
+
"project.connected",
|
|
49
|
+
"project.disconnected",
|
|
50
|
+
"request.started",
|
|
51
|
+
"request.completed",
|
|
52
|
+
"log.created",
|
|
53
|
+
"error.created",
|
|
54
|
+
"query.executed",
|
|
55
|
+
"websocket.connected",
|
|
56
|
+
"websocket.message",
|
|
57
|
+
"performance.updated",
|
|
58
|
+
"app.snapshot",
|
|
59
|
+
"client.hello",
|
|
60
|
+
"client.welcome",
|
|
61
|
+
"stream.pause",
|
|
62
|
+
"stream.resume",
|
|
63
|
+
"state.clear",
|
|
64
|
+
"state.snapshot",
|
|
65
|
+
"state.ack",
|
|
66
|
+
"error"
|
|
67
|
+
];
|
|
68
|
+
function isDevToolsEventName(name) {
|
|
69
|
+
return DEVTOOLS_EVENTS.includes(name);
|
|
70
|
+
}
|
|
71
|
+
function createMessage(event, payload, options) {
|
|
72
|
+
return {
|
|
73
|
+
v: PROTOCOL_VERSION,
|
|
74
|
+
id: options?.id ?? randomId(),
|
|
75
|
+
projectId: options?.projectId,
|
|
76
|
+
ts: options?.ts ?? Date.now(),
|
|
77
|
+
event,
|
|
78
|
+
payload
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function randomId(prefix) {
|
|
82
|
+
const core = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID().replace(/-/g, "").slice(0, 16) : Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
|
|
83
|
+
return prefix ? `${prefix}_${core}` : core;
|
|
84
|
+
}
|
|
85
|
+
function parseMessage(raw) {
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(raw);
|
|
88
|
+
if (typeof parsed === "object" && parsed !== null && "event" in parsed && "payload" in parsed && isDevToolsEventName(String(parsed.event))) {
|
|
89
|
+
return parsed;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/redact.ts
|
|
98
|
+
var DEFAULT_REDACT_KEYS = [
|
|
99
|
+
"password",
|
|
100
|
+
"token",
|
|
101
|
+
"access_token",
|
|
102
|
+
"accessToken",
|
|
103
|
+
"refresh_token",
|
|
104
|
+
"refreshToken",
|
|
105
|
+
"authorization",
|
|
106
|
+
"cookie",
|
|
107
|
+
"cookies",
|
|
108
|
+
"secret",
|
|
109
|
+
"apiKey",
|
|
110
|
+
"api_key",
|
|
111
|
+
"client_secret",
|
|
112
|
+
"clientSecret",
|
|
113
|
+
"private_key",
|
|
114
|
+
"privateKey",
|
|
115
|
+
"session",
|
|
116
|
+
"set-cookie"
|
|
117
|
+
];
|
|
118
|
+
var MAX_CAPTURE_BYTES = 4 * 1024;
|
|
119
|
+
var MAX_DEPTH = 4;
|
|
120
|
+
var DEFAULT_PLACEHOLDER = "[REDACTED]";
|
|
121
|
+
var TRUNCATION_SUFFIX = "\u2026[truncated]";
|
|
122
|
+
var Redactor = class {
|
|
123
|
+
deny;
|
|
124
|
+
allow;
|
|
125
|
+
placeholder;
|
|
126
|
+
maxBytes;
|
|
127
|
+
maxDepth;
|
|
128
|
+
constructor(options) {
|
|
129
|
+
this.deny = new Set([...DEFAULT_REDACT_KEYS, ...options?.redact ?? []].map(normalizeKey));
|
|
130
|
+
this.allow = new Set((options?.allow ?? []).map(normalizeKey));
|
|
131
|
+
this.placeholder = options?.placeholder ?? DEFAULT_PLACEHOLDER;
|
|
132
|
+
this.maxBytes = options?.maxBytes ?? MAX_CAPTURE_BYTES;
|
|
133
|
+
this.maxDepth = options?.maxDepth ?? MAX_DEPTH;
|
|
134
|
+
}
|
|
135
|
+
/** True when the given key is sensitive and not allowed. */
|
|
136
|
+
isSensitive(key) {
|
|
137
|
+
const normalized = normalizeKey(key);
|
|
138
|
+
if (this.allow.has(normalized)) return false;
|
|
139
|
+
if (this.deny.has(normalized)) return true;
|
|
140
|
+
for (const denied of this.deny) {
|
|
141
|
+
if (normalized.includes(denied)) return true;
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Deep-copy a value while redacting sensitive keys, truncating oversized
|
|
147
|
+
* strings and enforcing depth limits. Circular references become '[Circular]'.
|
|
148
|
+
*/
|
|
149
|
+
redact(value, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
150
|
+
if (value === null || typeof value !== "object") {
|
|
151
|
+
return redactPrimitive(value);
|
|
152
|
+
}
|
|
153
|
+
if (seen.has(value)) return "[Circular]";
|
|
154
|
+
if (depth >= this.maxDepth) return "[MaxDepth]";
|
|
155
|
+
seen.add(value);
|
|
156
|
+
try {
|
|
157
|
+
if (Array.isArray(value)) {
|
|
158
|
+
return value.slice(0, 50).map((item) => this.redact(item, depth + 1, seen));
|
|
159
|
+
}
|
|
160
|
+
if (value instanceof Error) {
|
|
161
|
+
return { name: value.name, message: value.message };
|
|
162
|
+
}
|
|
163
|
+
if (value instanceof Date) return value.toISOString();
|
|
164
|
+
const out = {};
|
|
165
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
166
|
+
out[key] = this.isSensitive(key) ? this.placeholder : this.redact(raw, depth + 1, seen);
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
} finally {
|
|
170
|
+
seen.delete(value);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Redact sensitive substrings (key=value / key: value / bearer tokens) from free-form text. */
|
|
174
|
+
redactString(text) {
|
|
175
|
+
let out = text;
|
|
176
|
+
for (const denied of this.deny) {
|
|
177
|
+
const camel = denied;
|
|
178
|
+
const pattern = new RegExp(`(${escapeRegExp(camel)}|${escapeRegExp(denied.replace(/[-_]/g, ""))})\\s*[=:]\\s*([^\\s,&;]+)`, "gi");
|
|
179
|
+
out = out.replace(pattern, `$1=${this.placeholder}`);
|
|
180
|
+
}
|
|
181
|
+
out = out.replace(/bearer\s+[a-z0-9._-]+/gi, `Bearer ${this.placeholder}`);
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
/** Serialize a value safely: redacted, size-capped, never throws. */
|
|
185
|
+
serialize(value) {
|
|
186
|
+
try {
|
|
187
|
+
const safe = this.redact(value);
|
|
188
|
+
const json = JSON.stringify(safe) ?? String(safe);
|
|
189
|
+
return truncateUtf8(json, this.maxBytes, TRUNCATION_SUFFIX);
|
|
190
|
+
} catch {
|
|
191
|
+
return "[Unserializable]";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
var defaultRedactor = new Redactor();
|
|
196
|
+
function redactValue(value) {
|
|
197
|
+
return defaultRedactor.redact(value);
|
|
198
|
+
}
|
|
199
|
+
function redactText(text) {
|
|
200
|
+
return defaultRedactor.redactString(text);
|
|
201
|
+
}
|
|
202
|
+
function redactPrimitive(value) {
|
|
203
|
+
if (typeof value === "string") return defaultRedactor.redactString(value);
|
|
204
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) return value;
|
|
205
|
+
if (typeof value === "function") return "[Function]";
|
|
206
|
+
if (typeof value === "bigint") return value.toString() + "n";
|
|
207
|
+
return String(value);
|
|
208
|
+
}
|
|
209
|
+
function normalizeKey(key) {
|
|
210
|
+
return key.toLowerCase().replace(/[-_\s]/g, "");
|
|
211
|
+
}
|
|
212
|
+
function escapeRegExp(text) {
|
|
213
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
214
|
+
}
|
|
215
|
+
function truncateUtf8(text, maxBytes, suffix) {
|
|
216
|
+
const buf = Buffer.from(text, "utf8");
|
|
217
|
+
if (buf.length <= maxBytes) return text;
|
|
218
|
+
const cut = Math.max(0, maxBytes - Buffer.byteLength(suffix, "utf8"));
|
|
219
|
+
return buf.subarray(0, cut).toString("utf8") + suffix;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/editor-urls.ts
|
|
223
|
+
var EDITOR_SCHEMES = {
|
|
224
|
+
vscode: "vscode",
|
|
225
|
+
cursor: "cursor",
|
|
226
|
+
zed: "zed",
|
|
227
|
+
sublime: "subl"
|
|
228
|
+
};
|
|
229
|
+
function detectPlatform(platformGetter) {
|
|
230
|
+
try {
|
|
231
|
+
const platform = platformGetter ? platformGetter() : process.platform;
|
|
232
|
+
if (platform === "win32" || platform === "darwin" || platform === "linux") return platform;
|
|
233
|
+
return "unknown";
|
|
234
|
+
} catch {
|
|
235
|
+
return "unknown";
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function encodeFilePath(absolutePath, platform) {
|
|
239
|
+
const normalized = platform === "win32" ? absolutePath.replace(/\\/g, "/") : absolutePath;
|
|
240
|
+
const withDrive = platform === "win32" && /^[A-Za-z]:\//.test(normalized) ? `/${normalized}` : normalized;
|
|
241
|
+
return withDrive.replace(/^\//, "").split("/").map((segment, index) => index === 0 && /^[A-Za-z]:$/.test(segment) ? segment : encodeURIComponent(segment)).join("/");
|
|
242
|
+
}
|
|
243
|
+
function buildEditorUrl(editor, file, platformGetter) {
|
|
244
|
+
const absolutePath = file.absolutePath ?? file.file;
|
|
245
|
+
if (!absolutePath) return null;
|
|
246
|
+
const platform = detectPlatform(platformGetter);
|
|
247
|
+
const scheme = EDITOR_SCHEMES[editor];
|
|
248
|
+
const path = encodeFilePath(absolutePath, platform);
|
|
249
|
+
const line = file.line ?? 1;
|
|
250
|
+
const column = file.column ?? 1;
|
|
251
|
+
if (editor === "sublime") {
|
|
252
|
+
return `subl://open?url=file://${path}&line=${line}&column=${column}`;
|
|
253
|
+
}
|
|
254
|
+
return `${scheme}://file/${path}:${line}:${column}`;
|
|
255
|
+
}
|
|
256
|
+
function vscodeUrl(file) {
|
|
257
|
+
return buildEditorUrl("vscode", file);
|
|
258
|
+
}
|
|
259
|
+
function cursorUrl(file) {
|
|
260
|
+
return buildEditorUrl("cursor", file);
|
|
261
|
+
}
|
|
262
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
263
|
+
0 && (module.exports = {
|
|
264
|
+
DEFAULT_REDACT_KEYS,
|
|
265
|
+
DEVTOOLS_EVENTS,
|
|
266
|
+
MAX_CAPTURE_BYTES,
|
|
267
|
+
MAX_DEPTH,
|
|
268
|
+
PROTOCOL_VERSION,
|
|
269
|
+
Redactor,
|
|
270
|
+
buildEditorUrl,
|
|
271
|
+
createMessage,
|
|
272
|
+
cursorUrl,
|
|
273
|
+
defaultRedactor,
|
|
274
|
+
detectPlatform,
|
|
275
|
+
isDevToolsEventName,
|
|
276
|
+
parseMessage,
|
|
277
|
+
randomId,
|
|
278
|
+
redactText,
|
|
279
|
+
redactValue,
|
|
280
|
+
vscodeUrl
|
|
281
|
+
});
|
|
282
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/events.ts","../src/redact.ts","../src/editor-urls.ts"],"sourcesContent":["/**\n * @angelitosystems/devtools-protocol\n *\n * Typed WebSocket protocol shared by the NestJS DevTools SDK, server and dashboard.\n * Zero runtime dependencies — safe to embed in every tier.\n */\nexport * from './types';\nexport * from './events';\nexport * from './redact';\nexport * from './editor-urls';\n","/** Wire protocol version. Bump on breaking payload changes. */\nexport const PROTOCOL_VERSION = 1 as const;\n\n/** JSON-RPC-ish envelope shared by every message on the wire. */\nexport interface DevToolsMessage<T = unknown> {\n /** Envelope format version. */\n v: typeof PROTOCOL_VERSION;\n /** Unique message id. */\n id: string;\n /** Originating project (absent for client->server handshake-less control traffic). */\n projectId?: string;\n /** Milliseconds since epoch. */\n ts: number;\n /** Event discriminator. */\n event: DevToolsEventName;\n /** Event payload. */\n payload: T;\n}\n\n/** All event names supported by protocol v1. */\nexport type DevToolsEventName =\n // lifecycle\n | 'project.connected'\n | 'project.disconnected'\n // http\n | 'request.started'\n | 'request.completed'\n // observability\n | 'log.created'\n | 'error.created'\n | 'query.executed'\n // realtime\n | 'websocket.connected'\n | 'websocket.message'\n // perf\n | 'performance.updated'\n // application graph\n | 'app.snapshot'\n // control plane\n | 'client.hello'\n | 'client.welcome'\n | 'stream.pause'\n | 'stream.resume'\n | 'state.clear'\n | 'state.snapshot'\n | 'state.ack'\n | 'error';\n\n/** Union of every typed payload keyed by its event name. */\nexport interface DevToolsEventMap {\n 'project.connected': ProjectInfo;\n 'project.disconnected': { projectId: string; reason?: string };\n 'request.started': RequestStartedPayload;\n 'request.completed': RequestCompletedPayload;\n 'log.created': LogPayload;\n 'error.created': ErrorPayload;\n 'query.executed': QueryPayload;\n 'websocket.connected': GatewayConnectionPayload;\n 'websocket.message': GatewayMessagePayload;\n 'performance.updated': PerformanceSnapshot;\n 'app.snapshot': AppSnapshot;\n 'client.hello': ClientHello;\n 'client.welcome': { serverVersion: string; protocol: typeof PROTOCOL_VERSION };\n 'stream.pause': Record<string, never>;\n 'stream.resume': Record<string, never>;\n 'state.clear': { scope: 'logs' | 'requests' | 'errors' | 'queries' | 'all' };\n 'state.snapshot': StateSnapshot;\n 'state.ack': { ok: true };\n error: { code: string; message: string };\n}\n\n/** Discriminated union of all wire messages. */\nexport type DevToolsEvent = {\n [K in DevToolsEventName]: DevToolsMessage<DevToolsEventMap[K]>;\n}[DevToolsEventName];\n\n/** Metadata every NestJS application reports when it connects. */\nexport interface ProjectInfo {\n projectId: string;\n projectName: string;\n environment: string;\n hostname: string;\n port: number | null;\n pid: number;\n runtime: string;\n runtimeVersion: string;\n nodeVersion: string;\n nestjsVersion: string | null;\n sdkVersion: string;\n}\n\n/** One timeline entry of a request (guard, interceptor, service call...). */\nexport interface TimelineSpan {\n /** Logical layer, e.g. 'middleware' | 'guard' | 'interceptor' | 'pipe' | 'controller' | 'service' | 'database' | 'response'. */\n layer: string;\n /** Human label, e.g. 'JwtAuthGuard' or 'SELECT users'. */\n label: string;\n /** ms */\n duration: number;\n startedAt: number;\n status?: 'ok' | 'error';\n detail?: string;\n}\n\n/** Emitted the moment a request enters the SDK. */\nexport interface RequestStartedPayload {\n requestId: string;\n projectId: string;\n method: string;\n url: string;\n route?: string;\n httpVersion?: string;\n headers: Record<string, string>;\n query: Record<string, unknown>;\n params?: Record<string, unknown>;\n ip?: string;\n userAgent?: string;\n startedAt: number;\n}\n\n/** Emitted when the response finishes. */\nexport interface RequestCompletedPayload {\n requestId: string;\n projectId: string;\n method: string;\n url: string;\n route?: string;\n statusCode: number;\n duration: number;\n startedAt: number;\n timeline: TimelineSpan[];\n query?: Record<string, unknown>;\n responsePreview?: string;\n responseBody?: unknown;\n requestBody?: unknown;\n errored: boolean;\n}\n\n/** A captured console or logger entry. */\nexport interface LogPayload {\n requestId?: string;\n projectId: string;\n level: LogLevel;\n message: string;\n arguments?: unknown[];\n stack?: string;\n source?: SourceLocation;\n context?: string;\n processId: number;\n timestamp: number;\n}\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'verbose';\n\n/** File/line/column triple resolved from source maps when available. */\nexport interface SourceLocation {\n file: string;\n line: number;\n column: number;\n /** Absolute path when resolvable on the host machine. */\n absolutePath?: string;\n /** function name if the stack exposed one */\n function?: string;\n}\n\n/** A captured exception with source mapping. */\nexport interface ErrorPayload {\n requestId?: string;\n projectId: string;\n name: string;\n message: string;\n stack?: string;\n source?: SourceLocation;\n /** stable hash for grouping identical errors */\n fingerprint: string;\n context?: string;\n request?: { method: string; url: string; statusCode?: number };\n controller?: string;\n service?: string;\n timestamp: number;\n /** server-side occurrence count for grouped errors */\n occurrences?: number;\n}\n\n/** A captured database query. */\nexport interface QueryPayload {\n requestId?: string;\n projectId: string;\n provider: 'prisma' | 'typeorm' | 'sequelize' | 'mikroorm' | 'other';\n sql: string;\n duration: number;\n database?: string;\n parameters?: unknown[];\n timestamp: number;\n}\n\n/** Gateway-level connection snapshot. */\nexport interface GatewayConnectionPayload {\n projectId: string;\n gateway: string;\n namespace: string;\n connections: number;\n timestamp: number;\n}\n\n/** Individual gateway event flow. */\nexport interface GatewayMessagePayload {\n projectId: string;\n gateway: string;\n event: string;\n direction: 'received' | 'sent';\n payloadSize: number;\n error?: string;\n duration?: number;\n requestId?: string;\n timestamp: number;\n}\n\n/** Point-in-time process metrics. */\nexport interface PerformanceSnapshot {\n projectId: string;\n timestamp: number;\n cpuPercent: number;\n memoryUsedBytes: number;\n memoryTotalBytes: number;\n heapUsedBytes: number;\n heapTotalBytes: number;\n eventLoopLagMs: number;\n activeRequests: number;\n requestsPerSecond: number;\n averageLatencyMs: number;\n p95LatencyMs: number;\n p99LatencyMs: number;\n errorsPerSecond: number;\n /** true when the process reports memory pressure */\n memoryPressure?: boolean;\n}\n\n/** Static description of the NestJS application graph. */\nexport interface AppSnapshot {\n projectId: string;\n projectName: string;\n modules: AppModuleNode[];\n nestjsVersion: string | null;\n capturedAt: number;\n}\n\n/** A module and its members in the application graph. */\nexport interface AppModuleNode {\n name: string;\n imports: string[];\n controllers: AppMemberNode[];\n providers: AppMemberNode[];\n exports: string[];\n}\n\n/** A member (controller/provider/guard/pipe/...) inside a module. */\nexport interface AppMemberNode {\n name: string;\n type: 'controller' | 'provider' | 'guard' | 'interceptor' | 'pipe' | 'filter' | 'gateway';\n routes?: string[];\n}\n\n/** What a dashboard/control client announces when it connects. */\nexport interface ClientHello {\n kind: 'dashboard' | 'cli' | 'other';\n name?: string;\n version?: string;\n}\n\n/** Full server state handed to newly connected dashboards. */\nexport interface StateSnapshot {\n projects: ProjectInfo[];\n requests: RequestCompletedPayload[];\n logs: LogPayload[];\n errors: ErrorPayload[];\n queries: QueryPayload[];\n performance: Record<string, PerformanceSnapshot>;\n apps: Record<string, AppSnapshot>;\n}\n","import type { DevToolsEventMap, DevToolsEventName, DevToolsMessage } from './types';\nimport { PROTOCOL_VERSION } from './types';\n\n/** Const array of every event name (runtime mirror of the type union). */\nexport const DEVTOOLS_EVENTS = [\n 'project.connected',\n 'project.disconnected',\n 'request.started',\n 'request.completed',\n 'log.created',\n 'error.created',\n 'query.executed',\n 'websocket.connected',\n 'websocket.message',\n 'performance.updated',\n 'app.snapshot',\n 'client.hello',\n 'client.welcome',\n 'stream.pause',\n 'stream.resume',\n 'state.clear',\n 'state.snapshot',\n 'state.ack',\n 'error',\n] as const satisfies readonly DevToolsEventName[];\n\n/** True when `name` is a known protocol event. */\nexport function isDevToolsEventName(name: string): name is DevToolsEventName {\n return (DEVTOOLS_EVENTS as readonly string[]).includes(name);\n}\n\n/** Build a fully-typed wire message. */\nexport function createMessage<K extends DevToolsEventName>(\n event: K,\n payload: DevToolsEventMap[K],\n options?: { projectId?: string; id?: string; ts?: number },\n): DevToolsMessage<DevToolsEventMap[K]> {\n return {\n v: PROTOCOL_VERSION,\n id: options?.id ?? randomId(),\n projectId: options?.projectId,\n ts: options?.ts ?? Date.now(),\n event,\n payload,\n };\n}\n\n/** Cheap collision-resistant id (no crypto dependency needed for wire correlation). */\nexport function randomId(prefix?: string): string {\n const core =\n typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID().replace(/-/g, '').slice(0, 16)\n : Math.random().toString(36).slice(2, 10) + Date.now().toString(36);\n return prefix ? `${prefix}_${core}` : core;\n}\n\n/** Type guard narrowing an unknown parsed frame into a DevToolsMessage. */\nexport function parseMessage(raw: string): DevToolsMessage | null {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'event' in parsed &&\n 'payload' in parsed &&\n isDevToolsEventName(String((parsed as { event: unknown }).event))\n ) {\n return parsed as DevToolsMessage;\n }\n return null;\n } catch {\n return null;\n }\n}\n","/** Default keys that are always redacted unless explicitly allowed. */\nexport const DEFAULT_REDACT_KEYS = [\n 'password',\n 'token',\n 'access_token',\n 'accessToken',\n 'refresh_token',\n 'refreshToken',\n 'authorization',\n 'cookie',\n 'cookies',\n 'secret',\n 'apiKey',\n 'api_key',\n 'client_secret',\n 'clientSecret',\n 'private_key',\n 'privateKey',\n 'session',\n 'set-cookie',\n] as const;\n\n/** Default capture ceilings shared by SDK and server. */\nexport const MAX_CAPTURE_BYTES = 4 * 1024;\nexport const MAX_DEPTH = 4;\n\n/** Options for deep redaction of arbitrary values. */\nexport interface RedactOptions {\n /** Extra key names to redact (case-insensitive, partial match allowed). */\n redact?: string[];\n /** Keys that should never be redacted even if they look sensitive. */\n allow?: string[];\n /** Placeholder string used for redacted values. */\n placeholder?: string;\n /** Max serialized size before truncation. */\n maxBytes?: number;\n /** Max object depth. */\n maxDepth?: number;\n}\n\nconst DEFAULT_PLACEHOLDER = '[REDACTED]';\nconst TRUNCATION_SUFFIX = '…[truncated]';\n\n/** Normalized set of denylist and allowlist key matchers. */\nexport class Redactor {\n private readonly deny: Set<string>;\n private readonly allow: Set<string>;\n private readonly placeholder: string;\n private readonly maxBytes: number;\n private readonly maxDepth: number;\n\n constructor(options?: RedactOptions) {\n this.deny = new Set([...DEFAULT_REDACT_KEYS, ...(options?.redact ?? [])].map(normalizeKey));\n this.allow = new Set((options?.allow ?? []).map(normalizeKey));\n this.placeholder = options?.placeholder ?? DEFAULT_PLACEHOLDER;\n this.maxBytes = options?.maxBytes ?? MAX_CAPTURE_BYTES;\n this.maxDepth = options?.maxDepth ?? MAX_DEPTH;\n }\n\n /** True when the given key is sensitive and not allowed. */\n isSensitive(key: string): boolean {\n const normalized = normalizeKey(key);\n if (this.allow.has(normalized)) return false;\n if (this.deny.has(normalized)) return true;\n // partial match: `userPassword`, `authTokenValue`, `x-api-key`...\n for (const denied of this.deny) {\n if (normalized.includes(denied)) return true;\n }\n return false;\n }\n\n /**\n * Deep-copy a value while redacting sensitive keys, truncating oversized\n * strings and enforcing depth limits. Circular references become '[Circular]'.\n */\n redact(value: unknown, depth = 0, seen = new Set<unknown>()): unknown {\n if (value === null || typeof value !== 'object') {\n return redactPrimitive(value);\n }\n if (seen.has(value)) return '[Circular]';\n if (depth >= this.maxDepth) return '[MaxDepth]';\n\n seen.add(value);\n try {\n if (Array.isArray(value)) {\n return value.slice(0, 50).map((item) => this.redact(item, depth + 1, seen));\n }\n if (value instanceof Error) {\n return { name: value.name, message: value.message };\n }\n if (value instanceof Date) return value.toISOString();\n\n const out: Record<string, unknown> = {};\n for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {\n out[key] = this.isSensitive(key) ? this.placeholder : this.redact(raw, depth + 1, seen);\n }\n return out;\n } finally {\n seen.delete(value);\n }\n }\n\n /** Redact sensitive substrings (key=value / key: value / bearer tokens) from free-form text. */\n redactString(text: string): string {\n let out = text;\n for (const denied of this.deny) {\n const camel = denied;\n const pattern = new RegExp(`(${escapeRegExp(camel)}|${escapeRegExp(denied.replace(/[-_]/g, ''))})\\\\s*[=:]\\\\s*([^\\\\s,&;]+)`, 'gi');\n out = out.replace(pattern, `$1=${this.placeholder}`);\n }\n out = out.replace(/bearer\\s+[a-z0-9._-]+/gi, `Bearer ${this.placeholder}`);\n return out;\n }\n\n /** Serialize a value safely: redacted, size-capped, never throws. */\n serialize(value: unknown): string {\n try {\n const safe = this.redact(value);\n const json = JSON.stringify(safe) ?? String(safe);\n return truncateUtf8(json, this.maxBytes, TRUNCATION_SUFFIX);\n } catch {\n return '[Unserializable]';\n }\n }\n}\n\n/** Shared default redactor instance for quick helpers. */\nexport const defaultRedactor = new Redactor();\n\n/** Convenience: deep-redact with the default policy. */\nexport function redactValue(value: unknown): unknown {\n return defaultRedactor.redact(value);\n}\n\n/** Convenience: scrub a free-form string with the default policy. */\nexport function redactText(text: string): string {\n return defaultRedactor.redactString(text);\n}\n\nfunction redactPrimitive(value: unknown): unknown {\n if (typeof value === 'string') return defaultRedactor.redactString(value);\n if (typeof value === 'number' || typeof value === 'boolean' || value === null) return value;\n if (typeof value === 'function') return '[Function]';\n if (typeof value === 'bigint') return value.toString() + 'n';\n return String(value);\n}\n\nfunction normalizeKey(key: string): string {\n return key.toLowerCase().replace(/[-_\\s]/g, '');\n}\n\nfunction escapeRegExp(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/** UTF-8-safe truncation that never splits a multi-byte character. */\nfunction truncateUtf8(text: string, maxBytes: number, suffix: string): string {\n const buf = Buffer.from(text, 'utf8');\n if (buf.length <= maxBytes) return text;\n const cut = Math.max(0, maxBytes - Buffer.byteLength(suffix, 'utf8'));\n return buf.subarray(0, cut).toString('utf8') + suffix;\n}\n","/**\n * Deep links that open a file at a specific line/column in an editor.\n * Paths are generated for the OS the DevTools *server* runs on — never assume\n * Windows or POSIX layout; detect it at runtime.\n */\n\nexport type EditorKind = 'vscode' | 'cursor' | 'zed' | 'sublime';\n\nconst EDITOR_SCHEMES: Record<EditorKind, string> = {\n vscode: 'vscode',\n cursor: 'cursor',\n zed: 'zed',\n sublime: 'subl',\n};\n\n/** Returns 'win32' | 'darwin' | 'linux' | 'unknown' without touching process when unavailable. */\nexport function detectPlatform(platformGetter?: () => string): 'win32' | 'darwin' | 'linux' | 'unknown' {\n try {\n const platform = platformGetter ? platformGetter() : process.platform;\n if (platform === 'win32' || platform === 'darwin' || platform === 'linux') return platform;\n return 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nfunction encodeFilePath(absolutePath: string, platform: string): string {\n // VS Code expects forward slashes; keep drive letters like /C:/... on Windows.\n const normalized = platform === 'win32' ? absolutePath.replace(/\\\\/g, '/') : absolutePath;\n const withDrive = platform === 'win32' && /^[A-Za-z]:\\//.test(normalized) ? `/${normalized}` : normalized;\n // the scheme already carries 'file/', so drop any leading slash;\n // preserve the drive-letter colon (C:) which VS Code expects unencoded\n return withDrive\n .replace(/^\\//, '')\n .split('/')\n .map((segment, index) => (index === 0 && /^[A-Za-z]:$/.test(segment) ? segment : encodeURIComponent(segment)))\n .join('/');\n}\n\n/**\n * Build an editor deep link such as:\n * vscode://file/Users/me/app/src/users.service.ts:87:21\n */\nexport function buildEditorUrl(\n editor: EditorKind,\n file: { absolutePath?: string; file?: string; line?: number; column?: number },\n platformGetter?: () => string,\n): string | null {\n const absolutePath = file.absolutePath ?? file.file;\n if (!absolutePath) return null;\n\n const platform = detectPlatform(platformGetter);\n const scheme = EDITOR_SCHEMES[editor];\n const path = encodeFilePath(absolutePath, platform);\n const line = file.line ?? 1;\n const column = file.column ?? 1;\n\n if (editor === 'sublime') {\n return `subl://open?url=file://${path}&line=${line}&column=${column}`;\n }\n return `${scheme}://file/${path}:${line}:${column}`;\n}\n\n/** First available vscode:// link for a source location. */\nexport function vscodeUrl(file: { absolutePath?: string; file?: string; line?: number; column?: number }): string | null {\n return buildEditorUrl('vscode', file);\n}\n\n/** First available cursor:// link for a source location. */\nexport function cursorUrl(file: { absolutePath?: string; file?: string; line?: number; column?: number }): string | null {\n return buildEditorUrl('cursor', file);\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/** Wire protocol version. Bump on breaking payload changes. */
|
|
2
|
+
declare const PROTOCOL_VERSION: 1;
|
|
3
|
+
/** JSON-RPC-ish envelope shared by every message on the wire. */
|
|
4
|
+
interface DevToolsMessage<T = unknown> {
|
|
5
|
+
/** Envelope format version. */
|
|
6
|
+
v: typeof PROTOCOL_VERSION;
|
|
7
|
+
/** Unique message id. */
|
|
8
|
+
id: string;
|
|
9
|
+
/** Originating project (absent for client->server handshake-less control traffic). */
|
|
10
|
+
projectId?: string;
|
|
11
|
+
/** Milliseconds since epoch. */
|
|
12
|
+
ts: number;
|
|
13
|
+
/** Event discriminator. */
|
|
14
|
+
event: DevToolsEventName;
|
|
15
|
+
/** Event payload. */
|
|
16
|
+
payload: T;
|
|
17
|
+
}
|
|
18
|
+
/** All event names supported by protocol v1. */
|
|
19
|
+
type DevToolsEventName = 'project.connected' | 'project.disconnected' | 'request.started' | 'request.completed' | 'log.created' | 'error.created' | 'query.executed' | 'websocket.connected' | 'websocket.message' | 'performance.updated' | 'app.snapshot' | 'client.hello' | 'client.welcome' | 'stream.pause' | 'stream.resume' | 'state.clear' | 'state.snapshot' | 'state.ack' | 'error';
|
|
20
|
+
/** Union of every typed payload keyed by its event name. */
|
|
21
|
+
interface DevToolsEventMap {
|
|
22
|
+
'project.connected': ProjectInfo;
|
|
23
|
+
'project.disconnected': {
|
|
24
|
+
projectId: string;
|
|
25
|
+
reason?: string;
|
|
26
|
+
};
|
|
27
|
+
'request.started': RequestStartedPayload;
|
|
28
|
+
'request.completed': RequestCompletedPayload;
|
|
29
|
+
'log.created': LogPayload;
|
|
30
|
+
'error.created': ErrorPayload;
|
|
31
|
+
'query.executed': QueryPayload;
|
|
32
|
+
'websocket.connected': GatewayConnectionPayload;
|
|
33
|
+
'websocket.message': GatewayMessagePayload;
|
|
34
|
+
'performance.updated': PerformanceSnapshot;
|
|
35
|
+
'app.snapshot': AppSnapshot;
|
|
36
|
+
'client.hello': ClientHello;
|
|
37
|
+
'client.welcome': {
|
|
38
|
+
serverVersion: string;
|
|
39
|
+
protocol: typeof PROTOCOL_VERSION;
|
|
40
|
+
};
|
|
41
|
+
'stream.pause': Record<string, never>;
|
|
42
|
+
'stream.resume': Record<string, never>;
|
|
43
|
+
'state.clear': {
|
|
44
|
+
scope: 'logs' | 'requests' | 'errors' | 'queries' | 'all';
|
|
45
|
+
};
|
|
46
|
+
'state.snapshot': StateSnapshot;
|
|
47
|
+
'state.ack': {
|
|
48
|
+
ok: true;
|
|
49
|
+
};
|
|
50
|
+
error: {
|
|
51
|
+
code: string;
|
|
52
|
+
message: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Discriminated union of all wire messages. */
|
|
56
|
+
type DevToolsEvent = {
|
|
57
|
+
[K in DevToolsEventName]: DevToolsMessage<DevToolsEventMap[K]>;
|
|
58
|
+
}[DevToolsEventName];
|
|
59
|
+
/** Metadata every NestJS application reports when it connects. */
|
|
60
|
+
interface ProjectInfo {
|
|
61
|
+
projectId: string;
|
|
62
|
+
projectName: string;
|
|
63
|
+
environment: string;
|
|
64
|
+
hostname: string;
|
|
65
|
+
port: number | null;
|
|
66
|
+
pid: number;
|
|
67
|
+
runtime: string;
|
|
68
|
+
runtimeVersion: string;
|
|
69
|
+
nodeVersion: string;
|
|
70
|
+
nestjsVersion: string | null;
|
|
71
|
+
sdkVersion: string;
|
|
72
|
+
}
|
|
73
|
+
/** One timeline entry of a request (guard, interceptor, service call...). */
|
|
74
|
+
interface TimelineSpan {
|
|
75
|
+
/** Logical layer, e.g. 'middleware' | 'guard' | 'interceptor' | 'pipe' | 'controller' | 'service' | 'database' | 'response'. */
|
|
76
|
+
layer: string;
|
|
77
|
+
/** Human label, e.g. 'JwtAuthGuard' or 'SELECT users'. */
|
|
78
|
+
label: string;
|
|
79
|
+
/** ms */
|
|
80
|
+
duration: number;
|
|
81
|
+
startedAt: number;
|
|
82
|
+
status?: 'ok' | 'error';
|
|
83
|
+
detail?: string;
|
|
84
|
+
}
|
|
85
|
+
/** Emitted the moment a request enters the SDK. */
|
|
86
|
+
interface RequestStartedPayload {
|
|
87
|
+
requestId: string;
|
|
88
|
+
projectId: string;
|
|
89
|
+
method: string;
|
|
90
|
+
url: string;
|
|
91
|
+
route?: string;
|
|
92
|
+
httpVersion?: string;
|
|
93
|
+
headers: Record<string, string>;
|
|
94
|
+
query: Record<string, unknown>;
|
|
95
|
+
params?: Record<string, unknown>;
|
|
96
|
+
ip?: string;
|
|
97
|
+
userAgent?: string;
|
|
98
|
+
startedAt: number;
|
|
99
|
+
}
|
|
100
|
+
/** Emitted when the response finishes. */
|
|
101
|
+
interface RequestCompletedPayload {
|
|
102
|
+
requestId: string;
|
|
103
|
+
projectId: string;
|
|
104
|
+
method: string;
|
|
105
|
+
url: string;
|
|
106
|
+
route?: string;
|
|
107
|
+
statusCode: number;
|
|
108
|
+
duration: number;
|
|
109
|
+
startedAt: number;
|
|
110
|
+
timeline: TimelineSpan[];
|
|
111
|
+
query?: Record<string, unknown>;
|
|
112
|
+
responsePreview?: string;
|
|
113
|
+
responseBody?: unknown;
|
|
114
|
+
requestBody?: unknown;
|
|
115
|
+
errored: boolean;
|
|
116
|
+
}
|
|
117
|
+
/** A captured console or logger entry. */
|
|
118
|
+
interface LogPayload {
|
|
119
|
+
requestId?: string;
|
|
120
|
+
projectId: string;
|
|
121
|
+
level: LogLevel;
|
|
122
|
+
message: string;
|
|
123
|
+
arguments?: unknown[];
|
|
124
|
+
stack?: string;
|
|
125
|
+
source?: SourceLocation;
|
|
126
|
+
context?: string;
|
|
127
|
+
processId: number;
|
|
128
|
+
timestamp: number;
|
|
129
|
+
}
|
|
130
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'verbose';
|
|
131
|
+
/** File/line/column triple resolved from source maps when available. */
|
|
132
|
+
interface SourceLocation {
|
|
133
|
+
file: string;
|
|
134
|
+
line: number;
|
|
135
|
+
column: number;
|
|
136
|
+
/** Absolute path when resolvable on the host machine. */
|
|
137
|
+
absolutePath?: string;
|
|
138
|
+
/** function name if the stack exposed one */
|
|
139
|
+
function?: string;
|
|
140
|
+
}
|
|
141
|
+
/** A captured exception with source mapping. */
|
|
142
|
+
interface ErrorPayload {
|
|
143
|
+
requestId?: string;
|
|
144
|
+
projectId: string;
|
|
145
|
+
name: string;
|
|
146
|
+
message: string;
|
|
147
|
+
stack?: string;
|
|
148
|
+
source?: SourceLocation;
|
|
149
|
+
/** stable hash for grouping identical errors */
|
|
150
|
+
fingerprint: string;
|
|
151
|
+
context?: string;
|
|
152
|
+
request?: {
|
|
153
|
+
method: string;
|
|
154
|
+
url: string;
|
|
155
|
+
statusCode?: number;
|
|
156
|
+
};
|
|
157
|
+
controller?: string;
|
|
158
|
+
service?: string;
|
|
159
|
+
timestamp: number;
|
|
160
|
+
/** server-side occurrence count for grouped errors */
|
|
161
|
+
occurrences?: number;
|
|
162
|
+
}
|
|
163
|
+
/** A captured database query. */
|
|
164
|
+
interface QueryPayload {
|
|
165
|
+
requestId?: string;
|
|
166
|
+
projectId: string;
|
|
167
|
+
provider: 'prisma' | 'typeorm' | 'sequelize' | 'mikroorm' | 'other';
|
|
168
|
+
sql: string;
|
|
169
|
+
duration: number;
|
|
170
|
+
database?: string;
|
|
171
|
+
parameters?: unknown[];
|
|
172
|
+
timestamp: number;
|
|
173
|
+
}
|
|
174
|
+
/** Gateway-level connection snapshot. */
|
|
175
|
+
interface GatewayConnectionPayload {
|
|
176
|
+
projectId: string;
|
|
177
|
+
gateway: string;
|
|
178
|
+
namespace: string;
|
|
179
|
+
connections: number;
|
|
180
|
+
timestamp: number;
|
|
181
|
+
}
|
|
182
|
+
/** Individual gateway event flow. */
|
|
183
|
+
interface GatewayMessagePayload {
|
|
184
|
+
projectId: string;
|
|
185
|
+
gateway: string;
|
|
186
|
+
event: string;
|
|
187
|
+
direction: 'received' | 'sent';
|
|
188
|
+
payloadSize: number;
|
|
189
|
+
error?: string;
|
|
190
|
+
duration?: number;
|
|
191
|
+
requestId?: string;
|
|
192
|
+
timestamp: number;
|
|
193
|
+
}
|
|
194
|
+
/** Point-in-time process metrics. */
|
|
195
|
+
interface PerformanceSnapshot {
|
|
196
|
+
projectId: string;
|
|
197
|
+
timestamp: number;
|
|
198
|
+
cpuPercent: number;
|
|
199
|
+
memoryUsedBytes: number;
|
|
200
|
+
memoryTotalBytes: number;
|
|
201
|
+
heapUsedBytes: number;
|
|
202
|
+
heapTotalBytes: number;
|
|
203
|
+
eventLoopLagMs: number;
|
|
204
|
+
activeRequests: number;
|
|
205
|
+
requestsPerSecond: number;
|
|
206
|
+
averageLatencyMs: number;
|
|
207
|
+
p95LatencyMs: number;
|
|
208
|
+
p99LatencyMs: number;
|
|
209
|
+
errorsPerSecond: number;
|
|
210
|
+
/** true when the process reports memory pressure */
|
|
211
|
+
memoryPressure?: boolean;
|
|
212
|
+
}
|
|
213
|
+
/** Static description of the NestJS application graph. */
|
|
214
|
+
interface AppSnapshot {
|
|
215
|
+
projectId: string;
|
|
216
|
+
projectName: string;
|
|
217
|
+
modules: AppModuleNode[];
|
|
218
|
+
nestjsVersion: string | null;
|
|
219
|
+
capturedAt: number;
|
|
220
|
+
}
|
|
221
|
+
/** A module and its members in the application graph. */
|
|
222
|
+
interface AppModuleNode {
|
|
223
|
+
name: string;
|
|
224
|
+
imports: string[];
|
|
225
|
+
controllers: AppMemberNode[];
|
|
226
|
+
providers: AppMemberNode[];
|
|
227
|
+
exports: string[];
|
|
228
|
+
}
|
|
229
|
+
/** A member (controller/provider/guard/pipe/...) inside a module. */
|
|
230
|
+
interface AppMemberNode {
|
|
231
|
+
name: string;
|
|
232
|
+
type: 'controller' | 'provider' | 'guard' | 'interceptor' | 'pipe' | 'filter' | 'gateway';
|
|
233
|
+
routes?: string[];
|
|
234
|
+
}
|
|
235
|
+
/** What a dashboard/control client announces when it connects. */
|
|
236
|
+
interface ClientHello {
|
|
237
|
+
kind: 'dashboard' | 'cli' | 'other';
|
|
238
|
+
name?: string;
|
|
239
|
+
version?: string;
|
|
240
|
+
}
|
|
241
|
+
/** Full server state handed to newly connected dashboards. */
|
|
242
|
+
interface StateSnapshot {
|
|
243
|
+
projects: ProjectInfo[];
|
|
244
|
+
requests: RequestCompletedPayload[];
|
|
245
|
+
logs: LogPayload[];
|
|
246
|
+
errors: ErrorPayload[];
|
|
247
|
+
queries: QueryPayload[];
|
|
248
|
+
performance: Record<string, PerformanceSnapshot>;
|
|
249
|
+
apps: Record<string, AppSnapshot>;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Const array of every event name (runtime mirror of the type union). */
|
|
253
|
+
declare const DEVTOOLS_EVENTS: readonly ["project.connected", "project.disconnected", "request.started", "request.completed", "log.created", "error.created", "query.executed", "websocket.connected", "websocket.message", "performance.updated", "app.snapshot", "client.hello", "client.welcome", "stream.pause", "stream.resume", "state.clear", "state.snapshot", "state.ack", "error"];
|
|
254
|
+
/** True when `name` is a known protocol event. */
|
|
255
|
+
declare function isDevToolsEventName(name: string): name is DevToolsEventName;
|
|
256
|
+
/** Build a fully-typed wire message. */
|
|
257
|
+
declare function createMessage<K extends DevToolsEventName>(event: K, payload: DevToolsEventMap[K], options?: {
|
|
258
|
+
projectId?: string;
|
|
259
|
+
id?: string;
|
|
260
|
+
ts?: number;
|
|
261
|
+
}): DevToolsMessage<DevToolsEventMap[K]>;
|
|
262
|
+
/** Cheap collision-resistant id (no crypto dependency needed for wire correlation). */
|
|
263
|
+
declare function randomId(prefix?: string): string;
|
|
264
|
+
/** Type guard narrowing an unknown parsed frame into a DevToolsMessage. */
|
|
265
|
+
declare function parseMessage(raw: string): DevToolsMessage | null;
|
|
266
|
+
|
|
267
|
+
/** Default keys that are always redacted unless explicitly allowed. */
|
|
268
|
+
declare const DEFAULT_REDACT_KEYS: readonly ["password", "token", "access_token", "accessToken", "refresh_token", "refreshToken", "authorization", "cookie", "cookies", "secret", "apiKey", "api_key", "client_secret", "clientSecret", "private_key", "privateKey", "session", "set-cookie"];
|
|
269
|
+
/** Default capture ceilings shared by SDK and server. */
|
|
270
|
+
declare const MAX_CAPTURE_BYTES: number;
|
|
271
|
+
declare const MAX_DEPTH = 4;
|
|
272
|
+
/** Options for deep redaction of arbitrary values. */
|
|
273
|
+
interface RedactOptions {
|
|
274
|
+
/** Extra key names to redact (case-insensitive, partial match allowed). */
|
|
275
|
+
redact?: string[];
|
|
276
|
+
/** Keys that should never be redacted even if they look sensitive. */
|
|
277
|
+
allow?: string[];
|
|
278
|
+
/** Placeholder string used for redacted values. */
|
|
279
|
+
placeholder?: string;
|
|
280
|
+
/** Max serialized size before truncation. */
|
|
281
|
+
maxBytes?: number;
|
|
282
|
+
/** Max object depth. */
|
|
283
|
+
maxDepth?: number;
|
|
284
|
+
}
|
|
285
|
+
/** Normalized set of denylist and allowlist key matchers. */
|
|
286
|
+
declare class Redactor {
|
|
287
|
+
private readonly deny;
|
|
288
|
+
private readonly allow;
|
|
289
|
+
private readonly placeholder;
|
|
290
|
+
private readonly maxBytes;
|
|
291
|
+
private readonly maxDepth;
|
|
292
|
+
constructor(options?: RedactOptions);
|
|
293
|
+
/** True when the given key is sensitive and not allowed. */
|
|
294
|
+
isSensitive(key: string): boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Deep-copy a value while redacting sensitive keys, truncating oversized
|
|
297
|
+
* strings and enforcing depth limits. Circular references become '[Circular]'.
|
|
298
|
+
*/
|
|
299
|
+
redact(value: unknown, depth?: number, seen?: Set<unknown>): unknown;
|
|
300
|
+
/** Redact sensitive substrings (key=value / key: value / bearer tokens) from free-form text. */
|
|
301
|
+
redactString(text: string): string;
|
|
302
|
+
/** Serialize a value safely: redacted, size-capped, never throws. */
|
|
303
|
+
serialize(value: unknown): string;
|
|
304
|
+
}
|
|
305
|
+
/** Shared default redactor instance for quick helpers. */
|
|
306
|
+
declare const defaultRedactor: Redactor;
|
|
307
|
+
/** Convenience: deep-redact with the default policy. */
|
|
308
|
+
declare function redactValue(value: unknown): unknown;
|
|
309
|
+
/** Convenience: scrub a free-form string with the default policy. */
|
|
310
|
+
declare function redactText(text: string): string;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Deep links that open a file at a specific line/column in an editor.
|
|
314
|
+
* Paths are generated for the OS the DevTools *server* runs on — never assume
|
|
315
|
+
* Windows or POSIX layout; detect it at runtime.
|
|
316
|
+
*/
|
|
317
|
+
type EditorKind = 'vscode' | 'cursor' | 'zed' | 'sublime';
|
|
318
|
+
/** Returns 'win32' | 'darwin' | 'linux' | 'unknown' without touching process when unavailable. */
|
|
319
|
+
declare function detectPlatform(platformGetter?: () => string): 'win32' | 'darwin' | 'linux' | 'unknown';
|
|
320
|
+
/**
|
|
321
|
+
* Build an editor deep link such as:
|
|
322
|
+
* vscode://file/Users/me/app/src/users.service.ts:87:21
|
|
323
|
+
*/
|
|
324
|
+
declare function buildEditorUrl(editor: EditorKind, file: {
|
|
325
|
+
absolutePath?: string;
|
|
326
|
+
file?: string;
|
|
327
|
+
line?: number;
|
|
328
|
+
column?: number;
|
|
329
|
+
}, platformGetter?: () => string): string | null;
|
|
330
|
+
/** First available vscode:// link for a source location. */
|
|
331
|
+
declare function vscodeUrl(file: {
|
|
332
|
+
absolutePath?: string;
|
|
333
|
+
file?: string;
|
|
334
|
+
line?: number;
|
|
335
|
+
column?: number;
|
|
336
|
+
}): string | null;
|
|
337
|
+
/** First available cursor:// link for a source location. */
|
|
338
|
+
declare function cursorUrl(file: {
|
|
339
|
+
absolutePath?: string;
|
|
340
|
+
file?: string;
|
|
341
|
+
line?: number;
|
|
342
|
+
column?: number;
|
|
343
|
+
}): string | null;
|
|
344
|
+
|
|
345
|
+
export { type AppMemberNode, type AppModuleNode, type AppSnapshot, type ClientHello, DEFAULT_REDACT_KEYS, DEVTOOLS_EVENTS, type DevToolsEvent, type DevToolsEventMap, type DevToolsEventName, type DevToolsMessage, type EditorKind, type ErrorPayload, type GatewayConnectionPayload, type GatewayMessagePayload, type LogLevel, type LogPayload, MAX_CAPTURE_BYTES, MAX_DEPTH, PROTOCOL_VERSION, type PerformanceSnapshot, type ProjectInfo, type QueryPayload, type RedactOptions, Redactor, type RequestCompletedPayload, type RequestStartedPayload, type SourceLocation, type StateSnapshot, type TimelineSpan, buildEditorUrl, createMessage, cursorUrl, defaultRedactor, detectPlatform, isDevToolsEventName, parseMessage, randomId, redactText, redactValue, vscodeUrl };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/** Wire protocol version. Bump on breaking payload changes. */
|
|
2
|
+
declare const PROTOCOL_VERSION: 1;
|
|
3
|
+
/** JSON-RPC-ish envelope shared by every message on the wire. */
|
|
4
|
+
interface DevToolsMessage<T = unknown> {
|
|
5
|
+
/** Envelope format version. */
|
|
6
|
+
v: typeof PROTOCOL_VERSION;
|
|
7
|
+
/** Unique message id. */
|
|
8
|
+
id: string;
|
|
9
|
+
/** Originating project (absent for client->server handshake-less control traffic). */
|
|
10
|
+
projectId?: string;
|
|
11
|
+
/** Milliseconds since epoch. */
|
|
12
|
+
ts: number;
|
|
13
|
+
/** Event discriminator. */
|
|
14
|
+
event: DevToolsEventName;
|
|
15
|
+
/** Event payload. */
|
|
16
|
+
payload: T;
|
|
17
|
+
}
|
|
18
|
+
/** All event names supported by protocol v1. */
|
|
19
|
+
type DevToolsEventName = 'project.connected' | 'project.disconnected' | 'request.started' | 'request.completed' | 'log.created' | 'error.created' | 'query.executed' | 'websocket.connected' | 'websocket.message' | 'performance.updated' | 'app.snapshot' | 'client.hello' | 'client.welcome' | 'stream.pause' | 'stream.resume' | 'state.clear' | 'state.snapshot' | 'state.ack' | 'error';
|
|
20
|
+
/** Union of every typed payload keyed by its event name. */
|
|
21
|
+
interface DevToolsEventMap {
|
|
22
|
+
'project.connected': ProjectInfo;
|
|
23
|
+
'project.disconnected': {
|
|
24
|
+
projectId: string;
|
|
25
|
+
reason?: string;
|
|
26
|
+
};
|
|
27
|
+
'request.started': RequestStartedPayload;
|
|
28
|
+
'request.completed': RequestCompletedPayload;
|
|
29
|
+
'log.created': LogPayload;
|
|
30
|
+
'error.created': ErrorPayload;
|
|
31
|
+
'query.executed': QueryPayload;
|
|
32
|
+
'websocket.connected': GatewayConnectionPayload;
|
|
33
|
+
'websocket.message': GatewayMessagePayload;
|
|
34
|
+
'performance.updated': PerformanceSnapshot;
|
|
35
|
+
'app.snapshot': AppSnapshot;
|
|
36
|
+
'client.hello': ClientHello;
|
|
37
|
+
'client.welcome': {
|
|
38
|
+
serverVersion: string;
|
|
39
|
+
protocol: typeof PROTOCOL_VERSION;
|
|
40
|
+
};
|
|
41
|
+
'stream.pause': Record<string, never>;
|
|
42
|
+
'stream.resume': Record<string, never>;
|
|
43
|
+
'state.clear': {
|
|
44
|
+
scope: 'logs' | 'requests' | 'errors' | 'queries' | 'all';
|
|
45
|
+
};
|
|
46
|
+
'state.snapshot': StateSnapshot;
|
|
47
|
+
'state.ack': {
|
|
48
|
+
ok: true;
|
|
49
|
+
};
|
|
50
|
+
error: {
|
|
51
|
+
code: string;
|
|
52
|
+
message: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Discriminated union of all wire messages. */
|
|
56
|
+
type DevToolsEvent = {
|
|
57
|
+
[K in DevToolsEventName]: DevToolsMessage<DevToolsEventMap[K]>;
|
|
58
|
+
}[DevToolsEventName];
|
|
59
|
+
/** Metadata every NestJS application reports when it connects. */
|
|
60
|
+
interface ProjectInfo {
|
|
61
|
+
projectId: string;
|
|
62
|
+
projectName: string;
|
|
63
|
+
environment: string;
|
|
64
|
+
hostname: string;
|
|
65
|
+
port: number | null;
|
|
66
|
+
pid: number;
|
|
67
|
+
runtime: string;
|
|
68
|
+
runtimeVersion: string;
|
|
69
|
+
nodeVersion: string;
|
|
70
|
+
nestjsVersion: string | null;
|
|
71
|
+
sdkVersion: string;
|
|
72
|
+
}
|
|
73
|
+
/** One timeline entry of a request (guard, interceptor, service call...). */
|
|
74
|
+
interface TimelineSpan {
|
|
75
|
+
/** Logical layer, e.g. 'middleware' | 'guard' | 'interceptor' | 'pipe' | 'controller' | 'service' | 'database' | 'response'. */
|
|
76
|
+
layer: string;
|
|
77
|
+
/** Human label, e.g. 'JwtAuthGuard' or 'SELECT users'. */
|
|
78
|
+
label: string;
|
|
79
|
+
/** ms */
|
|
80
|
+
duration: number;
|
|
81
|
+
startedAt: number;
|
|
82
|
+
status?: 'ok' | 'error';
|
|
83
|
+
detail?: string;
|
|
84
|
+
}
|
|
85
|
+
/** Emitted the moment a request enters the SDK. */
|
|
86
|
+
interface RequestStartedPayload {
|
|
87
|
+
requestId: string;
|
|
88
|
+
projectId: string;
|
|
89
|
+
method: string;
|
|
90
|
+
url: string;
|
|
91
|
+
route?: string;
|
|
92
|
+
httpVersion?: string;
|
|
93
|
+
headers: Record<string, string>;
|
|
94
|
+
query: Record<string, unknown>;
|
|
95
|
+
params?: Record<string, unknown>;
|
|
96
|
+
ip?: string;
|
|
97
|
+
userAgent?: string;
|
|
98
|
+
startedAt: number;
|
|
99
|
+
}
|
|
100
|
+
/** Emitted when the response finishes. */
|
|
101
|
+
interface RequestCompletedPayload {
|
|
102
|
+
requestId: string;
|
|
103
|
+
projectId: string;
|
|
104
|
+
method: string;
|
|
105
|
+
url: string;
|
|
106
|
+
route?: string;
|
|
107
|
+
statusCode: number;
|
|
108
|
+
duration: number;
|
|
109
|
+
startedAt: number;
|
|
110
|
+
timeline: TimelineSpan[];
|
|
111
|
+
query?: Record<string, unknown>;
|
|
112
|
+
responsePreview?: string;
|
|
113
|
+
responseBody?: unknown;
|
|
114
|
+
requestBody?: unknown;
|
|
115
|
+
errored: boolean;
|
|
116
|
+
}
|
|
117
|
+
/** A captured console or logger entry. */
|
|
118
|
+
interface LogPayload {
|
|
119
|
+
requestId?: string;
|
|
120
|
+
projectId: string;
|
|
121
|
+
level: LogLevel;
|
|
122
|
+
message: string;
|
|
123
|
+
arguments?: unknown[];
|
|
124
|
+
stack?: string;
|
|
125
|
+
source?: SourceLocation;
|
|
126
|
+
context?: string;
|
|
127
|
+
processId: number;
|
|
128
|
+
timestamp: number;
|
|
129
|
+
}
|
|
130
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'verbose';
|
|
131
|
+
/** File/line/column triple resolved from source maps when available. */
|
|
132
|
+
interface SourceLocation {
|
|
133
|
+
file: string;
|
|
134
|
+
line: number;
|
|
135
|
+
column: number;
|
|
136
|
+
/** Absolute path when resolvable on the host machine. */
|
|
137
|
+
absolutePath?: string;
|
|
138
|
+
/** function name if the stack exposed one */
|
|
139
|
+
function?: string;
|
|
140
|
+
}
|
|
141
|
+
/** A captured exception with source mapping. */
|
|
142
|
+
interface ErrorPayload {
|
|
143
|
+
requestId?: string;
|
|
144
|
+
projectId: string;
|
|
145
|
+
name: string;
|
|
146
|
+
message: string;
|
|
147
|
+
stack?: string;
|
|
148
|
+
source?: SourceLocation;
|
|
149
|
+
/** stable hash for grouping identical errors */
|
|
150
|
+
fingerprint: string;
|
|
151
|
+
context?: string;
|
|
152
|
+
request?: {
|
|
153
|
+
method: string;
|
|
154
|
+
url: string;
|
|
155
|
+
statusCode?: number;
|
|
156
|
+
};
|
|
157
|
+
controller?: string;
|
|
158
|
+
service?: string;
|
|
159
|
+
timestamp: number;
|
|
160
|
+
/** server-side occurrence count for grouped errors */
|
|
161
|
+
occurrences?: number;
|
|
162
|
+
}
|
|
163
|
+
/** A captured database query. */
|
|
164
|
+
interface QueryPayload {
|
|
165
|
+
requestId?: string;
|
|
166
|
+
projectId: string;
|
|
167
|
+
provider: 'prisma' | 'typeorm' | 'sequelize' | 'mikroorm' | 'other';
|
|
168
|
+
sql: string;
|
|
169
|
+
duration: number;
|
|
170
|
+
database?: string;
|
|
171
|
+
parameters?: unknown[];
|
|
172
|
+
timestamp: number;
|
|
173
|
+
}
|
|
174
|
+
/** Gateway-level connection snapshot. */
|
|
175
|
+
interface GatewayConnectionPayload {
|
|
176
|
+
projectId: string;
|
|
177
|
+
gateway: string;
|
|
178
|
+
namespace: string;
|
|
179
|
+
connections: number;
|
|
180
|
+
timestamp: number;
|
|
181
|
+
}
|
|
182
|
+
/** Individual gateway event flow. */
|
|
183
|
+
interface GatewayMessagePayload {
|
|
184
|
+
projectId: string;
|
|
185
|
+
gateway: string;
|
|
186
|
+
event: string;
|
|
187
|
+
direction: 'received' | 'sent';
|
|
188
|
+
payloadSize: number;
|
|
189
|
+
error?: string;
|
|
190
|
+
duration?: number;
|
|
191
|
+
requestId?: string;
|
|
192
|
+
timestamp: number;
|
|
193
|
+
}
|
|
194
|
+
/** Point-in-time process metrics. */
|
|
195
|
+
interface PerformanceSnapshot {
|
|
196
|
+
projectId: string;
|
|
197
|
+
timestamp: number;
|
|
198
|
+
cpuPercent: number;
|
|
199
|
+
memoryUsedBytes: number;
|
|
200
|
+
memoryTotalBytes: number;
|
|
201
|
+
heapUsedBytes: number;
|
|
202
|
+
heapTotalBytes: number;
|
|
203
|
+
eventLoopLagMs: number;
|
|
204
|
+
activeRequests: number;
|
|
205
|
+
requestsPerSecond: number;
|
|
206
|
+
averageLatencyMs: number;
|
|
207
|
+
p95LatencyMs: number;
|
|
208
|
+
p99LatencyMs: number;
|
|
209
|
+
errorsPerSecond: number;
|
|
210
|
+
/** true when the process reports memory pressure */
|
|
211
|
+
memoryPressure?: boolean;
|
|
212
|
+
}
|
|
213
|
+
/** Static description of the NestJS application graph. */
|
|
214
|
+
interface AppSnapshot {
|
|
215
|
+
projectId: string;
|
|
216
|
+
projectName: string;
|
|
217
|
+
modules: AppModuleNode[];
|
|
218
|
+
nestjsVersion: string | null;
|
|
219
|
+
capturedAt: number;
|
|
220
|
+
}
|
|
221
|
+
/** A module and its members in the application graph. */
|
|
222
|
+
interface AppModuleNode {
|
|
223
|
+
name: string;
|
|
224
|
+
imports: string[];
|
|
225
|
+
controllers: AppMemberNode[];
|
|
226
|
+
providers: AppMemberNode[];
|
|
227
|
+
exports: string[];
|
|
228
|
+
}
|
|
229
|
+
/** A member (controller/provider/guard/pipe/...) inside a module. */
|
|
230
|
+
interface AppMemberNode {
|
|
231
|
+
name: string;
|
|
232
|
+
type: 'controller' | 'provider' | 'guard' | 'interceptor' | 'pipe' | 'filter' | 'gateway';
|
|
233
|
+
routes?: string[];
|
|
234
|
+
}
|
|
235
|
+
/** What a dashboard/control client announces when it connects. */
|
|
236
|
+
interface ClientHello {
|
|
237
|
+
kind: 'dashboard' | 'cli' | 'other';
|
|
238
|
+
name?: string;
|
|
239
|
+
version?: string;
|
|
240
|
+
}
|
|
241
|
+
/** Full server state handed to newly connected dashboards. */
|
|
242
|
+
interface StateSnapshot {
|
|
243
|
+
projects: ProjectInfo[];
|
|
244
|
+
requests: RequestCompletedPayload[];
|
|
245
|
+
logs: LogPayload[];
|
|
246
|
+
errors: ErrorPayload[];
|
|
247
|
+
queries: QueryPayload[];
|
|
248
|
+
performance: Record<string, PerformanceSnapshot>;
|
|
249
|
+
apps: Record<string, AppSnapshot>;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Const array of every event name (runtime mirror of the type union). */
|
|
253
|
+
declare const DEVTOOLS_EVENTS: readonly ["project.connected", "project.disconnected", "request.started", "request.completed", "log.created", "error.created", "query.executed", "websocket.connected", "websocket.message", "performance.updated", "app.snapshot", "client.hello", "client.welcome", "stream.pause", "stream.resume", "state.clear", "state.snapshot", "state.ack", "error"];
|
|
254
|
+
/** True when `name` is a known protocol event. */
|
|
255
|
+
declare function isDevToolsEventName(name: string): name is DevToolsEventName;
|
|
256
|
+
/** Build a fully-typed wire message. */
|
|
257
|
+
declare function createMessage<K extends DevToolsEventName>(event: K, payload: DevToolsEventMap[K], options?: {
|
|
258
|
+
projectId?: string;
|
|
259
|
+
id?: string;
|
|
260
|
+
ts?: number;
|
|
261
|
+
}): DevToolsMessage<DevToolsEventMap[K]>;
|
|
262
|
+
/** Cheap collision-resistant id (no crypto dependency needed for wire correlation). */
|
|
263
|
+
declare function randomId(prefix?: string): string;
|
|
264
|
+
/** Type guard narrowing an unknown parsed frame into a DevToolsMessage. */
|
|
265
|
+
declare function parseMessage(raw: string): DevToolsMessage | null;
|
|
266
|
+
|
|
267
|
+
/** Default keys that are always redacted unless explicitly allowed. */
|
|
268
|
+
declare const DEFAULT_REDACT_KEYS: readonly ["password", "token", "access_token", "accessToken", "refresh_token", "refreshToken", "authorization", "cookie", "cookies", "secret", "apiKey", "api_key", "client_secret", "clientSecret", "private_key", "privateKey", "session", "set-cookie"];
|
|
269
|
+
/** Default capture ceilings shared by SDK and server. */
|
|
270
|
+
declare const MAX_CAPTURE_BYTES: number;
|
|
271
|
+
declare const MAX_DEPTH = 4;
|
|
272
|
+
/** Options for deep redaction of arbitrary values. */
|
|
273
|
+
interface RedactOptions {
|
|
274
|
+
/** Extra key names to redact (case-insensitive, partial match allowed). */
|
|
275
|
+
redact?: string[];
|
|
276
|
+
/** Keys that should never be redacted even if they look sensitive. */
|
|
277
|
+
allow?: string[];
|
|
278
|
+
/** Placeholder string used for redacted values. */
|
|
279
|
+
placeholder?: string;
|
|
280
|
+
/** Max serialized size before truncation. */
|
|
281
|
+
maxBytes?: number;
|
|
282
|
+
/** Max object depth. */
|
|
283
|
+
maxDepth?: number;
|
|
284
|
+
}
|
|
285
|
+
/** Normalized set of denylist and allowlist key matchers. */
|
|
286
|
+
declare class Redactor {
|
|
287
|
+
private readonly deny;
|
|
288
|
+
private readonly allow;
|
|
289
|
+
private readonly placeholder;
|
|
290
|
+
private readonly maxBytes;
|
|
291
|
+
private readonly maxDepth;
|
|
292
|
+
constructor(options?: RedactOptions);
|
|
293
|
+
/** True when the given key is sensitive and not allowed. */
|
|
294
|
+
isSensitive(key: string): boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Deep-copy a value while redacting sensitive keys, truncating oversized
|
|
297
|
+
* strings and enforcing depth limits. Circular references become '[Circular]'.
|
|
298
|
+
*/
|
|
299
|
+
redact(value: unknown, depth?: number, seen?: Set<unknown>): unknown;
|
|
300
|
+
/** Redact sensitive substrings (key=value / key: value / bearer tokens) from free-form text. */
|
|
301
|
+
redactString(text: string): string;
|
|
302
|
+
/** Serialize a value safely: redacted, size-capped, never throws. */
|
|
303
|
+
serialize(value: unknown): string;
|
|
304
|
+
}
|
|
305
|
+
/** Shared default redactor instance for quick helpers. */
|
|
306
|
+
declare const defaultRedactor: Redactor;
|
|
307
|
+
/** Convenience: deep-redact with the default policy. */
|
|
308
|
+
declare function redactValue(value: unknown): unknown;
|
|
309
|
+
/** Convenience: scrub a free-form string with the default policy. */
|
|
310
|
+
declare function redactText(text: string): string;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Deep links that open a file at a specific line/column in an editor.
|
|
314
|
+
* Paths are generated for the OS the DevTools *server* runs on — never assume
|
|
315
|
+
* Windows or POSIX layout; detect it at runtime.
|
|
316
|
+
*/
|
|
317
|
+
type EditorKind = 'vscode' | 'cursor' | 'zed' | 'sublime';
|
|
318
|
+
/** Returns 'win32' | 'darwin' | 'linux' | 'unknown' without touching process when unavailable. */
|
|
319
|
+
declare function detectPlatform(platformGetter?: () => string): 'win32' | 'darwin' | 'linux' | 'unknown';
|
|
320
|
+
/**
|
|
321
|
+
* Build an editor deep link such as:
|
|
322
|
+
* vscode://file/Users/me/app/src/users.service.ts:87:21
|
|
323
|
+
*/
|
|
324
|
+
declare function buildEditorUrl(editor: EditorKind, file: {
|
|
325
|
+
absolutePath?: string;
|
|
326
|
+
file?: string;
|
|
327
|
+
line?: number;
|
|
328
|
+
column?: number;
|
|
329
|
+
}, platformGetter?: () => string): string | null;
|
|
330
|
+
/** First available vscode:// link for a source location. */
|
|
331
|
+
declare function vscodeUrl(file: {
|
|
332
|
+
absolutePath?: string;
|
|
333
|
+
file?: string;
|
|
334
|
+
line?: number;
|
|
335
|
+
column?: number;
|
|
336
|
+
}): string | null;
|
|
337
|
+
/** First available cursor:// link for a source location. */
|
|
338
|
+
declare function cursorUrl(file: {
|
|
339
|
+
absolutePath?: string;
|
|
340
|
+
file?: string;
|
|
341
|
+
line?: number;
|
|
342
|
+
column?: number;
|
|
343
|
+
}): string | null;
|
|
344
|
+
|
|
345
|
+
export { type AppMemberNode, type AppModuleNode, type AppSnapshot, type ClientHello, DEFAULT_REDACT_KEYS, DEVTOOLS_EVENTS, type DevToolsEvent, type DevToolsEventMap, type DevToolsEventName, type DevToolsMessage, type EditorKind, type ErrorPayload, type GatewayConnectionPayload, type GatewayMessagePayload, type LogLevel, type LogPayload, MAX_CAPTURE_BYTES, MAX_DEPTH, PROTOCOL_VERSION, type PerformanceSnapshot, type ProjectInfo, type QueryPayload, type RedactOptions, Redactor, type RequestCompletedPayload, type RequestStartedPayload, type SourceLocation, type StateSnapshot, type TimelineSpan, buildEditorUrl, createMessage, cursorUrl, defaultRedactor, detectPlatform, isDevToolsEventName, parseMessage, randomId, redactText, redactValue, vscodeUrl };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var PROTOCOL_VERSION = 1;
|
|
3
|
+
|
|
4
|
+
// src/events.ts
|
|
5
|
+
var DEVTOOLS_EVENTS = [
|
|
6
|
+
"project.connected",
|
|
7
|
+
"project.disconnected",
|
|
8
|
+
"request.started",
|
|
9
|
+
"request.completed",
|
|
10
|
+
"log.created",
|
|
11
|
+
"error.created",
|
|
12
|
+
"query.executed",
|
|
13
|
+
"websocket.connected",
|
|
14
|
+
"websocket.message",
|
|
15
|
+
"performance.updated",
|
|
16
|
+
"app.snapshot",
|
|
17
|
+
"client.hello",
|
|
18
|
+
"client.welcome",
|
|
19
|
+
"stream.pause",
|
|
20
|
+
"stream.resume",
|
|
21
|
+
"state.clear",
|
|
22
|
+
"state.snapshot",
|
|
23
|
+
"state.ack",
|
|
24
|
+
"error"
|
|
25
|
+
];
|
|
26
|
+
function isDevToolsEventName(name) {
|
|
27
|
+
return DEVTOOLS_EVENTS.includes(name);
|
|
28
|
+
}
|
|
29
|
+
function createMessage(event, payload, options) {
|
|
30
|
+
return {
|
|
31
|
+
v: PROTOCOL_VERSION,
|
|
32
|
+
id: options?.id ?? randomId(),
|
|
33
|
+
projectId: options?.projectId,
|
|
34
|
+
ts: options?.ts ?? Date.now(),
|
|
35
|
+
event,
|
|
36
|
+
payload
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function randomId(prefix) {
|
|
40
|
+
const core = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID().replace(/-/g, "").slice(0, 16) : Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
|
|
41
|
+
return prefix ? `${prefix}_${core}` : core;
|
|
42
|
+
}
|
|
43
|
+
function parseMessage(raw) {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(raw);
|
|
46
|
+
if (typeof parsed === "object" && parsed !== null && "event" in parsed && "payload" in parsed && isDevToolsEventName(String(parsed.event))) {
|
|
47
|
+
return parsed;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/redact.ts
|
|
56
|
+
var DEFAULT_REDACT_KEYS = [
|
|
57
|
+
"password",
|
|
58
|
+
"token",
|
|
59
|
+
"access_token",
|
|
60
|
+
"accessToken",
|
|
61
|
+
"refresh_token",
|
|
62
|
+
"refreshToken",
|
|
63
|
+
"authorization",
|
|
64
|
+
"cookie",
|
|
65
|
+
"cookies",
|
|
66
|
+
"secret",
|
|
67
|
+
"apiKey",
|
|
68
|
+
"api_key",
|
|
69
|
+
"client_secret",
|
|
70
|
+
"clientSecret",
|
|
71
|
+
"private_key",
|
|
72
|
+
"privateKey",
|
|
73
|
+
"session",
|
|
74
|
+
"set-cookie"
|
|
75
|
+
];
|
|
76
|
+
var MAX_CAPTURE_BYTES = 4 * 1024;
|
|
77
|
+
var MAX_DEPTH = 4;
|
|
78
|
+
var DEFAULT_PLACEHOLDER = "[REDACTED]";
|
|
79
|
+
var TRUNCATION_SUFFIX = "\u2026[truncated]";
|
|
80
|
+
var Redactor = class {
|
|
81
|
+
deny;
|
|
82
|
+
allow;
|
|
83
|
+
placeholder;
|
|
84
|
+
maxBytes;
|
|
85
|
+
maxDepth;
|
|
86
|
+
constructor(options) {
|
|
87
|
+
this.deny = new Set([...DEFAULT_REDACT_KEYS, ...options?.redact ?? []].map(normalizeKey));
|
|
88
|
+
this.allow = new Set((options?.allow ?? []).map(normalizeKey));
|
|
89
|
+
this.placeholder = options?.placeholder ?? DEFAULT_PLACEHOLDER;
|
|
90
|
+
this.maxBytes = options?.maxBytes ?? MAX_CAPTURE_BYTES;
|
|
91
|
+
this.maxDepth = options?.maxDepth ?? MAX_DEPTH;
|
|
92
|
+
}
|
|
93
|
+
/** True when the given key is sensitive and not allowed. */
|
|
94
|
+
isSensitive(key) {
|
|
95
|
+
const normalized = normalizeKey(key);
|
|
96
|
+
if (this.allow.has(normalized)) return false;
|
|
97
|
+
if (this.deny.has(normalized)) return true;
|
|
98
|
+
for (const denied of this.deny) {
|
|
99
|
+
if (normalized.includes(denied)) return true;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Deep-copy a value while redacting sensitive keys, truncating oversized
|
|
105
|
+
* strings and enforcing depth limits. Circular references become '[Circular]'.
|
|
106
|
+
*/
|
|
107
|
+
redact(value, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
108
|
+
if (value === null || typeof value !== "object") {
|
|
109
|
+
return redactPrimitive(value);
|
|
110
|
+
}
|
|
111
|
+
if (seen.has(value)) return "[Circular]";
|
|
112
|
+
if (depth >= this.maxDepth) return "[MaxDepth]";
|
|
113
|
+
seen.add(value);
|
|
114
|
+
try {
|
|
115
|
+
if (Array.isArray(value)) {
|
|
116
|
+
return value.slice(0, 50).map((item) => this.redact(item, depth + 1, seen));
|
|
117
|
+
}
|
|
118
|
+
if (value instanceof Error) {
|
|
119
|
+
return { name: value.name, message: value.message };
|
|
120
|
+
}
|
|
121
|
+
if (value instanceof Date) return value.toISOString();
|
|
122
|
+
const out = {};
|
|
123
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
124
|
+
out[key] = this.isSensitive(key) ? this.placeholder : this.redact(raw, depth + 1, seen);
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
} finally {
|
|
128
|
+
seen.delete(value);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** Redact sensitive substrings (key=value / key: value / bearer tokens) from free-form text. */
|
|
132
|
+
redactString(text) {
|
|
133
|
+
let out = text;
|
|
134
|
+
for (const denied of this.deny) {
|
|
135
|
+
const camel = denied;
|
|
136
|
+
const pattern = new RegExp(`(${escapeRegExp(camel)}|${escapeRegExp(denied.replace(/[-_]/g, ""))})\\s*[=:]\\s*([^\\s,&;]+)`, "gi");
|
|
137
|
+
out = out.replace(pattern, `$1=${this.placeholder}`);
|
|
138
|
+
}
|
|
139
|
+
out = out.replace(/bearer\s+[a-z0-9._-]+/gi, `Bearer ${this.placeholder}`);
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
/** Serialize a value safely: redacted, size-capped, never throws. */
|
|
143
|
+
serialize(value) {
|
|
144
|
+
try {
|
|
145
|
+
const safe = this.redact(value);
|
|
146
|
+
const json = JSON.stringify(safe) ?? String(safe);
|
|
147
|
+
return truncateUtf8(json, this.maxBytes, TRUNCATION_SUFFIX);
|
|
148
|
+
} catch {
|
|
149
|
+
return "[Unserializable]";
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
var defaultRedactor = new Redactor();
|
|
154
|
+
function redactValue(value) {
|
|
155
|
+
return defaultRedactor.redact(value);
|
|
156
|
+
}
|
|
157
|
+
function redactText(text) {
|
|
158
|
+
return defaultRedactor.redactString(text);
|
|
159
|
+
}
|
|
160
|
+
function redactPrimitive(value) {
|
|
161
|
+
if (typeof value === "string") return defaultRedactor.redactString(value);
|
|
162
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) return value;
|
|
163
|
+
if (typeof value === "function") return "[Function]";
|
|
164
|
+
if (typeof value === "bigint") return value.toString() + "n";
|
|
165
|
+
return String(value);
|
|
166
|
+
}
|
|
167
|
+
function normalizeKey(key) {
|
|
168
|
+
return key.toLowerCase().replace(/[-_\s]/g, "");
|
|
169
|
+
}
|
|
170
|
+
function escapeRegExp(text) {
|
|
171
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
172
|
+
}
|
|
173
|
+
function truncateUtf8(text, maxBytes, suffix) {
|
|
174
|
+
const buf = Buffer.from(text, "utf8");
|
|
175
|
+
if (buf.length <= maxBytes) return text;
|
|
176
|
+
const cut = Math.max(0, maxBytes - Buffer.byteLength(suffix, "utf8"));
|
|
177
|
+
return buf.subarray(0, cut).toString("utf8") + suffix;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/editor-urls.ts
|
|
181
|
+
var EDITOR_SCHEMES = {
|
|
182
|
+
vscode: "vscode",
|
|
183
|
+
cursor: "cursor",
|
|
184
|
+
zed: "zed",
|
|
185
|
+
sublime: "subl"
|
|
186
|
+
};
|
|
187
|
+
function detectPlatform(platformGetter) {
|
|
188
|
+
try {
|
|
189
|
+
const platform = platformGetter ? platformGetter() : process.platform;
|
|
190
|
+
if (platform === "win32" || platform === "darwin" || platform === "linux") return platform;
|
|
191
|
+
return "unknown";
|
|
192
|
+
} catch {
|
|
193
|
+
return "unknown";
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function encodeFilePath(absolutePath, platform) {
|
|
197
|
+
const normalized = platform === "win32" ? absolutePath.replace(/\\/g, "/") : absolutePath;
|
|
198
|
+
const withDrive = platform === "win32" && /^[A-Za-z]:\//.test(normalized) ? `/${normalized}` : normalized;
|
|
199
|
+
return withDrive.replace(/^\//, "").split("/").map((segment, index) => index === 0 && /^[A-Za-z]:$/.test(segment) ? segment : encodeURIComponent(segment)).join("/");
|
|
200
|
+
}
|
|
201
|
+
function buildEditorUrl(editor, file, platformGetter) {
|
|
202
|
+
const absolutePath = file.absolutePath ?? file.file;
|
|
203
|
+
if (!absolutePath) return null;
|
|
204
|
+
const platform = detectPlatform(platformGetter);
|
|
205
|
+
const scheme = EDITOR_SCHEMES[editor];
|
|
206
|
+
const path = encodeFilePath(absolutePath, platform);
|
|
207
|
+
const line = file.line ?? 1;
|
|
208
|
+
const column = file.column ?? 1;
|
|
209
|
+
if (editor === "sublime") {
|
|
210
|
+
return `subl://open?url=file://${path}&line=${line}&column=${column}`;
|
|
211
|
+
}
|
|
212
|
+
return `${scheme}://file/${path}:${line}:${column}`;
|
|
213
|
+
}
|
|
214
|
+
function vscodeUrl(file) {
|
|
215
|
+
return buildEditorUrl("vscode", file);
|
|
216
|
+
}
|
|
217
|
+
function cursorUrl(file) {
|
|
218
|
+
return buildEditorUrl("cursor", file);
|
|
219
|
+
}
|
|
220
|
+
export {
|
|
221
|
+
DEFAULT_REDACT_KEYS,
|
|
222
|
+
DEVTOOLS_EVENTS,
|
|
223
|
+
MAX_CAPTURE_BYTES,
|
|
224
|
+
MAX_DEPTH,
|
|
225
|
+
PROTOCOL_VERSION,
|
|
226
|
+
Redactor,
|
|
227
|
+
buildEditorUrl,
|
|
228
|
+
createMessage,
|
|
229
|
+
cursorUrl,
|
|
230
|
+
defaultRedactor,
|
|
231
|
+
detectPlatform,
|
|
232
|
+
isDevToolsEventName,
|
|
233
|
+
parseMessage,
|
|
234
|
+
randomId,
|
|
235
|
+
redactText,
|
|
236
|
+
redactValue,
|
|
237
|
+
vscodeUrl
|
|
238
|
+
};
|
|
239
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/events.ts","../src/redact.ts","../src/editor-urls.ts"],"sourcesContent":["/** Wire protocol version. Bump on breaking payload changes. */\nexport const PROTOCOL_VERSION = 1 as const;\n\n/** JSON-RPC-ish envelope shared by every message on the wire. */\nexport interface DevToolsMessage<T = unknown> {\n /** Envelope format version. */\n v: typeof PROTOCOL_VERSION;\n /** Unique message id. */\n id: string;\n /** Originating project (absent for client->server handshake-less control traffic). */\n projectId?: string;\n /** Milliseconds since epoch. */\n ts: number;\n /** Event discriminator. */\n event: DevToolsEventName;\n /** Event payload. */\n payload: T;\n}\n\n/** All event names supported by protocol v1. */\nexport type DevToolsEventName =\n // lifecycle\n | 'project.connected'\n | 'project.disconnected'\n // http\n | 'request.started'\n | 'request.completed'\n // observability\n | 'log.created'\n | 'error.created'\n | 'query.executed'\n // realtime\n | 'websocket.connected'\n | 'websocket.message'\n // perf\n | 'performance.updated'\n // application graph\n | 'app.snapshot'\n // control plane\n | 'client.hello'\n | 'client.welcome'\n | 'stream.pause'\n | 'stream.resume'\n | 'state.clear'\n | 'state.snapshot'\n | 'state.ack'\n | 'error';\n\n/** Union of every typed payload keyed by its event name. */\nexport interface DevToolsEventMap {\n 'project.connected': ProjectInfo;\n 'project.disconnected': { projectId: string; reason?: string };\n 'request.started': RequestStartedPayload;\n 'request.completed': RequestCompletedPayload;\n 'log.created': LogPayload;\n 'error.created': ErrorPayload;\n 'query.executed': QueryPayload;\n 'websocket.connected': GatewayConnectionPayload;\n 'websocket.message': GatewayMessagePayload;\n 'performance.updated': PerformanceSnapshot;\n 'app.snapshot': AppSnapshot;\n 'client.hello': ClientHello;\n 'client.welcome': { serverVersion: string; protocol: typeof PROTOCOL_VERSION };\n 'stream.pause': Record<string, never>;\n 'stream.resume': Record<string, never>;\n 'state.clear': { scope: 'logs' | 'requests' | 'errors' | 'queries' | 'all' };\n 'state.snapshot': StateSnapshot;\n 'state.ack': { ok: true };\n error: { code: string; message: string };\n}\n\n/** Discriminated union of all wire messages. */\nexport type DevToolsEvent = {\n [K in DevToolsEventName]: DevToolsMessage<DevToolsEventMap[K]>;\n}[DevToolsEventName];\n\n/** Metadata every NestJS application reports when it connects. */\nexport interface ProjectInfo {\n projectId: string;\n projectName: string;\n environment: string;\n hostname: string;\n port: number | null;\n pid: number;\n runtime: string;\n runtimeVersion: string;\n nodeVersion: string;\n nestjsVersion: string | null;\n sdkVersion: string;\n}\n\n/** One timeline entry of a request (guard, interceptor, service call...). */\nexport interface TimelineSpan {\n /** Logical layer, e.g. 'middleware' | 'guard' | 'interceptor' | 'pipe' | 'controller' | 'service' | 'database' | 'response'. */\n layer: string;\n /** Human label, e.g. 'JwtAuthGuard' or 'SELECT users'. */\n label: string;\n /** ms */\n duration: number;\n startedAt: number;\n status?: 'ok' | 'error';\n detail?: string;\n}\n\n/** Emitted the moment a request enters the SDK. */\nexport interface RequestStartedPayload {\n requestId: string;\n projectId: string;\n method: string;\n url: string;\n route?: string;\n httpVersion?: string;\n headers: Record<string, string>;\n query: Record<string, unknown>;\n params?: Record<string, unknown>;\n ip?: string;\n userAgent?: string;\n startedAt: number;\n}\n\n/** Emitted when the response finishes. */\nexport interface RequestCompletedPayload {\n requestId: string;\n projectId: string;\n method: string;\n url: string;\n route?: string;\n statusCode: number;\n duration: number;\n startedAt: number;\n timeline: TimelineSpan[];\n query?: Record<string, unknown>;\n responsePreview?: string;\n responseBody?: unknown;\n requestBody?: unknown;\n errored: boolean;\n}\n\n/** A captured console or logger entry. */\nexport interface LogPayload {\n requestId?: string;\n projectId: string;\n level: LogLevel;\n message: string;\n arguments?: unknown[];\n stack?: string;\n source?: SourceLocation;\n context?: string;\n processId: number;\n timestamp: number;\n}\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'verbose';\n\n/** File/line/column triple resolved from source maps when available. */\nexport interface SourceLocation {\n file: string;\n line: number;\n column: number;\n /** Absolute path when resolvable on the host machine. */\n absolutePath?: string;\n /** function name if the stack exposed one */\n function?: string;\n}\n\n/** A captured exception with source mapping. */\nexport interface ErrorPayload {\n requestId?: string;\n projectId: string;\n name: string;\n message: string;\n stack?: string;\n source?: SourceLocation;\n /** stable hash for grouping identical errors */\n fingerprint: string;\n context?: string;\n request?: { method: string; url: string; statusCode?: number };\n controller?: string;\n service?: string;\n timestamp: number;\n /** server-side occurrence count for grouped errors */\n occurrences?: number;\n}\n\n/** A captured database query. */\nexport interface QueryPayload {\n requestId?: string;\n projectId: string;\n provider: 'prisma' | 'typeorm' | 'sequelize' | 'mikroorm' | 'other';\n sql: string;\n duration: number;\n database?: string;\n parameters?: unknown[];\n timestamp: number;\n}\n\n/** Gateway-level connection snapshot. */\nexport interface GatewayConnectionPayload {\n projectId: string;\n gateway: string;\n namespace: string;\n connections: number;\n timestamp: number;\n}\n\n/** Individual gateway event flow. */\nexport interface GatewayMessagePayload {\n projectId: string;\n gateway: string;\n event: string;\n direction: 'received' | 'sent';\n payloadSize: number;\n error?: string;\n duration?: number;\n requestId?: string;\n timestamp: number;\n}\n\n/** Point-in-time process metrics. */\nexport interface PerformanceSnapshot {\n projectId: string;\n timestamp: number;\n cpuPercent: number;\n memoryUsedBytes: number;\n memoryTotalBytes: number;\n heapUsedBytes: number;\n heapTotalBytes: number;\n eventLoopLagMs: number;\n activeRequests: number;\n requestsPerSecond: number;\n averageLatencyMs: number;\n p95LatencyMs: number;\n p99LatencyMs: number;\n errorsPerSecond: number;\n /** true when the process reports memory pressure */\n memoryPressure?: boolean;\n}\n\n/** Static description of the NestJS application graph. */\nexport interface AppSnapshot {\n projectId: string;\n projectName: string;\n modules: AppModuleNode[];\n nestjsVersion: string | null;\n capturedAt: number;\n}\n\n/** A module and its members in the application graph. */\nexport interface AppModuleNode {\n name: string;\n imports: string[];\n controllers: AppMemberNode[];\n providers: AppMemberNode[];\n exports: string[];\n}\n\n/** A member (controller/provider/guard/pipe/...) inside a module. */\nexport interface AppMemberNode {\n name: string;\n type: 'controller' | 'provider' | 'guard' | 'interceptor' | 'pipe' | 'filter' | 'gateway';\n routes?: string[];\n}\n\n/** What a dashboard/control client announces when it connects. */\nexport interface ClientHello {\n kind: 'dashboard' | 'cli' | 'other';\n name?: string;\n version?: string;\n}\n\n/** Full server state handed to newly connected dashboards. */\nexport interface StateSnapshot {\n projects: ProjectInfo[];\n requests: RequestCompletedPayload[];\n logs: LogPayload[];\n errors: ErrorPayload[];\n queries: QueryPayload[];\n performance: Record<string, PerformanceSnapshot>;\n apps: Record<string, AppSnapshot>;\n}\n","import type { DevToolsEventMap, DevToolsEventName, DevToolsMessage } from './types';\nimport { PROTOCOL_VERSION } from './types';\n\n/** Const array of every event name (runtime mirror of the type union). */\nexport const DEVTOOLS_EVENTS = [\n 'project.connected',\n 'project.disconnected',\n 'request.started',\n 'request.completed',\n 'log.created',\n 'error.created',\n 'query.executed',\n 'websocket.connected',\n 'websocket.message',\n 'performance.updated',\n 'app.snapshot',\n 'client.hello',\n 'client.welcome',\n 'stream.pause',\n 'stream.resume',\n 'state.clear',\n 'state.snapshot',\n 'state.ack',\n 'error',\n] as const satisfies readonly DevToolsEventName[];\n\n/** True when `name` is a known protocol event. */\nexport function isDevToolsEventName(name: string): name is DevToolsEventName {\n return (DEVTOOLS_EVENTS as readonly string[]).includes(name);\n}\n\n/** Build a fully-typed wire message. */\nexport function createMessage<K extends DevToolsEventName>(\n event: K,\n payload: DevToolsEventMap[K],\n options?: { projectId?: string; id?: string; ts?: number },\n): DevToolsMessage<DevToolsEventMap[K]> {\n return {\n v: PROTOCOL_VERSION,\n id: options?.id ?? randomId(),\n projectId: options?.projectId,\n ts: options?.ts ?? Date.now(),\n event,\n payload,\n };\n}\n\n/** Cheap collision-resistant id (no crypto dependency needed for wire correlation). */\nexport function randomId(prefix?: string): string {\n const core =\n typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID().replace(/-/g, '').slice(0, 16)\n : Math.random().toString(36).slice(2, 10) + Date.now().toString(36);\n return prefix ? `${prefix}_${core}` : core;\n}\n\n/** Type guard narrowing an unknown parsed frame into a DevToolsMessage. */\nexport function parseMessage(raw: string): DevToolsMessage | null {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'event' in parsed &&\n 'payload' in parsed &&\n isDevToolsEventName(String((parsed as { event: unknown }).event))\n ) {\n return parsed as DevToolsMessage;\n }\n return null;\n } catch {\n return null;\n }\n}\n","/** Default keys that are always redacted unless explicitly allowed. */\nexport const DEFAULT_REDACT_KEYS = [\n 'password',\n 'token',\n 'access_token',\n 'accessToken',\n 'refresh_token',\n 'refreshToken',\n 'authorization',\n 'cookie',\n 'cookies',\n 'secret',\n 'apiKey',\n 'api_key',\n 'client_secret',\n 'clientSecret',\n 'private_key',\n 'privateKey',\n 'session',\n 'set-cookie',\n] as const;\n\n/** Default capture ceilings shared by SDK and server. */\nexport const MAX_CAPTURE_BYTES = 4 * 1024;\nexport const MAX_DEPTH = 4;\n\n/** Options for deep redaction of arbitrary values. */\nexport interface RedactOptions {\n /** Extra key names to redact (case-insensitive, partial match allowed). */\n redact?: string[];\n /** Keys that should never be redacted even if they look sensitive. */\n allow?: string[];\n /** Placeholder string used for redacted values. */\n placeholder?: string;\n /** Max serialized size before truncation. */\n maxBytes?: number;\n /** Max object depth. */\n maxDepth?: number;\n}\n\nconst DEFAULT_PLACEHOLDER = '[REDACTED]';\nconst TRUNCATION_SUFFIX = '…[truncated]';\n\n/** Normalized set of denylist and allowlist key matchers. */\nexport class Redactor {\n private readonly deny: Set<string>;\n private readonly allow: Set<string>;\n private readonly placeholder: string;\n private readonly maxBytes: number;\n private readonly maxDepth: number;\n\n constructor(options?: RedactOptions) {\n this.deny = new Set([...DEFAULT_REDACT_KEYS, ...(options?.redact ?? [])].map(normalizeKey));\n this.allow = new Set((options?.allow ?? []).map(normalizeKey));\n this.placeholder = options?.placeholder ?? DEFAULT_PLACEHOLDER;\n this.maxBytes = options?.maxBytes ?? MAX_CAPTURE_BYTES;\n this.maxDepth = options?.maxDepth ?? MAX_DEPTH;\n }\n\n /** True when the given key is sensitive and not allowed. */\n isSensitive(key: string): boolean {\n const normalized = normalizeKey(key);\n if (this.allow.has(normalized)) return false;\n if (this.deny.has(normalized)) return true;\n // partial match: `userPassword`, `authTokenValue`, `x-api-key`...\n for (const denied of this.deny) {\n if (normalized.includes(denied)) return true;\n }\n return false;\n }\n\n /**\n * Deep-copy a value while redacting sensitive keys, truncating oversized\n * strings and enforcing depth limits. Circular references become '[Circular]'.\n */\n redact(value: unknown, depth = 0, seen = new Set<unknown>()): unknown {\n if (value === null || typeof value !== 'object') {\n return redactPrimitive(value);\n }\n if (seen.has(value)) return '[Circular]';\n if (depth >= this.maxDepth) return '[MaxDepth]';\n\n seen.add(value);\n try {\n if (Array.isArray(value)) {\n return value.slice(0, 50).map((item) => this.redact(item, depth + 1, seen));\n }\n if (value instanceof Error) {\n return { name: value.name, message: value.message };\n }\n if (value instanceof Date) return value.toISOString();\n\n const out: Record<string, unknown> = {};\n for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {\n out[key] = this.isSensitive(key) ? this.placeholder : this.redact(raw, depth + 1, seen);\n }\n return out;\n } finally {\n seen.delete(value);\n }\n }\n\n /** Redact sensitive substrings (key=value / key: value / bearer tokens) from free-form text. */\n redactString(text: string): string {\n let out = text;\n for (const denied of this.deny) {\n const camel = denied;\n const pattern = new RegExp(`(${escapeRegExp(camel)}|${escapeRegExp(denied.replace(/[-_]/g, ''))})\\\\s*[=:]\\\\s*([^\\\\s,&;]+)`, 'gi');\n out = out.replace(pattern, `$1=${this.placeholder}`);\n }\n out = out.replace(/bearer\\s+[a-z0-9._-]+/gi, `Bearer ${this.placeholder}`);\n return out;\n }\n\n /** Serialize a value safely: redacted, size-capped, never throws. */\n serialize(value: unknown): string {\n try {\n const safe = this.redact(value);\n const json = JSON.stringify(safe) ?? String(safe);\n return truncateUtf8(json, this.maxBytes, TRUNCATION_SUFFIX);\n } catch {\n return '[Unserializable]';\n }\n }\n}\n\n/** Shared default redactor instance for quick helpers. */\nexport const defaultRedactor = new Redactor();\n\n/** Convenience: deep-redact with the default policy. */\nexport function redactValue(value: unknown): unknown {\n return defaultRedactor.redact(value);\n}\n\n/** Convenience: scrub a free-form string with the default policy. */\nexport function redactText(text: string): string {\n return defaultRedactor.redactString(text);\n}\n\nfunction redactPrimitive(value: unknown): unknown {\n if (typeof value === 'string') return defaultRedactor.redactString(value);\n if (typeof value === 'number' || typeof value === 'boolean' || value === null) return value;\n if (typeof value === 'function') return '[Function]';\n if (typeof value === 'bigint') return value.toString() + 'n';\n return String(value);\n}\n\nfunction normalizeKey(key: string): string {\n return key.toLowerCase().replace(/[-_\\s]/g, '');\n}\n\nfunction escapeRegExp(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/** UTF-8-safe truncation that never splits a multi-byte character. */\nfunction truncateUtf8(text: string, maxBytes: number, suffix: string): string {\n const buf = Buffer.from(text, 'utf8');\n if (buf.length <= maxBytes) return text;\n const cut = Math.max(0, maxBytes - Buffer.byteLength(suffix, 'utf8'));\n return buf.subarray(0, cut).toString('utf8') + suffix;\n}\n","/**\n * Deep links that open a file at a specific line/column in an editor.\n * Paths are generated for the OS the DevTools *server* runs on — never assume\n * Windows or POSIX layout; detect it at runtime.\n */\n\nexport type EditorKind = 'vscode' | 'cursor' | 'zed' | 'sublime';\n\nconst EDITOR_SCHEMES: Record<EditorKind, string> = {\n vscode: 'vscode',\n cursor: 'cursor',\n zed: 'zed',\n sublime: 'subl',\n};\n\n/** Returns 'win32' | 'darwin' | 'linux' | 'unknown' without touching process when unavailable. */\nexport function detectPlatform(platformGetter?: () => string): 'win32' | 'darwin' | 'linux' | 'unknown' {\n try {\n const platform = platformGetter ? platformGetter() : process.platform;\n if (platform === 'win32' || platform === 'darwin' || platform === 'linux') return platform;\n return 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nfunction encodeFilePath(absolutePath: string, platform: string): string {\n // VS Code expects forward slashes; keep drive letters like /C:/... on Windows.\n const normalized = platform === 'win32' ? absolutePath.replace(/\\\\/g, '/') : absolutePath;\n const withDrive = platform === 'win32' && /^[A-Za-z]:\\//.test(normalized) ? `/${normalized}` : normalized;\n // the scheme already carries 'file/', so drop any leading slash;\n // preserve the drive-letter colon (C:) which VS Code expects unencoded\n return withDrive\n .replace(/^\\//, '')\n .split('/')\n .map((segment, index) => (index === 0 && /^[A-Za-z]:$/.test(segment) ? segment : encodeURIComponent(segment)))\n .join('/');\n}\n\n/**\n * Build an editor deep link such as:\n * vscode://file/Users/me/app/src/users.service.ts:87:21\n */\nexport function buildEditorUrl(\n editor: EditorKind,\n file: { absolutePath?: string; file?: string; line?: number; column?: number },\n platformGetter?: () => string,\n): string | null {\n const absolutePath = file.absolutePath ?? file.file;\n if (!absolutePath) return null;\n\n const platform = detectPlatform(platformGetter);\n const scheme = EDITOR_SCHEMES[editor];\n const path = encodeFilePath(absolutePath, platform);\n const line = file.line ?? 1;\n const column = file.column ?? 1;\n\n if (editor === 'sublime') {\n return `subl://open?url=file://${path}&line=${line}&column=${column}`;\n }\n return `${scheme}://file/${path}:${line}:${column}`;\n}\n\n/** First available vscode:// link for a source location. */\nexport function vscodeUrl(file: { absolutePath?: string; file?: string; line?: number; column?: number }): string | null {\n return buildEditorUrl('vscode', file);\n}\n\n/** First available cursor:// link for a source location. */\nexport function cursorUrl(file: { absolutePath?: string; file?: string; line?: number; column?: number }): string | null {\n return buildEditorUrl('cursor', file);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@angelitosystems/devtools-protocol",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Typed WebSocket protocol shared by the NestJS DevTools SDK, server and dashboard.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean --sourcemap",
|
|
22
|
+
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
23
|
+
"test": "bun test"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"tsup": "^8.3.0",
|
|
27
|
+
"typescript": "^5.7.0"
|
|
28
|
+
}
|
|
29
|
+
}
|