@geekmidas/client 10.0.0-alpha.84 → 10.0.0-alpha.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as traceContextInjector, i as sampledAt, n as InvalidClientSampleRate, r as TraceContextInjector, t as ClientTelemetryOptions } from "./telemetry-BbRAq81t.cjs";
1
+ import { a as traceContextInjector, i as sampledAt, n as InvalidClientSampleRate, r as TraceContextInjector, t as ClientTelemetryOptions } from "./telemetry-g3BE86Wd.cjs";
2
2
  import { EndpointString, ErrorTransformer, ExtractEndpointConfig, ExtractEndpointResponse, ExtractMethod, ExtractPathParams, ExtractQueryParams, ExtractRequestBody, ExtractResponse, FetcherOptions, FilterEndpointByMethod, FilteredRequestConfig, IsConfigRequired, MutationEndpoint, OpenAPIRoutes, ParseEndpoint, QueryEndpoint, RequestConfig, TypedApiFunction, TypedEndpoint, ValidEndpoint, WrappedApiFunction, WrappedResult } from "./types.cjs";
3
3
  import { CALL_METHODS, CallMethod, MethodCall, MethodCalls, RoutesFor, methodCalls } from "./methods.cjs";
4
4
  import { ApiKeyProvider, AuthFetcherOptions, AuthStrategy, AuthenticatedEndpoints, AwsSigner, GetEndpointAuth, PublicEndpoints, SecuritySchemeObject, TokenProvider, UsedSecuritySchemes, createAuthAwareFetcher } from "./auth-fetcher.cjs";
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as traceContextInjector, i as sampledAt, n as InvalidClientSampleRate, r as TraceContextInjector, t as ClientTelemetryOptions } from "./telemetry-BbRAq81t.mjs";
1
+ import { a as traceContextInjector, i as sampledAt, n as InvalidClientSampleRate, r as TraceContextInjector, t as ClientTelemetryOptions } from "./telemetry-g3BE86Wd.mjs";
2
2
  import { EndpointString, ErrorTransformer, ExtractEndpointConfig, ExtractEndpointResponse, ExtractMethod, ExtractPathParams, ExtractQueryParams, ExtractRequestBody, ExtractResponse, FetcherOptions, FilterEndpointByMethod, FilteredRequestConfig, IsConfigRequired, MutationEndpoint, OpenAPIRoutes, ParseEndpoint, QueryEndpoint, RequestConfig, TypedApiFunction, TypedEndpoint, ValidEndpoint, WrappedApiFunction, WrappedResult } from "./types.mjs";
3
3
  import { CALL_METHODS, CallMethod, MethodCall, MethodCalls, RoutesFor, methodCalls } from "./methods.mjs";
4
4
  import { ApiKeyProvider, AuthFetcherOptions, AuthStrategy, AuthenticatedEndpoints, AwsSigner, GetEndpointAuth, PublicEndpoints, SecuritySchemeObject, TokenProvider, UsedSecuritySchemes, createAuthAwareFetcher } from "./auth-fetcher.mjs";
@@ -15,7 +15,10 @@
15
15
  * flag decided once per page view from {@link ClientTelemetryOptions.sampleRate}.
16
16
  *
17
17
  * Nothing is sent to any origin but the API's own: a trace id is an identifier,
