@glasstrace/sdk 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/async-context/index.cjs +15005 -0
  2. package/dist/async-context/index.cjs.map +1 -0
  3. package/dist/async-context/index.d.cts +174 -0
  4. package/dist/async-context/index.d.ts +174 -0
  5. package/dist/async-context/index.js +13 -0
  6. package/dist/{capture-error-CTgSYxek.d.ts → capture-error-BeuEXXJO.d.cts} +40 -2
  7. package/dist/{capture-error-BmQz7xF6.d.cts → capture-error-D02pzB7q.d.ts} +40 -2
  8. package/dist/chunk-CL3OVHPO.js +23 -0
  9. package/dist/chunk-CL3OVHPO.js.map +1 -0
  10. package/dist/{chunk-WL6BXEJ5.js → chunk-DKV53A2C.js} +2 -2
  11. package/dist/{chunk-3PJP5Y3U.js → chunk-GWIEUBFR.js} +3 -3
  12. package/dist/{chunk-H57MQGNU.js → chunk-H6WJ63X2.js} +2 -2
  13. package/dist/{chunk-NN5YCETI.js → chunk-HD6JIFKN.js} +2 -2
  14. package/dist/{chunk-P45NZR4J.js → chunk-JHUNLPSS.js} +35 -1
  15. package/dist/{chunk-P45NZR4J.js.map → chunk-JHUNLPSS.js.map} +1 -1
  16. package/dist/{chunk-UQKI476D.js → chunk-M6EWJCAT.js} +2 -2
  17. package/dist/chunk-QHV7NFON.js +130 -0
  18. package/dist/chunk-QHV7NFON.js.map +1 -0
  19. package/dist/{chunk-M2TLX6NM.js → chunk-QXITSNYM.js} +3 -3
  20. package/dist/chunk-RQ5BIWDT.js +119 -0
  21. package/dist/chunk-RQ5BIWDT.js.map +1 -0
  22. package/dist/{chunk-OWPA7GHV.js → chunk-XEPC4NFL.js} +1398 -1037
  23. package/dist/chunk-XEPC4NFL.js.map +1 -0
  24. package/dist/cli/init.cjs +4 -4
  25. package/dist/cli/init.cjs.map +1 -1
  26. package/dist/cli/init.js +7 -7
  27. package/dist/cli/mcp-add.cjs +1 -1
  28. package/dist/cli/mcp-add.cjs.map +1 -1
  29. package/dist/cli/mcp-add.js +3 -3
  30. package/dist/cli/uninit.js +3 -3
  31. package/dist/cli/upgrade-instructions.cjs +1 -1
  32. package/dist/cli/upgrade-instructions.js +3 -3
  33. package/dist/cli/validate.cjs.map +1 -1
  34. package/dist/cli/validate.js +2 -2
  35. package/dist/{edge-entry-AWO70gje.d.ts → correlation-id-B_K8adD6.d.ts} +1 -1
  36. package/dist/{edge-entry-DaeG7D7S.d.cts → correlation-id-NAapJ5jn.d.cts} +1 -1
  37. package/dist/edge-entry.cjs +295 -26
  38. package/dist/edge-entry.cjs.map +1 -1
  39. package/dist/edge-entry.d.cts +5 -2
  40. package/dist/edge-entry.d.ts +5 -2
  41. package/dist/edge-entry.js +12 -3
  42. package/dist/index.cjs +3613 -3211
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/{index.d-Dq33YwFT.d.cts → index.d-CkTf_boH.d.cts} +1 -1
  45. package/dist/{index.d-Dq33YwFT.d.ts → index.d-CkTf_boH.d.ts} +1 -1
  46. package/dist/index.d.cts +7 -4
  47. package/dist/index.d.ts +7 -4
  48. package/dist/index.js +6 -5
  49. package/dist/index.js.map +1 -1
  50. package/dist/middleware/index.cjs +15014 -0
  51. package/dist/middleware/index.cjs.map +1 -0
  52. package/dist/middleware/index.d.cts +183 -0
  53. package/dist/middleware/index.d.ts +183 -0
  54. package/dist/middleware/index.js +13 -0
  55. package/dist/middleware/index.js.map +1 -0
  56. package/dist/node-entry.cjs +3613 -3211
  57. package/dist/node-entry.cjs.map +1 -1
  58. package/dist/node-entry.d.cts +3 -3
  59. package/dist/node-entry.d.ts +3 -3
  60. package/dist/node-entry.js +8 -7
  61. package/dist/node-subpath.cjs.map +1 -1
  62. package/dist/node-subpath.d.cts +1 -1
  63. package/dist/node-subpath.d.ts +1 -1
  64. package/dist/node-subpath.js +3 -3
  65. package/dist/{source-map-uploader-XFUEVV7I.js → source-map-uploader-MMJ2WCL4.js} +3 -3
  66. package/dist/source-map-uploader-MMJ2WCL4.js.map +1 -0
  67. package/package.json +13 -1
  68. package/dist/chunk-OWPA7GHV.js.map +0 -1
  69. /package/dist/{source-map-uploader-XFUEVV7I.js.map → async-context/index.js.map} +0 -0
  70. /package/dist/{chunk-WL6BXEJ5.js.map → chunk-DKV53A2C.js.map} +0 -0
  71. /package/dist/{chunk-3PJP5Y3U.js.map → chunk-GWIEUBFR.js.map} +0 -0
  72. /package/dist/{chunk-H57MQGNU.js.map → chunk-H6WJ63X2.js.map} +0 -0
  73. /package/dist/{chunk-NN5YCETI.js.map → chunk-HD6JIFKN.js.map} +0 -0
  74. /package/dist/{chunk-UQKI476D.js.map → chunk-M6EWJCAT.js.map} +0 -0
  75. /package/dist/{chunk-M2TLX6NM.js.map → chunk-QXITSNYM.js.map} +0 -0
@@ -5,7 +5,7 @@ import {
5
5
  PresignedUploadResponseSchema,
6
6
  SourceMapManifestResponseSchema,
7
7
  SourceMapUploadResponseSchema
8
- } from "./chunk-P45NZR4J.js";
8
+ } from "./chunk-JHUNLPSS.js";
9
9
 
10
10
  // src/source-map-uploader.ts
11
11
  import * as fs from "node:fs/promises";
@@ -331,4 +331,4 @@ export {
331
331
  uploadSourceMapsPresigned,
332
332
  uploadSourceMapsAuto
333
333
  };
