{"version":3,"sources":["../src/test-span-collector.ts"],"names":["SpanStatusCode"],"mappings":"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","file":"test-span-collector.cjs","sourcesContent":["/**\n * TestSpanCollector — SpanExporter that groups finished spans by traceId\n * and drains per-trace for embedding in test metadata.\n *\n * @example\n * ```typescript\n * import { TestSpanCollector } from 'autotel/test-span-collector';\n * import { SimpleSpanProcessor } from 'autotel/processors';\n * import { getAutotelTracerProvider } from 'autotel';\n *\n * const collector = new TestSpanCollector();\n * const provider = getAutotelTracerProvider();\n * provider.addSpanProcessor(new SimpleSpanProcessor(collector));\n *\n * // After a test span ends:\n * const spans = collector.drainTrace(traceId, rootSpanId);\n * // spans contains only descendants of rootSpanId\n * ```\n */\n\nimport type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport { SpanStatusCode } from '@opentelemetry/api';\n\n/** @see ExportResultCode from @opentelemetry/core */\nconst ExportResultCode = { SUCCESS: 0, FAILED: 1 } as const;\n\n/** Attribute value types that survive serialization */\ntype SerializableValue =\n  | string\n  | number\n  | boolean\n  | string[]\n  | number[]\n  | boolean[];\n\n/**\n * Portable serialized span for embedding in test metadata.\n * `startTimeMs` is derived from OTel HrTime — epoch-based wall-clock ms in the current SDK.\n *\n * Defined as a `type` (not `interface`) so it is assignable to\n * `Record<string, unknown>` in TypeScript 6+ strict mode.\n */\nexport type SerializedSpan = {\n  spanId: string;\n  parentSpanId?: string;\n  name: string;\n  startTimeMs: number;\n  durationMs: number;\n  status: 'ok' | 'error' | 'unset';\n  statusMessage?: string;\n  attributes?: Record<string, SerializableValue>;\n};\n\nexport class TestSpanCollector implements SpanExporter {\n  private traces = new Map<string, ReadableSpan[]>();\n\n  export(\n    spans: ReadableSpan[],\n    callback: (result: { code: number }) => void,\n  ): void {\n    for (const span of spans) {\n      const traceId = span.spanContext().traceId;\n      let list = this.traces.get(traceId);\n      if (!list) {\n        list = [];\n        this.traces.set(traceId, list);\n      }\n      list.push(span);\n    }\n    callback({ code: ExportResultCode.SUCCESS });\n  }\n\n  /**\n   * Drain and serialize spans that are descendants of `rootSpanId` within `traceId`.\n   * Filters to the subtree rooted at the test span to prevent cross-test mixing.\n   * Removes the entire traceId entry from the collector.\n   */\n  drainTrace(traceId: string, rootSpanId: string): SerializedSpan[] {\n    const allSpans = this.traces.get(traceId);\n    this.traces.delete(traceId);\n    if (!allSpans?.length) return [];\n\n    // Build spanId → span index for efficient parent-chain walking\n    const byId = new Map<string, ReadableSpan>();\n    for (const s of allSpans) byId.set(s.spanContext().spanId, s);\n\n    // Filter to spans that are the root or descendants of rootSpanId\n    const included = allSpans.filter((s) => {\n      let id: string | undefined = s.spanContext().spanId;\n      while (id) {\n        if (id === rootSpanId) return true;\n        const parent = byId.get(id);\n        const parentId = parent?.parentSpanContext?.spanId || undefined;\n        if (parentId === id) break; // cycle guard\n        id = parentId;\n      }\n      return false;\n    });\n\n    return included.map((s) => serializeSpan(s));\n  }\n\n  shutdown(): Promise<void> {\n    this.traces.clear();\n    return Promise.resolve();\n  }\n\n  forceFlush(): Promise<void> {\n    return Promise.resolve();\n  }\n}\n\nfunction hrTimeToMs(hr: [number, number]): number {\n  return hr[0] * 1000 + hr[1] / 1_000_000;\n}\n\nfunction isSerializable(v: unknown): v is SerializableValue {\n  if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')\n    return true;\n  if (Array.isArray(v) && v.length > 0) {\n    const t = typeof v[0];\n    return (\n      (t === 'string' || t === 'number' || t === 'boolean') &&\n      v.every((e) => typeof e === t)\n    );\n  }\n  return false;\n}\n\nexport function serializeSpan(span: ReadableSpan): SerializedSpan {\n  const attrs: Record<string, SerializableValue> = {};\n  for (const [k, v] of Object.entries(span.attributes)) {\n    if (isSerializable(v)) attrs[k] = v;\n  }\n  return {\n    spanId: span.spanContext().spanId,\n    parentSpanId: span.parentSpanContext?.spanId || undefined,\n    name: span.name,\n    startTimeMs: hrTimeToMs(span.startTime as [number, number]),\n    durationMs: hrTimeToMs(span.duration as [number, number]),\n    status:\n      span.status.code === SpanStatusCode.ERROR\n        ? 'error'\n        : span.status.code === SpanStatusCode.OK\n          ? 'ok'\n          : 'unset',\n    statusMessage: span.status.message || undefined,\n    attributes: Object.keys(attrs).length > 0 ? attrs : undefined,\n  };\n}\n"]}