18
- * and a third party has no business correlating a user's requests by it.
18
+ * and a third party has no business correlating a user's requests by it. And
19
+ * nothing at all where a `fetch` instrumentation is listening (a server with
20
+ * OpenTelemetry's undici instrumentation): it writes the request's own
21
+ * context, and two `traceparent` headers would be read as none.
19
22
  *
20
23
  * `@opentelemetry/api` is not imported. Every copy of it — whichever version
21
24
  * an app bundles — registers its globals on `globalThis` under
@@ -62,4 +65,4 @@ declare function sampledAt(traceId: string, rate: number): boolean;
62
65
  declare function traceContextInjector(baseURL: string, telemetry: boolean | ClientTelemetryOptions | undefined): TraceContextInjector | undefined;
63
66
  //#endregion
64
67
  export { traceContextInjector as a, sampledAt as i, InvalidClientSampleRate as n, TraceContextInjector as r, ClientTelemetryOptions as t };
65
- //# sourceMappingURL=telemetry-BbRAq81t.d.cts.map
68
+ //# sourceMappingURL=telemetry-g3BE86Wd.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-g3BE86Wd.d.cts","names":[],"sources":["../src/telemetry.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA8BiB;;;;;;;;;;;EAWhB;;;cAIY,gCAAgC;WACvB;EAArB,YAAqB;;;KASV,wBACX,aACA,SAAS;;;;;;;;;;iBAiEM,UAAU,iBAAiB;;;;;iBA6E3B,qBACf,iBACA,qBAAqB,qCACnB"}
@@ -15,7 +15,10 @@
15
15
  * flag decided once per page view from {@link ClientTelemetryOptions.sampleRate}.
16
16
  *
17
17
  * Nothing is sent to any origin but the API's own: a trace id is an identifier,
18
- * and a third party has no business correlating a user's requests by it.
18
+ * and a third party has no business correlating a user's requests by it. And
19
+ * nothing at all where a `fetch` instrumentation is listening (a server with
20
+ * OpenTelemetry's undici instrumentation): it writes the request's own
21
+ * context, and two `traceparent` headers would be read as none.
19
22
  *
20
23
  * `@opentelemetry/api` is not imported. Every copy of it — whichever version
21
24
  * an app bundles — registers its globals on `globalThis` under
@@ -62,4 +65,4 @@ declare function sampledAt(traceId: string, rate: number): boolean;
62
65
  declare function traceContextInjector(baseURL: string, telemetry: boolean | ClientTelemetryOptions | undefined): TraceContextInjector | undefined;
63
66
  //#endregion
64
67
  export { traceContextInjector as a, sampledAt as i, InvalidClientSampleRate as n, TraceContextInjector as r, ClientTelemetryOptions as t };
65
- //# sourceMappingURL=telemetry-BbRAq81t.d.mts.map
68
+ //# sourceMappingURL=telemetry-g3BE86Wd.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-g3BE86Wd.d.mts","names":[],"sources":["../src/telemetry.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA8BiB;;;;;;;;;;;EAWhB;;;cAIY,gCAAgC;WACvB;EAArB,YAAqB;;;KASV,wBACX,aACA,SAAS;;;;;;;;;;iBAiEM,UAAU,iBAAiB;;;;;iBA6E3B,qBACf,iBACA,qBAAqB,qCACnB"}
@@ -74,6 +74,24 @@ function originOf(url) {
74
74
  return;
75
75
  }
76
76
  }
77
+ let fetchRequests;
78
+ /**
79
+ * Whether something instruments `fetch` in this process: OpenTelemetry's
80
+ * undici instrumentation, or an APM built on it, listening on undici's
81
+ * diagnostics channel. It opens each request's CLIENT span and appends that
82
+ * span's `traceparent` itself, so one written here as well would reach the API
83
+ * as two values joined — `a, b` — which no propagator reads.
84
+ *
85
+ * Asked through `process.getBuiltinModule`, so a browser bundle imports no
86
+ * Node module; anywhere without it, nothing is listening.
87
+ */
88
+ function fetchIsInstrumented() {
89
+ if (fetchRequests === void 0) {
90
+ const getBuiltinModule = globalThis.process?.getBuiltinModule;
91
+ fetchRequests = (getBuiltinModule?.("node:diagnostics_channel"))?.channel("undici:request:create") ?? null;
92
+ }
93
+ return fetchRequests?.hasSubscribers === true;
94
+ }
77
95
  /**
78
96
  * An injector for a client whose API is at `baseURL`, or undefined when
79
97
  * telemetry is off.
@@ -91,6 +109,7 @@ function traceContextInjector(baseURL, telemetry) {
91
109
  return (url, headers) => {
92
110
  if (!apiOrigin || originOf(url) !== apiOrigin) return;
93
111
  if (Object.keys(headers).some((h) => h.toLowerCase() === "traceparent")) return;
112
+ if (fetchIsInstrumented()) return;
94
113
  const active = fromActiveContext();
95
114
  if (active) {
96
115
  Object.assign(headers, active);
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.cjs","names":[],"sources":["../src/telemetry.ts"],"sourcesContent":["/**\n * W3C trace context on the requests a client makes to its own API, so a user\n * action in the browser and the API request it causes are one trace.\n *\n * Two levels, chosen per request:\n *\n * - **An OpenTelemetry context is active** — a browser SDK has a span open, or\n * this runs on a server (a Next.js server component, an API calling another\n * API) inside a request span. The globally registered propagator writes the\n * headers, so the current span is the parent: `traceparent`, `tracestate`,\n * and whatever else that propagator carries.\n * - **Otherwise** (level 1): one trace id per page view — per page load in a\n * browser, per client in Node — a fresh span id per request, and a sampled\n * flag decided once per page view from {@link ClientTelemetryOptions.sampleRate}.\n *\n * Nothing is sent to any origin but the API's own: a trace id is an identifier,\n * and a third party has no business correlating a user's requests by it.\n *\n * `@opentelemetry/api` is not imported. Every copy of it — whichever version\n * an app bundles — registers its globals on `globalThis` under\n * `Symbol.for('opentelemetry.js.api.1')`, which is how two copies in one page\n * already share a provider. Reading the propagator and context manager from\n * there costs a browser bundle nothing when no SDK is loaded, and still finds\n * the SDK when one is.\n */\n\n/** Telemetry on a client: `true` for the defaults, or these options. */\nexport interface ClientTelemetryOptions {\n\t/**\n\t * The fraction of page views whose requests are sampled, from 0 to 1, when\n\t * no OpenTelemetry context is active. Decided once per page view, so a\n\t * page's requests are all kept or all dropped together.\n\t *\n\t * Use the stage's rate. The API caps an incoming sampled flag at its own\n\t * rate anyway, and decides from the trace id the same way this does, so a\n\t * page view sampled here at the API's rate is sampled there too.\n\t * @default 1\n\t */\n\tsampleRate?: number;\n}\n\n/** A `sampleRate` outside 0–1, which no sampler can honour. */\nexport class InvalidClientSampleRate extends Error {\n\tconstructor(readonly sampleRate: number) {\n\t\tsuper(\n\t\t\t`telemetry.sampleRate must be a number from 0 (no page views) to 1 (every page view), got ${sampleRate}. Pass the stage's sample rate, such as 0.1.`,\n\t\t);\n\t\tthis.name = 'InvalidClientSampleRate';\n\t}\n}\n\n/** Adds trace headers to a request to `url`, when it is the API's own. */\nexport type TraceContextInjector = (\n\turl: string,\n\theaders: Record<string, string>,\n) => void;\n\n/** The slice of `@opentelemetry/api`'s global registry this reads. */\ninterface OtelGlobal {\n\tcontext?: { active(): unknown };\n\tpropagation?: {\n\t\tinject(\n\t\t\tcontext: unknown,\n\t\t\tcarrier: Record<string, string>,\n\t\t\tsetter: {\n\t\t\t\tset(carrier: Record<string, string>, key: string, value: string): void;\n\t\t\t},\n\t\t): void;\n\t};\n}\n\nconst OTEL_GLOBAL = Symbol.for('opentelemetry.js.api.1');\n\nconst setter = {\n\tset(carrier: Record<string, string>, key: string, value: string) {\n\t\tcarrier[key] = value;\n\t},\n};\n\n/**\n * The headers the registered propagator writes for the active context, or\n * undefined when there is no SDK or no span to continue.\n */\nfunction fromActiveContext(): Record<string, string> | undefined {\n\tconst api = (globalThis as Record<symbol, OtelGlobal | undefined>)[\n\t\tOTEL_GLOBAL\n\t];\n\tif (!api?.propagation || !api.context) return undefined;\n\n\tconst carrier: Record<string, string> = {};\n\ttry {\n\t\tapi.propagation.inject(api.context.active(), carrier, setter);\n\t} catch {\n\t\treturn undefined;\n\t}\n\t// The W3C propagator writes nothing without a valid, unsuppressed span.\n\treturn carrier.traceparent ? carrier : undefined;\n}\n\n/** `bytes` crypto-random bytes as lowercase hex, never all zeros. */\nfunction randomHex(bytes: number): string {\n\tconst buffer = new Uint8Array(bytes);\n\tdo {\n\t\tglobalThis.crypto.getRandomValues(buffer);\n\t} while (buffer.every((byte) => byte === 0));\n\tlet hex = '';\n\tfor (const byte of buffer) hex += byte.toString(16).padStart(2, '0');\n\treturn hex;\n}\n\n/**\n * Whether a trace is sampled at `rate`, from its id.\n *\n * The same rule as OpenTelemetry's `TraceIdRatioBasedSampler`: the id's four\n * 32-bit words XORed together, under `rate` of the 32-bit range. The trace id\n * is random, so this samples `rate` of page views — and the API, applying the\n * same rule to the same id at the same rate, keeps exactly the page views kept\n * here rather than a random `rate` of them.\n */\nexport function sampledAt(traceId: string, rate: number): boolean {\n\tif (rate >= 1) return true;\n\tif (rate <= 0) return false;\n\tlet accumulation = 0;\n\tfor (let i = 0; i < traceId.length; i += 8) {\n\t\taccumulation = (accumulation ^ parseInt(traceId.slice(i, i + 8), 16)) >>> 0;\n\t}\n\treturn accumulation < Math.floor(rate * 0xffffffff);\n}\n\n/** A page view: one trace id for every request made during it. */\ninterface PageView {\n\ttraceId: string;\n}\n\nlet browserPageView: PageView | undefined;\n\n/**\n * The page view a request belongs to: the page load's in a browser, shared by\n * every client on the page, or this client's own elsewhere — a server process\n * outlives any one user action, so one trace for all of it would mean nothing.\n */\nfunction pageViewFor(own: () => PageView): PageView {\n\tconst browser =\n\t\ttypeof window !== 'undefined' && typeof document !== 'undefined';\n\tif (!browser) return own();\n\tbrowserPageView ??= { traceId: randomHex(16) };\n\treturn browserPageView;\n}\n\n/** The origin of `url`, resolved the way `fetch` would, or undefined. */\nfunction originOf(url: string): string | undefined {\n\ttry {\n\t\tconst base = (globalThis as { location?: { href?: string } }).location\n\t\t\t?.href;\n\t\treturn new URL(url, base).origin;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * An injector for a client whose API is at `baseURL`, or undefined when\n * telemetry is off.\n */\nexport function traceContextInjector(\n\tbaseURL: string,\n\ttelemetry: boolean | ClientTelemetryOptions | undefined,\n): TraceContextInjector | undefined {\n\tif (!telemetry) return undefined;\n\n\tconst sampleRate = telemetry === true ? 1 : (telemetry.sampleRate ?? 1);\n\tif (!Number.isFinite(sampleRate) || sampleRate < 0 || sampleRate > 1) {\n\t\tthrow new InvalidClientSampleRate(sampleRate);\n\t}\n\n\tconst apiOrigin = originOf(baseURL || '/');\n\tlet own: PageView | undefined;\n\tconst ownPageView = () => {\n\t\town ??= { traceId: randomHex(16) };\n\t\treturn own;\n\t};\n\n\treturn (url, headers) => {\n\t\t// Only the API's own origin: never a third party, and never anything\n\t\t// whose origin cannot be told.\n\t\tif (!apiOrigin || originOf(url) !== apiOrigin) return;\n\t\t// A caller that set its own context keeps it.\n\t\tif (Object.keys(headers).some((h) => h.toLowerCase() === 'traceparent'))\n\t\t\treturn;\n\n\t\tconst active = fromActiveContext();\n\t\tif (active) {\n\t\t\tObject.assign(headers, active);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { traceId } = pageViewFor(ownPageView);\n\t\tconst flags = sampledAt(traceId, sampleRate) ? '01' : '00';\n\t\theaders.traceparent = `00-${traceId}-${randomHex(8)}-${flags}`;\n\t};\n}\n"],"mappings":";;;AA0CA,IAAa,0BAAb,cAA6C,MAAM;CAC7B;CAArB,YAAY,YAA6B;EACxC,MACC,4FAA4F,WAAW,6CACxG;EAHoB,KAAA,aAAA;EAIpB,KAAK,OAAO;CACb;AACD;AAsBA,MAAM,cAAc,OAAO,IAAI,wBAAwB;AAEvD,MAAM,SAAS,EACd,IAAI,SAAiC,KAAa,OAAe;CAChE,QAAQ,OAAO;AAChB,EACD;;;;;AAMA,SAAS,oBAAwD;CAChE,MAAM,MAAO,WACZ;CAED,IAAI,CAAC,KAAK,eAAe,CAAC,IAAI,SAAS,OAAO,KAAA;CAE9C,MAAM,UAAkC,CAAC;CACzC,IAAI;EACH,IAAI,YAAY,OAAO,IAAI,QAAQ,OAAO,GAAG,SAAS,MAAM;CAC7D,QAAQ;EACP;CACD;CAEA,OAAO,QAAQ,cAAc,UAAU,KAAA;AACxC;;AAGA,SAAS,UAAU,OAAuB;CACzC,MAAM,SAAS,IAAI,WAAW,KAAK;CACnC;EACC,WAAW,OAAO,gBAAgB,MAAM;QAChC,OAAO,OAAO,SAAS,SAAS,CAAC;CAC1C,IAAI,MAAM;CACV,KAAK,MAAM,QAAQ,QAAQ,OAAO,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;CACnE,OAAO;AACR;;;;;;;;;;AAWA,SAAgB,UAAU,SAAiB,MAAuB;CACjE,IAAI,QAAQ,GAAG,OAAO;CACtB,IAAI,QAAQ,GAAG,OAAO;CACtB,IAAI,eAAe;CACnB,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,GACxC,gBAAgB,eAAe,SAAS,QAAQ,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO;CAE3E,OAAO,eAAe,KAAK,MAAM,OAAO,UAAU;AACnD;AAOA,IAAI;;;;;;AAOJ,SAAS,YAAY,KAA+B;CAGnD,IAAI,EADH,OAAO,WAAW,eAAe,OAAO,aAAa,cACxC,OAAO,IAAI;CACzB,oBAAoB,EAAE,SAAS,UAAU,EAAE,EAAE;CAC7C,OAAO;AACR;;AAGA,SAAS,SAAS,KAAiC;CAClD,IAAI;EACH,MAAM,OAAQ,WAAgD,UAC3D;EACH,OAAO,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC;CAC3B,QAAQ;EACP;CACD;AACD;;;;;AAMA,SAAgB,qBACf,SACA,WACmC;CACnC,IAAI,CAAC,WAAW,OAAO,KAAA;CAEvB,MAAM,aAAa,cAAc,OAAO,IAAK,UAAU,cAAc;CACrE,IAAI,CAAC,OAAO,SAAS,UAAU,KAAK,aAAa,KAAK,aAAa,GAClE,MAAM,IAAI,wBAAwB,UAAU;CAG7C,MAAM,YAAY,SAAS,WAAW,GAAG;CACzC,IAAI;CACJ,MAAM,oBAAoB;EACzB,QAAQ,EAAE,SAAS,UAAU,EAAE,EAAE;EACjC,OAAO;CACR;CAEA,QAAQ,KAAK,YAAY;EAGxB,IAAI,CAAC,aAAa,SAAS,GAAG,MAAM,WAAW;EAE/C,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,MAAM,MAAM,EAAE,YAAY,MAAM,aAAa,GACrE;EAED,MAAM,SAAS,kBAAkB;EACjC,IAAI,QAAQ;GACX,OAAO,OAAO,SAAS,MAAM;GAC7B;EACD;EAEA,MAAM,EAAE,YAAY,YAAY,WAAW;EAC3C,MAAM,QAAQ,UAAU,SAAS,UAAU,IAAI,OAAO;EACtD,QAAQ,cAAc,MAAM,QAAQ,GAAG,UAAU,CAAC,EAAE,GAAG;CACxD;AACD"}
1
+ {"version":3,"file":"telemetry.cjs","names":[],"sources":["../src/telemetry.ts"],"sourcesContent":["/**\n * W3C trace context on the requests a client makes to its own API, so a user\n * action in the browser and the API request it causes are one trace.\n *\n * Two levels, chosen per request:\n *\n * - **An OpenTelemetry context is active** — a browser SDK has a span open, or\n * this runs on a server (a Next.js server component, an API calling another\n * API) inside a request span. The globally registered propagator writes the\n * headers, so the current span is the parent: `traceparent`, `tracestate`,\n * and whatever else that propagator carries.\n * - **Otherwise** (level 1): one trace id per page view — per page load in a\n * browser, per client in Node — a fresh span id per request, and a sampled\n * flag decided once per page view from {@link ClientTelemetryOptions.sampleRate}.\n *\n * Nothing is sent to any origin but the API's own: a trace id is an identifier,\n * and a third party has no business correlating a user's requests by it. And\n * nothing at all where a `fetch` instrumentation is listening (a server with\n * OpenTelemetry's undici instrumentation): it writes the request's own\n * context, and two `traceparent` headers would be read as none.\n *\n * `@opentelemetry/api` is not imported. Every copy of it — whichever version\n * an app bundles — registers its globals on `globalThis` under\n * `Symbol.for('opentelemetry.js.api.1')`, which is how two copies in one page\n * already share a provider. Reading the propagator and context manager from\n * there costs a browser bundle nothing when no SDK is loaded, and still finds\n * the SDK when one is.\n */\n\n/** Telemetry on a client: `true` for the defaults, or these options. */\nexport interface ClientTelemetryOptions {\n\t/**\n\t * The fraction of page views whose requests are sampled, from 0 to 1, when\n\t * no OpenTelemetry context is active. Decided once per page view, so a\n\t * page's requests are all kept or all dropped together.\n\t *\n\t * Use the stage's rate. The API caps an incoming sampled flag at its own\n\t * rate anyway, and decides from the trace id the same way this does, so a\n\t * page view sampled here at the API's rate is sampled there too.\n\t * @default 1\n\t */\n\tsampleRate?: number;\n}\n\n/** A `sampleRate` outside 0–1, which no sampler can honour. */\nexport class InvalidClientSampleRate extends Error {\n\tconstructor(readonly sampleRate: number) {\n\t\tsuper(\n\t\t\t`telemetry.sampleRate must be a number from 0 (no page views) to 1 (every page view), got ${sampleRate}. Pass the stage's sample rate, such as 0.1.`,\n\t\t);\n\t\tthis.name = 'InvalidClientSampleRate';\n\t}\n}\n\n/** Adds trace headers to a request to `url`, when it is the API's own. */\nexport type TraceContextInjector = (\n\turl: string,\n\theaders: Record<string, string>,\n) => void;\n\n/** The slice of `@opentelemetry/api`'s global registry this reads. */\ninterface OtelGlobal {\n\tcontext?: { active(): unknown };\n\tpropagation?: {\n\t\tinject(\n\t\t\tcontext: unknown,\n\t\t\tcarrier: Record<string, string>,\n\t\t\tsetter: {\n\t\t\t\tset(carrier: Record<string, string>, key: string, value: string): void;\n\t\t\t},\n\t\t): void;\n\t};\n}\n\nconst OTEL_GLOBAL = Symbol.for('opentelemetry.js.api.1');\n\nconst setter = {\n\tset(carrier: Record<string, string>, key: string, value: string) {\n\t\tcarrier[key] = value;\n\t},\n};\n\n/**\n * The headers the registered propagator writes for the active context, or\n * undefined when there is no SDK or no span to continue.\n */\nfunction fromActiveContext(): Record<string, string> | undefined {\n\tconst api = (globalThis as Record<symbol, OtelGlobal | undefined>)[\n\t\tOTEL_GLOBAL\n\t];\n\tif (!api?.propagation || !api.context) return undefined;\n\n\tconst carrier: Record<string, string> = {};\n\ttry {\n\t\tapi.propagation.inject(api.context.active(), carrier, setter);\n\t} catch {\n\t\treturn undefined;\n\t}\n\t// The W3C propagator writes nothing without a valid, unsuppressed span.\n\treturn carrier.traceparent ? carrier : undefined;\n}\n\n/** `bytes` crypto-random bytes as lowercase hex, never all zeros. */\nfunction randomHex(bytes: number): string {\n\tconst buffer = new Uint8Array(bytes);\n\tdo {\n\t\tglobalThis.crypto.getRandomValues(buffer);\n\t} while (buffer.every((byte) => byte === 0));\n\tlet hex = '';\n\tfor (const byte of buffer) hex += byte.toString(16).padStart(2, '0');\n\treturn hex;\n}\n\n/**\n * Whether a trace is sampled at `rate`, from its id.\n *\n * The same rule as OpenTelemetry's `TraceIdRatioBasedSampler`: the id's four\n * 32-bit words XORed together, under `rate` of the 32-bit range. The trace id\n * is random, so this samples `rate` of page views — and the API, applying the\n * same rule to the same id at the same rate, keeps exactly the page views kept\n * here rather than a random `rate` of them.\n */\nexport function sampledAt(traceId: string, rate: number): boolean {\n\tif (rate >= 1) return true;\n\tif (rate <= 0) return false;\n\tlet accumulation = 0;\n\tfor (let i = 0; i < traceId.length; i += 8) {\n\t\taccumulation = (accumulation ^ parseInt(traceId.slice(i, i + 8), 16)) >>> 0;\n\t}\n\treturn accumulation < Math.floor(rate * 0xffffffff);\n}\n\n/** A page view: one trace id for every request made during it. */\ninterface PageView {\n\ttraceId: string;\n}\n\nlet browserPageView: PageView | undefined;\n\n/**\n * The page view a request belongs to: the page load's in a browser, shared by\n * every client on the page, or this client's own elsewhere — a server process\n * outlives any one user action, so one trace for all of it would mean nothing.\n */\nfunction pageViewFor(own: () => PageView): PageView {\n\tconst browser =\n\t\ttypeof window !== 'undefined' && typeof document !== 'undefined';\n\tif (!browser) return own();\n\tbrowserPageView ??= { traceId: randomHex(16) };\n\treturn browserPageView;\n}\n\n/** The origin of `url`, resolved the way `fetch` would, or undefined. */\nfunction originOf(url: string): string | undefined {\n\ttry {\n\t\tconst base = (globalThis as { location?: { href?: string } }).location\n\t\t\t?.href;\n\t\treturn new URL(url, base).origin;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** The slice of `node:diagnostics_channel` this reads. */\ninterface DiagnosticsChannels {\n\tchannel(name: string): { hasSubscribers: boolean };\n}\n\nlet fetchRequests: { hasSubscribers: boolean } | null | undefined;\n\n/**\n * Whether something instruments `fetch` in this process: OpenTelemetry's\n * undici instrumentation, or an APM built on it, listening on undici's\n * diagnostics channel. It opens each request's CLIENT span and appends that\n * span's `traceparent` itself, so one written here as well would reach the API\n * as two values joined — `a, b` — which no propagator reads.\n *\n * Asked through `process.getBuiltinModule`, so a browser bundle imports no\n * Node module; anywhere without it, nothing is listening.\n */\nfunction fetchIsInstrumented(): boolean {\n\tif (fetchRequests === undefined) {\n\t\tconst getBuiltinModule = (\n\t\t\tglobalThis as {\n\t\t\t\tprocess?: { getBuiltinModule?: (id: string) => unknown };\n\t\t\t}\n\t\t).process?.getBuiltinModule;\n\t\tconst channels = getBuiltinModule?.('node:diagnostics_channel') as\n\t\t\t| DiagnosticsChannels\n\t\t\t| undefined;\n\t\tfetchRequests = channels?.channel('undici:request:create') ?? null;\n\t}\n\treturn fetchRequests?.hasSubscribers === true;\n}\n\n/**\n * An injector for a client whose API is at `baseURL`, or undefined when\n * telemetry is off.\n */\nexport function traceContextInjector(\n\tbaseURL: string,\n\ttelemetry: boolean | ClientTelemetryOptions | undefined,\n): TraceContextInjector | undefined {\n\tif (!telemetry) return undefined;\n\n\tconst sampleRate = telemetry === true ? 1 : (telemetry.sampleRate ?? 1);\n\tif (!Number.isFinite(sampleRate) || sampleRate < 0 || sampleRate > 1) {\n\t\tthrow new InvalidClientSampleRate(sampleRate);\n\t}\n\n\tconst apiOrigin = originOf(baseURL || '/');\n\tlet own: PageView | undefined;\n\tconst ownPageView = () => {\n\t\town ??= { traceId: randomHex(16) };\n\t\treturn own;\n\t};\n\n\treturn (url, headers) => {\n\t\t// Only the API's own origin: never a third party, and never anything\n\t\t// whose origin cannot be told.\n\t\tif (!apiOrigin || originOf(url) !== apiOrigin) return;\n\t\t// A caller that set its own context keeps it.\n\t\tif (Object.keys(headers).some((h) => h.toLowerCase() === 'traceparent'))\n\t\t\treturn;\n\t\t// A fetch instrumentation writes the request's own context.\n\t\tif (fetchIsInstrumented()) return;\n\n\t\tconst active = fromActiveContext();\n\t\tif (active) {\n\t\t\tObject.assign(headers, active);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { traceId } = pageViewFor(ownPageView);\n\t\tconst flags = sampledAt(traceId, sampleRate) ? '01' : '00';\n\t\theaders.traceparent = `00-${traceId}-${randomHex(8)}-${flags}`;\n\t};\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { a as traceContextInjector, i as sampledAt, n as InvalidClientSampleRate, r as TraceContextInjector, t as ClientTelemetryOptions } from "./telemetry-BbRAq81t.cjs";
1
+ import { a as traceContextInjector, i as sampledAt, n as InvalidClientSampleRate, r as TraceContextInjector, t as ClientTelemetryOptions } from "./telemetry-g3BE86Wd.cjs";
2
2
  export { ClientTelemetryOptions, InvalidClientSampleRate, TraceContextInjector, sampledAt, traceContextInjector };
@@ -1,2 +1,2 @@
1
- import { a as traceContextInjector, i as sampledAt, n as InvalidClientSampleRate, r as TraceContextInjector, t as ClientTelemetryOptions } from "./telemetry-BbRAq81t.mjs";
1
+ import { a as traceContextInjector, i as sampledAt, n as InvalidClientSampleRate, r as TraceContextInjector, t as ClientTelemetryOptions } from "./telemetry-g3BE86Wd.mjs";
2
2
  export { ClientTelemetryOptions, InvalidClientSampleRate, TraceContextInjector, sampledAt, traceContextInjector };
@@ -73,6 +73,24 @@ function originOf(url) {
73
73
  return;
74
74
  }
75
75
  }
76
+ let fetchRequests;
77
+ /**
78
+ * Whether something instruments `fetch` in this process: OpenTelemetry's
79
+ * undici instrumentation, or an APM built on it, listening on undici's
80
+ * diagnostics channel. It opens each request's CLIENT span and appends that
81
+ * span's `traceparent` itself, so one written here as well would reach the API
82
+ * as two values joined — `a, b` — which no propagator reads.
83
+ *
84
+ * Asked through `process.getBuiltinModule`, so a browser bundle imports no
85
+ * Node module; anywhere without it, nothing is listening.
86
+ */
87
+ function fetchIsInstrumented() {
88
+ if (fetchRequests === void 0) {
89
+ const getBuiltinModule = globalThis.process?.getBuiltinModule;
90
+ fetchRequests = (getBuiltinModule?.("node:diagnostics_channel"))?.channel("undici:request:create") ?? null;
91
+ }
92
+ return fetchRequests?.hasSubscribers === true;
93
+ }
76
94
  /**
77
95
  * An injector for a client whose API is at `baseURL`, or undefined when
78
96
  * telemetry is off.
@@ -90,6 +108,7 @@ function traceContextInjector(baseURL, telemetry) {
90
108
  return (url, headers) => {
91
109
  if (!apiOrigin || originOf(url) !== apiOrigin) return;
92
110
  if (Object.keys(headers).some((h) => h.toLowerCase() === "traceparent")) return;
111
+ if (fetchIsInstrumented()) return;
93
112
  const active = fromActiveContext();
94
113
  if (active) {
95
114
  Object.assign(headers, active);
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.mjs","names":[],"sources":["../src/telemetry.ts"],"sourcesContent":["/**\n * W3C trace context on the requests a client makes to its own API, so a user\n * action in the browser and the API request it causes are one trace.\n *\n * Two levels, chosen per request:\n *\n * - **An OpenTelemetry context is active** — a browser SDK has a span open, or\n * this runs on a server (a Next.js server component, an API calling another\n * API) inside a request span. The globally registered propagator writes the\n * headers, so the current span is the parent: `traceparent`, `tracestate`,\n * and whatever else that propagator carries.\n * - **Otherwise** (level 1): one trace id per page view — per page load in a\n * browser, per client in Node — a fresh span id per request, and a sampled\n * flag decided once per page view from {@link ClientTelemetryOptions.sampleRate}.\n *\n * Nothing is sent to any origin but the API's own: a trace id is an identifier,\n * and a third party has no business correlating a user's requests by it.\n *\n * `@opentelemetry/api` is not imported. Every copy of it — whichever version\n * an app bundles — registers its globals on `globalThis` under\n * `Symbol.for('opentelemetry.js.api.1')`, which is how two copies in one page\n * already share a provider. Reading the propagator and context manager from\n * there costs a browser bundle nothing when no SDK is loaded, and still finds\n * the SDK when one is.\n */\n\n/** Telemetry on a client: `true` for the defaults, or these options. */\nexport interface ClientTelemetryOptions {\n\t/**\n\t * The fraction of page views whose requests are sampled, from 0 to 1, when\n\t * no OpenTelemetry context is active. Decided once per page view, so a\n\t * page's requests are all kept or all dropped together.\n\t *\n\t * Use the stage's rate. The API caps an incoming sampled flag at its own\n\t * rate anyway, and decides from the trace id the same way this does, so a\n\t * page view sampled here at the API's rate is sampled there too.\n\t * @default 1\n\t */\n\tsampleRate?: number;\n}\n\n/** A `sampleRate` outside 0–1, which no sampler can honour. */\nexport class InvalidClientSampleRate extends Error {\n\tconstructor(readonly sampleRate: number) {\n\t\tsuper(\n\t\t\t`telemetry.sampleRate must be a number from 0 (no page views) to 1 (every page view), got ${sampleRate}. Pass the stage's sample rate, such as 0.1.`,\n\t\t);\n\t\tthis.name = 'InvalidClientSampleRate';\n\t}\n}\n\n/** Adds trace headers to a request to `url`, when it is the API's own. */\nexport type TraceContextInjector = (\n\turl: string,\n\theaders: Record<string, string>,\n) => void;\n\n/** The slice of `@opentelemetry/api`'s global registry this reads. */\ninterface OtelGlobal {\n\tcontext?: { active(): unknown };\n\tpropagation?: {\n\t\tinject(\n\t\t\tcontext: unknown,\n\t\t\tcarrier: Record<string, string>,\n\t\t\tsetter: {\n\t\t\t\tset(carrier: Record<string, string>, key: string, value: string): void;\n\t\t\t},\n\t\t): void;\n\t};\n}\n\nconst OTEL_GLOBAL = Symbol.for('opentelemetry.js.api.1');\n\nconst setter = {\n\tset(carrier: Record<string, string>, key: string, value: string) {\n\t\tcarrier[key] = value;\n\t},\n};\n\n/**\n * The headers the registered propagator writes for the active context, or\n * undefined when there is no SDK or no span to continue.\n */\nfunction fromActiveContext(): Record<string, string> | undefined {\n\tconst api = (globalThis as Record<symbol, OtelGlobal | undefined>)[\n\t\tOTEL_GLOBAL\n\t];\n\tif (!api?.propagation || !api.context) return undefined;\n\n\tconst carrier: Record<string, string> = {};\n\ttry {\n\t\tapi.propagation.inject(api.context.active(), carrier, setter);\n\t} catch {\n\t\treturn undefined;\n\t}\n\t// The W3C propagator writes nothing without a valid, unsuppressed span.\n\treturn carrier.traceparent ? carrier : undefined;\n}\n\n/** `bytes` crypto-random bytes as lowercase hex, never all zeros. */\nfunction randomHex(bytes: number): string {\n\tconst buffer = new Uint8Array(bytes);\n\tdo {\n\t\tglobalThis.crypto.getRandomValues(buffer);\n\t} while (buffer.every((byte) => byte === 0));\n\tlet hex = '';\n\tfor (const byte of buffer) hex += byte.toString(16).padStart(2, '0');\n\treturn hex;\n}\n\n/**\n * Whether a trace is sampled at `rate`, from its id.\n *\n * The same rule as OpenTelemetry's `TraceIdRatioBasedSampler`: the id's four\n * 32-bit words XORed together, under `rate` of the 32-bit range. The trace id\n * is random, so this samples `rate` of page views — and the API, applying the\n * same rule to the same id at the same rate, keeps exactly the page views kept\n * here rather than a random `rate` of them.\n */\nexport function sampledAt(traceId: string, rate: number): boolean {\n\tif (rate >= 1) return true;\n\tif (rate <= 0) return false;\n\tlet accumulation = 0;\n\tfor (let i = 0; i < traceId.length; i += 8) {\n\t\taccumulation = (accumulation ^ parseInt(traceId.slice(i, i + 8), 16)) >>> 0;\n\t}\n\treturn accumulation < Math.floor(rate * 0xffffffff);\n}\n\n/** A page view: one trace id for every request made during it. */\ninterface PageView {\n\ttraceId: string;\n}\n\nlet browserPageView: PageView | undefined;\n\n/**\n * The page view a request belongs to: the page load's in a browser, shared by\n * every client on the page, or this client's own elsewhere — a server process\n * outlives any one user action, so one trace for all of it would mean nothing.\n */\nfunction pageViewFor(own: () => PageView): PageView {\n\tconst browser =\n\t\ttypeof window !== 'undefined' && typeof document !== 'undefined';\n\tif (!browser) return own();\n\tbrowserPageView ??= { traceId: randomHex(16) };\n\treturn browserPageView;\n}\n\n/** The origin of `url`, resolved the way `fetch` would, or undefined. */\nfunction originOf(url: string): string | undefined {\n\ttry {\n\t\tconst base = (globalThis as { location?: { href?: string } }).location\n\t\t\t?.href;\n\t\treturn new URL(url, base).origin;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * An injector for a client whose API is at `baseURL`, or undefined when\n * telemetry is off.\n */\nexport function traceContextInjector(\n\tbaseURL: string,\n\ttelemetry: boolean | ClientTelemetryOptions | undefined,\n): TraceContextInjector | undefined {\n\tif (!telemetry) return undefined;\n\n\tconst sampleRate = telemetry === true ? 1 : (telemetry.sampleRate ?? 1);\n\tif (!Number.isFinite(sampleRate) || sampleRate < 0 || sampleRate > 1) {\n\t\tthrow new InvalidClientSampleRate(sampleRate);\n\t}\n\n\tconst apiOrigin = originOf(baseURL || '/');\n\tlet own: PageView | undefined;\n\tconst ownPageView = () => {\n\t\town ??= { traceId: randomHex(16) };\n\t\treturn own;\n\t};\n\n\treturn (url, headers) => {\n\t\t// Only the API's own origin: never a third party, and never anything\n\t\t// whose origin cannot be told.\n\t\tif (!apiOrigin || originOf(url) !== apiOrigin) return;\n\t\t// A caller that set its own context keeps it.\n\t\tif (Object.keys(headers).some((h) => h.toLowerCase() === 'traceparent'))\n\t\t\treturn;\n\n\t\tconst active = fromActiveContext();\n\t\tif (active) {\n\t\t\tObject.assign(headers, active);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { traceId } = pageViewFor(ownPageView);\n\t\tconst flags = sampledAt(traceId, sampleRate) ? '01' : '00';\n\t\theaders.traceparent = `00-${traceId}-${randomHex(8)}-${flags}`;\n\t};\n}\n"],"mappings":";;AA0CA,IAAa,0BAAb,cAA6C,MAAM;CAC7B;CAArB,YAAY,YAA6B;EACxC,MACC,4FAA4F,WAAW,6CACxG;EAHoB,KAAA,aAAA;EAIpB,KAAK,OAAO;CACb;AACD;AAsBA,MAAM,cAAc,OAAO,IAAI,wBAAwB;AAEvD,MAAM,SAAS,EACd,IAAI,SAAiC,KAAa,OAAe;CAChE,QAAQ,OAAO;AAChB,EACD;;;;;AAMA,SAAS,oBAAwD;CAChE,MAAM,MAAO,WACZ;CAED,IAAI,CAAC,KAAK,eAAe,CAAC,IAAI,SAAS,OAAO,KAAA;CAE9C,MAAM,UAAkC,CAAC;CACzC,IAAI;EACH,IAAI,YAAY,OAAO,IAAI,QAAQ,OAAO,GAAG,SAAS,MAAM;CAC7D,QAAQ;EACP;CACD;CAEA,OAAO,QAAQ,cAAc,UAAU,KAAA;AACxC;;AAGA,SAAS,UAAU,OAAuB;CACzC,MAAM,SAAS,IAAI,WAAW,KAAK;CACnC;EACC,WAAW,OAAO,gBAAgB,MAAM;QAChC,OAAO,OAAO,SAAS,SAAS,CAAC;CAC1C,IAAI,MAAM;CACV,KAAK,MAAM,QAAQ,QAAQ,OAAO,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;CACnE,OAAO;AACR;;;;;;;;;;AAWA,SAAgB,UAAU,SAAiB,MAAuB;CACjE,IAAI,QAAQ,GAAG,OAAO;CACtB,IAAI,QAAQ,GAAG,OAAO;CACtB,IAAI,eAAe;CACnB,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,GACxC,gBAAgB,eAAe,SAAS,QAAQ,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO;CAE3E,OAAO,eAAe,KAAK,MAAM,OAAO,UAAU;AACnD;AAOA,IAAI;;;;;;AAOJ,SAAS,YAAY,KAA+B;CAGnD,IAAI,EADH,OAAO,WAAW,eAAe,OAAO,aAAa,cACxC,OAAO,IAAI;CACzB,oBAAoB,EAAE,SAAS,UAAU,EAAE,EAAE;CAC7C,OAAO;AACR;;AAGA,SAAS,SAAS,KAAiC;CAClD,IAAI;EACH,MAAM,OAAQ,WAAgD,UAC3D;EACH,OAAO,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC;CAC3B,QAAQ;EACP;CACD;AACD;;;;;AAMA,SAAgB,qBACf,SACA,WACmC;CACnC,IAAI,CAAC,WAAW,OAAO,KAAA;CAEvB,MAAM,aAAa,cAAc,OAAO,IAAK,UAAU,cAAc;CACrE,IAAI,CAAC,OAAO,SAAS,UAAU,KAAK,aAAa,KAAK,aAAa,GAClE,MAAM,IAAI,wBAAwB,UAAU;CAG7C,MAAM,YAAY,SAAS,WAAW,GAAG;CACzC,IAAI;CACJ,MAAM,oBAAoB;EACzB,QAAQ,EAAE,SAAS,UAAU,EAAE,EAAE;EACjC,OAAO;CACR;CAEA,QAAQ,KAAK,YAAY;EAGxB,IAAI,CAAC,aAAa,SAAS,GAAG,MAAM,WAAW;EAE/C,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,MAAM,MAAM,EAAE,YAAY,MAAM,aAAa,GACrE;EAED,MAAM,SAAS,kBAAkB;EACjC,IAAI,QAAQ;GACX,OAAO,OAAO,SAAS,MAAM;GAC7B;EACD;EAEA,MAAM,EAAE,YAAY,YAAY,WAAW;EAC3C,MAAM,QAAQ,UAAU,SAAS,UAAU,IAAI,OAAO;EACtD,QAAQ,cAAc,MAAM,QAAQ,GAAG,UAAU,CAAC,EAAE,GAAG;CACxD;AACD"}
1
+ {"version":3,"file":"telemetry.mjs","names":[],"sources":["../src/telemetry.ts"],"sourcesContent":["/**\n * W3C trace context on the requests a client makes to its own API, so a user\n * action in the browser and the API request it causes are one trace.\n *\n * Two levels, chosen per request:\n *\n * - **An OpenTelemetry context is active** — a browser SDK has a span open, or\n * this runs on a server (a Next.js server component, an API calling another\n * API) inside a request span. The globally registered propagator writes the\n * headers, so the current span is the parent: `traceparent`, `tracestate`,\n * and whatever else that propagator carries.\n * - **Otherwise** (level 1): one trace id per page view — per page load in a\n * browser, per client in Node — a fresh span id per request, and a sampled\n * flag decided once per page view from {@link ClientTelemetryOptions.sampleRate}.\n *\n * Nothing is sent to any origin but the API's own: a trace id is an identifier,\n * and a third party has no business correlating a user's requests by it. And\n * nothing at all where a `fetch` instrumentation is listening (a server with\n * OpenTelemetry's undici instrumentation): it writes the request's own\n * context, and two `traceparent` headers would be read as none.\n *\n * `@opentelemetry/api` is not imported. Every copy of it — whichever version\n * an app bundles — registers its globals on `globalThis` under\n * `Symbol.for('opentelemetry.js.api.1')`, which is how two copies in one page\n * already share a provider. Reading the propagator and context manager from\n * there costs a browser bundle nothing when no SDK is loaded, and still finds\n * the SDK when one is.\n */\n\n/** Telemetry on a client: `true` for the defaults, or these options. */\nexport interface ClientTelemetryOptions {\n\t/**\n\t * The fraction of page views whose requests are sampled, from 0 to 1, when\n\t * no OpenTelemetry context is active. Decided once per page view, so a\n\t * page's requests are all kept or all dropped together.\n\t *\n\t * Use the stage's rate. The API caps an incoming sampled flag at its own\n\t * rate anyway, and decides from the trace id the same way this does, so a\n\t * page view sampled here at the API's rate is sampled there too.\n\t * @default 1\n\t */\n\tsampleRate?: number;\n}\n\n/** A `sampleRate` outside 0–1, which no sampler can honour. */\nexport class InvalidClientSampleRate extends Error {\n\tconstructor(readonly sampleRate: number) {\n\t\tsuper(\n\t\t\t`telemetry.sampleRate must be a number from 0 (no page views) to 1 (every page view), got ${sampleRate}. Pass the stage's sample rate, such as 0.1.`,\n\t\t);\n\t\tthis.name = 'InvalidClientSampleRate';\n\t}\n}\n\n/** Adds trace headers to a request to `url`, when it is the API's own. */\nexport type TraceContextInjector = (\n\turl: string,\n\theaders: Record<string, string>,\n) => void;\n\n/** The slice of `@opentelemetry/api`'s global registry this reads. */\ninterface OtelGlobal {\n\tcontext?: { active(): unknown };\n\tpropagation?: {\n\t\tinject(\n\t\t\tcontext: unknown,\n\t\t\tcarrier: Record<string, string>,\n\t\t\tsetter: {\n\t\t\t\tset(carrier: Record<string, string>, key: string, value: string): void;\n\t\t\t},\n\t\t): void;\n\t};\n}\n\nconst OTEL_GLOBAL = Symbol.for('opentelemetry.js.api.1');\n\nconst setter = {\n\tset(carrier: Record<string, string>, key: string, value: string) {\n\t\tcarrier[key] = value;\n\t},\n};\n\n/**\n * The headers the registered propagator writes for the active context, or\n * undefined when there is no SDK or no span to continue.\n */\nfunction fromActiveContext(): Record<string, string> | undefined {\n\tconst api = (globalThis as Record<symbol, OtelGlobal | undefined>)[\n\t\tOTEL_GLOBAL\n\t];\n\tif (!api?.propagation || !api.context) return undefined;\n\n\tconst carrier: Record<string, string> = {};\n\ttry {\n\t\tapi.propagation.inject(api.context.active(), carrier, setter);\n\t} catch {\n\t\treturn undefined;\n\t}\n\t// The W3C propagator writes nothing without a valid, unsuppressed span.\n\treturn carrier.traceparent ? carrier : undefined;\n}\n\n/** `bytes` crypto-random bytes as lowercase hex, never all zeros. */\nfunction randomHex(bytes: number): string {\n\tconst buffer = new Uint8Array(bytes);\n\tdo {\n\t\tglobalThis.crypto.getRandomValues(buffer);\n\t} while (buffer.every((byte) => byte === 0));\n\tlet hex = '';\n\tfor (const byte of buffer) hex += byte.toString(16).padStart(2, '0');\n\treturn hex;\n}\n\n/**\n * Whether a trace is sampled at `rate`, from its id.\n *\n * The same rule as OpenTelemetry's `TraceIdRatioBasedSampler`: the id's four\n * 32-bit words XORed together, under `rate` of the 32-bit range. The trace id\n * is random, so this samples `rate` of page views — and the API, applying the\n * same rule to the same id at the same rate, keeps exactly the page views kept\n * here rather than a random `rate` of them.\n */\nexport function sampledAt(traceId: string, rate: number): boolean {\n\tif (rate >= 1) return true;\n\tif (rate <= 0) return false;\n\tlet accumulation = 0;\n\tfor (let i = 0; i < traceId.length; i += 8) {\n\t\taccumulation = (accumulation ^ parseInt(traceId.slice(i, i + 8), 16)) >>> 0;\n\t}\n\treturn accumulation < Math.floor(rate * 0xffffffff);\n}\n\n/** A page view: one trace id for every request made during it. */\ninterface PageView {\n\ttraceId: string;\n}\n\nlet browserPageView: PageView | undefined;\n\n/**\n * The page view a request belongs to: the page load's in a browser, shared by\n * every client on the page, or this client's own elsewhere — a server process\n * outlives any one user action, so one trace for all of it would mean nothing.\n */\nfunction pageViewFor(own: () => PageView): PageView {\n\tconst browser =\n\t\ttypeof window !== 'undefined' && typeof document !== 'undefined';\n\tif (!browser) return own();\n\tbrowserPageView ??= { traceId: randomHex(16) };\n\treturn browserPageView;\n}\n\n/** The origin of `url`, resolved the way `fetch` would, or undefined. */\nfunction originOf(url: string): string | undefined {\n\ttry {\n\t\tconst base = (globalThis as { location?: { href?: string } }).location\n\t\t\t?.href;\n\t\treturn new URL(url, base).origin;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** The slice of `node:diagnostics_channel` this reads. */\ninterface DiagnosticsChannels {\n\tchannel(name: string): { hasSubscribers: boolean };\n}\n\nlet fetchRequests: { hasSubscribers: boolean } | null | undefined;\n\n/**\n * Whether something instruments `fetch` in this process: OpenTelemetry's\n * undici instrumentation, or an APM built on it, listening on undici's\n * diagnostics channel. It opens each request's CLIENT span and appends that\n * span's `traceparent` itself, so one written here as well would reach the API\n * as two values joined — `a, b` — which no propagator reads.\n *\n * Asked through `process.getBuiltinModule`, so a browser bundle imports no\n * Node module; anywhere without it, nothing is listening.\n */\nfunction fetchIsInstrumented(): boolean {\n\tif (fetchRequests === undefined) {\n\t\tconst getBuiltinModule = (\n\t\t\tglobalThis as {\n\t\t\t\tprocess?: { getBuiltinModule?: (id: string) => unknown };\n\t\t\t}\n\t\t).process?.getBuiltinModule;\n\t\tconst channels = getBuiltinModule?.('node:diagnostics_channel') as\n\t\t\t| DiagnosticsChannels\n\t\t\t| undefined;\n\t\tfetchRequests = channels?.channel('undici:request:create') ?? null;\n\t}\n\treturn fetchRequests?.hasSubscribers === true;\n}\n\n/**\n * An injector for a client whose API is at `baseURL`, or undefined when\n * telemetry is off.\n */\nexport function traceContextInjector(\n\tbaseURL: string,\n\ttelemetry: boolean | ClientTelemetryOptions | undefined,\n): TraceContextInjector | undefined {\n\tif (!telemetry) return undefined;\n\n\tconst sampleRate = telemetry === true ? 1 : (telemetry.sampleRate ?? 1);\n\tif (!Number.isFinite(sampleRate) || sampleRate < 0 || sampleRate > 1) {\n\t\tthrow new InvalidClientSampleRate(sampleRate);\n\t}\n\n\tconst apiOrigin = originOf(baseURL || '/');\n\tlet own: PageView | undefined;\n\tconst ownPageView = () => {\n\t\town ??= { traceId: randomHex(16) };\n\t\treturn own;\n\t};\n\n\treturn (url, headers) => {\n\t\t// Only the API's own origin: never a third party, and never anything\n\t\t// whose origin cannot be told.\n\t\tif (!apiOrigin || originOf(url) !== apiOrigin) return;\n\t\t// A caller that set its own context keeps it.\n\t\tif (Object.keys(headers).some((h) => h.toLowerCase() === 'traceparent'))\n\t\t\treturn;\n\t\t// A fetch instrumentation writes the request's own context.\n\t\tif (fetchIsInstrumented()) return;\n\n\t\tconst active = fromActiveContext();\n\t\tif (active) {\n\t\t\tObject.assign(headers, active);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { traceId } = pageViewFor(ownPageView);\n\t\tconst flags = sampledAt(traceId, sampleRate) ? '01' : '00';\n\t\theaders.traceparent = `00-${traceId}-${randomHex(8)}-${flags}`;\n\t};\n}\n"],"mappings":"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"}
package/dist/types.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as ClientTelemetryOptions } from "./telemetry-BbRAq81t.cjs";
1
+ import { t as ClientTelemetryOptions } from "./telemetry-g3BE86Wd.cjs";
2
2
  //#region src/types.d.ts
3
3
  export type OpenAPIRoutes<Paths> = keyof Paths;
4
4
  type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options';
package/dist/types.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as ClientTelemetryOptions } from "./telemetry-BbRAq81t.mjs";
1
+ import { t as ClientTelemetryOptions } from "./telemetry-g3BE86Wd.mjs";
2
2
  //#region src/types.d.ts
3
3
  export type OpenAPIRoutes<Paths> = keyof Paths;
4
4
  type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/client",
3
- "version": "10.0.0-alpha.84",
3
+ "version": "10.0.0-alpha.86",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -123,16 +123,16 @@
123
123
  "@types/react-dom": "~19.3.0",
124
124
  "jsdom": "~26.1.0",
125
125
  "msw": "~2.15.0",
126
- "@geekmidas/constructs": "^10.0.0-alpha.84",
127
- "@geekmidas/schema": "^10.0.0-alpha.84"
126
+ "@geekmidas/constructs": "^10.0.0-alpha.86",
127
+ "@geekmidas/schema": "^10.0.0-alpha.86"
128
128
  },
129
129
  "peerDependencies": {
130
130
  "@tanstack/react-query": ">=5.0.0",
131
131
  "react": ">=18.0.0",
132
132
  "react-dom": ">=18.0.0",
133
133
  "zod": ">=4.6.5",
134
- "@geekmidas/constructs": "^10.0.0-alpha.84",
135
- "@geekmidas/schema": "^10.0.0-alpha.84"
134
+ "@geekmidas/constructs": "^10.0.0-alpha.86",
135
+ "@geekmidas/schema": "^10.0.0-alpha.86"
136
136
  },
137
137
  "peerDependenciesMeta": {
138
138
  "@geekmidas/constructs": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"telemetry-BbRAq81t.d.cts","names":[],"sources":["../src/telemetry.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;UA2BiB;;;;;;;;;;;EAWhB;;;cAIY,gCAAgC;WACvB;EAArB,YAAqB;;;KASV,wBACX,aACA,SAAS;;;;;;;;;;iBAiEM,UAAU,iBAAiB;;;;;iBA6C3B,qBACf,iBACA,qBAAqB,qCACnB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"telemetry-BbRAq81t.d.mts","names":[],"sources":["../src/telemetry.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;UA2BiB;;;;;;;;;;;EAWhB;;;cAIY,gCAAgC;WACvB;EAArB,YAAqB;;;KASV,wBACX,aACA,SAAS;;;;;;;;;;iBAiEM,UAAU,iBAAiB;;;;;iBA6C3B,qBACf,iBACA,qBAAqB,qCACnB"}