334
- //# sourceMappingURL=chunk-UQKI476D.js.map
334
+ //# sourceMappingURL=chunk-M6EWJCAT.js.map
@@ -0,0 +1,130 @@
1
+ import {
2
+ tryEmitLifecycleEvent
3
+ } from "./chunk-CL3OVHPO.js";
4
+ import {
5
+ SpanStatusCode,
6
+ trace
7
+ } from "./chunk-DQ25VOKK.js";
8
+ import {
9
+ GLASSTRACE_ATTRIBUTE_NAMES
10
+ } from "./chunk-JHUNLPSS.js";
11
+
12
+ // src/middleware/index.ts
13
+ var ATTR = GLASSTRACE_ATTRIBUTE_NAMES;
14
+ var TRACER_NAME = "@glasstrace/sdk/middleware";
15
+ var _skippedUninstalledEmitted = false;
16
+ function _resetForTesting() {
17
+ _skippedUninstalledEmitted = false;
18
+ }
19
+ function extractRequestPath(req) {
20
+ if (req === null || typeof req !== "object") return void 0;
21
+ try {
22
+ const nextUrl = req.nextUrl;
23
+ if (nextUrl !== null && typeof nextUrl === "object") {
24
+ const pathname = nextUrl.pathname;
25
+ if (typeof pathname === "string" && pathname.length > 0) {
26
+ return pathname;
27
+ }
28
+ }
29
+ const url = req.url;
30
+ if (typeof url === "string" && url.length > 0) {
31
+ try {
32
+ return new URL(url).pathname;
33
+ } catch {
34
+ try {
35
+ return new URL(url, "http://localhost").pathname;
36
+ } catch {
37
+ return void 0;
38
+ }
39
+ }
40
+ }
41
+ } catch {
42
+ }
43
+ return void 0;
44
+ }
45
+ var INVALID_TRACE_ID = "00000000000000000000000000000000";
46
+ function isNoopSpan(span) {
47
+ try {
48
+ return span.spanContext().traceId === INVALID_TRACE_ID;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+ function tracedRequestMiddleware(options, handler) {
54
+ if (typeof options.name !== "string" || options.name.length === 0) {
55
+ throw new TypeError(
56
+ "tracedRequestMiddleware: options.name must be a non-empty string"
57
+ );
58
+ }
59
+ const wrapped = ((req, ...rest) => {
60
+ const tracer = trace.getTracer(TRACER_NAME);
61
+ return tracer.startActiveSpan(options.name, (span) => {
62
+ if (isNoopSpan(span)) {
63
+ if (!_skippedUninstalledEmitted) {
64
+ _skippedUninstalledEmitted = true;
65
+ tryEmitLifecycleEvent("middleware:skipped_uninstalled", {});
66
+ }
67
+ endSpanSafely(span);
68
+ return handler(req, ...rest);
69
+ }
70
+ try {
71
+ if (options.attributes) {
72
+ span.setAttributes(options.attributes);
73
+ }
74
+ const path = extractRequestPath(req);
75
+ if (path !== void 0) {
76
+ span.setAttribute(ATTR.CAUSAL_MIDDLEWARE_FOR_REQUEST, path);
77
+ }
78
+ } catch {
79
+ }
80
+ let result;
81
+ try {
82
+ result = handler(req, ...rest);
83
+ } catch (error) {
84
+ recordSpanError(span, error);
85
+ endSpanSafely(span);
86
+ throw error;
87
+ }
88
+ if (result !== null && typeof result === "object" && typeof result.then === "function") {
89
+ return result.then(
90
+ (value) => {
91
+ endSpanSafely(span);
92
+ return value;
93
+ },
94
+ (error) => {
95
+ recordSpanError(span, error);
96
+ endSpanSafely(span);
97
+ throw error;
98
+ }
99
+ );
100
+ }
101
+ endSpanSafely(span);
102
+ return result;
103
+ });
104
+ });
105
+ return wrapped;
106
+ }
107
+ function recordSpanError(span, error) {
108
+ const normalized = error instanceof Error ? error : typeof error === "string" ? error : new Error(String(error));
109
+ const statusMessage = normalized instanceof Error ? normalized.message : normalized;
110
+ try {
111
+ span.recordException(normalized);
112
+ } catch {
113
+ }
114
+ try {
115
+ span.setStatus({ code: SpanStatusCode.ERROR, message: statusMessage });
116
+ } catch {
117
+ }
118
+ }
119
+ function endSpanSafely(span) {
120
+ try {
121
+ span.end();
122
+ } catch {
123
+ }
124
+ }
125
+
126
+ export {
127
+ _resetForTesting,
128
+ tracedRequestMiddleware
129
+ };
130
+ //# sourceMappingURL=chunk-QHV7NFON.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware/index.ts"],"sourcesContent":["/**\n * Request-middleware-ownership instrumentation for Glasstrace.\n *\n * Subpath: `@glasstrace/sdk/middleware`\n *\n * This module exposes {@link tracedRequestMiddleware}, a wrapper that\n * turns a Next.js `middleware.ts` function (or any generic\n * `Request → Response`-shaped handler) into a span-emitting middleware\n * function. Each invocation opens a child span and tags it with the\n * `glasstrace.causal.middleware_for_request` causal-evidence attribute\n * carrying the originating request's normalized path so the\n * product-side trace-summary transform can link the middleware span\n * to the owning HTTP request trace (DISC-1537 / SDK-046).\n *\n * Edge-runtime safety\n * -------------------\n * The wrapper is included in the SDK's edge bundle\n * (`packages/sdk/src/edge-entry.ts`). Its closure imports only the\n * OTel API, the protocol constants, and the\n * `./optional-lifecycle.js` bridge — none of which reach into\n * `node:*` built-ins or the `process` global. The F003 closure scan\n * (`packages/sdk/scripts/check-edge-bundle.mjs`) enforces this on\n * every build.\n *\n * Causal-evidence form\n * --------------------\n * The wrapper attaches the originating request path as a span\n * attribute (`glasstrace.causal.middleware_for_request`). It does NOT\n * emit an OTel `Link`. Reasons:\n *\n * 1. The Next.js Edge runtime does not propagate AsyncLocalStorage\n * into `middleware.ts`, so there is no in-process\n * `SpanContext` to link to in that environment. Attribute-only\n * causality works in both Node and Edge runtimes; a Link would\n * degrade to a no-op (no parent context) on Edge.\n * 2. The product-side trace-summary transform reconstructs\n * ownership from `glasstrace.causal.*` attributes per\n * DISC-1539 §51-58; it does not require a Link.\n *\n * Invariants\n * ----------\n *\n * - The wrapped function preserves the user's call-site type so\n * Next.js's `middleware` export contract (`(req: NextRequest) =>\n * NextResponse | Response`) flows through unchanged.\n * - The middleware span MUST NOT overwrite `glasstrace.route`,\n * `glasstrace.http.status_code`, or `glasstrace.http.duration_ms`\n * on the parent HTTP span — root-request semantics are owned by\n * the enriching exporter (`packages/sdk/src/enriching-exporter.ts`).\n * - On a thrown handler error: span ends with `ERROR` status +\n * `recordException`; rethrows. The exception is normalized to\n * `Error | string` first so non-Error throwables (number, plain\n * object) do not crash `recordException`.\n * - Always ends the span (`finally`), even on `throw`.\n *\n * @module @glasstrace/sdk/middleware\n */\n\nimport {\n trace,\n SpanStatusCode,\n type AttributeValue,\n} from \"@opentelemetry/api\";\nimport { GLASSTRACE_ATTRIBUTE_NAMES } from \"@glasstrace/protocol\";\nimport { tryEmitLifecycleEvent } from \"../optional-lifecycle.js\";\n\nconst ATTR = GLASSTRACE_ATTRIBUTE_NAMES;\n\n/**\n * Module-level OTel tracer name for the middleware subpath. Resolves\n * through the global `ProxyTracerProvider` so the wrapper picks up\n * whatever provider the SDK has detected or registered (the SDK's\n * own enriching exporter, Sentry's processor in coexistence mode,\n * etc.). Re-resolved on every call site rather than cached at module\n * top level so test harnesses can install a provider after this\n * module is imported. This mirrors the tRPC subpath at\n * `packages/sdk/src/trpc/index.ts:128`.\n */\nconst TRACER_NAME = \"@glasstrace/sdk/middleware\";\n\n/**\n * Module-level once-flag for the\n * `middleware:skipped_uninstalled` lifecycle event. The flag is\n * exported via {@link _resetForTesting} so unit tests can re-arm it\n * between scenarios.\n */\nlet _skippedUninstalledEmitted = false;\n\n/**\n * INTERNAL — clears the once-flag for the\n * `middleware:skipped_uninstalled` lifecycle event. Invoked by\n * Vitest fixtures only; not part of the public surface.\n */\nexport function _resetForTesting(): void {\n _skippedUninstalledEmitted = false;\n}\n\n/**\n * Permissive structural bound for a request-middleware function. The\n * shape is the intersection of Next.js's `middleware.ts` export\n * (`(req: NextRequest, event?: NextFetchEvent) => NextResponse |\n * Response | Promise<NextResponse | Response> | undefined`) and the\n * generic Web Fetch API (`(req: Request, ...rest: any[]) => Response\n * | Promise<Response>`). The parameter list is typed with `any[]` for\n * the rest position so any caller-narrowed signature is assignable\n * without the wrapper having to import `next/server` types.\n *\n * Exported so consumers can reference it for type-inference assertions\n * (e.g., proving a strongly-typed handler fits the bound). The\n * runtime contract is fixed by the Web Fetch API: the first argument\n * is a `Request`-shaped object and the return is a `Response`-shaped\n * value (or a Promise of one).\n */\n// The `any[]` in the rest position is load-bearing: a tighter bound\n// (e.g. `unknown[]`) would reject `(req, event?) => ...` because\n// `unknown` cannot be passed in the contravariant parameter position\n// without an explicit cast at every call site. Capturing\n// `H extends RequestMiddlewareFunction` preserves caller types\n// through the wrapper's `: H` return.\n//\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type RequestMiddlewareFunction = (req: any, ...rest: any[]) => unknown;\n\n/**\n * Options for {@link tracedRequestMiddleware}.\n *\n * @example\n * ```ts\n * import { tracedRequestMiddleware } from \"@glasstrace/sdk/middleware\";\n * import type { NextRequest } from \"next/server\";\n *\n * export const middleware = tracedRequestMiddleware(\n * { name: \"auth-middleware\", attributes: { \"auth.required\": true } },\n * async (req: NextRequest) => {\n * // … your auth logic here …\n * return NextResponse.next();\n * },\n * );\n * ```\n */\nexport interface TracedRequestMiddlewareOptions {\n /**\n * Span name. Required, non-empty string. Used as the OTel span name\n * and appears in trace timelines. Names should be stable across\n * runs so the product-side transform can reason about middleware\n * identity (e.g., \"auth-middleware\", \"rate-limiter\"); avoid\n * embedding request data in the name.\n */\n name: string;\n /**\n * Optional attributes attached to the span before the wrapped\n * handler runs. Forwarded to OTel as-is via `span.setAttributes()`.\n * The SDK does not redact, sanitize, or scan values here — callers\n * MUST avoid placing tokens, credentials, or other sensitive data\n * in `attributes`.\n *\n * Sensitive request/response data is captured through gated SDK\n * paths (e.g., `glasstrace.error.response_body`), not through this\n * surface.\n */\n attributes?: Record<string, AttributeValue>;\n}\n\n/**\n * Extract the originating request path from a Fetch-API `Request` or\n * `NextRequest`-shaped object. Preference order:\n *\n * 1. `req.nextUrl.pathname` — present on `NextRequest`. This is\n * Next.js's parsed URL and is the most reliable source on the\n * Next.js Edge runtime, where `req.url` may be a relative form\n * depending on framework-internal rewrites.\n * 2. `new URL(req.url).pathname` — present on the generic Fetch\n * `Request`. The URL constructor accepts an absolute URL; if\n * `req.url` is relative we synthesize a base of\n * `http://localhost` to make parsing succeed (the host is\n * discarded — only the path is used).\n * 3. `undefined` — when neither field exists or both fail to\n * parse, the wrapper omits the causal attribute rather than\n * emitting a guessed value, per the SDK-046 product brief's\n * \"missing or unknown evidence is preferable to guessed\n * evidence\" rule (DISC-1537 / DISC-1539 product handoff).\n *\n * This function never throws.\n */\nfunction extractRequestPath(req: unknown): string | undefined {\n if (req === null || typeof req !== \"object\") return undefined;\n try {\n // NextRequest.nextUrl — already a URL-like object with .pathname.\n const nextUrl = (req as { nextUrl?: unknown }).nextUrl;\n if (nextUrl !== null && typeof nextUrl === \"object\") {\n const pathname = (nextUrl as { pathname?: unknown }).pathname;\n if (typeof pathname === \"string\" && pathname.length > 0) {\n return pathname;\n }\n }\n // Fall back to req.url. On Edge / Web Fetch this is always\n // absolute; on some Node frameworks it can be relative\n // (`/api/foo?x=1`), so synthesize a base if the URL constructor\n // throws on the absolute parse.\n const url = (req as { url?: unknown }).url;\n if (typeof url === \"string\" && url.length > 0) {\n try {\n return new URL(url).pathname;\n } catch {\n try {\n return new URL(url, \"http://localhost\").pathname;\n } catch {\n return undefined;\n }\n }\n }\n } catch {\n // Defensive: any unexpected shape failure returns undefined.\n }\n return undefined;\n}\n\n/**\n * Sentinel trace ID returned by the OTel API's noop tracer\n * (`@opentelemetry/api`'s `NonRecordingSpan`). Per the OTel\n * specification's noop semantics, the noop SpanContext exposes\n * `traceId === \"00000000000000000000000000000000\"`. Used by\n * {@link isNoopSpan} below to discriminate \"no provider registered\"\n * from \"real provider whose sampler dropped this span\" — the latter\n * also returns `isRecording() === false` but produces a valid\n * 32-char hex trace ID because the SDK assigns one before sampler\n * invocation for propagation purposes.\n */\nconst INVALID_TRACE_ID = \"00000000000000000000000000000000\";\n\n/**\n * Type guard for OTel spans created by the noop tracer. The OTel\n * API's noop SpanContext returns the all-zeros sentinel for\n * `traceId`; real SDK-emitted spans always have a valid 32-char hex\n * trace ID (even when a sampler decided to DROP the span). Using\n * the SpanContext discriminator keeps the SDK-not-registered fast\n * path from misfiring under normal head sampling configurations\n * (Copilot review 2026-05-08).\n *\n * Returning `true` here means the caller should NOT proceed with\n * span enrichment; the noop tracer would discard everything anyway.\n */\nfunction isNoopSpan(\n span: ReturnType<ReturnType<typeof trace.getTracer>[\"startSpan\"]>,\n): boolean {\n try {\n return span.spanContext().traceId === INVALID_TRACE_ID;\n } catch {\n // Defensive: treat \"could not determine\" as not-noop so we\n // continue down the enrichment path. The noop tracer itself\n // never throws from `spanContext()`.\n return false;\n }\n}\n\n/**\n * Wrap a Next.js / generic-fetch request-middleware function in an\n * OTel span tagged with `glasstrace.causal.middleware_for_request`.\n *\n * Each call to the returned function:\n *\n * 1. Detects the SDK's registration state. When the OTel API is\n * still on the noop tracer (SDK not registered, or\n * `OtelState.UNCONFIGURED`), runs the wrapped handler directly\n * and emits a `middleware:skipped_uninstalled` lifecycle event\n * (at most once per process). No span is opened.\n * 2. Otherwise opens a span named `options.name` under the active\n * OTel context (typically the HTTP server span on Node;\n * detached on Edge where AsyncLocalStorage is not available).\n * Sets `options.attributes` first, then attaches the originating\n * request's path (via {@link extractRequestPath}) as\n * `glasstrace.causal.middleware_for_request`. The path is\n * omitted when extraction returns `undefined` so absent evidence\n * is preferred over guessed evidence.\n * 3. Awaits the wrapped handler.\n * 4. On a thrown error: normalizes the throwable to `Error | string`\n * so `recordException` does not throw on non-Error values; sets\n * `span.status` to `ERROR` with the error's message; rethrows the\n * original (un-normalized) error verbatim.\n * 5. On a successful return: leaves the span status `UNSET` per OTel\n * instrumentation-library guidance (explicit `OK` would shadow\n * downstream consumers' error transitions).\n * 6. Always ends the span, even on `throw` or `return`.\n *\n * Type-inference: the returned function preserves the input function's\n * type `H`, so caller-narrowed signatures (e.g., `(req: NextRequest)\n * => NextResponse`) flow through unchanged.\n *\n * @param options - Span name and optional pre-start attributes.\n * @param handler - The user's middleware handler. Must accept a\n * request-shaped object as its first argument and return (or\n * resolve to) a response-shaped value.\n * @returns The wrapped handler with the same call signature and\n * return type as `handler`.\n *\n * @example Next.js middleware.ts\n * ```ts\n * import { tracedRequestMiddleware } from \"@glasstrace/sdk/middleware\";\n * import { NextResponse, type NextRequest } from \"next/server\";\n *\n * export const middleware = tracedRequestMiddleware(\n * { name: \"auth-middleware\" },\n * async (req: NextRequest) => {\n * if (!req.cookies.get(\"session\")) {\n * return NextResponse.redirect(new URL(\"/login\", req.url));\n * }\n * return NextResponse.next();\n * },\n * );\n *\n * export const config = { matcher: [\"/dashboard/:path*\"] };\n * ```\n */\nexport function tracedRequestMiddleware<H extends RequestMiddlewareFunction>(\n options: TracedRequestMiddlewareOptions,\n handler: H,\n): H {\n // Eager validation: a mis-typed call site fails at wrapper-\n // construction time (typically at module load) rather than at\n // first request, when the failure is harder to diagnose.\n if (typeof options.name !== \"string\" || options.name.length === 0) {\n throw new TypeError(\n \"tracedRequestMiddleware: options.name must be a non-empty string\",\n );\n }\n\n // Capture options + handler lexically. Do not read from `this` —\n // Next.js's middleware loader invokes this as a plain function, not\n // a method, so `this` is undefined.\n const wrapped = ((req: Parameters<H>[0], ...rest: unknown[]): unknown => {\n const tracer = trace.getTracer(TRACER_NAME);\n return tracer.startActiveSpan(options.name, (span) => {\n // SDK-not-registered fast path. Detecting via the public\n // `isRecording()` method on the started span is the canonical\n // OTel-API-only probe — the noop tracer's `NonRecordingSpan`\n // returns `false`, real SDK-emitted spans return `true`. This\n // avoids the more expensive workaround of opening a probe span\n // ahead of time, which would emit a useless span on every\n // request when a real provider is registered.\n if (isNoopSpan(span)) {\n if (!_skippedUninstalledEmitted) {\n _skippedUninstalledEmitted = true;\n tryEmitLifecycleEvent(\"middleware:skipped_uninstalled\", {});\n }\n // The noop span needs no enrichment and no end() because the\n // noop implementation is a no-op for both, but call end()\n // anyway for symmetry with the real-span path.\n endSpanSafely(span);\n return (handler as (...args: unknown[]) => unknown)(req, ...rest);\n }\n\n // Set caller-supplied attributes first so they appear on the\n // span before any internal attribute we add below.\n try {\n if (options.attributes) {\n span.setAttributes(options.attributes);\n }\n const path = extractRequestPath(req);\n if (path !== undefined) {\n span.setAttribute(ATTR.CAUSAL_MIDDLEWARE_FOR_REQUEST, path);\n }\n } catch {\n // Attribute-setting failures are advisory; never block the\n // wrapped handler from running.\n }\n\n let result: unknown;\n try {\n result = (handler as (...args: unknown[]) => unknown)(req, ...rest);\n } catch (error) {\n recordSpanError(span, error);\n endSpanSafely(span);\n throw error;\n }\n\n // The handler may be sync or async. If async, attach the\n // span-end + error-recording on the promise chain; otherwise\n // end the span synchronously and return the value.\n if (\n result !== null &&\n typeof result === \"object\" &&\n typeof (result as Promise<unknown>).then === \"function\"\n ) {\n return (result as Promise<unknown>).then(\n (value) => {\n endSpanSafely(span);\n return value;\n },\n (error: unknown) => {\n recordSpanError(span, error);\n endSpanSafely(span);\n throw error;\n },\n );\n }\n\n endSpanSafely(span);\n return result;\n });\n }) as H;\n\n return wrapped;\n}\n\n/**\n * Records an exception on a span and sets the span status to `ERROR`.\n * Both calls are independently guarded so a failing `recordException`\n * cannot prevent the status transition, and vice versa. The user's\n * original error value is preserved verbatim — wrapping is purely a\n * span-side normalization.\n */\nfunction recordSpanError(\n span: ReturnType<ReturnType<typeof trace.getTracer>[\"startSpan\"]>,\n error: unknown,\n): void {\n const normalized: Error | string =\n error instanceof Error\n ? error\n : typeof error === \"string\"\n ? error\n : new Error(String(error));\n const statusMessage =\n normalized instanceof Error ? normalized.message : normalized;\n try {\n span.recordException(normalized);\n } catch {\n // Swallow: instrumentation must never replace the user's error.\n }\n try {\n span.setStatus({ code: SpanStatusCode.ERROR, message: statusMessage });\n } catch {\n // Swallow: independent from recordException so a failing\n // recordException does not prevent the ERROR status.\n }\n}\n\n/**\n * Ends a span, swallowing any throw from the OTel implementation. A\n * misbehaving `span.end()` must not replace the wrapped handler's\n * return value or thrown error with an unrelated one.\n */\nfunction endSpanSafely(\n span: ReturnType<ReturnType<typeof trace.getTracer>[\"startSpan\"]>,\n): void {\n try {\n span.end();\n } catch {\n // Span lifecycle errors are always non-fatal at this layer.\n }\n}\n"],"mappings":"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","names":[]}
@@ -2,12 +2,12 @@ import {
2
2
  atomicWriteFile,
3
3
  refreshGenericMcpConfigAtRuntime,
4
4
  resolveEffectiveMcpCredential
5
- } from "./chunk-WL6BXEJ5.js";
5
+ } from "./chunk-DKV53A2C.js";
6
6
  import {
7
7
  DEFAULT_CAPTURE_CONFIG,
8
8
  SdkCachedConfigSchema,
9
9
  SdkInitResponseSchema
10
- } from "./chunk-P45NZR4J.js";
10
+ } from "./chunk-JHUNLPSS.js";
11
11
  import {
12
12
  __require
13
13
  } from "./chunk-NSBPE2FW.js";
