@angelitosystems/devtools-protocol 1.0.6 → 1.0.8
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 +12 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(index_exports, {
|
|
|
24
24
|
DEVTOOLS_EVENTS: () => DEVTOOLS_EVENTS,
|
|
25
25
|
MAX_CAPTURE_BYTES: () => MAX_CAPTURE_BYTES,
|
|
26
26
|
MAX_DEPTH: () => MAX_DEPTH,
|
|
27
|
+
MIN_SUPPORTED_PROTOCOL_VERSION: () => MIN_SUPPORTED_PROTOCOL_VERSION,
|
|
27
28
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
28
29
|
Redactor: () => Redactor,
|
|
29
30
|
buildEditorUrl: () => buildEditorUrl,
|
|
@@ -32,6 +33,7 @@ __export(index_exports, {
|
|
|
32
33
|
defaultRedactor: () => defaultRedactor,
|
|
33
34
|
detectPlatform: () => detectPlatform,
|
|
34
35
|
isDevToolsEventName: () => isDevToolsEventName,
|
|
36
|
+
isSupportedProtocolVersion: () => isSupportedProtocolVersion,
|
|
35
37
|
parseMessage: () => parseMessage,
|
|
36
38
|
randomId: () => randomId,
|
|
37
39
|
redactText: () => redactText,
|
|
@@ -42,6 +44,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
42
44
|
|
|
43
45
|
// src/types.ts
|
|
44
46
|
var PROTOCOL_VERSION = 1;
|
|
47
|
+
var MIN_SUPPORTED_PROTOCOL_VERSION = 1;
|
|
45
48
|
|
|
46
49
|
// src/events.ts
|
|
47
50
|
var DEVTOOLS_EVENTS = [
|
|
@@ -55,6 +58,9 @@ var DEVTOOLS_EVENTS = [
|
|
|
55
58
|
"websocket.connected",
|
|
56
59
|
"websocket.message",
|
|
57
60
|
"performance.updated",
|
|
61
|
+
"profile.started",
|
|
62
|
+
"profile.completed",
|
|
63
|
+
"plugin.event",
|
|
58
64
|
"app.snapshot",
|
|
59
65
|
"client.hello",
|
|
60
66
|
"client.welcome",
|
|
@@ -85,7 +91,7 @@ function randomId(prefix) {
|
|
|
85
91
|
function parseMessage(raw) {
|
|
86
92
|
try {
|
|
87
93
|
const parsed = JSON.parse(raw);
|
|
88
|
-
if (typeof parsed === "object" && parsed !== null && "event" in parsed && "payload" in parsed && isDevToolsEventName(String(parsed.event))) {
|
|
94
|
+
if (typeof parsed === "object" && parsed !== null && "event" in parsed && "payload" in parsed && "v" in parsed && Number(parsed.v) === PROTOCOL_VERSION && typeof parsed.id === "string" && typeof parsed.ts === "number" && isDevToolsEventName(String(parsed.event))) {
|
|
89
95
|
return parsed;
|
|
90
96
|
}
|
|
91
97
|
return null;
|
|
@@ -93,6 +99,9 @@ function parseMessage(raw) {
|
|
|
93
99
|
return null;
|
|
94
100
|
}
|
|
95
101
|
}
|
|
102
|
+
function isSupportedProtocolVersion(version) {
|
|
103
|
+
return Number.isInteger(version) && version >= MIN_SUPPORTED_PROTOCOL_VERSION && version <= PROTOCOL_VERSION;
|
|
104
|
+
}
|
|
96
105
|
|
|
97
106
|
// src/redact.ts
|
|
98
107
|
var DEFAULT_REDACT_KEYS = [
|
|
@@ -265,6 +274,7 @@ function cursorUrl(file) {
|
|
|
265
274
|
DEVTOOLS_EVENTS,
|
|
266
275
|
MAX_CAPTURE_BYTES,
|
|
267
276
|
MAX_DEPTH,
|
|
277
|
+
MIN_SUPPORTED_PROTOCOL_VERSION,
|
|
268
278
|
PROTOCOL_VERSION,
|
|
269
279
|
Redactor,
|
|
270
280
|
buildEditorUrl,
|
|
@@ -273,6 +283,7 @@ function cursorUrl(file) {
|
|
|
273
283
|
defaultRedactor,
|
|
274
284
|
detectPlatform,
|
|
275
285
|
isDevToolsEventName,
|
|
286
|
+
isSupportedProtocolVersion,
|
|
276
287
|
parseMessage,
|
|
277
288
|
randomId,
|
|
278
289
|
redactText,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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 /** Source location when available (file, line, column, function). */\n source?: SourceLocation;\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 headers?: Record<string, string>;\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 websocketConnections: GatewayConnectionPayload[];\n websocketMessages: GatewayMessagePayload[];\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":[]}
|
|
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;\nexport const MIN_SUPPORTED_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 | 'profile.started'\n | 'profile.completed'\n | 'plugin.event'\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 'profile.started': ProfileStartedPayload;\n 'profile.completed': ProfileCompletedPayload;\n 'plugin.event': PluginEventPayload;\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 /** Source location when available (file, line, column, function). */\n source?: SourceLocation;\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 headers?: Record<string, string>;\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/** A profiling session requested by a developer or extension. */\nexport interface ProfileStartedPayload {\n projectId: string;\n profileId: string;\n kind: 'cpu' | 'heap';\n startedAt: number;\n durationMs?: number;\n}\n\n/** Result metadata for a completed profiling session. */\nexport interface ProfileCompletedPayload {\n projectId: string;\n profileId: string;\n kind: 'cpu' | 'heap';\n startedAt: number;\n completedAt: number;\n data?: unknown;\n}\n\n/** Namespaced event emitted by a registered plugin. */\nexport interface PluginEventPayload {\n projectId: string;\n plugin: string;\n name: string;\n data?: unknown;\n timestamp: number;\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 websocketConnections: GatewayConnectionPayload[];\n websocketMessages: GatewayMessagePayload[];\n performance: Record<string, PerformanceSnapshot>;\n apps: Record<string, AppSnapshot>;\n}\n","import type { DevToolsEventMap, DevToolsEventName, DevToolsMessage } from './types';\nimport { MIN_SUPPORTED_PROTOCOL_VERSION, 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 'profile.started',\n 'profile.completed',\n 'plugin.event',\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 'v' in parsed &&\n Number((parsed as { v: unknown }).v) === PROTOCOL_VERSION &&\n typeof (parsed as { id?: unknown }).id === 'string' &&\n typeof (parsed as { ts?: unknown }).ts === 'number' &&\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\n/** True when a peer can safely communicate with this package. */\nexport function isSupportedProtocolVersion(version: number): boolean {\n return Number.isInteger(version) && version >= MIN_SUPPORTED_PROTOCOL_VERSION && version <= PROTOCOL_VERSION;\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
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Wire protocol version. Bump on breaking payload changes. */
|
|
2
2
|
declare const PROTOCOL_VERSION: 1;
|
|
3
|
+
declare const MIN_SUPPORTED_PROTOCOL_VERSION: 1;
|
|
3
4
|
/** JSON-RPC-ish envelope shared by every message on the wire. */
|
|
4
5
|
interface DevToolsMessage<T = unknown> {
|
|
5
6
|
/** Envelope format version. */
|
|
@@ -16,7 +17,7 @@ interface DevToolsMessage<T = unknown> {
|
|
|
16
17
|
payload: T;
|
|
17
18
|
}
|
|
18
19
|
/** 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
|
+
type DevToolsEventName = 'project.connected' | 'project.disconnected' | 'request.started' | 'request.completed' | 'log.created' | 'error.created' | 'query.executed' | 'websocket.connected' | 'websocket.message' | 'performance.updated' | 'profile.started' | 'profile.completed' | 'plugin.event' | 'app.snapshot' | 'client.hello' | 'client.welcome' | 'stream.pause' | 'stream.resume' | 'state.clear' | 'state.snapshot' | 'state.ack' | 'error';
|
|
20
21
|
/** Union of every typed payload keyed by its event name. */
|
|
21
22
|
interface DevToolsEventMap {
|
|
22
23
|
'project.connected': ProjectInfo;
|
|
@@ -32,6 +33,9 @@ interface DevToolsEventMap {
|
|
|
32
33
|
'websocket.connected': GatewayConnectionPayload;
|
|
33
34
|
'websocket.message': GatewayMessagePayload;
|
|
34
35
|
'performance.updated': PerformanceSnapshot;
|
|
36
|
+
'profile.started': ProfileStartedPayload;
|
|
37
|
+
'profile.completed': ProfileCompletedPayload;
|
|
38
|
+
'plugin.event': PluginEventPayload;
|
|
35
39
|
'app.snapshot': AppSnapshot;
|
|
36
40
|
'client.hello': ClientHello;
|
|
37
41
|
'client.welcome': {
|
|
@@ -213,6 +217,31 @@ interface PerformanceSnapshot {
|
|
|
213
217
|
/** true when the process reports memory pressure */
|
|
214
218
|
memoryPressure?: boolean;
|
|
215
219
|
}
|
|
220
|
+
/** A profiling session requested by a developer or extension. */
|
|
221
|
+
interface ProfileStartedPayload {
|
|
222
|
+
projectId: string;
|
|
223
|
+
profileId: string;
|
|
224
|
+
kind: 'cpu' | 'heap';
|
|
225
|
+
startedAt: number;
|
|
226
|
+
durationMs?: number;
|
|
227
|
+
}
|
|
228
|
+
/** Result metadata for a completed profiling session. */
|
|
229
|
+
interface ProfileCompletedPayload {
|
|
230
|
+
projectId: string;
|
|
231
|
+
profileId: string;
|
|
232
|
+
kind: 'cpu' | 'heap';
|
|
233
|
+
startedAt: number;
|
|
234
|
+
completedAt: number;
|
|
235
|
+
data?: unknown;
|
|
236
|
+
}
|
|
237
|
+
/** Namespaced event emitted by a registered plugin. */
|
|
238
|
+
interface PluginEventPayload {
|
|
239
|
+
projectId: string;
|
|
240
|
+
plugin: string;
|
|
241
|
+
name: string;
|
|
242
|
+
data?: unknown;
|
|
243
|
+
timestamp: number;
|
|
244
|
+
}
|
|
216
245
|
/** Static description of the NestJS application graph. */
|
|
217
246
|
interface AppSnapshot {
|
|
218
247
|
projectId: string;
|
|
@@ -255,7 +284,7 @@ interface StateSnapshot {
|
|
|
255
284
|
}
|
|
256
285
|
|
|
257
286
|
/** Const array of every event name (runtime mirror of the type union). */
|
|
258
|
-
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"];
|
|
287
|
+
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", "profile.started", "profile.completed", "plugin.event", "app.snapshot", "client.hello", "client.welcome", "stream.pause", "stream.resume", "state.clear", "state.snapshot", "state.ack", "error"];
|
|
259
288
|
/** True when `name` is a known protocol event. */
|
|
260
289
|
declare function isDevToolsEventName(name: string): name is DevToolsEventName;
|
|
261
290
|
/** Build a fully-typed wire message. */
|
|
@@ -268,6 +297,8 @@ declare function createMessage<K extends DevToolsEventName>(event: K, payload: D
|
|
|
268
297
|
declare function randomId(prefix?: string): string;
|
|
269
298
|
/** Type guard narrowing an unknown parsed frame into a DevToolsMessage. */
|
|
270
299
|
declare function parseMessage(raw: string): DevToolsMessage | null;
|
|
300
|
+
/** True when a peer can safely communicate with this package. */
|
|
301
|
+
declare function isSupportedProtocolVersion(version: number): boolean;
|
|
271
302
|
|
|
272
303
|
/** Default keys that are always redacted unless explicitly allowed. */
|
|
273
304
|
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"];
|
|
@@ -347,4 +378,4 @@ declare function cursorUrl(file: {
|
|
|
347
378
|
column?: number;
|
|
348
379
|
}): string | null;
|
|
349
380
|
|
|
350
|
-
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 };
|
|
381
|
+
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, MIN_SUPPORTED_PROTOCOL_VERSION, PROTOCOL_VERSION, type PerformanceSnapshot, type PluginEventPayload, type ProfileCompletedPayload, type ProfileStartedPayload, type ProjectInfo, type QueryPayload, type RedactOptions, Redactor, type RequestCompletedPayload, type RequestStartedPayload, type SourceLocation, type StateSnapshot, type TimelineSpan, buildEditorUrl, createMessage, cursorUrl, defaultRedactor, detectPlatform, isDevToolsEventName, isSupportedProtocolVersion, parseMessage, randomId, redactText, redactValue, vscodeUrl };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Wire protocol version. Bump on breaking payload changes. */
|
|
2
2
|
declare const PROTOCOL_VERSION: 1;
|
|
3
|
+
declare const MIN_SUPPORTED_PROTOCOL_VERSION: 1;
|
|
3
4
|
/** JSON-RPC-ish envelope shared by every message on the wire. */
|
|
4
5
|
interface DevToolsMessage<T = unknown> {
|
|
5
6
|
/** Envelope format version. */
|
|
@@ -16,7 +17,7 @@ interface DevToolsMessage<T = unknown> {
|
|
|
16
17
|
payload: T;
|
|
17
18
|
}
|
|
18
19
|
/** 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
|
+
type DevToolsEventName = 'project.connected' | 'project.disconnected' | 'request.started' | 'request.completed' | 'log.created' | 'error.created' | 'query.executed' | 'websocket.connected' | 'websocket.message' | 'performance.updated' | 'profile.started' | 'profile.completed' | 'plugin.event' | 'app.snapshot' | 'client.hello' | 'client.welcome' | 'stream.pause' | 'stream.resume' | 'state.clear' | 'state.snapshot' | 'state.ack' | 'error';
|
|
20
21
|
/** Union of every typed payload keyed by its event name. */
|
|
21
22
|
interface DevToolsEventMap {
|
|
22
23
|
'project.connected': ProjectInfo;
|
|
@@ -32,6 +33,9 @@ interface DevToolsEventMap {
|
|
|
32
33
|
'websocket.connected': GatewayConnectionPayload;
|
|
33
34
|
'websocket.message': GatewayMessagePayload;
|
|
34
35
|
'performance.updated': PerformanceSnapshot;
|
|
36
|
+
'profile.started': ProfileStartedPayload;
|
|
37
|
+
'profile.completed': ProfileCompletedPayload;
|
|
38
|
+
'plugin.event': PluginEventPayload;
|
|
35
39
|
'app.snapshot': AppSnapshot;
|
|
36
40
|
'client.hello': ClientHello;
|
|
37
41
|
'client.welcome': {
|
|
@@ -213,6 +217,31 @@ interface PerformanceSnapshot {
|
|
|
213
217
|
/** true when the process reports memory pressure */
|
|
214
218
|
memoryPressure?: boolean;
|
|
215
219
|
}
|
|
220
|
+
/** A profiling session requested by a developer or extension. */
|
|
221
|
+
interface ProfileStartedPayload {
|
|
222
|
+
projectId: string;
|
|
223
|
+
profileId: string;
|
|
224
|
+
kind: 'cpu' | 'heap';
|
|
225
|
+
startedAt: number;
|
|
226
|
+
durationMs?: number;
|
|
227
|
+
}
|
|
228
|
+
/** Result metadata for a completed profiling session. */
|
|
229
|
+
interface ProfileCompletedPayload {
|
|
230
|
+
projectId: string;
|
|
231
|
+
profileId: string;
|
|
232
|
+
kind: 'cpu' | 'heap';
|
|
233
|
+
startedAt: number;
|
|
234
|
+
completedAt: number;
|
|
235
|
+
data?: unknown;
|
|
236
|
+
}
|
|
237
|
+
/** Namespaced event emitted by a registered plugin. */
|
|
238
|
+
interface PluginEventPayload {
|
|
239
|
+
projectId: string;
|
|
240
|
+
plugin: string;
|
|
241
|
+
name: string;
|
|
242
|
+
data?: unknown;
|
|
243
|
+
timestamp: number;
|
|
244
|
+
}
|
|
216
245
|
/** Static description of the NestJS application graph. */
|
|
217
246
|
interface AppSnapshot {
|
|
218
247
|
projectId: string;
|
|
@@ -255,7 +284,7 @@ interface StateSnapshot {
|
|
|
255
284
|
}
|
|
256
285
|
|
|
257
286
|
/** Const array of every event name (runtime mirror of the type union). */
|
|
258
|
-
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"];
|
|
287
|
+
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", "profile.started", "profile.completed", "plugin.event", "app.snapshot", "client.hello", "client.welcome", "stream.pause", "stream.resume", "state.clear", "state.snapshot", "state.ack", "error"];
|
|
259
288
|
/** True when `name` is a known protocol event. */
|
|
260
289
|
declare function isDevToolsEventName(name: string): name is DevToolsEventName;
|
|
261
290
|
/** Build a fully-typed wire message. */
|
|
@@ -268,6 +297,8 @@ declare function createMessage<K extends DevToolsEventName>(event: K, payload: D
|
|
|
268
297
|
declare function randomId(prefix?: string): string;
|
|
269
298
|
/** Type guard narrowing an unknown parsed frame into a DevToolsMessage. */
|
|
270
299
|
declare function parseMessage(raw: string): DevToolsMessage | null;
|
|
300
|
+
/** True when a peer can safely communicate with this package. */
|
|
301
|
+
declare function isSupportedProtocolVersion(version: number): boolean;
|
|
271
302
|
|
|
272
303
|
/** Default keys that are always redacted unless explicitly allowed. */
|
|
273
304
|
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"];
|
|
@@ -347,4 +378,4 @@ declare function cursorUrl(file: {
|
|
|
347
378
|
column?: number;
|
|
348
379
|
}): string | null;
|
|
349
380
|
|
|
350
|
-
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 };
|
|
381
|
+
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, MIN_SUPPORTED_PROTOCOL_VERSION, PROTOCOL_VERSION, type PerformanceSnapshot, type PluginEventPayload, type ProfileCompletedPayload, type ProfileStartedPayload, type ProjectInfo, type QueryPayload, type RedactOptions, Redactor, type RequestCompletedPayload, type RequestStartedPayload, type SourceLocation, type StateSnapshot, type TimelineSpan, buildEditorUrl, createMessage, cursorUrl, defaultRedactor, detectPlatform, isDevToolsEventName, isSupportedProtocolVersion, parseMessage, randomId, redactText, redactValue, vscodeUrl };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/types.ts
|
|
2
2
|
var PROTOCOL_VERSION = 1;
|
|
3
|
+
var MIN_SUPPORTED_PROTOCOL_VERSION = 1;
|
|
3
4
|
|
|
4
5
|
// src/events.ts
|
|
5
6
|
var DEVTOOLS_EVENTS = [
|
|
@@ -13,6 +14,9 @@ var DEVTOOLS_EVENTS = [
|
|
|
13
14
|
"websocket.connected",
|
|
14
15
|
"websocket.message",
|
|
15
16
|
"performance.updated",
|
|
17
|
+
"profile.started",
|
|
18
|
+
"profile.completed",
|
|
19
|
+
"plugin.event",
|
|
16
20
|
"app.snapshot",
|
|
17
21
|
"client.hello",
|
|
18
22
|
"client.welcome",
|
|
@@ -43,7 +47,7 @@ function randomId(prefix) {
|
|
|
43
47
|
function parseMessage(raw) {
|
|
44
48
|
try {
|
|
45
49
|
const parsed = JSON.parse(raw);
|
|
46
|
-
if (typeof parsed === "object" && parsed !== null && "event" in parsed && "payload" in parsed && isDevToolsEventName(String(parsed.event))) {
|
|
50
|
+
if (typeof parsed === "object" && parsed !== null && "event" in parsed && "payload" in parsed && "v" in parsed && Number(parsed.v) === PROTOCOL_VERSION && typeof parsed.id === "string" && typeof parsed.ts === "number" && isDevToolsEventName(String(parsed.event))) {
|
|
47
51
|
return parsed;
|
|
48
52
|
}
|
|
49
53
|
return null;
|
|
@@ -51,6 +55,9 @@ function parseMessage(raw) {
|
|
|
51
55
|
return null;
|
|
52
56
|
}
|
|
53
57
|
}
|
|
58
|
+
function isSupportedProtocolVersion(version) {
|
|
59
|
+
return Number.isInteger(version) && version >= MIN_SUPPORTED_PROTOCOL_VERSION && version <= PROTOCOL_VERSION;
|
|
60
|
+
}
|
|
54
61
|
|
|
55
62
|
// src/redact.ts
|
|
56
63
|
var DEFAULT_REDACT_KEYS = [
|
|
@@ -222,6 +229,7 @@ export {
|
|
|
222
229
|
DEVTOOLS_EVENTS,
|
|
223
230
|
MAX_CAPTURE_BYTES,
|
|
224
231
|
MAX_DEPTH,
|
|
232
|
+
MIN_SUPPORTED_PROTOCOL_VERSION,
|
|
225
233
|
PROTOCOL_VERSION,
|
|
226
234
|
Redactor,
|
|
227
235
|
buildEditorUrl,
|
|
@@ -230,6 +238,7 @@ export {
|
|
|
230
238
|
defaultRedactor,
|
|
231
239
|
detectPlatform,
|
|
232
240
|
isDevToolsEventName,
|
|
241
|
+
isSupportedProtocolVersion,
|
|
233
242
|
parseMessage,
|
|
234
243
|
randomId,
|
|
235
244
|
redactText,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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 /** Source location when available (file, line, column, function). */\n source?: SourceLocation;\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 headers?: Record<string, string>;\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 websocketConnections: GatewayConnectionPayload[];\n websocketMessages: GatewayMessagePayload[];\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":[]}
|
|
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;\nexport const MIN_SUPPORTED_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 | 'profile.started'\n | 'profile.completed'\n | 'plugin.event'\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 'profile.started': ProfileStartedPayload;\n 'profile.completed': ProfileCompletedPayload;\n 'plugin.event': PluginEventPayload;\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 /** Source location when available (file, line, column, function). */\n source?: SourceLocation;\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 headers?: Record<string, string>;\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/** A profiling session requested by a developer or extension. */\nexport interface ProfileStartedPayload {\n projectId: string;\n profileId: string;\n kind: 'cpu' | 'heap';\n startedAt: number;\n durationMs?: number;\n}\n\n/** Result metadata for a completed profiling session. */\nexport interface ProfileCompletedPayload {\n projectId: string;\n profileId: string;\n kind: 'cpu' | 'heap';\n startedAt: number;\n completedAt: number;\n data?: unknown;\n}\n\n/** Namespaced event emitted by a registered plugin. */\nexport interface PluginEventPayload {\n projectId: string;\n plugin: string;\n name: string;\n data?: unknown;\n timestamp: number;\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 websocketConnections: GatewayConnectionPayload[];\n websocketMessages: GatewayMessagePayload[];\n performance: Record<string, PerformanceSnapshot>;\n apps: Record<string, AppSnapshot>;\n}\n","import type { DevToolsEventMap, DevToolsEventName, DevToolsMessage } from './types';\nimport { MIN_SUPPORTED_PROTOCOL_VERSION, 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 'profile.started',\n 'profile.completed',\n 'plugin.event',\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 'v' in parsed &&\n Number((parsed as { v: unknown }).v) === PROTOCOL_VERSION &&\n typeof (parsed as { id?: unknown }).id === 'string' &&\n typeof (parsed as { ts?: unknown }).ts === 'number' &&\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\n/** True when a peer can safely communicate with this package. */\nexport function isSupportedProtocolVersion(version: number): boolean {\n return Number.isInteger(version) && version >= MIN_SUPPORTED_PROTOCOL_VERSION && version <= PROTOCOL_VERSION;\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":[]}
|