@genn-inc/cluebase-frontend-sdk 0.0.1

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 (140) hide show
  1. package/README.md +222 -0
  2. package/dist/_generated/correlation-attribute-keys.d.ts +2 -0
  3. package/dist/_generated/correlation-attribute-keys.js +33 -0
  4. package/dist/_generated/identity-pii-policy.d.ts +38 -0
  5. package/dist/_generated/identity-pii-policy.js +103 -0
  6. package/dist/_generated/ingest-privacy-tables.d.ts +120 -0
  7. package/dist/_generated/ingest-privacy-tables.js +890 -0
  8. package/dist/_generated/otel-resource-keys.d.ts +21 -0
  9. package/dist/_generated/otel-resource-keys.js +72 -0
  10. package/dist/_generated/otel-span-conversion.d.ts +48 -0
  11. package/dist/_generated/otel-span-conversion.js +69 -0
  12. package/dist/_generated/parameter-snapshot-leaf.d.ts +11 -0
  13. package/dist/_generated/parameter-snapshot-leaf.js +37 -0
  14. package/dist/_generated/sdk-runtime-policy.d.ts +7 -0
  15. package/dist/_generated/sdk-runtime-policy.js +14 -0
  16. package/dist/_generated/usage-governor-tables.d.ts +15 -0
  17. package/dist/_generated/usage-governor-tables.js +44 -0
  18. package/dist/context/browser-storage.d.ts +15 -0
  19. package/dist/context/browser-storage.js +38 -0
  20. package/dist/context/environment.d.ts +37 -0
  21. package/dist/context/environment.js +229 -0
  22. package/dist/context/identity.d.ts +16 -0
  23. package/dist/context/identity.js +139 -0
  24. package/dist/context/organization.d.ts +16 -0
  25. package/dist/context/organization.js +60 -0
  26. package/dist/context/session.d.ts +28 -0
  27. package/dist/context/session.js +130 -0
  28. package/dist/context/tab.d.ts +22 -0
  29. package/dist/context/tab.js +117 -0
  30. package/dist/context/trace.d.ts +76 -0
  31. package/dist/context/trace.js +163 -0
  32. package/dist/exporter/batch.d.ts +94 -0
  33. package/dist/exporter/batch.js +485 -0
  34. package/dist/exporter/browser-outbox-records.d.ts +159 -0
  35. package/dist/exporter/browser-outbox-records.js +341 -0
  36. package/dist/exporter/browser-outbox.d.ts +112 -0
  37. package/dist/exporter/browser-outbox.js +876 -0
  38. package/dist/exporter/client.d.ts +92 -0
  39. package/dist/exporter/client.js +541 -0
  40. package/dist/exporter/delivery-response-contract.d.ts +103 -0
  41. package/dist/exporter/delivery-response-contract.js +147 -0
  42. package/dist/exporter/delivery-response.d.ts +37 -0
  43. package/dist/exporter/delivery-response.js +151 -0
  44. package/dist/exporter/diagnostics.d.ts +42 -0
  45. package/dist/exporter/diagnostics.js +148 -0
  46. package/dist/exporter/event-id.d.ts +10 -0
  47. package/dist/exporter/event-id.js +75 -0
  48. package/dist/exporter/frontend-resource.d.ts +20 -0
  49. package/dist/exporter/frontend-resource.js +52 -0
  50. package/dist/exporter/local-minimization.d.ts +29 -0
  51. package/dist/exporter/local-minimization.js +87 -0
  52. package/dist/exporter/observation-source-event-exporter.d.ts +69 -0
  53. package/dist/exporter/observation-source-event-exporter.js +154 -0
  54. package/dist/exporter/parameter-snapshot.d.ts +14 -0
  55. package/dist/exporter/parameter-snapshot.js +215 -0
  56. package/dist/exporter/path-template.d.ts +1 -0
  57. package/dist/exporter/path-template.js +67 -0
  58. package/dist/exporter/raw-event-to-source-signal.d.ts +12 -0
  59. package/dist/exporter/raw-event-to-source-signal.js +297 -0
  60. package/dist/exporter/retry.d.ts +8 -0
  61. package/dist/exporter/retry.js +8 -0
  62. package/dist/exporter/sanitize.d.ts +30 -0
  63. package/dist/exporter/sanitize.js +177 -0
  64. package/dist/exporter/schema-evidence.d.ts +20 -0
  65. package/dist/exporter/schema-evidence.js +232 -0
  66. package/dist/exporter/source-event.d.ts +99 -0
  67. package/dist/exporter/source-event.js +13 -0
  68. package/dist/exporter/span-to-source-signal.d.ts +8 -0
  69. package/dist/exporter/span-to-source-signal.js +174 -0
  70. package/dist/exporter/text-fingerprint.d.ts +9 -0
  71. package/dist/exporter/text-fingerprint.js +16 -0
  72. package/dist/index.d.ts +25 -0
  73. package/dist/index.js +158 -0
  74. package/dist/instrumentation/action.d.ts +2 -0
  75. package/dist/instrumentation/action.js +171 -0
  76. package/dist/instrumentation/cluebase-authored-attributes.d.ts +4 -0
  77. package/dist/instrumentation/cluebase-authored-attributes.js +32 -0
  78. package/dist/instrumentation/common.d.ts +73 -0
  79. package/dist/instrumentation/common.js +90 -0
  80. package/dist/instrumentation/dom.d.ts +63 -0
  81. package/dist/instrumentation/dom.js +640 -0
  82. package/dist/instrumentation/drag.d.ts +2 -0
  83. package/dist/instrumentation/drag.js +82 -0
  84. package/dist/instrumentation/error.d.ts +2 -0
  85. package/dist/instrumentation/error.js +318 -0
  86. package/dist/instrumentation/form.d.ts +2 -0
  87. package/dist/instrumentation/form.js +132 -0
  88. package/dist/instrumentation/input.d.ts +2 -0
  89. package/dist/instrumentation/input.js +180 -0
  90. package/dist/instrumentation/location.d.ts +11 -0
  91. package/dist/instrumentation/location.js +69 -0
  92. package/dist/instrumentation/naming-evidence.d.ts +20 -0
  93. package/dist/instrumentation/naming-evidence.js +11 -0
  94. package/dist/instrumentation/navigation.d.ts +2 -0
  95. package/dist/instrumentation/navigation.js +144 -0
  96. package/dist/instrumentation/network-request.d.ts +103 -0
  97. package/dist/instrumentation/network-request.js +285 -0
  98. package/dist/instrumentation/network-span-attributes.d.ts +25 -0
  99. package/dist/instrumentation/network-span-attributes.js +153 -0
  100. package/dist/instrumentation/network.d.ts +16 -0
  101. package/dist/instrumentation/network.js +502 -0
  102. package/dist/instrumentation/page.d.ts +2 -0
  103. package/dist/instrumentation/page.js +56 -0
  104. package/dist/instrumentation/performance.d.ts +2 -0
  105. package/dist/instrumentation/performance.js +365 -0
  106. package/dist/integrations/next-router.d.ts +16 -0
  107. package/dist/integrations/next-router.js +18 -0
  108. package/dist/integrations/react-router.d.ts +7 -0
  109. package/dist/integrations/react-router.js +61 -0
  110. package/dist/internal/internal-log.d.ts +23 -0
  111. package/dist/internal/internal-log.js +47 -0
  112. package/dist/internal/metrics.d.ts +10 -0
  113. package/dist/internal/metrics.js +50 -0
  114. package/dist/provider/_version.d.ts +1 -0
  115. package/dist/provider/_version.js +3 -0
  116. package/dist/provider/config.d.ts +12 -0
  117. package/dist/provider/config.js +177 -0
  118. package/dist/provider/constants.d.ts +62 -0
  119. package/dist/provider/constants.js +118 -0
  120. package/dist/provider/contracts.d.ts +51 -0
  121. package/dist/provider/contracts.js +46 -0
  122. package/dist/provider/privacy-policy-store.d.ts +20 -0
  123. package/dist/provider/privacy-policy-store.js +56 -0
  124. package/dist/provider/sampling.d.ts +2 -0
  125. package/dist/provider/sampling.js +17 -0
  126. package/dist/provider/sdk-event-pipeline.d.ts +60 -0
  127. package/dist/provider/sdk-event-pipeline.js +478 -0
  128. package/dist/provider/sdk-input-normalization.d.ts +7 -0
  129. package/dist/provider/sdk-input-normalization.js +113 -0
  130. package/dist/provider/sdk-otel-pipeline.d.ts +14 -0
  131. package/dist/provider/sdk-otel-pipeline.js +148 -0
  132. package/dist/provider/sdk-runtime-surfaces.d.ts +49 -0
  133. package/dist/provider/sdk-runtime-surfaces.js +216 -0
  134. package/dist/provider/sdk.d.ts +2 -0
  135. package/dist/provider/sdk.js +614 -0
  136. package/dist/provider/types.d.ts +323 -0
  137. package/dist/provider/types.js +1 -0
  138. package/dist/provider/usage-governor.d.ts +7 -0
  139. package/dist/provider/usage-governor.js +116 -0
  140. package/package.json +59 -0
