@angelitosystems/devtools-protocol 1.0.4 → 1.0.6

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.
@@ -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}\n\n/** Emitted the moment a request enters the SDK. */\nexport interface RequestStartedPayload {\n requestId: string;\n projectId: string;\n method: string;\n url: string;\n route?: string;\n httpVersion?: string;\n headers: Record<string, string>;\n query: Record<string, unknown>;\n params?: Record<string, unknown>;\n ip?: string;\n userAgent?: string;\n startedAt: number;\n}\n\n/** Emitted when the response finishes. */\nexport interface RequestCompletedPayload {\n requestId: string;\n projectId: string;\n method: string;\n url: string;\n route?: string;\n statusCode: number;\n duration: number;\n startedAt: number;\n timeline: TimelineSpan[];\n query?: Record<string, unknown>;\n responsePreview?: string;\n responseBody?: unknown;\n requestBody?: unknown;\n errored: boolean;\n}\n\n/** A captured console or logger entry. */\nexport interface LogPayload {\n requestId?: string;\n projectId: string;\n level: LogLevel;\n message: string;\n arguments?: unknown[];\n stack?: string;\n source?: SourceLocation;\n context?: string;\n processId: number;\n timestamp: number;\n}\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'verbose';\n\n/** File/line/column triple resolved from source maps when available. */\nexport interface SourceLocation {\n file: string;\n line: number;\n column: number;\n /** Absolute path when resolvable on the host machine. */\n absolutePath?: string;\n /** function name if the stack exposed one */\n function?: string;\n}\n\n/** A captured exception with source mapping. */\nexport interface ErrorPayload {\n requestId?: string;\n projectId: string;\n name: string;\n message: string;\n stack?: string;\n source?: SourceLocation;\n /** stable hash for grouping identical errors */\n fingerprint: string;\n context?: string;\n request?: { method: string; url: string; statusCode?: number };\n controller?: string;\n service?: string;\n timestamp: number;\n /** server-side occurrence count for grouped errors */\n occurrences?: number;\n}\n\n/** A captured database query. */\nexport interface QueryPayload {\n requestId?: string;\n projectId: string;\n provider: 'prisma' | 'typeorm' | 'sequelize' | 'mikroorm' | 'other';\n sql: string;\n duration: number;\n database?: string;\n parameters?: unknown[];\n timestamp: number;\n}\n\n/** Gateway-level connection snapshot. */\nexport interface GatewayConnectionPayload {\n projectId: string;\n gateway: string;\n namespace: string;\n connections: number;\n timestamp: number;\n}\n\n/** Individual gateway event flow. */\nexport interface GatewayMessagePayload {\n projectId: string;\n gateway: string;\n event: string;\n direction: 'received' | 'sent';\n payloadSize: number;\n error?: string;\n duration?: number;\n requestId?: string;\n timestamp: number;\n}\n\n/** Point-in-time process metrics. */\nexport interface PerformanceSnapshot {\n projectId: string;\n timestamp: number;\n cpuPercent: number;\n memoryUsedBytes: number;\n memoryTotalBytes: number;\n heapUsedBytes: number;\n heapTotalBytes: number;\n eventLoopLagMs: number;\n activeRequests: number;\n requestsPerSecond: number;\n averageLatencyMs: number;\n p95LatencyMs: number;\n p99LatencyMs: number;\n errorsPerSecond: number;\n /** true when the process reports memory pressure */\n memoryPressure?: boolean;\n}\n\n/** Static description of the NestJS application graph. */\nexport interface AppSnapshot {\n projectId: string;\n projectName: string;\n modules: AppModuleNode[];\n nestjsVersion: string | null;\n capturedAt: number;\n}\n\n/** A module and its members in the application graph. */\nexport interface AppModuleNode {\n name: string;\n imports: string[];\n controllers: AppMemberNode[];\n providers: AppMemberNode[];\n exports: string[];\n}\n\n/** A member (controller/provider/guard/pipe/...) inside a module. */\nexport interface AppMemberNode {\n name: string;\n type: 'controller' | 'provider' | 'guard' | 'interceptor' | 'pipe' | 'filter' | 'gateway';\n routes?: string[];\n}\n\n/** What a dashboard/control client announces when it connects. */\nexport interface ClientHello {\n kind: 'dashboard' | 'cli' | 'other';\n name?: string;\n version?: string;\n}\n\n/** Full server state handed to newly connected dashboards. */\nexport interface StateSnapshot {\n projects: ProjectInfo[];\n requests: RequestCompletedPayload[];\n logs: LogPayload[];\n errors: ErrorPayload[];\n queries: QueryPayload[];\n performance: Record<string, PerformanceSnapshot>;\n apps: Record<string, AppSnapshot>;\n}\n","import type { DevToolsEventMap, DevToolsEventName, DevToolsMessage } from './types';\nimport { PROTOCOL_VERSION } from './types';\n\n/** Const array of every event name (runtime mirror of the type union). */\nexport const DEVTOOLS_EVENTS = [\n 'project.connected',\n 'project.disconnected',\n 'request.started',\n 'request.completed',\n 'log.created',\n 'error.created',\n 'query.executed',\n 'websocket.connected',\n 'websocket.message',\n 'performance.updated',\n 'app.snapshot',\n 'client.hello',\n 'client.welcome',\n 'stream.pause',\n 'stream.resume',\n 'state.clear',\n 'state.snapshot',\n 'state.ack',\n 'error',\n] as const satisfies readonly DevToolsEventName[];\n\n/** True when `name` is a known protocol event. */\nexport function isDevToolsEventName(name: string): name is DevToolsEventName {\n return (DEVTOOLS_EVENTS as readonly string[]).includes(name);\n}\n\n/** Build a fully-typed wire message. */\nexport function createMessage<K extends DevToolsEventName>(\n event: K,\n payload: DevToolsEventMap[K],\n options?: { projectId?: string; id?: string; ts?: number },\n): DevToolsMessage<DevToolsEventMap[K]> {\n return {\n v: PROTOCOL_VERSION,\n id: options?.id ?? randomId(),\n projectId: options?.projectId,\n ts: options?.ts ?? Date.now(),\n event,\n payload,\n };\n}\n\n/** Cheap collision-resistant id (no crypto dependency needed for wire correlation). */\nexport function randomId(prefix?: string): string {\n const core =\n typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID().replace(/-/g, '').slice(0, 16)\n : Math.random().toString(36).slice(2, 10) + Date.now().toString(36);\n return prefix ? `${prefix}_${core}` : core;\n}\n\n/** Type guard narrowing an unknown parsed frame into a DevToolsMessage. */\nexport function parseMessage(raw: string): DevToolsMessage | null {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'event' in parsed &&\n 'payload' in parsed &&\n isDevToolsEventName(String((parsed as { event: unknown }).event))\n ) {\n return parsed as DevToolsMessage;\n }\n return null;\n } catch {\n return null;\n }\n}\n","/** Default keys that are always redacted unless explicitly allowed. */\nexport const DEFAULT_REDACT_KEYS = [\n 'password',\n 'token',\n 'access_token',\n 'accessToken',\n 'refresh_token',\n 'refreshToken',\n 'authorization',\n 'cookie',\n 'cookies',\n 'secret',\n 'apiKey',\n 'api_key',\n 'client_secret',\n 'clientSecret',\n 'private_key',\n 'privateKey',\n 'session',\n 'set-cookie',\n] as const;\n\n/** Default capture ceilings shared by SDK and server. */\nexport const MAX_CAPTURE_BYTES = 4 * 1024;\nexport const MAX_DEPTH = 4;\n\n/** Options for deep redaction of arbitrary values. */\nexport interface RedactOptions {\n /** Extra key names to redact (case-insensitive, partial match allowed). */\n redact?: string[];\n /** Keys that should never be redacted even if they look sensitive. */\n allow?: string[];\n /** Placeholder string used for redacted values. */\n placeholder?: string;\n /** Max serialized size before truncation. */\n maxBytes?: number;\n /** Max object depth. */\n maxDepth?: number;\n}\n\nconst DEFAULT_PLACEHOLDER = '[REDACTED]';\nconst TRUNCATION_SUFFIX = '…[truncated]';\n\n/** Normalized set of denylist and allowlist key matchers. */\nexport class Redactor {\n private readonly deny: Set<string>;\n private readonly allow: Set<string>;\n private readonly placeholder: string;\n private readonly maxBytes: number;\n private readonly maxDepth: number;\n\n constructor(options?: RedactOptions) {\n this.deny = new Set([...DEFAULT_REDACT_KEYS, ...(options?.redact ?? [])].map(normalizeKey));\n this.allow = new Set((options?.allow ?? []).map(normalizeKey));\n this.placeholder = options?.placeholder ?? DEFAULT_PLACEHOLDER;\n this.maxBytes = options?.maxBytes ?? MAX_CAPTURE_BYTES;\n this.maxDepth = options?.maxDepth ?? MAX_DEPTH;\n }\n\n /** True when the given key is sensitive and not allowed. */\n isSensitive(key: string): boolean {\n const normalized = normalizeKey(key);\n if (this.allow.has(normalized)) return false;\n if (this.deny.has(normalized)) return true;\n // partial match: `userPassword`, `authTokenValue`, `x-api-key`...\n for (const denied of this.deny) {\n if (normalized.includes(denied)) return true;\n }\n return false;\n }\n\n /**\n * Deep-copy a value while redacting sensitive keys, truncating oversized\n * strings and enforcing depth limits. Circular references become '[Circular]'.\n */\n redact(value: unknown, depth = 0, seen = new Set<unknown>()): unknown {\n if (value === null || typeof value !== 'object') {\n return redactPrimitive(value);\n }\n if (seen.has(value)) return '[Circular]';\n if (depth >= this.maxDepth) return '[MaxDepth]';\n\n seen.add(value);\n try {\n if (Array.isArray(value)) {\n return value.slice(0, 50).map((item) => this.redact(item, depth + 1, seen));\n }\n if (value instanceof Error) {\n return { name: value.name, message: value.message };\n }\n if (value instanceof Date) return value.toISOString();\n\n const out: Record<string, unknown> = {};\n for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {\n out[key] = this.isSensitive(key) ? this.placeholder : this.redact(raw, depth + 1, seen);\n }\n return out;\n } finally {\n seen.delete(value);\n }\n }\n\n /** Redact sensitive substrings (key=value / key: value / bearer tokens) from free-form text. */\n redactString(text: string): string {\n let out = text;\n for (const denied of this.deny) {\n const camel = denied;\n const pattern = new RegExp(`(${escapeRegExp(camel)}|${escapeRegExp(denied.replace(/[-_]/g, ''))})\\\\s*[=:]\\\\s*([^\\\\s,&;]+)`, 'gi');\n out = out.replace(pattern, `$1=${this.placeholder}`);\n }\n out = out.replace(/bearer\\s+[a-z0-9._-]+/gi, `Bearer ${this.placeholder}`);\n return out;\n }\n\n /** Serialize a value safely: redacted, size-capped, never throws. */\n serialize(value: unknown): string {\n try {\n const safe = this.redact(value);\n const json = JSON.stringify(safe) ?? String(safe);\n return truncateUtf8(json, this.maxBytes, TRUNCATION_SUFFIX);\n } catch {\n return '[Unserializable]';\n }\n }\n}\n\n/** Shared default redactor instance for quick helpers. */\nexport const defaultRedactor = new Redactor();\n\n/** Convenience: deep-redact with the default policy. */\nexport function redactValue(value: unknown): unknown {\n return defaultRedactor.redact(value);\n}\n\n/** Convenience: scrub a free-form string with the default policy. */\nexport function redactText(text: string): string {\n return defaultRedactor.redactString(text);\n}\n\nfunction redactPrimitive(value: unknown): unknown {\n if (typeof value === 'string') return defaultRedactor.redactString(value);\n if (typeof value === 'number' || typeof value === 'boolean' || value === null) return value;\n if (typeof value === 'function') return '[Function]';\n if (typeof value === 'bigint') return value.toString() + 'n';\n return String(value);\n}\n\nfunction normalizeKey(key: string): string {\n return key.toLowerCase().replace(/[-_\\s]/g, '');\n}\n\nfunction escapeRegExp(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/** UTF-8-safe truncation that never splits a multi-byte character. */\nfunction truncateUtf8(text: string, maxBytes: number, suffix: string): string {\n const buf = Buffer.from(text, 'utf8');\n if (buf.length <= maxBytes) return text;\n const cut = Math.max(0, maxBytes - Buffer.byteLength(suffix, 'utf8'));\n return buf.subarray(0, cut).toString('utf8') + suffix;\n}\n","/**\n * Deep links that open a file at a specific line/column in an editor.\n * Paths are generated for the OS the DevTools *server* runs on — never assume\n * Windows or POSIX layout; detect it at runtime.\n */\n\nexport type EditorKind = 'vscode' | 'cursor' | 'zed' | 'sublime';\n\nconst EDITOR_SCHEMES: Record<EditorKind, string> = {\n vscode: 'vscode',\n cursor: 'cursor',\n zed: 'zed',\n sublime: 'subl',\n};\n\n/** Returns 'win32' | 'darwin' | 'linux' | 'unknown' without touching process when unavailable. */\nexport function detectPlatform(platformGetter?: () => string): 'win32' | 'darwin' | 'linux' | 'unknown' {\n try {\n const platform = platformGetter ? platformGetter() : process.platform;\n if (platform === 'win32' || platform === 'darwin' || platform === 'linux') return platform;\n return 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nfunction encodeFilePath(absolutePath: string, platform: string): string {\n // VS Code expects forward slashes; keep drive letters like /C:/... on Windows.\n const normalized = platform === 'win32' ? absolutePath.replace(/\\\\/g, '/') : absolutePath;\n const withDrive = platform === 'win32' && /^[A-Za-z]:\\//.test(normalized) ? `/${normalized}` : normalized;\n // the scheme already carries 'file/', so drop any leading slash;\n // preserve the drive-letter colon (C:) which VS Code expects unencoded\n return withDrive\n .replace(/^\\//, '')\n .split('/')\n .map((segment, index) => (index === 0 && /^[A-Za-z]:$/.test(segment) ? segment : encodeURIComponent(segment)))\n .join('/');\n}\n\n/**\n * Build an editor deep link such as:\n * vscode://file/Users/me/app/src/users.service.ts:87:21\n */\nexport function buildEditorUrl(\n editor: EditorKind,\n file: { absolutePath?: string; file?: string; line?: number; column?: number },\n platformGetter?: () => string,\n): string | null {\n const absolutePath = file.absolutePath ?? file.file;\n if (!absolutePath) return null;\n\n const platform = detectPlatform(platformGetter);\n const scheme = EDITOR_SCHEMES[editor];\n const path = encodeFilePath(absolutePath, platform);\n const line = file.line ?? 1;\n const column = file.column ?? 1;\n\n if (editor === 'sublime') {\n return `subl://open?url=file://${path}&line=${line}&column=${column}`;\n }\n return `${scheme}://file/${path}:${line}:${column}`;\n}\n\n/** First available vscode:// link for a source location. */\nexport function vscodeUrl(file: { absolutePath?: string; file?: string; line?: number; column?: number }): string | null {\n return buildEditorUrl('vscode', file);\n}\n\n/** First available cursor:// link for a source location. */\nexport function cursorUrl(file: { absolutePath?: string; file?: string; line?: number; column?: number }): string | null {\n return buildEditorUrl('cursor', file);\n}\n"],"mappings":"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","names":[]}
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":[]}
package/dist/index.d.cts CHANGED
@@ -81,6 +81,8 @@ interface TimelineSpan {
81
81
  startedAt: number;
82
82
  status?: 'ok' | 'error';
83
83
  detail?: string;
84
+ /** Source location when available (file, line, column, function). */
85
+ source?: SourceLocation;
84
86
  }
85
87
  /** Emitted the moment a request enters the SDK. */
86
88
  interface RequestStartedPayload {
@@ -109,6 +111,7 @@ interface RequestCompletedPayload {
109
111
  startedAt: number;
110
112
  timeline: TimelineSpan[];
111
113
  query?: Record<string, unknown>;
114
+ headers?: Record<string, string>;
112
115
  responsePreview?: string;
113
116
  responseBody?: unknown;
114
117
  requestBody?: unknown;
@@ -245,6 +248,8 @@ interface StateSnapshot {
245
248
  logs: LogPayload[];
246
249
  errors: ErrorPayload[];
247
250
  queries: QueryPayload[];
251
+ websocketConnections: GatewayConnectionPayload[];
252
+ websocketMessages: GatewayMessagePayload[];
248
253
  performance: Record<string, PerformanceSnapshot>;
249
254
  apps: Record<string, AppSnapshot>;
250
255
  }
package/dist/index.d.ts CHANGED
@@ -81,6 +81,8 @@ interface TimelineSpan {
81
81
  startedAt: number;
82
82
  status?: 'ok' | 'error';
83
83
  detail?: string;
84
+ /** Source location when available (file, line, column, function). */
85
+ source?: SourceLocation;
84
86
  }
85
87
  /** Emitted the moment a request enters the SDK. */
86
88
  interface RequestStartedPayload {
@@ -109,6 +111,7 @@ interface RequestCompletedPayload {
109
111
  startedAt: number;
110
112
  timeline: TimelineSpan[];
111
113
  query?: Record<string, unknown>;
114
+ headers?: Record<string, string>;
112
115
  responsePreview?: string;
113
116
  responseBody?: unknown;
114
117
  requestBody?: unknown;
@@ -245,6 +248,8 @@ interface StateSnapshot {
245
248
  logs: LogPayload[];
246
249
  errors: ErrorPayload[];
247
250
  queries: QueryPayload[];
251
+ websocketConnections: GatewayConnectionPayload[];
252
+ websocketMessages: GatewayMessagePayload[];
248
253
  performance: Record<string, PerformanceSnapshot>;
249
254
  apps: Record<string, AppSnapshot>;
250
255
  }
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}\n\n/** Emitted the moment a request enters the SDK. */\nexport interface RequestStartedPayload {\n requestId: string;\n projectId: string;\n method: string;\n url: string;\n route?: string;\n httpVersion?: string;\n headers: Record<string, string>;\n query: Record<string, unknown>;\n params?: Record<string, unknown>;\n ip?: string;\n userAgent?: string;\n startedAt: number;\n}\n\n/** Emitted when the response finishes. */\nexport interface RequestCompletedPayload {\n requestId: string;\n projectId: string;\n method: string;\n url: string;\n route?: string;\n statusCode: number;\n duration: number;\n startedAt: number;\n timeline: TimelineSpan[];\n query?: Record<string, unknown>;\n responsePreview?: string;\n responseBody?: unknown;\n requestBody?: unknown;\n errored: boolean;\n}\n\n/** A captured console or logger entry. */\nexport interface LogPayload {\n requestId?: string;\n projectId: string;\n level: LogLevel;\n message: string;\n arguments?: unknown[];\n stack?: string;\n source?: SourceLocation;\n context?: string;\n processId: number;\n timestamp: number;\n}\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'verbose';\n\n/** File/line/column triple resolved from source maps when available. */\nexport interface SourceLocation {\n file: string;\n line: number;\n column: number;\n /** Absolute path when resolvable on the host machine. */\n absolutePath?: string;\n /** function name if the stack exposed one */\n function?: string;\n}\n\n/** A captured exception with source mapping. */\nexport interface ErrorPayload {\n requestId?: string;\n projectId: string;\n name: string;\n message: string;\n stack?: string;\n source?: SourceLocation;\n /** stable hash for grouping identical errors */\n fingerprint: string;\n context?: string;\n request?: { method: string; url: string; statusCode?: number };\n controller?: string;\n service?: string;\n timestamp: number;\n /** server-side occurrence count for grouped errors */\n occurrences?: number;\n}\n\n/** A captured database query. */\nexport interface QueryPayload {\n requestId?: string;\n projectId: string;\n provider: 'prisma' | 'typeorm' | 'sequelize' | 'mikroorm' | 'other';\n sql: string;\n duration: number;\n database?: string;\n parameters?: unknown[];\n timestamp: number;\n}\n\n/** Gateway-level connection snapshot. */\nexport interface GatewayConnectionPayload {\n projectId: string;\n gateway: string;\n namespace: string;\n connections: number;\n timestamp: number;\n}\n\n/** Individual gateway event flow. */\nexport interface GatewayMessagePayload {\n projectId: string;\n gateway: string;\n event: string;\n direction: 'received' | 'sent';\n payloadSize: number;\n error?: string;\n duration?: number;\n requestId?: string;\n timestamp: number;\n}\n\n/** Point-in-time process metrics. */\nexport interface PerformanceSnapshot {\n projectId: string;\n timestamp: number;\n cpuPercent: number;\n memoryUsedBytes: number;\n memoryTotalBytes: number;\n heapUsedBytes: number;\n heapTotalBytes: number;\n eventLoopLagMs: number;\n activeRequests: number;\n requestsPerSecond: number;\n averageLatencyMs: number;\n p95LatencyMs: number;\n p99LatencyMs: number;\n errorsPerSecond: number;\n /** true when the process reports memory pressure */\n memoryPressure?: boolean;\n}\n\n/** Static description of the NestJS application graph. */\nexport interface AppSnapshot {\n projectId: string;\n projectName: string;\n modules: AppModuleNode[];\n nestjsVersion: string | null;\n capturedAt: number;\n}\n\n/** A module and its members in the application graph. */\nexport interface AppModuleNode {\n name: string;\n imports: string[];\n controllers: AppMemberNode[];\n providers: AppMemberNode[];\n exports: string[];\n}\n\n/** A member (controller/provider/guard/pipe/...) inside a module. */\nexport interface AppMemberNode {\n name: string;\n type: 'controller' | 'provider' | 'guard' | 'interceptor' | 'pipe' | 'filter' | 'gateway';\n routes?: string[];\n}\n\n/** What a dashboard/control client announces when it connects. */\nexport interface ClientHello {\n kind: 'dashboard' | 'cli' | 'other';\n name?: string;\n version?: string;\n}\n\n/** Full server state handed to newly connected dashboards. */\nexport interface StateSnapshot {\n projects: ProjectInfo[];\n requests: RequestCompletedPayload[];\n logs: LogPayload[];\n errors: ErrorPayload[];\n queries: QueryPayload[];\n performance: Record<string, PerformanceSnapshot>;\n apps: Record<string, AppSnapshot>;\n}\n","import type { DevToolsEventMap, DevToolsEventName, DevToolsMessage } from './types';\nimport { PROTOCOL_VERSION } from './types';\n\n/** Const array of every event name (runtime mirror of the type union). */\nexport const DEVTOOLS_EVENTS = [\n 'project.connected',\n 'project.disconnected',\n 'request.started',\n 'request.completed',\n 'log.created',\n 'error.created',\n 'query.executed',\n 'websocket.connected',\n 'websocket.message',\n 'performance.updated',\n 'app.snapshot',\n 'client.hello',\n 'client.welcome',\n 'stream.pause',\n 'stream.resume',\n 'state.clear',\n 'state.snapshot',\n 'state.ack',\n 'error',\n] as const satisfies readonly DevToolsEventName[];\n\n/** True when `name` is a known protocol event. */\nexport function isDevToolsEventName(name: string): name is DevToolsEventName {\n return (DEVTOOLS_EVENTS as readonly string[]).includes(name);\n}\n\n/** Build a fully-typed wire message. */\nexport function createMessage<K extends DevToolsEventName>(\n event: K,\n payload: DevToolsEventMap[K],\n options?: { projectId?: string; id?: string; ts?: number },\n): DevToolsMessage<DevToolsEventMap[K]> {\n return {\n v: PROTOCOL_VERSION,\n id: options?.id ?? randomId(),\n projectId: options?.projectId,\n ts: options?.ts ?? Date.now(),\n event,\n payload,\n };\n}\n\n/** Cheap collision-resistant id (no crypto dependency needed for wire correlation). */\nexport function randomId(prefix?: string): string {\n const core =\n typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID().replace(/-/g, '').slice(0, 16)\n : Math.random().toString(36).slice(2, 10) + Date.now().toString(36);\n return prefix ? `${prefix}_${core}` : core;\n}\n\n/** Type guard narrowing an unknown parsed frame into a DevToolsMessage. */\nexport function parseMessage(raw: string): DevToolsMessage | null {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'event' in parsed &&\n 'payload' in parsed &&\n isDevToolsEventName(String((parsed as { event: unknown }).event))\n ) {\n return parsed as DevToolsMessage;\n }\n return null;\n } catch {\n return null;\n }\n}\n","/** Default keys that are always redacted unless explicitly allowed. */\nexport const DEFAULT_REDACT_KEYS = [\n 'password',\n 'token',\n 'access_token',\n 'accessToken',\n 'refresh_token',\n 'refreshToken',\n 'authorization',\n 'cookie',\n 'cookies',\n 'secret',\n 'apiKey',\n 'api_key',\n 'client_secret',\n 'clientSecret',\n 'private_key',\n 'privateKey',\n 'session',\n 'set-cookie',\n] as const;\n\n/** Default capture ceilings shared by SDK and server. */\nexport const MAX_CAPTURE_BYTES = 4 * 1024;\nexport const MAX_DEPTH = 4;\n\n/** Options for deep redaction of arbitrary values. */\nexport interface RedactOptions {\n /** Extra key names to redact (case-insensitive, partial match allowed). */\n redact?: string[];\n /** Keys that should never be redacted even if they look sensitive. */\n allow?: string[];\n /** Placeholder string used for redacted values. */\n placeholder?: string;\n /** Max serialized size before truncation. */\n maxBytes?: number;\n /** Max object depth. */\n maxDepth?: number;\n}\n\nconst DEFAULT_PLACEHOLDER = '[REDACTED]';\nconst TRUNCATION_SUFFIX = '…[truncated]';\n\n/** Normalized set of denylist and allowlist key matchers. */\nexport class Redactor {\n private readonly deny: Set<string>;\n private readonly allow: Set<string>;\n private readonly placeholder: string;\n private readonly maxBytes: number;\n private readonly maxDepth: number;\n\n constructor(options?: RedactOptions) {\n this.deny = new Set([...DEFAULT_REDACT_KEYS, ...(options?.redact ?? [])].map(normalizeKey));\n this.allow = new Set((options?.allow ?? []).map(normalizeKey));\n this.placeholder = options?.placeholder ?? DEFAULT_PLACEHOLDER;\n this.maxBytes = options?.maxBytes ?? MAX_CAPTURE_BYTES;\n this.maxDepth = options?.maxDepth ?? MAX_DEPTH;\n }\n\n /** True when the given key is sensitive and not allowed. */\n isSensitive(key: string): boolean {\n const normalized = normalizeKey(key);\n if (this.allow.has(normalized)) return false;\n if (this.deny.has(normalized)) return true;\n // partial match: `userPassword`, `authTokenValue`, `x-api-key`...\n for (const denied of this.deny) {\n if (normalized.includes(denied)) return true;\n }\n return false;\n }\n\n /**\n * Deep-copy a value while redacting sensitive keys, truncating oversized\n * strings and enforcing depth limits. Circular references become '[Circular]'.\n */\n redact(value: unknown, depth = 0, seen = new Set<unknown>()): unknown {\n if (value === null || typeof value !== 'object') {\n return redactPrimitive(value);\n }\n if (seen.has(value)) return '[Circular]';\n if (depth >= this.maxDepth) return '[MaxDepth]';\n\n seen.add(value);\n try {\n if (Array.isArray(value)) {\n return value.slice(0, 50).map((item) => this.redact(item, depth + 1, seen));\n }\n if (value instanceof Error) {\n return { name: value.name, message: value.message };\n }\n if (value instanceof Date) return value.toISOString();\n\n const out: Record<string, unknown> = {};\n for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {\n out[key] = this.isSensitive(key) ? this.placeholder : this.redact(raw, depth + 1, seen);\n }\n return out;\n } finally {\n seen.delete(value);\n }\n }\n\n /** Redact sensitive substrings (key=value / key: value / bearer tokens) from free-form text. */\n redactString(text: string): string {\n let out = text;\n for (const denied of this.deny) {\n const camel = denied;\n const pattern = new RegExp(`(${escapeRegExp(camel)}|${escapeRegExp(denied.replace(/[-_]/g, ''))})\\\\s*[=:]\\\\s*([^\\\\s,&;]+)`, 'gi');\n out = out.replace(pattern, `$1=${this.placeholder}`);\n }\n out = out.replace(/bearer\\s+[a-z0-9._-]+/gi, `Bearer ${this.placeholder}`);\n return out;\n }\n\n /** Serialize a value safely: redacted, size-capped, never throws. */\n serialize(value: unknown): string {\n try {\n const safe = this.redact(value);\n const json = JSON.stringify(safe) ?? String(safe);\n return truncateUtf8(json, this.maxBytes, TRUNCATION_SUFFIX);\n } catch {\n return '[Unserializable]';\n }\n }\n}\n\n/** Shared default redactor instance for quick helpers. */\nexport const defaultRedactor = new Redactor();\n\n/** Convenience: deep-redact with the default policy. */\nexport function redactValue(value: unknown): unknown {\n return defaultRedactor.redact(value);\n}\n\n/** Convenience: scrub a free-form string with the default policy. */\nexport function redactText(text: string): string {\n return defaultRedactor.redactString(text);\n}\n\nfunction redactPrimitive(value: unknown): unknown {\n if (typeof value === 'string') return defaultRedactor.redactString(value);\n if (typeof value === 'number' || typeof value === 'boolean' || value === null) return value;\n if (typeof value === 'function') return '[Function]';\n if (typeof value === 'bigint') return value.toString() + 'n';\n return String(value);\n}\n\nfunction normalizeKey(key: string): string {\n return key.toLowerCase().replace(/[-_\\s]/g, '');\n}\n\nfunction escapeRegExp(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/** UTF-8-safe truncation that never splits a multi-byte character. */\nfunction truncateUtf8(text: string, maxBytes: number, suffix: string): string {\n const buf = Buffer.from(text, 'utf8');\n if (buf.length <= maxBytes) return text;\n const cut = Math.max(0, maxBytes - Buffer.byteLength(suffix, 'utf8'));\n return buf.subarray(0, cut).toString('utf8') + suffix;\n}\n","/**\n * Deep links that open a file at a specific line/column in an editor.\n * Paths are generated for the OS the DevTools *server* runs on — never assume\n * Windows or POSIX layout; detect it at runtime.\n */\n\nexport type EditorKind = 'vscode' | 'cursor' | 'zed' | 'sublime';\n\nconst EDITOR_SCHEMES: Record<EditorKind, string> = {\n vscode: 'vscode',\n cursor: 'cursor',\n zed: 'zed',\n sublime: 'subl',\n};\n\n/** Returns 'win32' | 'darwin' | 'linux' | 'unknown' without touching process when unavailable. */\nexport function detectPlatform(platformGetter?: () => string): 'win32' | 'darwin' | 'linux' | 'unknown' {\n try {\n const platform = platformGetter ? platformGetter() : process.platform;\n if (platform === 'win32' || platform === 'darwin' || platform === 'linux') return platform;\n return 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nfunction encodeFilePath(absolutePath: string, platform: string): string {\n // VS Code expects forward slashes; keep drive letters like /C:/... on Windows.\n const normalized = platform === 'win32' ? absolutePath.replace(/\\\\/g, '/') : absolutePath;\n const withDrive = platform === 'win32' && /^[A-Za-z]:\\//.test(normalized) ? `/${normalized}` : normalized;\n // the scheme already carries 'file/', so drop any leading slash;\n // preserve the drive-letter colon (C:) which VS Code expects unencoded\n return withDrive\n .replace(/^\\//, '')\n .split('/')\n .map((segment, index) => (index === 0 && /^[A-Za-z]:$/.test(segment) ? segment : encodeURIComponent(segment)))\n .join('/');\n}\n\n/**\n * Build an editor deep link such as:\n * vscode://file/Users/me/app/src/users.service.ts:87:21\n */\nexport function buildEditorUrl(\n editor: EditorKind,\n file: { absolutePath?: string; file?: string; line?: number; column?: number },\n platformGetter?: () => string,\n): string | null {\n const absolutePath = file.absolutePath ?? file.file;\n if (!absolutePath) return null;\n\n const platform = detectPlatform(platformGetter);\n const scheme = EDITOR_SCHEMES[editor];\n const path = encodeFilePath(absolutePath, platform);\n const line = file.line ?? 1;\n const column = file.column ?? 1;\n\n if (editor === 'sublime') {\n return `subl://open?url=file://${path}&line=${line}&column=${column}`;\n }\n return `${scheme}://file/${path}:${line}:${column}`;\n}\n\n/** First available vscode:// link for a source location. */\nexport function vscodeUrl(file: { absolutePath?: string; file?: string; line?: number; column?: number }): string | null {\n return buildEditorUrl('vscode', file);\n}\n\n/** First available cursor:// link for a source location. */\nexport function cursorUrl(file: { absolutePath?: string; file?: string; line?: number; column?: number }): string | null {\n return buildEditorUrl('cursor', file);\n}\n"],"mappings":"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","names":[]}
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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angelitosystems/devtools-protocol",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Typed WebSocket protocol shared by the NestJS DevTools SDK, server and dashboard.",
5
5
  "license": "MIT",
6
6
  "type": "module",