@@ -692,4 +692,4 @@ export {
692
692
  didLastInitSucceed,
693
693
  verifyInitReachable
694
694
  };
695
- //# sourceMappingURL=chunk-M2TLX6NM.js.map
695
+ //# sourceMappingURL=chunk-QXITSNYM.js.map
@@ -0,0 +1,119 @@
1
+ import {
2
+ tryEmitLifecycleEvent
3
+ } from "./chunk-CL3OVHPO.js";
4
+ import {
5
+ SpanStatusCode,
6
+ context,
7
+ trace
8
+ } from "./chunk-DQ25VOKK.js";
9
+ import {
10
+ GLASSTRACE_ATTRIBUTE_NAMES
11
+ } from "./chunk-JHUNLPSS.js";
12
+
13
+ // src/async-context/index.ts
14
+ var ATTR = GLASSTRACE_ATTRIBUTE_NAMES;
15
+ var TRACER_NAME = "@glasstrace/sdk/async-context";
16
+ var INVALID_TRACE_ID = "00000000000000000000000000000000";
17
+ var _skippedUninstalledEmitted = false;
18
+ var _noOriginatingContextEmitted = false;
19
+ function _resetForTesting() {
20
+ _skippedUninstalledEmitted = false;
21
+ _noOriginatingContextEmitted = false;
22
+ }
23
+ function withAsyncCausality(options, fn) {
24
+ if (typeof options.name !== "string" || options.name.length === 0) {
25
+ throw new TypeError(
26
+ "withAsyncCausality: options.name must be a non-empty string"
27
+ );
28
+ }
29
+ if (typeof fn !== "function") {
30
+ throw new TypeError("withAsyncCausality: fn must be a function");
31
+ }
32
+ const capturedContext = (() => {
33
+ try {
34
+ const active = trace.getActiveSpan();
35
+ if (!active) return void 0;
36
+ const ctx = active.spanContext();
37
+ if (ctx.traceId === INVALID_TRACE_ID) return void 0;
38
+ return ctx;
39
+ } catch {
40
+ return void 0;
41
+ }
42
+ })();
43
+ return async () => {
44
+ const tracer = trace.getTracer(TRACER_NAME);
45
+ const span = tracer.startSpan(options.name, {
46
+ root: true,
47
+ links: capturedContext !== void 0 ? [{ context: capturedContext }] : void 0
48
+ });
49
+ if (isNoopSpan(span)) {
50
+ if (!_skippedUninstalledEmitted) {
51
+ _skippedUninstalledEmitted = true;
52
+ tryEmitLifecycleEvent("async:skipped_uninstalled", {});
53
+ }
54
+ endSpanSafely(span);
55
+ return Promise.resolve(fn());
56
+ }
57
+ if (capturedContext === void 0 && !_noOriginatingContextEmitted) {
58
+ _noOriginatingContextEmitted = true;
59
+ tryEmitLifecycleEvent("async:no_originating_context", {});
60
+ }
61
+ try {
62
+ if (options.attributes) {
63
+ span.setAttributes(options.attributes);
64
+ }
65
+ if (capturedContext !== void 0) {
66
+ span.setAttribute(
67
+ ATTR.CAUSAL_POST_RESPONSE_ASYNC,
68
+ capturedContext.traceId
69
+ );
70
+ span.setAttribute(ATTR.CAUSAL_AFFECTS_HTTP_STATUS, false);
71
+ span.setAttribute(ATTR.CAUSAL_AFFECTS_HTTP_DURATION, false);
72
+ }
73
+ } catch {
74
+ }
75
+ try {
76
+ const value = await context.with(
77
+ trace.setSpan(context.active(), span),
78
+ fn
79
+ );
80
+ return value;
81
+ } catch (error) {
82
+ recordSpanError(span, error);
83
+ throw error;
84
+ } finally {
85
+ endSpanSafely(span);
86
+ }
87
+ };
88
+ }
89
+ function isNoopSpan(span) {
90
+ try {
91
+ return span.spanContext().traceId === INVALID_TRACE_ID;
92
+ } catch {
93
+ return false;
94
+ }
95
+ }
96
+ function recordSpanError(span, error) {
97
+ const normalized = error instanceof Error ? error : typeof error === "string" ? error : new Error(String(error));
98
+ const statusMessage = normalized instanceof Error ? normalized.message : normalized;
99
+ try {
100
+ span.recordException(normalized);
101
+ } catch {
102
+ }
103
+ try {
104
+ span.setStatus({ code: SpanStatusCode.ERROR, message: statusMessage });
105
+ } catch {
106
+ }
107
+ }
108
+ function endSpanSafely(span) {
109
+ try {
110
+ span.end();
111
+ } catch {
112
+ }
113
+ }
114
+
115
+ export {
116
+ _resetForTesting,
117
+ withAsyncCausality
118
+ };
119
+ //# sourceMappingURL=chunk-RQ5BIWDT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/async-context/index.ts"],"sourcesContent":["/**\n * Post-response async causality instrumentation for Glasstrace.\n *\n * Subpath: `@glasstrace/sdk/async-context`\n *\n * This module exposes {@link withAsyncCausality}, a continuation-\n * passing wrapper that captures the active OTel `SpanContext` at call\n * time and binds it to a callback. When the callback runs later\n * (Next.js `after()`, queue dispatchers, webhook fire-and-forget),\n * the wrapper opens a span linked to the originating trace via two\n * channels:\n *\n * - An OTel `Link` to the captured `SpanContext` — the OTel-native\n * pointer between two spans in different traces. Surfaces in\n * standard OTel-aware UIs (Jaeger, Honeycomb, etc.) as a\n * \"follows from\" relationship.\n * - A `glasstrace.causal.post_response_async` attribute carrying\n * the captured trace ID (32-char hex). Used by the product-side\n * trace-summary transform (per DISC-1539's product handoff) to\n * reconstruct ownership without resolving the Link. Two\n * companion booleans\n * (`glasstrace.causal.affects_http_status` and\n * `glasstrace.causal.affects_http_duration`) document that the\n * async work does NOT participate in the root request's outcome.\n *\n * Both channels are emitted together so the SDK is robust to\n * downstream transforms that resolve causality through either form.\n *\n * Edge-runtime safety\n * -------------------\n * The wrapper is included in the SDK's edge bundle\n * (`packages/sdk/src/edge-entry.ts`). Its closure imports only the\n * OTel API, the protocol constants, and the\n * `./optional-lifecycle.js` bridge — none of which reach into\n * `node:*` built-ins or the `process` global. The F003 closure scan\n * (`packages/sdk/scripts/check-edge-bundle.mjs`) enforces this on\n * every build.\n *\n * Strategy: continuation-passing, NOT global ALS propagation\n * ---------------------------------------------------------\n * Per the SDK-046 brief §2.3: ALS continuity across Next.js `after()`\n * is uncertain (the framework may schedule via `queueMicrotask`\n * (preserves ALS) or via cross-tick scheduling (drops ALS)). Relying\n * on ALS would couple the SDK to Next internals. Continuation-passing\n * makes the causality explicit — the user wraps the callback they\n * pass to `after()` / their queue, and the captured `SpanContext`\n * travels with the closure regardless of how the framework schedules\n * it.\n *\n * @module @glasstrace/sdk/async-context\n */\n\nimport {\n trace,\n context,\n SpanStatusCode,\n type AttributeValue,\n type SpanContext,\n} from \"@opentelemetry/api\";\nimport { GLASSTRACE_ATTRIBUTE_NAMES } from \"@glasstrace/protocol\";\nimport { tryEmitLifecycleEvent } from \"../optional-lifecycle.js\";\n\nconst ATTR = GLASSTRACE_ATTRIBUTE_NAMES;\n\n/**\n * Module-level OTel tracer name for the async-context subpath.\n * Resolves through the global `ProxyTracerProvider` so the wrapper\n * picks up whatever provider the SDK has registered. Re-resolved on\n * every call site rather than cached at module top level so test\n * harnesses can install a provider after this module is imported.\n * Mirrors the tRPC subpath at `packages/sdk/src/trpc/index.ts:128`.\n */\nconst TRACER_NAME = \"@glasstrace/sdk/async-context\";\n\n/**\n * The W3C-spec-defined invalid trace ID. The OTel API noop tracer\n * returns this value from `Span.spanContext().traceId`. We use this\n * to detect both (a) the SDK-not-registered case via the\n * noop-tracer probe and (b) the no-active-span case via the\n * captured `SpanContext`.\n */\nconst INVALID_TRACE_ID = \"00000000000000000000000000000000\";\n\n/**\n * Module-level once-flags for lifecycle events. Cleared via\n * {@link _resetForTesting}.\n */\nlet _skippedUninstalledEmitted = false;\nlet _noOriginatingContextEmitted = false;\n\n/**\n * INTERNAL — clears once-flags for unit tests; not part of the\n * public surface.\n */\nexport function _resetForTesting(): void {\n _skippedUninstalledEmitted = false;\n _noOriginatingContextEmitted = false;\n}\n\n/**\n * Options for {@link withAsyncCausality}.\n *\n * @example\n * ```ts\n * import { withAsyncCausality } from \"@glasstrace/sdk/async-context\";\n * import { after } from \"next/server\";\n *\n * export async function POST(req: Request) {\n * const result = await processRequest(req);\n * after(\n * withAsyncCausality(\n * { name: \"send-confirmation-email\" },\n * async () => sendEmail(result.userId),\n * ),\n * );\n * return Response.json({ ok: true });\n * }\n * ```\n */\nexport interface WithAsyncCausalityOptions {\n /**\n * Span name for the async work. Required, non-empty string. Used as\n * the OTel span name and appears in trace timelines. Names should\n * be stable across runs (e.g., \"send-confirmation-email\",\n * \"enqueue-webhook-dispatch\"); avoid embedding payload data in the\n * name.\n */\n name: string;\n /**\n * Optional attributes attached to the span before the wrapped\n * callback runs. Forwarded to OTel as-is via `span.setAttributes()`.\n * The SDK does not redact, sanitize, or scan values here — callers\n * MUST avoid placing tokens, credentials, or other sensitive data\n * in `attributes`.\n */\n attributes?: Record<string, AttributeValue>;\n}\n\n/**\n * Capture the active OTel `SpanContext` at call time, bind it to a\n * callback, and return a continuation that will emit a\n * causally-linked span when invoked.\n *\n * The returned continuation:\n *\n * 1. Detects the SDK's registration state. When the OTel API is\n * still on the noop tracer, runs the wrapped callback directly\n * and emits an `async:skipped_uninstalled` lifecycle event (at\n * most once per process). No span is opened.\n * 2. Otherwise opens a span named `options.name` as a NEW root\n * span (not parented to the captured context — `after()` /\n * queue dispatchers run outside the originating request's OTel\n * context, so the async work belongs to a separate trace).\n * 3. When a captured `SpanContext` exists with a valid trace ID,\n * attaches:\n * - an OTel `Link` to that `SpanContext` (the OTel-native\n * form),\n * - the `glasstrace.causal.post_response_async` attribute\n * carrying the trace ID (the transform-readable form),\n * - `glasstrace.causal.affects_http_status = false` and\n * `glasstrace.causal.affects_http_duration = false`\n * documenting that the async work does NOT participate in\n * the root request's outcome.\n * When no valid `SpanContext` was captured, none of these are\n * emitted (per SDK-046's \"missing or unknown evidence is\n * preferable to guessed evidence\" rule) and an\n * `async:no_originating_context` lifecycle event fires (at\n * most once per process).\n * 4. Awaits the wrapped callback.\n * 5. On a thrown error: normalizes the throwable; sets `ERROR`\n * status with `recordException`; rethrows the original error\n * verbatim.\n * 6. On a successful return: leaves status `UNSET`.\n * 7. Always ends the span.\n *\n * The continuation returns a Promise resolving to the callback's\n * return value (Promise-or-value semantics: a sync callback's value\n * is wrapped in `Promise.resolve()`).\n *\n * @param options - Span name and optional pre-start attributes.\n * @param fn - The async callback to run later. May be sync or async;\n * the wrapper always returns a Promise to give a consistent\n * continuation shape regardless of `fn`'s synchronicity.\n * @returns A continuation `() => Promise<T>` that, when invoked,\n * emits the causally-linked span and runs `fn`.\n *\n * @example Next.js after() — typical use\n * ```ts\n * import { withAsyncCausality } from \"@glasstrace/sdk/async-context\";\n * import { after } from \"next/server\";\n *\n * export async function POST(req: Request) {\n * const result = await processRequest(req);\n * after(\n * withAsyncCausality(\n * { name: \"send-confirmation-email\" },\n * async () => sendEmail(result.userId),\n * ),\n * );\n * return Response.json({ ok: true });\n * }\n * ```\n *\n * @example Queue dispatcher — capture before enqueue\n * ```ts\n * const dispatch = withAsyncCausality(\n * { name: \"process-webhook\" },\n * async () => handler(payload),\n * );\n * await queue.enqueue(dispatch);\n * ```\n */\nexport function withAsyncCausality<T>(\n options: WithAsyncCausalityOptions,\n fn: () => Promise<T> | T,\n): () => Promise<T> {\n if (typeof options.name !== \"string\" || options.name.length === 0) {\n throw new TypeError(\n \"withAsyncCausality: options.name must be a non-empty string\",\n );\n }\n if (typeof fn !== \"function\") {\n throw new TypeError(\"withAsyncCausality: fn must be a function\");\n }\n\n // Capture-time: snapshot the active SpanContext, if any. The\n // capture happens synchronously in the wrapper-construction call,\n // BEFORE the user passes the continuation to `after()` /\n // `queue.enqueue()`. Reading via `trace.getActiveSpan()` rather\n // than `context.active()` mirrors `captureCorrelationId()` at\n // `packages/sdk/src/correlation-id.ts:83` — the active-span API is\n // the public OTel surface for this.\n const capturedContext: SpanContext | undefined = (() => {\n try {\n const active = trace.getActiveSpan();\n if (!active) return undefined;\n const ctx = active.spanContext();\n // Reject the noop-tracer's invalid trace ID. Treating it as\n // captured would emit a Link to all-zeros, which is misleading.\n if (ctx.traceId === INVALID_TRACE_ID) return undefined;\n return ctx;\n } catch {\n return undefined;\n }\n })();\n\n return async (): Promise<T> => {\n const tracer = trace.getTracer(TRACER_NAME);\n // The async span is a NEW root: post-response work runs outside\n // the originating request's OTel context, so parenting to the\n // captured context would put two unrelated runtime executions in\n // the same trace tree. Causality is communicated via the Link +\n // attribute pair instead.\n const span = tracer.startSpan(options.name, {\n root: true,\n links:\n capturedContext !== undefined\n ? [{ context: capturedContext }]\n : undefined,\n });\n\n // SDK-not-registered fast path. The public `isRecording()` probe\n // returns `false` on noop spans without producing an exported\n // span; this avoids emitting a useless probe span on every\n // continuation when a real provider is registered.\n if (isNoopSpan(span)) {\n if (!_skippedUninstalledEmitted) {\n _skippedUninstalledEmitted = true;\n tryEmitLifecycleEvent(\"async:skipped_uninstalled\", {});\n }\n endSpanSafely(span);\n return Promise.resolve(fn());\n }\n\n if (capturedContext === undefined && !_noOriginatingContextEmitted) {\n _noOriginatingContextEmitted = true;\n tryEmitLifecycleEvent(\"async:no_originating_context\", {});\n }\n\n try {\n if (options.attributes) {\n span.setAttributes(options.attributes);\n }\n if (capturedContext !== undefined) {\n span.setAttribute(\n ATTR.CAUSAL_POST_RESPONSE_ASYNC,\n capturedContext.traceId,\n );\n span.setAttribute(ATTR.CAUSAL_AFFECTS_HTTP_STATUS, false);\n span.setAttribute(ATTR.CAUSAL_AFFECTS_HTTP_DURATION, false);\n }\n } catch {\n // Attribute failures are advisory; do not block fn().\n }\n\n try {\n // Activate the span as the current OTel context during fn()\n // execution so any nested spans (auto-instrumented OR manual)\n // are parented under this span. Without `context.with`, the\n // started span exists but is not in the active context, so\n // child spans become orphan roots (Copilot review 2026-05-08).\n // We use `context.with` rather than `tracer.startActiveSpan`\n // so the existing setup (Link/attribute application above)\n // can run pre-activation; the existing setup is small but\n // not trivial to reorder.\n const value = await context.with(\n trace.setSpan(context.active(), span),\n fn,\n );\n return value;\n } catch (error) {\n recordSpanError(span, error);\n throw error;\n } finally {\n endSpanSafely(span);\n }\n };\n}\n\n/**\n * Type guard for OTel noop spans. Mirrors the same check in\n * `../middleware/index.ts`. The OTel API's noop tracer\n * (`@opentelemetry/api`'s `NonRecordingSpan`) returns the all-zeros\n * sentinel trace ID `00000000000000000000000000000000` from\n * `Span.spanContext().traceId`. Real SDK-emitted spans — including\n * spans that a sampler chose to DROP (which legitimately return\n * `isRecording() === false`) — return a valid 32-char hex trace ID\n * because the SDK assigns a trace ID before sampler invocation for\n * propagation purposes. Using the SpanContext discriminator keeps\n * the SDK-not-registered fast path from misfiring under normal head\n * sampling configurations (Copilot review 2026-05-08).\n */\nfunction isNoopSpan(\n span: ReturnType<ReturnType<typeof trace.getTracer>[\"startSpan\"]>,\n): boolean {\n try {\n return span.spanContext().traceId === INVALID_TRACE_ID;\n } catch {\n return false;\n }\n}\n\n/**\n * See {@link ../middleware/index.ts} — duplicated here rather than\n * shared because the OTel `Span` type is structural and importing\n * a helper from a sibling module would force the modules to share a\n * deeper dependency. Both copies are exactly two non-throwing\n * `try`/`catch` blocks; the duplication is intentional and trivial.\n */\nfunction recordSpanError(\n span: ReturnType<ReturnType<typeof trace.getTracer>[\"startSpan\"]>,\n error: unknown,\n): void {\n const normalized: Error | string =\n error instanceof Error\n ? error\n : typeof error === \"string\"\n ? error\n : new Error(String(error));\n const statusMessage =\n normalized instanceof Error ? normalized.message : normalized;\n try {\n span.recordException(normalized);\n } catch {\n /* swallow */\n }\n try {\n span.setStatus({ code: SpanStatusCode.ERROR, message: statusMessage });\n } catch {\n /* swallow */\n }\n}\n\n/** See {@link ../middleware/index.ts}. */\nfunction endSpanSafely(\n span: ReturnType<ReturnType<typeof trace.getTracer>[\"startSpan\"]>,\n): void {\n try {\n span.end();\n } catch {\n /* swallow */\n }\n}\n"],"mappings":";;;;;;;;;;;;;AA8DA,IAAM,OAAO;AAUb,IAAM,cAAc;AASpB,IAAM,mBAAmB;AAMzB,IAAI,6BAA6B;AACjC,IAAI,+BAA+B;AAM5B,SAAS,mBAAyB;AACvC,+BAA6B;AAC7B,iCAA+B;AACjC;AAmHO,SAAS,mBACd,SACA,IACkB;AAClB,MAAI,OAAO,QAAQ,SAAS,YAAY,QAAQ,KAAK,WAAW,GAAG;AACjE,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,OAAO,OAAO,YAAY;AAC5B,UAAM,IAAI,UAAU,2CAA2C;AAAA,EACjE;AASA,QAAM,mBAA4C,MAAM;AACtD,QAAI;AACF,YAAM,SAAS,MAAM,cAAc;AACnC,UAAI,CAAC,OAAQ,QAAO;AACpB,YAAM,MAAM,OAAO,YAAY;AAG/B,UAAI,IAAI,YAAY,iBAAkB,QAAO;AAC7C,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,GAAG;AAEH,SAAO,YAAwB;AAC7B,UAAM,SAAS,MAAM,UAAU,WAAW;AAM1C,UAAM,OAAO,OAAO,UAAU,QAAQ,MAAM;AAAA,MAC1C,MAAM;AAAA,MACN,OACE,oBAAoB,SAChB,CAAC,EAAE,SAAS,gBAAgB,CAAC,IAC7B;AAAA,IACR,CAAC;AAMD,QAAI,WAAW,IAAI,GAAG;AACpB,UAAI,CAAC,4BAA4B;AAC/B,qCAA6B;AAC7B,8BAAsB,6BAA6B,CAAC,CAAC;AAAA,MACvD;AACA,oBAAc,IAAI;AAClB,aAAO,QAAQ,QAAQ,GAAG,CAAC;AAAA,IAC7B;AAEA,QAAI,oBAAoB,UAAa,CAAC,8BAA8B;AAClE,qCAA+B;AAC/B,4BAAsB,gCAAgC,CAAC,CAAC;AAAA,IAC1D;AAEA,QAAI;AACF,UAAI,QAAQ,YAAY;AACtB,aAAK,cAAc,QAAQ,UAAU;AAAA,MACvC;AACA,UAAI,oBAAoB,QAAW;AACjC,aAAK;AAAA,UACH,KAAK;AAAA,UACL,gBAAgB;AAAA,QAClB;AACA,aAAK,aAAa,KAAK,4BAA4B,KAAK;AACxD,aAAK,aAAa,KAAK,8BAA8B,KAAK;AAAA,MAC5D;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,QAAI;AAUF,YAAM,QAAQ,MAAM,QAAQ;AAAA,QAC1B,MAAM,QAAQ,QAAQ,OAAO,GAAG,IAAI;AAAA,QACpC;AAAA,MACF;AACA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,sBAAgB,MAAM,KAAK;AAC3B,YAAM;AAAA,IACR,UAAE;AACA,oBAAc,IAAI;AAAA,IACpB;AAAA,EACF;AACF;AAeA,SAAS,WACP,MACS;AACT,MAAI;AACF,WAAO,KAAK,YAAY,EAAE,YAAY;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,SAAS,gBACP,MACA,OACM;AACN,QAAM,aACJ,iBAAiB,QACb,QACA,OAAO,UAAU,WACf,QACA,IAAI,MAAM,OAAO,KAAK,CAAC;AAC/B,QAAM,gBACJ,sBAAsB,QAAQ,WAAW,UAAU;AACrD,MAAI;AACF,SAAK,gBAAgB,UAAU;AAAA,EACjC,QAAQ;AAAA,EAER;AACA,MAAI;AACF,SAAK,UAAU,EAAE,MAAM,eAAe,OAAO,SAAS,cAAc,CAAC;AAAA,EACvE,QAAQ;AAAA,EAER;AACF;AAGA,SAAS,cACP,MACM;AACN,MAAI;AACF,SAAK,IAAI;AAAA,EACX,QAAQ;AAAA,EAER;AACF;","names":[]}