@@ -0,0 +1,75 @@
1
+ const randomBytes = (length) => {
2
+ if (typeof globalThis.crypto === "undefined" ||
3
+ typeof globalThis.crypto.getRandomValues !== "function") {
4
+ throw new Error("Cluebase SDK requires crypto.getRandomValues for ID generation");
5
+ }
6
+ const bytes = new Uint8Array(length);
7
+ globalThis.crypto.getRandomValues(bytes);
8
+ return bytes;
9
+ };
10
+ const toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
11
+ let generatorId = null;
12
+ let monotonicCounter = 0;
13
+ let lastMonotonicTime = 0;
14
+ const getGeneratorId = () => {
15
+ generatorId ?? (generatorId = toHex(randomBytes(16)));
16
+ return generatorId;
17
+ };
18
+ const nextMonotonicParts = () => {
19
+ const performanceNow = typeof globalThis.performance !== "undefined" &&
20
+ typeof globalThis.performance.now === "function"
21
+ ? Math.floor(globalThis.performance.now() * 1000)
22
+ : Date.now();
23
+ const monotonicTime = performanceNow > lastMonotonicTime
24
+ ? performanceNow
25
+ : lastMonotonicTime + 1;
26
+ lastMonotonicTime = monotonicTime;
27
+ monotonicCounter = (monotonicCounter + 1) % Number.MAX_SAFE_INTEGER;
28
+ return { monotonicTime, counter: monotonicCounter };
29
+ };
30
+ export const generateMonotonicId = (prefix) => {
31
+ const { monotonicTime, counter } = nextMonotonicParts();
32
+ const generator = getGeneratorId();
33
+ const id = [
34
+ generator,
35
+ monotonicTime.toString(36),
36
+ counter.toString(36),
37
+ toHex(randomBytes(8)),
38
+ ].join("_");
39
+ return prefix ? `${prefix}_${id}` : id;
40
+ };
41
+ export const generateTraceId = () => {
42
+ const { monotonicTime, counter } = nextMonotonicParts();
43
+ const generator = getGeneratorId();
44
+ return [
45
+ generator.slice(0, 12),
46
+ monotonicTime.toString(16).padStart(12, "0").slice(-12),
47
+ counter.toString(16).padStart(4, "0").slice(-4),
48
+ toHex(randomBytes(2)),
49
+ ].join("");
50
+ };
51
+ export const generateEventId = () => {
52
+ return generateMonotonicId();
53
+ };
54
+ export const generateAnonymousId = () => {
55
+ return generateMonotonicId("anon");
56
+ };
57
+ export const generateSessionId = () => {
58
+ return generateMonotonicId("sess");
59
+ };
60
+ export const generateTabId = () => {
61
+ return generateMonotonicId("tab");
62
+ };
63
+ export const generateRequestId = () => {
64
+ return generateMonotonicId("req");
65
+ };
66
+ export const generateRequestSpanId = () => {
67
+ const { counter } = nextMonotonicParts();
68
+ return `${counter.toString(16).padStart(4, "0").slice(-4)}${toHex(randomBytes(6))}`;
69
+ };
70
+ export const generateInteractionId = () => {
71
+ return generateMonotonicId("int");
72
+ };
73
+ export const generateViewId = () => {
74
+ return generateMonotonicId("view");
75
+ };
@@ -0,0 +1,20 @@
1
+ import type { ResolvedCluebaseConfig } from "../provider/types.js";
2
+ export type FrontendResourceAttributes = Readonly<Record<string, string>>;
3
+ export type FrontendResourceInput = FrontendResourceAttributes | {
4
+ readonly attributes?: Readonly<Record<string, unknown>>;
5
+ };
6
+ type ResourceRecord = {
7
+ [key: string]: string;
8
+ };
9
+ /**
10
+ * Build the only Resource attributes emitted by the browser SDK's own OTel
11
+ * provider. Values are intentionally strings so the same snapshot can be
12
+ * used to construct an OTel Resource and to enrich raw source signals.
13
+ */
14
+ export declare const createFrontendResourceAttributes: (resolved: ResolvedCluebaseConfig) => FrontendResourceAttributes;
15
+ /**
16
+ * Copy only approved frontend Resource attributes into the serialized source
17
+ * signal. The returned object is a detached, bounded JSON-safe snapshot.
18
+ */
19
+ export declare const toFrontendSourceResource: (resource: FrontendResourceInput | undefined, deniedKeys?: readonly string[]) => ResourceRecord;
20
+ export {};
@@ -0,0 +1,52 @@
1
+ import { isDeniedKey } from "./sanitize.js";
2
+ import { SDK_VERSION } from "../provider/constants.js";
3
+ import { CLUEBASE_OTEL_SDK_SOURCE_REQUIRED_RESOURCE_KEYS, CLUEBASE_OTEL_SDK_SOURCE_RESOURCE_KEYS, } from "../_generated/otel-resource-keys.js";
4
+ const FRONTEND_RESOURCE_KEYS = new Set(CLUEBASE_OTEL_SDK_SOURCE_RESOURCE_KEYS);
5
+ const FRONTEND_REQUIRED_RESOURCE_KEYS = new Set(CLUEBASE_OTEL_SDK_SOURCE_REQUIRED_RESOURCE_KEYS);
6
+ const MAX_RESOURCE_VALUE_BYTES = 512;
7
+ /**
8
+ * Build the only Resource attributes emitted by the browser SDK's own OTel
9
+ * provider. Values are intentionally strings so the same snapshot can be
10
+ * used to construct an OTel Resource and to enrich raw source signals.
11
+ */
12
+ export const createFrontendResourceAttributes = (resolved) => ({
13
+ "service.name": resolved.producerId,
14
+ "service.version": resolved.release.frontendRelease ?? "unknown",
15
+ "cluebase.project_key": resolved.projectKey,
16
+ "cluebase.producer_id": resolved.producerId,
17
+ "cluebase.service_type": "frontend",
18
+ "cluebase.sdk.type": "browser",
19
+ "cluebase.sdk.version": SDK_VERSION,
20
+ "cluebase.sdk.collection_mode": resolved.collection?.mode ?? "standard",
21
+ "cluebase.runtime_language": "browser",
22
+ });
23
+ /**
24
+ * Copy only approved frontend Resource attributes into the serialized source
25
+ * signal. The returned object is a detached, bounded JSON-safe snapshot.
26
+ */
27
+ export const toFrontendSourceResource = (resource, deniedKeys = []) => {
28
+ const input = resource && "attributes" in resource ? resource.attributes : resource;
29
+ const output = {};
30
+ let entries;
31
+ try {
32
+ entries = Object.entries(input ?? {});
33
+ }
34
+ catch {
35
+ return output;
36
+ }
37
+ for (const [key, value] of entries) {
38
+ if (!FRONTEND_RESOURCE_KEYS.has(key) ||
39
+ (isDeniedKey(key, deniedKeys) && !FRONTEND_REQUIRED_RESOURCE_KEYS.has(key))) {
40
+ continue;
41
+ }
42
+ if (key === "cluebase.service_type" && value !== "frontend") {
43
+ continue;
44
+ }
45
+ if (typeof value === "string" &&
46
+ value.trim().length > 0 &&
47
+ new TextEncoder().encode(value).byteLength <= MAX_RESOURCE_VALUE_BYTES) {
48
+ output[key] = value;
49
+ }
50
+ }
51
+ return output;
52
+ };
@@ -0,0 +1,29 @@
1
+ type LocalValueType = "string" | "number" | "boolean" | "null" | "object" | "array";
2
+ export type LocalTextDescriptor = {
3
+ present: true;
4
+ length: number;
5
+ };
6
+ export type LocalValueDescriptor = {
7
+ present: true;
8
+ valueType: LocalValueType;
9
+ length?: number;
10
+ itemCount?: number;
11
+ keyCount?: number;
12
+ fileCount?: number;
13
+ };
14
+ export type LocalStructuredSummary = LocalValueDescriptor | {
15
+ present: true;
16
+ valueType: "object";
17
+ keyCount: number;
18
+ fields: Record<string, LocalStructuredSummary | null>;
19
+ } | {
20
+ present: true;
21
+ valueType: "array";
22
+ itemCount: number;
23
+ items: Array<LocalStructuredSummary | null>;
24
+ };
25
+ export declare const buildLocalTextDescriptor: (value: string | null | undefined) => LocalTextDescriptor | null;
26
+ export declare const buildLocalValueDescriptor: (value: unknown) => LocalValueDescriptor | null;
27
+ export declare const buildLocalStructuredSummary: (value: unknown, depth?: number) => LocalStructuredSummary | null;
28
+ export declare const buildLocalQuerySummary: (query: Record<string, string | string[]>) => Record<string, LocalStructuredSummary | null>;
29
+ export {};
@@ -0,0 +1,87 @@
1
+ const MAX_SUMMARY_DEPTH = 4;
2
+ const MAX_SUMMARY_KEYS = 32;
3
+ const MAX_SUMMARY_ITEMS = 12;
4
+ const isReadableObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5
+ export const buildLocalTextDescriptor = (value) => {
6
+ if (value == null) {
7
+ return null;
8
+ }
9
+ const trimmed = value.trim();
10
+ if (!trimmed) {
11
+ return null;
12
+ }
13
+ return {
14
+ present: true,
15
+ length: trimmed.length,
16
+ };
17
+ };
18
+ export const buildLocalValueDescriptor = (value) => {
19
+ if (value == null) {
20
+ return null;
21
+ }
22
+ if (typeof value === "string") {
23
+ const trimmed = value.trim();
24
+ return trimmed
25
+ ? { present: true, valueType: "string", length: trimmed.length }
26
+ : null;
27
+ }
28
+ if (typeof value === "number") {
29
+ return { present: true, valueType: "number" };
30
+ }
31
+ if (typeof value === "boolean") {
32
+ return { present: true, valueType: "boolean" };
33
+ }
34
+ if (Array.isArray(value)) {
35
+ return {
36
+ present: true,
37
+ valueType: "array",
38
+ itemCount: value.length,
39
+ };
40
+ }
41
+ if (isReadableObject(value)) {
42
+ const fileCount = typeof value.fileCount === "number" && Number.isFinite(value.fileCount)
43
+ ? Math.max(0, Math.floor(value.fileCount))
44
+ : undefined;
45
+ return {
46
+ present: true,
47
+ valueType: "object",
48
+ keyCount: Object.keys(value).length,
49
+ ...(typeof fileCount === "number" ? { fileCount } : {}),
50
+ };
51
+ }
52
+ return null;
53
+ };
54
+ export const buildLocalStructuredSummary = (value, depth = 0) => {
55
+ if (depth >= MAX_SUMMARY_DEPTH) {
56
+ return buildLocalValueDescriptor(value);
57
+ }
58
+ if (Array.isArray(value)) {
59
+ return {
60
+ present: true,
61
+ valueType: "array",
62
+ itemCount: value.length,
63
+ items: value
64
+ .slice(0, MAX_SUMMARY_ITEMS)
65
+ .map((entry) => buildLocalStructuredSummary(entry, depth + 1)),
66
+ };
67
+ }
68
+ if (isReadableObject(value)) {
69
+ const limitedEntries = Object.entries(value).slice(0, MAX_SUMMARY_KEYS);
70
+ return {
71
+ present: true,
72
+ valueType: "object",
73
+ keyCount: Object.keys(value).length,
74
+ fields: Object.fromEntries(limitedEntries.map(([key, child]) => [
75
+ key,
76
+ buildLocalStructuredSummary(child, depth + 1),
77
+ ])),
78
+ };
79
+ }
80
+ return buildLocalValueDescriptor(value);
81
+ };
82
+ export const buildLocalQuerySummary = (query) => {
83
+ return Object.fromEntries(Object.entries(query).map(([key, value]) => [
84
+ key,
85
+ buildLocalStructuredSummary(value),
86
+ ]));
87
+ };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Browser-SDK observation source event exporter.
3
+ *
4
+ * Production OTel {@link SpanExporter} for the Frontend SDK. Forwards each
5
+ * {@link ReadableSpan} as a source-signal envelope and routes it onto the
6
+ * SDK's existing transport buffer. Final classification and canonical shaping
7
+ * happen in the Cluebase backend ingestion core.
8
+ *
9
+ * The exporter is registered onto the global `WebTracerProvider` in
10
+ * `provider/sdk.ts:cluebase.init`. `enqueue(event)` accepts source-signal
11
+ * envelopes from capture modules.
12
+ */
13
+ import { type ExportResult, ExportResultCode } from "@opentelemetry/core";
14
+ import type { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
15
+ import type { CluebaseContext } from "../provider/types.js";
16
+ import type { CollectionPermit } from "./browser-outbox.js";
17
+ import type { BrowserSourceEvent, BrowserSourceSignalEvent } from "./source-event.js";
18
+ export declare const EXPORT_RESULT_SUCCESS: ExportResultCode;
19
+ export declare const EXPORT_RESULT_FAILED: ExportResultCode;
20
+ export interface ObservationSourceEventExporterOptions {
21
+ enqueueImpl: (event: BrowserSourceEvent, permit?: CollectionPermit) => void;
22
+ getCollectionPermit?: (span: ReadableSpan | null) => CollectionPermit | undefined;
23
+ flushImpl: () => Promise<void>;
24
+ shutdownImpl?: () => Promise<void>;
25
+ /**
26
+ * Returns the active {@link CluebaseContext} for the current SDK instance.
27
+ * Called once per `export()` invocation so browser-environment fields
28
+ * (device, browser, locale, etc.) reflect the SDK state at export time.
29
+ */
30
+ getContext: () => CluebaseContext;
31
+ deniedKeys?: readonly string[];
32
+ /**
33
+ * URL prefixes that should NOT produce source-signal events.
34
+ * Used to skip spans that the Cluebase ingest BatchTransport itself
35
+ * generates when the host installs ``@opentelemetry/instrumentation-fetch``
36
+ * (otherwise Cluebase's own POSTs would loop back through the exporter as
37
+ * fetch spans, generating duplicate ingest events ad-infinitum).
38
+ */
39
+ ignoreUrlPrefixes?: readonly string[];
40
+ }
41
+ export declare class ObservationSourceEventExporter implements SpanExporter {
42
+ private closed;
43
+ private readonly enqueueImpl;
44
+ private readonly getCollectionPermit;
45
+ private readonly flushImpl;
46
+ private readonly shutdownImpl?;
47
+ private readonly getContext;
48
+ private readonly deniedKeys;
49
+ private readonly ignoreUrlPrefixes;
50
+ private exportedSpanCount;
51
+ private droppedSpanCount;
52
+ private skippedLoopSpanCount;
53
+ constructor(options: ObservationSourceEventExporterOptions);
54
+ private ignoreSpanReason;
55
+ /**
56
+ * Enqueue a source-signal event emitted by capture modules through
57
+ * `context.onEvent`.
58
+ */
59
+ enqueue(event: BrowserSourceSignalEvent): void;
60
+ export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
61
+ forceFlush(): Promise<void>;
62
+ shutdown(): Promise<void>;
63
+ internalMetrics(): {
64
+ exported_span_count: number;
65
+ dropped_span_count: number;
66
+ skipped_loop_span_count: number;
67
+ closed: boolean;
68
+ };
69
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Browser-SDK observation source event exporter.
3
+ *
4
+ * Production OTel {@link SpanExporter} for the Frontend SDK. Forwards each
5
+ * {@link ReadableSpan} as a source-signal envelope and routes it onto the
6
+ * SDK's existing transport buffer. Final classification and canonical shaping
7
+ * happen in the Cluebase backend ingestion core.
8
+ *
9
+ * The exporter is registered onto the global `WebTracerProvider` in
10
+ * `provider/sdk.ts:cluebase.init`. `enqueue(event)` accepts source-signal
11
+ * envelopes from capture modules.
12
+ */
13
+ import { ExportResultCode } from "@opentelemetry/core";
14
+ import { logSwallowed } from "../internal/internal-log.js";
15
+ import { spanToSourceSignalEvent } from "./span-to-source-signal.js";
16
+ export const EXPORT_RESULT_SUCCESS = ExportResultCode.SUCCESS;
17
+ export const EXPORT_RESULT_FAILED = ExportResultCode.FAILED;
18
+ export class ObservationSourceEventExporter {
19
+ constructor(options) {
20
+ this.closed = false;
21
+ this.exportedSpanCount = 0;
22
+ this.droppedSpanCount = 0;
23
+ this.skippedLoopSpanCount = 0;
24
+ this.enqueueImpl = options.enqueueImpl;
25
+ this.getCollectionPermit = options.getCollectionPermit ?? null;
26
+ this.flushImpl = options.flushImpl;
27
+ this.shutdownImpl = options.shutdownImpl;
28
+ this.getContext = options.getContext;
29
+ this.deniedKeys = options.deniedKeys ?? [];
30
+ this.ignoreUrlPrefixes = (options.ignoreUrlPrefixes ?? []).filter((p) => typeof p === "string" && p.length > 0);
31
+ }
32
+ ignoreSpanReason(span) {
33
+ const spanFields = span;
34
+ const attrs = spanFields.attributes ?? {};
35
+ if (attrs["cluebase.capture.skip"] === true)
36
+ return "loop";
37
+ const httpUrl = attrs["http.url"];
38
+ const fullUrl = attrs["url.full"];
39
+ const url = typeof httpUrl === "string"
40
+ ? httpUrl
41
+ : typeof fullUrl === "string"
42
+ ? fullUrl
43
+ : undefined;
44
+ if (url &&
45
+ this.ignoreUrlPrefixes.some((prefix) => url.startsWith(prefix))) {
46
+ return "loop";
47
+ }
48
+ if (!Object.keys(attrs).some((key) => key.startsWith("cluebase."))) {
49
+ return "unmarked";
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Enqueue a source-signal event emitted by capture modules through
55
+ * `context.onEvent`.
56
+ */
57
+ enqueue(event) {
58
+ if (this.closed)
59
+ return;
60
+ const permit = this.getCollectionPermit?.(null);
61
+ if (this.getCollectionPermit && permit === undefined) {
62
+ return;
63
+ }
64
+ this.enqueueImpl(event, permit);
65
+ }
66
+ export(spans, resultCallback) {
67
+ if (this.closed) {
68
+ resultCallback({
69
+ code: ExportResultCode.FAILED,
70
+ error: new Error("exporter_closed"),
71
+ });
72
+ return;
73
+ }
74
+ try {
75
+ const context = this.getContext();
76
+ for (const span of spans) {
77
+ const ignoreReason = this.ignoreSpanReason(span);
78
+ if (ignoreReason) {
79
+ if (ignoreReason === "loop") {
80
+ this.skippedLoopSpanCount += 1;
81
+ }
82
+ continue;
83
+ }
84
+ try {
85
+ const permit = this.getCollectionPermit?.(span);
86
+ if (this.getCollectionPermit && permit === undefined) {
87
+ this.droppedSpanCount += 1;
88
+ continue;
89
+ }
90
+ const sourceSignal = spanToSourceSignalEvent(span, {
91
+ context,
92
+ deniedKeys: this.deniedKeys,
93
+ });
94
+ this.enqueueImpl(sourceSignal, permit);
95
+ this.exportedSpanCount += 1;
96
+ }
97
+ catch (cause) {
98
+ this.droppedSpanCount += 1;
99
+ logSwallowed("spanToSourceSignalEvent", cause, {
100
+ spanName: span.name,
101
+ });
102
+ }
103
+ }
104
+ void this.flushImpl()
105
+ .then(() => resultCallback({ code: ExportResultCode.SUCCESS }))
106
+ .catch((cause) => resultCallback({
107
+ code: ExportResultCode.FAILED,
108
+ error: cause instanceof Error ? cause : new Error(String(cause)),
109
+ }));
110
+ }
111
+ catch (cause) {
112
+ resultCallback({
113
+ code: ExportResultCode.FAILED,
114
+ error: cause instanceof Error ? cause : new Error(String(cause)),
115
+ });
116
+ }
117
+ }
118
+ async forceFlush() {
119
+ if (this.closed)
120
+ return;
121
+ try {
122
+ await this.flushImpl();
123
+ }
124
+ catch (cause) {
125
+ // forceFlush is best-effort; swallow to keep the OTel contract.
126
+ logSwallowed("forceFlush", cause);
127
+ }
128
+ }
129
+ async shutdown() {
130
+ if (this.closed)
131
+ return;
132
+ this.closed = true;
133
+ try {
134
+ if (this.shutdownImpl) {
135
+ await this.shutdownImpl();
136
+ }
137
+ else {
138
+ await this.flushImpl();
139
+ }
140
+ }
141
+ catch (cause) {
142
+ // shutdown is non-raising per the OTel contract.
143
+ logSwallowed("shutdown", cause);
144
+ }
145
+ }
146
+ internalMetrics() {
147
+ return {
148
+ exported_span_count: this.exportedSpanCount,
149
+ dropped_span_count: this.droppedSpanCount,
150
+ skipped_loop_span_count: this.skippedLoopSpanCount,
151
+ closed: this.closed,
152
+ };
153
+ }
154
+ }
@@ -0,0 +1,14 @@
1
+ import type { ParameterSnapshotValue } from "../provider/types.js";
2
+ export type ParameterSnapshotBundle = {
3
+ snapshot: ParameterSnapshotValue;
4
+ allowedValueKeys: string[];
5
+ };
6
+ type ParameterSnapshotOptions = {
7
+ allowedPaths: readonly string[];
8
+ deniedKeys: readonly string[];
9
+ pathPrefix?: string;
10
+ };
11
+ export declare const buildUnavailableParameterSnapshot: () => ParameterSnapshotValue;
12
+ export declare const truncateParameterSnapshotValue: (value: ParameterSnapshotValue) => ParameterSnapshotValue;
13
+ export declare const buildParameterSnapshotBundle: (value: unknown, options: ParameterSnapshotOptions) => ParameterSnapshotBundle;
14
+ export {};