@safaricom-mxl/effect-sdk 0.0.23 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,514 +1,7 @@
1
- import { t as __exportAll } from "../rolldown-runtime-D7D4PA-g.js";
2
- import * as WebSdk from "@effect/opentelemetry/WebSdk";
3
- import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
4
- import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
5
- import { Effect, Layer } from "effect";
1
+ import { _ as scrubUrl, a as browserDocument, c as clearIdentity, d as increment, f as setConsent, g as resetUrlSanitizersForTests, h as redactUrl, i as replayRecordOptions, l as decrement, m as addUrlSanitizer, o as browserNavigator, p as track, r as layer_exports, s as browserResourceAttributes, t as flushable_exports, u as identify } from "../flushable-CXe-q-GF.js";
6
2
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
7
- const RESERVED_EVENT_NAMES = ["session_start", "session_end"];
8
- const isReservedEventName = (name) => RESERVED_EVENT_NAMES.some((reserved) => reserved === name);
9
- const coerceTrackProps = (props) => {
10
- if (props === void 0) return {};
11
- const attributes = {};
12
- for (const [key, value] of Object.entries(props)) {
13
- if (value === void 0) continue;
14
- attributes[key] = typeof value === "string" ? value : String(value);
15
- }
16
- return attributes;
17
- };
18
- const toEventTimestamp = (value) => (value instanceof Date ? value : new Date(value ?? Date.now())).toISOString();
19
- const encodeProductEventLine = (input) => {
20
- const trimmed = typeof input.name === "string" ? input.name.trim() : "";
21
- if (trimmed.length === 0 || isReservedEventName(trimmed)) return;
22
- const options = input.options ?? {};
23
- return {
24
- attributes: coerceTrackProps(options.attributes),
25
- group_id: options.groupId ?? "",
26
- name: trimmed.slice(0, 80),
27
- page_path: options.pagePath ?? "",
28
- service_name: input.serviceName,
29
- session_id: options.sessionId ?? "",
30
- source: input.source,
31
- timestamp: toEventTimestamp(options.timestamp),
32
- traceparent: options.traceparent,
33
- url: options.url ?? "",
34
- user_id: options.userId ?? "",
35
- visitor_id: options.visitorId ?? ""
36
- };
37
- };
38
- const encodeNdjson = (lines) => `${lines.map((line) => JSON.stringify(line)).join("\n")}\n`;
39
- //#endregion
40
- //#region src/shared/identity.ts
41
- const normalizeIdentity = (input) => {
42
- if (input === null || input === void 0) return;
43
- if (typeof input === "string") {
44
- const id = input.trim();
45
- return id.length > 0 ? { id } : void 0;
46
- }
47
- const id = input.id.trim();
48
- if (id.length === 0) return;
49
- return {
50
- email: input.email,
51
- groupId: input.groupId,
52
- groupName: input.groupName,
53
- id,
54
- traits: input.traits
55
- };
56
- };
57
- //#endregion
58
- //#region src/version.ts
59
- const SDK_NAME = "mxl-effect-sdk";
60
- const SDK_VERSION = "0.0.15";
61
- //#endregion
62
- //#region src/browser/analytics.ts
63
- const DEFAULT_INGEST_URL = "http://127.0.0.1:8000";
64
- const MAX_QUEUE = 100;
65
- let config;
66
- let identity;
67
- let consentGranted = true;
68
- let sessionId = "";
69
- let queue = [];
70
- let started = false;
71
- const randomId = () => {
72
- if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID();
73
- return `mxl_${Math.random().toString(36).slice(2)}_${Date.now()}`;
74
- };
75
- const storageGet = (key) => {
76
- try {
77
- const value = globalThis.sessionStorage?.getItem(key);
78
- return value && value.length > 0 ? value : void 0;
79
- } catch {
80
- return;
81
- }
82
- };
83
- const storageSet = (key, value) => {
84
- try {
85
- globalThis.sessionStorage?.setItem(key, value);
86
- } catch {}
87
- };
88
- const resolveSessionId = () => {
89
- if (sessionId.length > 0) return sessionId;
90
- sessionId = storageGet("mxl.session.id") ?? randomId();
91
- storageSet("mxl.session.id", sessionId);
92
- return sessionId;
93
- };
94
- const ingestOrigin = () => (config?.ingestUrl ?? DEFAULT_INGEST_URL).replace(/\/$/, "");
95
- const headers = () => {
96
- const next = {
97
- "content-type": "application/json",
98
- "mxl-client-id": config?.clientId ?? "",
99
- "mxl-sdk-name": SDK_NAME,
100
- "mxl-sdk-version": SDK_VERSION
101
- };
102
- if (config?.apiKey) next["mxl-api-key"] = config.apiKey;
103
- return next;
104
- };
105
- const post = (path, body, contentType, keepalive = false) => {
106
- if (!config?.clientId) return;
107
- try {
108
- fetch(`${ingestOrigin()}${path}`, {
109
- body,
110
- headers: {
111
- ...headers(),
112
- "content-type": contentType
113
- },
114
- keepalive,
115
- method: "POST"
116
- });
117
- } catch {}
118
- };
119
- const captureAllowed = () => Boolean(config?.clientId) && consentGranted && started;
120
- const getCurrentIdentity = () => identity;
121
- const getCurrentUserId = () => identity?.id;
122
- const getCurrentSessionId = () => resolveSessionId();
123
- const configureBrowserAnalytics = (next) => {
124
- config = next;
125
- consentGranted = next.privacy?.requireConsent !== true;
126
- started = true;
127
- resolveSessionId();
128
- drainQueue();
129
- };
130
- const setConsent = (granted) => {
131
- consentGranted = granted;
132
- if (granted) drainQueue();
133
- };
134
- const identify = (input) => {
135
- identity = normalizeIdentity(input);
136
- if (!captureAllowed() || identity === void 0) return;
137
- post("/track", JSON.stringify({
138
- payload: {
139
- email: identity.email,
140
- firstName: typeof identity.traits?.["firstName"] === "string" ? identity.traits["firstName"] : void 0,
141
- lastName: typeof identity.traits?.["lastName"] === "string" ? identity.traits["lastName"] : void 0,
142
- profileId: identity.id,
143
- properties: {
144
- ...identity.traits ?? {},
145
- groupId: identity.groupId,
146
- groupName: identity.groupName
147
- }
148
- },
149
- type: "identify"
150
- }), "application/json");
151
- };
152
- const clearIdentity = () => {
153
- identity = void 0;
154
- };
155
- const postProductAndSession = (name, props, keepalive = false) => {
156
- const line = encodeProductEventLine({
157
- name,
158
- options: {
159
- attributes: props,
160
- groupId: identity?.groupId,
161
- sessionId: resolveSessionId(),
162
- userId: identity?.id
163
- },
164
- serviceName: config?.serviceName ?? "unknown",
165
- source: "browser"
166
- });
167
- if (line === void 0) return;
168
- post("/events", encodeNdjson([line]), "application/x-ndjson", keepalive);
169
- post("/sessionEvents", `${JSON.stringify({
170
- attributes: line.attributes,
171
- message: line.name,
172
- session_id: line.session_id,
173
- timestamp: line.timestamp,
174
- type: "custom"
175
- })}\n`, "application/x-ndjson", keepalive);
176
- };
177
- const drainQueue = (keepalive = false) => {
178
- if (!captureAllowed()) return;
179
- const pending = queue;
180
- queue = [];
181
- for (const event of pending) postProductAndSession(event.name, event.props, keepalive);
182
- };
183
- const track = (name, props) => {
184
- if (!started || !consentGranted || !config?.clientId) {
185
- if (queue.length >= MAX_QUEUE) queue.shift();
186
- queue.push({
187
- name,
188
- props
189
- });
190
- return;
191
- }
192
- postProductAndSession(name, props);
193
- };
194
- const postProfileDelta = (type, property, value) => {
195
- if (!captureAllowed() || identity === void 0) return;
196
- post("/track", JSON.stringify({
197
- payload: {
198
- profileId: identity.id,
199
- property,
200
- value: value ?? 1
201
- },
202
- type
203
- }), "application/json");
204
- };
205
- const increment = (property, value) => {
206
- postProfileDelta("increment", property, value);
207
- };
208
- const decrement = (property, value) => {
209
- postProfileDelta("decrement", property, value);
210
- };
211
- const flushAnalytics = (keepalive = false) => {
212
- drainQueue(keepalive);
213
- };
214
- //#endregion
215
- //#region src/browser/replay.ts
216
- let stopRecorder;
217
- let chunkSeq = 0;
218
- const ingestHeaders = (clientId, apiKey, contentType) => {
219
- const headers = {
220
- "content-type": contentType,
221
- "mxl-client-id": clientId,
222
- "mxl-sdk-name": SDK_NAME,
223
- "mxl-sdk-version": SDK_VERSION
224
- };
225
- if (apiKey) headers["mxl-api-key"] = apiKey;
226
- return headers;
227
- };
228
- const isWebSdkPresent = () => {
229
- const global = globalThis;
230
- return Boolean(global.__MXL_WEB_SDK__ || global.MxlWebSdk || global.__MXL_REPLAY_SESSION__);
231
- };
232
- const shouldRecordReplay = (replay) => {
233
- if (isWebSdkPresent()) return false;
234
- if (replay?.enabled === false) return false;
235
- const sampleRate = replay?.sampleRate ?? 1;
236
- return sampleRate >= 1 || Math.random() < sampleRate;
237
- };
238
- const sessionMetaRow = (serviceName) => {
239
- const identity = getCurrentIdentity();
240
- const started = /* @__PURE__ */ new Date();
241
- return {
242
- service_name: serviceName,
243
- session_id: getCurrentSessionId(),
244
- start_time: started.toISOString().replace("T", " ").replace("Z", ""),
245
- status: "active",
246
- url_initial: typeof globalThis.location?.href === "string" ? globalThis.location.href : "",
247
- user_agent: typeof globalThis.navigator?.userAgent === "string" ? globalThis.navigator.userAgent : "",
248
- user_id: identity?.id ?? "",
249
- version: 1
250
- };
251
- };
252
- const postSessionReplayMeta = (ingestUrl, clientId, serviceName, apiKey) => {
253
- try {
254
- fetch(`${ingestUrl.replace(/\/$/, "")}/sessionReplays/meta`, {
255
- body: `${JSON.stringify(sessionMetaRow(serviceName))}\n`,
256
- headers: ingestHeaders(clientId, apiKey, "application/x-ndjson"),
257
- keepalive: true,
258
- method: "POST"
259
- });
260
- } catch {}
261
- };
262
- const postSessionReplayBlob = (ingestUrl, clientId, meta, body, apiKey) => {
263
- try {
264
- fetch(`${ingestUrl.replace(/\/$/, "")}/sessionReplays/blob`, {
265
- body,
266
- headers: {
267
- ...ingestHeaders(clientId, apiKey, "application/octet-stream"),
268
- "x-mxl-chunk-seq": String(meta.chunkSeq),
269
- "x-mxl-duration-ms": String(meta.durationMs),
270
- "x-mxl-event-count": String(meta.eventCount),
271
- "x-mxl-is-checkpoint": meta.isCheckpoint ? "1" : "0",
272
- "x-mxl-session-id": meta.sessionId
273
- },
274
- keepalive: true,
275
- method: "POST"
276
- });
277
- } catch {}
278
- };
279
- const gzipJson = async (value) => {
280
- if (typeof CompressionStream === "undefined") return;
281
- const encoded = new TextEncoder().encode(JSON.stringify(value));
282
- const stream = new Blob([encoded]).stream().pipeThrough(new CompressionStream("gzip"));
283
- return new Uint8Array(await new Response(stream).arrayBuffer());
284
- };
285
- const stopReplay = () => {
286
- stopRecorder?.();
287
- stopRecorder = void 0;
288
- };
289
- const startReplay = async (config) => {
290
- if (!shouldRecordReplay(config.replay)) return;
291
- stopReplay();
292
- chunkSeq = 0;
293
- postSessionReplayMeta(config.ingestUrl, config.clientId, config.serviceName, config.apiKey);
294
- try {
295
- const rrweb = await import(
296
- /* @vite-ignore */
297
- "rrweb"
298
- );
299
- if (typeof rrweb.record !== "function") return;
300
- const events = [];
301
- const startedAt = Date.now();
302
- const stop = rrweb.record({
303
- emit: (event) => {
304
- events.push(event);
305
- },
306
- maskAllInputs: config.replay?.maskAllInputs ?? true,
307
- maskTextSelector: config.replay?.maskAllText === true ? "*" : void 0
308
- });
309
- stopRecorder = () => {
310
- stop?.();
311
- if (events.length === 0) return;
312
- gzipJson(events).then((gzipped) => {
313
- if (!gzipped) return;
314
- postSessionReplayBlob(config.ingestUrl, config.clientId, {
315
- chunkSeq: chunkSeq++,
316
- durationMs: Date.now() - startedAt,
317
- eventCount: events.length,
318
- isCheckpoint: true,
319
- sessionId: getCurrentSessionId()
320
- }, gzipped, config.apiKey);
321
- });
322
- };
323
- } catch {}
324
- };
325
- //#endregion
326
- //#region src/server/otlp.ts
327
- function normalizeOtlpBaseUrl(url) {
328
- const trimmed = url.replace(/\/$/, "");
329
- if (trimmed.endsWith("/v1/traces") || trimmed.endsWith("/v1/logs")) return trimmed.replace(/\/v1\/(traces|logs|metrics)$/, "");
330
- return trimmed;
331
- }
332
- //#endregion
333
- //#region src/browser/layer.ts
334
- var layer_exports = /* @__PURE__ */ __exportAll({
335
- clearIdentity: () => clearIdentity$1,
336
- identify: () => identify$1,
337
- layer: () => layer,
338
- stampUserIdOnSpan: () => stampUserIdOnSpan,
339
- track: () => track$1
340
- });
341
- const stampUserIdOnSpan = (span) => {
342
- const userId = getCurrentUserId();
343
- if (userId) span.setAttribute("user.id", userId);
344
- };
345
- var UserIdSpanProcessor = class {
346
- inner;
347
- constructor(inner) {
348
- this.inner = inner;
349
- }
350
- forceFlush() {
351
- return this.inner.forceFlush();
352
- }
353
- onEnd(span) {
354
- this.inner.onEnd(span);
355
- }
356
- onStart(span, parentContext) {
357
- stampUserIdOnSpan(span);
358
- this.inner.onStart(span, parentContext);
359
- }
360
- shutdown() {
361
- return this.inner.shutdown();
362
- }
363
- };
364
- function resolveBrowserEndpoint(config) {
365
- if (config.disabled) return;
366
- if (config.endpoint && config.endpoint.length > 0) return normalizeOtlpBaseUrl(config.endpoint);
367
- }
368
- /**
369
- * Browser Effect observability layer backed by OpenTelemetry WebSdk.
370
- *
371
- * Returns a no-op layer when `disabled` is true or no OTLP endpoint is configured.
372
- */
373
- const layer = (config = {}) => {
374
- if (config.clientId) {
375
- configureBrowserAnalytics({
376
- apiKey: config.apiKey,
377
- clientId: config.clientId,
378
- ingestUrl: config.ingestUrl,
379
- privacy: config.privacy,
380
- serviceName: config.serviceName
381
- });
382
- startReplay({
383
- apiKey: config.apiKey,
384
- clientId: config.clientId,
385
- ingestUrl: config.ingestUrl ?? "http://127.0.0.1:8000",
386
- replay: config.replay,
387
- serviceName: config.serviceName ?? "unknown"
388
- });
389
- }
390
- const endpoint = resolveBrowserEndpoint(config);
391
- if (!endpoint) return Layer.empty;
392
- const attributes = {
393
- "mxl.sdk.type": "browser",
394
- ...config.environment ? {
395
- "deployment.environment": config.environment,
396
- "deployment.environment.name": config.environment
397
- } : {},
398
- ...config.attributes ?? {}
399
- };
400
- return WebSdk.layer(() => ({
401
- resource: {
402
- attributes,
403
- serviceName: config.serviceName ?? "unknown",
404
- serviceVersion: config.serviceVersion
405
- },
406
- spanProcessor: new UserIdSpanProcessor(new BatchSpanProcessor(new OTLPTraceExporter({
407
- headers: config.headers,
408
- url: `${endpoint}/v1/traces`
409
- })))
410
- }));
411
- };
412
- const identify$1 = (input) => Effect.sync(() => identify(input));
413
- const clearIdentity$1 = Effect.sync(() => clearIdentity());
414
- const track$1 = (name, props) => Effect.sync(() => track(name, props));
415
- //#endregion
416
- //#region src/shared/flush-core.ts
417
- const COOLDOWN_MS = 6e4;
418
- const makeSerializedFlush = (flush) => {
419
- let pending = Promise.resolve();
420
- return (...args) => {
421
- pending = pending.then(() => flush(...args), () => flush(...args));
422
- return pending;
423
- };
424
- };
425
- const guardFlush = (prefix, flush, state) => {
426
- return async (...args) => {
427
- if (state && Date.now() < state.disabledUntil) return;
428
- try {
429
- await flush(...args);
430
- } catch (error) {
431
- if (state) state.disabledUntil = Date.now() + COOLDOWN_MS;
432
- console.error(`${prefix} flush failed; cooling down 60s`, error);
433
- }
434
- };
435
- };
436
- //#endregion
437
- //#region src/browser/flushable.ts
438
- var flushable_exports = /* @__PURE__ */ __exportAll({
439
- attachProcessor: () => attachProcessor,
440
- make: () => make
441
- });
442
- const processors = /* @__PURE__ */ new WeakMap();
443
- const DEFAULT_AUTO_FLUSH_MS = 5e3;
444
- const attachProcessor = (telemetry, processor) => {
445
- processors.set(telemetry, processor);
446
- };
447
- const make = (config = {}) => {
448
- const layer$1 = layer(config);
449
- const state = { disabledUntil: 0 };
450
- const listeners = [];
451
- const telemetry = {
452
- captureException: (error, options) => {
453
- track(options?.name ?? "uncaught_error", {
454
- ...options?.attributes,
455
- message: error instanceof Error ? error.message : String(error)
456
- });
457
- },
458
- dispose: async () => {
459
- for (const stop of listeners) stop();
460
- listeners.length = 0;
461
- stopReplay();
462
- await telemetry.flush();
463
- },
464
- flush: makeSerializedFlush(guardFlush("[MxlFlush]", async () => {
465
- flushAnalytics(true);
466
- await processors.get(telemetry)?.forceFlush();
467
- }, state)),
468
- layer: layer$1
469
- };
470
- const intervalMs = config.autoFlushInterval === void 0 ? DEFAULT_AUTO_FLUSH_MS : config.autoFlushInterval === false ? 0 : config.autoFlushInterval;
471
- if (intervalMs > 0) {
472
- const timer = setInterval(() => {
473
- telemetry.flush();
474
- }, intervalMs);
475
- timer.unref?.();
476
- listeners.push(() => {
477
- clearInterval(timer);
478
- });
479
- }
480
- const documentRef = globalThis.document;
481
- if (config.flushOnUnload !== false && documentRef) {
482
- const onHidden = () => {
483
- telemetry.flush();
484
- };
485
- const onVisibilityChange = () => {
486
- if (documentRef.visibilityState === "hidden") onHidden();
487
- };
488
- documentRef.addEventListener("visibilitychange", onVisibilityChange);
489
- globalThis.addEventListener?.("pagehide", onHidden);
490
- listeners.push(() => {
491
- documentRef.removeEventListener("visibilitychange", onVisibilityChange);
492
- globalThis.removeEventListener?.("pagehide", onHidden);
493
- });
494
- }
495
- if (config.captureGlobalErrors !== false && typeof globalThis.addEventListener === "function") {
496
- const onError = (event) => {
497
- const record = event;
498
- telemetry.captureException(record.error ?? record.reason ?? event);
499
- telemetry.flush();
500
- };
501
- globalThis.addEventListener("error", onError);
502
- globalThis.addEventListener("unhandledrejection", onError);
503
- listeners.push(() => {
504
- globalThis.removeEventListener("error", onError);
505
- globalThis.removeEventListener("unhandledrejection", onError);
506
- });
507
- }
508
- return telemetry;
509
- };
510
- //#endregion
511
- //#region src/shared/dependency-spans.ts
3
+ import { Effect } from "effect";
4
+ // src/shared/dependency-spans.ts
512
5
  const MAX_FLUSH_SPAN_LINKS = 32;
513
6
  function dbClientSpanAttributes(dbSystem, operation) {
514
7
  return {
@@ -612,7 +105,6 @@ async function withPromiseDependencySpan(name, attributes, operation, kind = "cl
612
105
  }
613
106
  });
614
107
  }
615
- //#endregion
616
- export { layer_exports as Mxl, flushable_exports as MxlFlush, addSpanLinksFromTraceCarriers, bufferSpanAttributes, clearIdentity, dbClientSpanAttributes, decrement, geoSpanAttributes, identify, increment, kafkaProducerSpanAttributes, queueConsumerSpanAttributes, queueProducerSpanAttributes, setConsent, track, withDependencySpan, withPromiseDependencySpan };
108
+ export { layer_exports as Mxl, flushable_exports as MxlFlush, addSpanLinksFromTraceCarriers, addUrlSanitizer, browserDocument, browserNavigator, browserResourceAttributes, bufferSpanAttributes, clearIdentity, dbClientSpanAttributes, decrement, geoSpanAttributes, identify, increment, kafkaProducerSpanAttributes, queueConsumerSpanAttributes, queueProducerSpanAttributes, redactUrl, replayRecordOptions, resetUrlSanitizersForTests, scrubUrl, setConsent, track, withDependencySpan, withPromiseDependencySpan };
617
109
 
618
110
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["identify","identifySync","clearIdentity","clearIdentitySync","track","trackSync","layer","browserLayer","traceApi","otelContext"],"sources":["../../src/shared/product-event.ts","../../src/shared/identity.ts","../../src/version.ts","../../src/browser/analytics.ts","../../src/browser/replay.ts","../../src/server/otlp.ts","../../src/browser/layer.ts","../../src/shared/flush-core.ts","../../src/browser/flushable.ts","../../src/shared/dependency-spans.ts"],"sourcesContent":["export type ProductEventSource = \"server\" | \"browser\";\n\nexport const MAX_EVENT_NAME_LENGTH = 80;\nexport const RESERVED_EVENT_NAMES = [\"session_start\", \"session_end\"] as const;\n\nexport interface ProductEventLine {\n readonly attributes: Record<string, string>;\n readonly group_id: string;\n readonly name: string;\n readonly page_path: string;\n readonly service_name: string;\n readonly session_id: string;\n readonly source: ProductEventSource;\n readonly timestamp: string;\n readonly traceparent?: string | undefined;\n readonly url: string;\n readonly user_id: string;\n readonly visitor_id: string;\n}\n\nexport interface TrackProps {\n readonly [key: string]: unknown;\n}\n\nexport interface TrackOptions {\n readonly attributes?: TrackProps | undefined;\n readonly groupId?: string | undefined;\n readonly pagePath?: string | undefined;\n readonly sessionId?: string | undefined;\n readonly timestamp?: Date | number | undefined;\n readonly traceparent?: string | undefined;\n readonly url?: string | undefined;\n readonly userId?: string | undefined;\n readonly visitorId?: string | undefined;\n}\n\nconst isReservedEventName = (name: string): boolean =>\n RESERVED_EVENT_NAMES.some((reserved) => reserved === name);\n\nexport const coerceTrackProps = (\n props: TrackProps | undefined\n): Record<string, string> => {\n if (props === undefined) {\n return {};\n }\n const attributes: Record<string, string> = {};\n for (const [key, value] of Object.entries(props)) {\n if (value === undefined) {\n continue;\n }\n attributes[key] = typeof value === \"string\" ? value : String(value);\n }\n return attributes;\n};\n\nexport const toEventTimestamp = (value: Date | number | undefined): string =>\n (value instanceof Date ? value : new Date(value ?? Date.now())).toISOString();\n\nexport const encodeProductEventLine = (input: {\n readonly name: string;\n readonly options?: TrackOptions | undefined;\n readonly serviceName: string;\n readonly source: ProductEventSource;\n}): ProductEventLine | undefined => {\n const trimmed = typeof input.name === \"string\" ? input.name.trim() : \"\";\n if (trimmed.length === 0 || isReservedEventName(trimmed)) {\n return undefined;\n }\n const options = input.options ?? {};\n return {\n attributes: coerceTrackProps(options.attributes),\n group_id: options.groupId ?? \"\",\n name: trimmed.slice(0, MAX_EVENT_NAME_LENGTH),\n page_path: options.pagePath ?? \"\",\n service_name: input.serviceName,\n session_id: options.sessionId ?? \"\",\n source: input.source,\n timestamp: toEventTimestamp(options.timestamp),\n traceparent: options.traceparent,\n url: options.url ?? \"\",\n user_id: options.userId ?? \"\",\n visitor_id: options.visitorId ?? \"\",\n };\n};\n\nexport const encodeNdjson = (lines: ReadonlyArray<ProductEventLine>): string =>\n `${lines.map((line) => JSON.stringify(line)).join(\"\\n\")}\\n`;\n","export interface MapleIdentity {\n readonly email?: string | undefined;\n readonly groupId?: string | undefined;\n readonly groupName?: string | undefined;\n readonly id: string;\n readonly traits?: Record<string, unknown> | undefined;\n}\n\nexport type IdentifyInput = string | MapleIdentity | null | undefined;\n\nexport const normalizeIdentity = (\n input: IdentifyInput\n): MapleIdentity | undefined => {\n if (input === null || input === undefined) {\n return undefined;\n }\n if (typeof input === \"string\") {\n const id = input.trim();\n return id.length > 0 ? { id } : undefined;\n }\n const id = input.id.trim();\n if (id.length === 0) {\n return undefined;\n }\n return {\n email: input.email,\n groupId: input.groupId,\n groupName: input.groupName,\n id,\n traits: input.traits,\n };\n};\n","export const SDK_NAME = \"mxl-effect-sdk\";\nexport const SDK_VERSION = \"0.0.15\";\n","import {\n encodeNdjson,\n encodeProductEventLine,\n type TrackProps,\n} from \"../shared/product-event.js\";\nimport {\n type IdentifyInput,\n type MapleIdentity,\n normalizeIdentity,\n} from \"../shared/identity.js\";\nimport { SDK_NAME, SDK_VERSION } from \"../version.js\";\n\nexport type { IdentifyInput, MapleIdentity, TrackProps };\n\nexport interface PrivacyOptions {\n readonly requireConsent?: boolean | undefined;\n}\n\nexport interface BrowserAnalyticsConfig {\n readonly apiKey?: string | undefined;\n readonly clientId?: string | undefined;\n readonly ingestUrl?: string | undefined;\n readonly privacy?: PrivacyOptions | undefined;\n readonly serviceName?: string | undefined;\n}\n\ninterface QueuedCustomEvent {\n readonly name: string;\n readonly props?: TrackProps | undefined;\n}\n\nconst DEFAULT_INGEST_URL = \"http://127.0.0.1:8000\";\nconst MAX_QUEUE = 100;\n\nlet config: BrowserAnalyticsConfig | undefined;\nlet identity: MapleIdentity | undefined;\nlet consentGranted = true;\nlet sessionId = \"\";\nlet queue: QueuedCustomEvent[] = [];\nlet started = false;\n\nconst randomId = (): string => {\n if (globalThis.crypto?.randomUUID) {\n return globalThis.crypto.randomUUID();\n }\n return `mxl_${Math.random().toString(36).slice(2)}_${Date.now()}`;\n};\n\nconst storageGet = (key: string): string | undefined => {\n try {\n const value = globalThis.sessionStorage?.getItem(key);\n return value && value.length > 0 ? value : undefined;\n } catch {\n return undefined;\n }\n};\n\nconst storageSet = (key: string, value: string): void => {\n try {\n globalThis.sessionStorage?.setItem(key, value);\n } catch {\n // Private mode or missing storage.\n }\n};\n\nconst resolveSessionId = (): string => {\n if (sessionId.length > 0) {\n return sessionId;\n }\n sessionId = storageGet(\"mxl.session.id\") ?? randomId();\n storageSet(\"mxl.session.id\", sessionId);\n return sessionId;\n};\n\nconst ingestOrigin = (): string =>\n (config?.ingestUrl ?? DEFAULT_INGEST_URL).replace(/\\/$/, \"\");\n\nconst headers = (): Record<string, string> => {\n const next: Record<string, string> = {\n \"content-type\": \"application/json\",\n \"mxl-client-id\": config?.clientId ?? \"\",\n \"mxl-sdk-name\": SDK_NAME,\n \"mxl-sdk-version\": SDK_VERSION,\n };\n if (config?.apiKey) {\n next[\"mxl-api-key\"] = config.apiKey;\n }\n return next;\n};\n\nconst post = (\n path: string,\n body: string,\n contentType: string,\n keepalive = false\n): void => {\n if (!config?.clientId) {\n return;\n }\n try {\n void fetch(`${ingestOrigin()}${path}`, {\n body,\n headers: { ...headers(), \"content-type\": contentType },\n keepalive,\n method: \"POST\",\n });\n } catch {\n // Never throw into the host app.\n }\n};\n\nconst captureAllowed = (): boolean =>\n Boolean(config?.clientId) && consentGranted && started;\n\nexport const getCurrentIdentity = (): MapleIdentity | undefined => identity;\n\nexport const getCurrentUserId = (): string | undefined => identity?.id;\n\nexport const getCurrentSessionId = (): string => resolveSessionId();\n\nexport const configureBrowserAnalytics = (\n next: BrowserAnalyticsConfig\n): void => {\n config = next;\n consentGranted = next.privacy?.requireConsent !== true;\n started = true;\n resolveSessionId();\n drainQueue();\n};\n\nexport const resetBrowserAnalytics = (): void => {\n config = undefined;\n identity = undefined;\n consentGranted = true;\n sessionId = \"\";\n queue = [];\n started = false;\n};\n\nexport const setConsent = (granted: boolean): void => {\n consentGranted = granted;\n if (granted) {\n drainQueue();\n }\n};\n\nexport const identify = (input?: IdentifyInput): void => {\n identity = normalizeIdentity(input);\n if (!captureAllowed() || identity === undefined) {\n return;\n }\n post(\n \"/track\",\n JSON.stringify({\n payload: {\n email: identity.email,\n firstName:\n typeof identity.traits?.[\"firstName\"] === \"string\"\n ? identity.traits[\"firstName\"]\n : undefined,\n lastName:\n typeof identity.traits?.[\"lastName\"] === \"string\"\n ? identity.traits[\"lastName\"]\n : undefined,\n profileId: identity.id,\n properties: {\n ...(identity.traits ?? {}),\n groupId: identity.groupId,\n groupName: identity.groupName,\n },\n },\n type: \"identify\",\n }),\n \"application/json\"\n );\n};\n\nexport const clearIdentity = (): void => {\n identity = undefined;\n};\n\nconst postProductAndSession = (\n name: string,\n props?: TrackProps,\n keepalive = false\n): void => {\n const line = encodeProductEventLine({\n name,\n options: {\n attributes: props,\n groupId: identity?.groupId,\n sessionId: resolveSessionId(),\n userId: identity?.id,\n },\n serviceName: config?.serviceName ?? \"unknown\",\n source: \"browser\",\n });\n if (line === undefined) {\n return;\n }\n post(\"/events\", encodeNdjson([line]), \"application/x-ndjson\", keepalive);\n post(\n \"/sessionEvents\",\n `${JSON.stringify({\n attributes: line.attributes,\n message: line.name,\n session_id: line.session_id,\n timestamp: line.timestamp,\n type: \"custom\",\n })}\\n`,\n \"application/x-ndjson\",\n keepalive\n );\n};\n\nconst drainQueue = (keepalive = false): void => {\n if (!captureAllowed()) {\n return;\n }\n const pending = queue;\n queue = [];\n for (const event of pending) {\n postProductAndSession(event.name, event.props, keepalive);\n }\n};\n\nexport const track = (name: string, props?: TrackProps): void => {\n if (!started || !consentGranted || !config?.clientId) {\n if (queue.length >= MAX_QUEUE) {\n queue.shift();\n }\n queue.push({ name, props });\n return;\n }\n postProductAndSession(name, props);\n};\n\nconst postProfileDelta = (\n type: \"increment\" | \"decrement\",\n property: string,\n value?: number\n): void => {\n if (!captureAllowed() || identity === undefined) {\n return;\n }\n post(\n \"/track\",\n JSON.stringify({\n payload: {\n profileId: identity.id,\n property,\n value: value ?? 1,\n },\n type,\n }),\n \"application/json\"\n );\n};\n\nexport const increment = (property: string, value?: number): void => {\n postProfileDelta(\"increment\", property, value);\n};\n\nexport const decrement = (property: string, value?: number): void => {\n postProfileDelta(\"decrement\", property, value);\n};\n\nexport const flushAnalytics = (keepalive = false): void => {\n drainQueue(keepalive);\n};\n","import { getCurrentIdentity, getCurrentSessionId } from \"./analytics.js\";\nimport { SDK_NAME, SDK_VERSION } from \"../version.js\";\n\nexport interface ClientReplayConfig {\n readonly enabled?: boolean | undefined;\n readonly maskAllInputs?: boolean | undefined;\n readonly maskAllText?: boolean | undefined;\n readonly sampleRate?: number | undefined;\n}\n\nexport interface ReplayChunkMeta {\n readonly chunkSeq: number;\n readonly durationMs: number;\n readonly eventCount: number;\n readonly isCheckpoint: boolean;\n readonly sessionId: string;\n}\n\ntype StopReplay = () => void;\n\nlet stopRecorder: StopReplay | undefined;\nlet chunkSeq = 0;\n\nconst ingestHeaders = (\n clientId: string,\n apiKey: string | undefined,\n contentType: string\n): Record<string, string> => {\n const headers: Record<string, string> = {\n \"content-type\": contentType,\n \"mxl-client-id\": clientId,\n \"mxl-sdk-name\": SDK_NAME,\n \"mxl-sdk-version\": SDK_VERSION,\n };\n if (apiKey) {\n headers[\"mxl-api-key\"] = apiKey;\n }\n return headers;\n};\n\nexport const isWebSdkPresent = (): boolean => {\n const global = globalThis as {\n MxlWebSdk?: unknown;\n __MXL_REPLAY_SESSION__?: unknown;\n __MXL_WEB_SDK__?: unknown;\n };\n return Boolean(\n global.__MXL_WEB_SDK__ || global.MxlWebSdk || global.__MXL_REPLAY_SESSION__\n );\n};\n\nexport const shouldRecordReplay = (\n replay: ClientReplayConfig | undefined\n): boolean => {\n if (isWebSdkPresent()) {\n return false;\n }\n if (replay?.enabled === false) {\n return false;\n }\n const sampleRate = replay?.sampleRate ?? 1;\n return sampleRate >= 1 || Math.random() < sampleRate;\n};\n\nexport const sessionMetaRow = (serviceName: string) => {\n const identity = getCurrentIdentity();\n const started = new Date();\n return {\n service_name: serviceName,\n session_id: getCurrentSessionId(),\n start_time: started.toISOString().replace(\"T\", \" \").replace(\"Z\", \"\"),\n status: \"active\",\n url_initial:\n typeof globalThis.location?.href === \"string\"\n ? globalThis.location.href\n : \"\",\n user_agent:\n typeof globalThis.navigator?.userAgent === \"string\"\n ? globalThis.navigator.userAgent\n : \"\",\n user_id: identity?.id ?? \"\",\n version: 1,\n };\n};\n\nexport const postSessionReplayMeta = (\n ingestUrl: string,\n clientId: string,\n serviceName: string,\n apiKey?: string\n): void => {\n try {\n void fetch(`${ingestUrl.replace(/\\/$/, \"\")}/sessionReplays/meta`, {\n body: `${JSON.stringify(sessionMetaRow(serviceName))}\\n`,\n headers: ingestHeaders(clientId, apiKey, \"application/x-ndjson\"),\n keepalive: true,\n method: \"POST\",\n });\n } catch {\n // Replay must never throw into the host app.\n }\n};\n\nexport const postSessionReplayBlob = (\n ingestUrl: string,\n clientId: string,\n meta: ReplayChunkMeta,\n body: Uint8Array,\n apiKey?: string\n): void => {\n try {\n void fetch(`${ingestUrl.replace(/\\/$/, \"\")}/sessionReplays/blob`, {\n body: body as unknown as Blob,\n headers: {\n ...ingestHeaders(clientId, apiKey, \"application/octet-stream\"),\n \"x-mxl-chunk-seq\": String(meta.chunkSeq),\n \"x-mxl-duration-ms\": String(meta.durationMs),\n \"x-mxl-event-count\": String(meta.eventCount),\n \"x-mxl-is-checkpoint\": meta.isCheckpoint ? \"1\" : \"0\",\n \"x-mxl-session-id\": meta.sessionId,\n },\n keepalive: true,\n method: \"POST\",\n });\n } catch {\n // Replay must never throw into the host app.\n }\n};\n\nconst gzipJson = async (value: unknown): Promise<Uint8Array | undefined> => {\n if (typeof CompressionStream === \"undefined\") {\n return undefined;\n }\n const encoded = new TextEncoder().encode(JSON.stringify(value));\n const stream = new Blob([encoded])\n .stream()\n .pipeThrough(new CompressionStream(\"gzip\"));\n return new Uint8Array(await new Response(stream).arrayBuffer());\n};\n\nexport const stopReplay = (): void => {\n stopRecorder?.();\n stopRecorder = undefined;\n};\n\nexport const startReplay = async (config: {\n readonly apiKey?: string | undefined;\n readonly clientId: string;\n readonly ingestUrl: string;\n readonly replay?: ClientReplayConfig | undefined;\n readonly serviceName: string;\n}): Promise<void> => {\n if (!shouldRecordReplay(config.replay)) {\n return;\n }\n stopReplay();\n chunkSeq = 0;\n postSessionReplayMeta(\n config.ingestUrl,\n config.clientId,\n config.serviceName,\n config.apiKey\n );\n try {\n const specifier = \"rrweb\";\n const rrweb = (await import(\n /* @vite-ignore */ specifier\n )) as {\n record?: (options: {\n emit: (event: unknown) => void;\n maskAllInputs?: boolean;\n maskTextSelector?: string;\n }) => (() => void) | undefined;\n };\n if (typeof rrweb.record !== \"function\") {\n return;\n }\n const events: unknown[] = [];\n const startedAt = Date.now();\n const stop = rrweb.record({\n emit: (event) => {\n events.push(event);\n },\n maskAllInputs: config.replay?.maskAllInputs ?? true,\n maskTextSelector: config.replay?.maskAllText === true ? \"*\" : undefined,\n });\n stopRecorder = () => {\n stop?.();\n if (events.length === 0) {\n return;\n }\n void gzipJson(events).then((gzipped) => {\n if (!gzipped) {\n return;\n }\n postSessionReplayBlob(\n config.ingestUrl,\n config.clientId,\n {\n chunkSeq: chunkSeq++,\n durationMs: Date.now() - startedAt,\n eventCount: events.length,\n isCheckpoint: true,\n sessionId: getCurrentSessionId(),\n },\n gzipped,\n config.apiKey\n );\n });\n };\n } catch {\n // rrweb is optional; metadata rows still make the session visible.\n }\n};\n","export function normalizeOtlpBaseUrl(url: string): string {\n const trimmed = url.replace(/\\/$/, \"\");\n if (trimmed.endsWith(\"/v1/traces\") || trimmed.endsWith(\"/v1/logs\")) {\n return trimmed.replace(/\\/v1\\/(traces|logs|metrics)$/, \"\");\n }\n return trimmed;\n}\n","import * as WebSdk from \"@effect/opentelemetry/WebSdk\";\nimport type { Context } from \"@opentelemetry/api\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport {\n BatchSpanProcessor,\n type ReadableSpan,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { Effect, Layer } from \"effect\";\n\nimport {\n clearIdentity as clearIdentitySync,\n configureBrowserAnalytics,\n getCurrentUserId,\n identify as identifySync,\n type IdentifyInput,\n type PrivacyOptions,\n type TrackProps,\n track as trackSync,\n} from \"./analytics.js\";\nimport { type ClientReplayConfig, startReplay } from \"./replay.js\";\nimport { normalizeOtlpBaseUrl } from \"../server/otlp.js\";\n\nexport interface MxlBrowserConfig {\n readonly apiKey?: string | undefined;\n readonly attributes?: Record<string, string> | undefined;\n readonly clientId?: string | undefined;\n readonly disabled?: boolean | undefined;\n readonly endpoint?: string | undefined;\n readonly environment?: string | undefined;\n readonly headers?: Record<string, string> | undefined;\n readonly ingestUrl?: string | undefined;\n readonly privacy?: PrivacyOptions | undefined;\n readonly replay?: ClientReplayConfig | undefined;\n readonly serviceName?: string | undefined;\n readonly serviceVersion?: string | undefined;\n}\n\nexport const stampUserIdOnSpan = (span: {\n setAttribute: (key: string, value: string) => unknown;\n}): void => {\n const userId = getCurrentUserId();\n if (userId) {\n span.setAttribute(\"user.id\", userId);\n }\n};\n\nclass UserIdSpanProcessor implements SpanProcessor {\n constructor(private readonly inner: SpanProcessor) {}\n\n forceFlush(): Promise<void> {\n return this.inner.forceFlush();\n }\n\n onEnd(span: ReadableSpan): void {\n this.inner.onEnd(span);\n }\n\n onStart(\n span: Parameters<SpanProcessor[\"onStart\"]>[0],\n parentContext: Context\n ): void {\n stampUserIdOnSpan(span);\n this.inner.onStart(span, parentContext);\n }\n\n shutdown(): Promise<void> {\n return this.inner.shutdown();\n }\n}\n\nfunction resolveBrowserEndpoint(config: MxlBrowserConfig): string | undefined {\n if (config.disabled) {\n return undefined;\n }\n if (config.endpoint && config.endpoint.length > 0) {\n return normalizeOtlpBaseUrl(config.endpoint);\n }\n return undefined;\n}\n\n/**\n * Browser Effect observability layer backed by OpenTelemetry WebSdk.\n *\n * Returns a no-op layer when `disabled` is true or no OTLP endpoint is configured.\n */\nexport const layer = (config: MxlBrowserConfig = {}) => {\n if (config.clientId) {\n configureBrowserAnalytics({\n apiKey: config.apiKey,\n clientId: config.clientId,\n ingestUrl: config.ingestUrl,\n privacy: config.privacy,\n serviceName: config.serviceName,\n });\n void startReplay({\n apiKey: config.apiKey,\n clientId: config.clientId,\n ingestUrl: config.ingestUrl ?? \"http://127.0.0.1:8000\",\n replay: config.replay,\n serviceName: config.serviceName ?? \"unknown\",\n });\n }\n const endpoint = resolveBrowserEndpoint(config);\n if (!endpoint) {\n return Layer.empty;\n }\n\n const attributes: Record<string, string> = {\n \"mxl.sdk.type\": \"browser\",\n ...(config.environment\n ? {\n \"deployment.environment\": config.environment,\n \"deployment.environment.name\": config.environment,\n }\n : {}),\n ...(config.attributes ?? {}),\n };\n\n // WebSdk.layer installs OtelTracer.make, which already binds Otel.context.\n return WebSdk.layer(() => ({\n resource: {\n attributes,\n serviceName: config.serviceName ?? \"unknown\",\n serviceVersion: config.serviceVersion,\n },\n spanProcessor: new UserIdSpanProcessor(\n new BatchSpanProcessor(\n new OTLPTraceExporter({\n headers: config.headers,\n url: `${endpoint}/v1/traces`,\n })\n )\n ),\n }));\n};\n\nexport const identify = (input?: IdentifyInput) =>\n Effect.sync(() => identifySync(input));\n\nexport const clearIdentity = Effect.sync(() => clearIdentitySync());\n\nexport const track = (name: string, props?: TrackProps) =>\n Effect.sync(() => trackSync(name, props));\n","export interface SignalState {\n disabledUntil: number;\n}\n\nexport const COOLDOWN_MS = 60_000;\n\nexport const makeSerializedFlush = <Args extends unknown[]>(\n flush: (...args: Args) => Promise<void>\n): ((...args: Args) => Promise<void>) => {\n let pending: Promise<void> = Promise.resolve();\n return (...args: Args) => {\n pending = pending.then(\n () => flush(...args),\n () => flush(...args)\n );\n return pending;\n };\n};\n\nexport const guardFlush = <Args extends unknown[]>(\n prefix: string,\n flush: (...args: Args) => Promise<void>,\n state?: SignalState\n): ((...args: Args) => Promise<void>) => {\n return async (...args: Args) => {\n if (state && Date.now() < state.disabledUntil) {\n return;\n }\n try {\n await flush(...args);\n } catch (error) {\n if (state) {\n state.disabledUntil = Date.now() + COOLDOWN_MS;\n }\n console.error(`${prefix} flush failed; cooling down 60s`, error);\n }\n };\n};\n\nexport const fetchTransport = async (input: {\n readonly body: string | Uint8Array;\n readonly headers: Record<string, string>;\n readonly keepalive?: boolean | undefined;\n readonly url: string;\n}): Promise<void> => {\n const body: BodyInit =\n typeof input.body === \"string\" ? input.body : new Uint8Array(input.body);\n const response = await fetch(input.url, {\n body,\n headers: input.headers,\n keepalive: input.keepalive,\n method: \"POST\",\n });\n if (!response.ok) {\n throw new Error(`POST ${input.url} → ${response.status}`);\n }\n};\n","import type { BatchSpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport { Layer } from \"effect\";\n\nimport { flushAnalytics, track } from \"./analytics.js\";\nimport type { MxlBrowserConfig } from \"./layer.js\";\nimport { layer as browserLayer } from \"./layer.js\";\nimport { stopReplay } from \"./replay.js\";\nimport { guardFlush, makeSerializedFlush } from \"../shared/flush-core.js\";\n\nexport interface MxlBrowserFlushableConfig extends MxlBrowserConfig {\n readonly autoFlushInterval?: number | false | undefined;\n readonly captureGlobalErrors?: boolean | undefined;\n readonly flushOnUnload?: boolean | undefined;\n}\n\nexport interface FlushableTelemetry {\n readonly captureException: (\n error: unknown,\n options?: { readonly attributes?: Record<string, string>; readonly name?: string }\n ) => void;\n readonly dispose: () => Promise<void>;\n readonly flush: () => Promise<void>;\n readonly layer: Layer.Layer<never>;\n}\n\nconst processors = new WeakMap<FlushableTelemetry, BatchSpanProcessor>();\nconst DEFAULT_AUTO_FLUSH_MS = 5_000;\n\nexport const attachProcessor = (\n telemetry: FlushableTelemetry,\n processor: BatchSpanProcessor\n): void => {\n processors.set(telemetry, processor);\n};\n\nexport const make = (\n config: MxlBrowserFlushableConfig = {}\n): FlushableTelemetry => {\n const layer = browserLayer(config);\n const state = { disabledUntil: 0 };\n const listeners: Array<() => void> = [];\n\n const telemetry: FlushableTelemetry = {\n captureException: (error, options) => {\n track(options?.name ?? \"uncaught_error\", {\n ...options?.attributes,\n message: error instanceof Error ? error.message : String(error),\n });\n },\n dispose: async () => {\n for (const stop of listeners) {\n stop();\n }\n listeners.length = 0;\n stopReplay();\n await telemetry.flush();\n },\n flush: makeSerializedFlush(\n guardFlush(\n \"[MxlFlush]\",\n async () => {\n flushAnalytics(true);\n await processors.get(telemetry)?.forceFlush();\n },\n state\n )\n ),\n layer,\n };\n\n const intervalMs =\n config.autoFlushInterval === undefined\n ? DEFAULT_AUTO_FLUSH_MS\n : config.autoFlushInterval === false\n ? 0\n : config.autoFlushInterval;\n if (intervalMs > 0) {\n const timer = setInterval(() => {\n void telemetry.flush();\n }, intervalMs);\n timer.unref?.();\n listeners.push(() => {\n clearInterval(timer);\n });\n }\n\n const documentRef = (\n globalThis as {\n document?: {\n addEventListener: (type: string, listener: () => void) => void;\n removeEventListener: (type: string, listener: () => void) => void;\n visibilityState?: string;\n };\n }\n ).document;\n\n if (config.flushOnUnload !== false && documentRef) {\n const onHidden = () => {\n void telemetry.flush();\n };\n const onVisibilityChange = () => {\n if (documentRef.visibilityState === \"hidden\") {\n onHidden();\n }\n };\n documentRef.addEventListener(\"visibilitychange\", onVisibilityChange);\n globalThis.addEventListener?.(\"pagehide\", onHidden);\n listeners.push(() => {\n documentRef.removeEventListener(\"visibilitychange\", onVisibilityChange);\n globalThis.removeEventListener?.(\"pagehide\", onHidden);\n });\n }\n\n if (config.captureGlobalErrors !== false && typeof globalThis.addEventListener === \"function\") {\n const onError = (event: Event) => {\n const record = event as { error?: unknown; reason?: unknown };\n telemetry.captureException(record.error ?? record.reason ?? event);\n void telemetry.flush();\n };\n globalThis.addEventListener(\"error\", onError);\n globalThis.addEventListener(\"unhandledrejection\", onError);\n listeners.push(() => {\n globalThis.removeEventListener(\"error\", onError);\n globalThis.removeEventListener(\"unhandledrejection\", onError);\n });\n }\n\n return telemetry;\n};\n","import {\n context as otelContext,\n propagation,\n SpanKind,\n SpanStatusCode,\n trace as traceApi,\n} from \"@opentelemetry/api\";\nimport { Effect } from \"effect\";\n\nexport type DbSystem = \"clickhouse\" | \"postgresql\" | \"redis\";\n\nexport type DependencySpanKind = \"client\" | \"producer\" | \"consumer\";\n\nconst MAX_FLUSH_SPAN_LINKS = 32;\n\nexport interface QueueTraceContextCarrier {\n readonly traceparent?: string | undefined;\n readonly tracestate?: string | undefined;\n readonly baggage?: string | undefined;\n}\n\nexport function dbClientSpanAttributes(\n dbSystem: DbSystem,\n operation?: string\n): Record<string, string> {\n return {\n \"db.system\": dbSystem,\n \"db.system.name\": dbSystem,\n \"peer.service\": dbSystem,\n ...(operation ? { \"db.operation.name\": operation } : {}),\n };\n}\n\nexport function queueProducerSpanAttributes(\n destinationName: string,\n peerService = \"mxl-worker\"\n): Record<string, string> {\n return {\n \"messaging.destination.name\": destinationName,\n \"messaging.system\": \"bullmq\",\n \"peer.service\": peerService,\n };\n}\n\nexport function queueConsumerSpanAttributes(\n destinationName: string\n): Record<string, string> {\n return {\n \"messaging.destination.name\": destinationName,\n \"messaging.system\": \"bullmq\",\n };\n}\n\nexport function kafkaProducerSpanAttributes(\n topic: string,\n peerService = \"kafka\"\n): Record<string, string> {\n return {\n \"messaging.destination.name\": topic,\n \"messaging.system\": \"kafka\",\n \"peer.service\": peerService,\n };\n}\n\nexport function bufferSpanAttributes(\n bufferName: string,\n operation: string\n): Record<string, string> {\n return {\n \"buffer.name\": bufferName,\n \"db.operation.name\": operation,\n \"db.system\": \"redis\",\n \"db.system.name\": \"redis\",\n \"peer.service\": \"redis\",\n };\n}\n\nexport function geoSpanAttributes(): Record<string, string> {\n return {\n \"db.operation.name\": \"resolve\",\n \"db.system\": \"maxmind\",\n \"db.system.name\": \"maxmind\",\n \"peer.service\": \"maxmind\",\n };\n}\n\nexport function addSpanLinksFromTraceCarriers(\n carriers: readonly QueueTraceContextCarrier[]\n): number {\n const span = traceApi.getActiveSpan();\n if (!span) {\n return 0;\n }\n\n const seen = new Set<string>();\n let linked = 0;\n\n for (const carrier of carriers) {\n if (!carrier.traceparent || seen.has(carrier.traceparent)) {\n continue;\n }\n seen.add(carrier.traceparent);\n\n const extracted = propagation.extract(otelContext.active(), carrier);\n const linkedContext = traceApi.getSpanContext(extracted);\n if (!linkedContext) {\n continue;\n }\n\n span.addLink({ context: linkedContext });\n linked += 1;\n\n if (linked >= MAX_FLUSH_SPAN_LINKS) {\n break;\n }\n }\n\n return linked;\n}\n\nexport interface WithDependencySpanOptions {\n readonly attributes?: Record<string, string> | undefined;\n readonly kind?: DependencySpanKind | undefined;\n}\n\nexport function withDependencySpan<A, E, R>(\n name: string,\n attributes: Record<string, string>,\n program: Effect.Effect<A, E, R>\n): Effect.Effect<A, E, R>;\nexport function withDependencySpan<A, E, R>(\n name: string,\n options: WithDependencySpanOptions,\n program: Effect.Effect<A, E, R>\n): Effect.Effect<A, E, R>;\nexport function withDependencySpan<A, E, R>(\n name: string,\n attributesOrOptions: Record<string, string> | WithDependencySpanOptions,\n program: Effect.Effect<A, E, R>\n): Effect.Effect<A, E, R> {\n const options = isWithDependencySpanOptions(attributesOrOptions)\n ? attributesOrOptions\n : { attributes: attributesOrOptions };\n\n const attributes = options.attributes ?? {};\n const kind: DependencySpanKind = options.kind ?? \"client\";\n\n return program.pipe(Effect.withSpan(name, { attributes, kind }));\n}\n\nfunction isWithDependencySpanOptions(\n value: Record<string, string> | WithDependencySpanOptions\n): value is WithDependencySpanOptions {\n return (\n typeof value === \"object\" &&\n (\"kind\" in value ||\n (\"attributes\" in value && typeof value.attributes === \"object\"))\n );\n}\n\n/** Promise-based dependency span for non-Effect code paths. */\nexport async function withPromiseDependencySpan<T>(\n name: string,\n attributes: Record<string, string>,\n operation: () => Promise<T>,\n kind: DependencySpanKind = \"client\"\n): Promise<T> {\n const tracer = traceApi.getTracer(\"mxl-effect-sdk\");\n const spanKind =\n kind === \"producer\"\n ? SpanKind.PRODUCER\n : kind === \"consumer\"\n ? SpanKind.CONSUMER\n : SpanKind.CLIENT;\n return tracer.startActiveSpan(\n name,\n { attributes, kind: spanKind },\n async (span) => {\n try {\n const result = await operation();\n span.end();\n return result;\n } catch (error) {\n if (error instanceof Error) {\n span.recordException(error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message,\n });\n } else {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: String(error),\n });\n }\n span.end();\n throw error;\n }\n }\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["traceApi","otelContext"],"sources":["../../src/shared/dependency-spans.ts"],"sourcesContent":["import {\n context as otelContext,\n propagation,\n SpanKind,\n SpanStatusCode,\n trace as traceApi,\n} from '@opentelemetry/api';\nimport { Effect } from 'effect';\n\nexport type DbSystem = 'clickhouse' | 'postgresql' | 'redis';\n\nexport type DependencySpanKind = 'client' | 'producer' | 'consumer';\n\nconst MAX_FLUSH_SPAN_LINKS = 32;\n\nexport interface QueueTraceContextCarrier {\n readonly traceparent?: string | undefined;\n readonly tracestate?: string | undefined;\n readonly baggage?: string | undefined;\n}\n\nexport function dbClientSpanAttributes(\n dbSystem: DbSystem,\n operation?: string,\n): Record<string, string> {\n return {\n 'db.system': dbSystem,\n 'db.system.name': dbSystem,\n 'peer.service': dbSystem,\n ...(operation ? { 'db.operation.name': operation } : {}),\n };\n}\n\nexport function queueProducerSpanAttributes(\n destinationName: string,\n peerService = 'mxl-worker',\n): Record<string, string> {\n return {\n 'messaging.destination.name': destinationName,\n 'messaging.system': 'bullmq',\n 'peer.service': peerService,\n };\n}\n\nexport function queueConsumerSpanAttributes(\n destinationName: string,\n): Record<string, string> {\n return {\n 'messaging.destination.name': destinationName,\n 'messaging.system': 'bullmq',\n };\n}\n\nexport function kafkaProducerSpanAttributes(\n topic: string,\n peerService = 'kafka',\n): Record<string, string> {\n return {\n 'messaging.destination.name': topic,\n 'messaging.system': 'kafka',\n 'peer.service': peerService,\n };\n}\n\nexport function bufferSpanAttributes(\n bufferName: string,\n operation: string,\n): Record<string, string> {\n return {\n 'buffer.name': bufferName,\n 'db.operation.name': operation,\n 'db.system': 'redis',\n 'db.system.name': 'redis',\n 'peer.service': 'redis',\n };\n}\n\nexport function geoSpanAttributes(): Record<string, string> {\n return {\n 'db.operation.name': 'resolve',\n 'db.system': 'maxmind',\n 'db.system.name': 'maxmind',\n 'peer.service': 'maxmind',\n };\n}\n\nexport function addSpanLinksFromTraceCarriers(\n carriers: readonly QueueTraceContextCarrier[],\n): number {\n const span = traceApi.getActiveSpan();\n if (!span) {\n return 0;\n }\n\n const seen = new Set<string>();\n let linked = 0;\n\n for (const carrier of carriers) {\n if (!carrier.traceparent || seen.has(carrier.traceparent)) {\n continue;\n }\n seen.add(carrier.traceparent);\n\n const extracted = propagation.extract(otelContext.active(), carrier);\n const linkedContext = traceApi.getSpanContext(extracted);\n if (!linkedContext) {\n continue;\n }\n\n span.addLink({ context: linkedContext });\n linked += 1;\n\n if (linked >= MAX_FLUSH_SPAN_LINKS) {\n break;\n }\n }\n\n return linked;\n}\n\nexport interface WithDependencySpanOptions {\n readonly attributes?: Record<string, string> | undefined;\n readonly kind?: DependencySpanKind | undefined;\n}\n\nexport function withDependencySpan<A, E, R>(\n name: string,\n attributes: Record<string, string>,\n program: Effect.Effect<A, E, R>,\n): Effect.Effect<A, E, R>;\nexport function withDependencySpan<A, E, R>(\n name: string,\n options: WithDependencySpanOptions,\n program: Effect.Effect<A, E, R>,\n): Effect.Effect<A, E, R>;\nexport function withDependencySpan<A, E, R>(\n name: string,\n attributesOrOptions: Record<string, string> | WithDependencySpanOptions,\n program: Effect.Effect<A, E, R>,\n): Effect.Effect<A, E, R> {\n const options = isWithDependencySpanOptions(attributesOrOptions)\n ? attributesOrOptions\n : { attributes: attributesOrOptions };\n\n const attributes = options.attributes ?? {};\n const kind: DependencySpanKind = options.kind ?? 'client';\n\n return program.pipe(Effect.withSpan(name, { attributes, kind }));\n}\n\nfunction isWithDependencySpanOptions(\n value: Record<string, string> | WithDependencySpanOptions,\n): value is WithDependencySpanOptions {\n return (\n typeof value === 'object' &&\n ('kind' in value ||\n ('attributes' in value && typeof value.attributes === 'object'))\n );\n}\n\n/** Promise-based dependency span for non-Effect code paths. */\nexport async function withPromiseDependencySpan<T>(\n name: string,\n attributes: Record<string, string>,\n operation: () => Promise<T>,\n kind: DependencySpanKind = 'client',\n): Promise<T> {\n const tracer = traceApi.getTracer('mxl-effect-sdk');\n const spanKind =\n kind === 'producer'\n ? SpanKind.PRODUCER\n : kind === 'consumer'\n ? SpanKind.CONSUMER\n : SpanKind.CLIENT;\n return tracer.startActiveSpan(\n name,\n { attributes, kind: spanKind },\n async (span) => {\n try {\n const result = await operation();\n span.end();\n return result;\n } catch (error) {\n if (error instanceof Error) {\n span.recordException(error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message,\n });\n } else {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: String(error),\n });\n }\n span.end();\n throw error;\n }\n },\n );\n}\n"],"mappings":";;;;AAaA,MAAM,uBAAuB;AAQ7B,SAAgB,uBACd,UACA,WACwB;CACxB,OAAO;EACL,aAAa;EACb,kBAAkB;EAClB,gBAAgB;EAChB,GAAI,YAAY,EAAE,qBAAqB,UAAU,IAAI,CAAC;CACxD;AACF;AAEA,SAAgB,4BACd,iBACA,cAAc,cACU;CACxB,OAAO;EACL,8BAA8B;EAC9B,oBAAoB;EACpB,gBAAgB;CAClB;AACF;AAEA,SAAgB,4BACd,iBACwB;CACxB,OAAO;EACL,8BAA8B;EAC9B,oBAAoB;CACtB;AACF;AAEA,SAAgB,4BACd,OACA,cAAc,SACU;CACxB,OAAO;EACL,8BAA8B;EAC9B,oBAAoB;EACpB,gBAAgB;CAClB;AACF;AAEA,SAAgB,qBACd,YACA,WACwB;CACxB,OAAO;EACL,eAAe;EACf,qBAAqB;EACrB,aAAa;EACb,kBAAkB;EAClB,gBAAgB;CAClB;AACF;AAEA,SAAgB,oBAA4C;CAC1D,OAAO;EACL,qBAAqB;EACrB,aAAa;EACb,kBAAkB;EAClB,gBAAgB;CAClB;AACF;AAEA,SAAgB,8BACd,UACQ;CACR,MAAM,OAAOA,MAAS,cAAc;CACpC,IAAI,CAAC,MACH,OAAO;CAGT,MAAM,uBAAO,IAAI,IAAY;CAC7B,IAAI,SAAS;CAEb,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,CAAC,QAAQ,eAAe,KAAK,IAAI,QAAQ,WAAW,GACtD;EAEF,KAAK,IAAI,QAAQ,WAAW;EAE5B,MAAM,YAAY,YAAY,QAAQC,QAAY,OAAO,GAAG,OAAO;EACnE,MAAM,gBAAgBD,MAAS,eAAe,SAAS;EACvD,IAAI,CAAC,eACH;EAGF,KAAK,QAAQ,EAAE,SAAS,cAAc,CAAC;EACvC,UAAU;EAEV,IAAI,UAAU,sBACZ;CAEJ;CAEA,OAAO;AACT;AAiBA,SAAgB,mBACd,MACA,qBACA,SACwB;CACxB,MAAM,UAAU,4BAA4B,mBAAmB,IAC3D,sBACA,EAAE,YAAY,oBAAoB;CAEtC,MAAM,aAAa,QAAQ,cAAc,CAAC;CAC1C,MAAM,OAA2B,QAAQ,QAAQ;CAEjD,OAAO,QAAQ,KAAK,OAAO,SAAS,MAAM;EAAE;EAAY;CAAK,CAAC,CAAC;AACjE;AAEA,SAAS,4BACP,OACoC;CACpC,OACE,OAAO,UAAU,aAChB,UAAU,SACR,gBAAgB,SAAS,OAAO,MAAM,eAAe;AAE5D;;AAGA,eAAsB,0BACpB,MACA,YACA,WACA,OAA2B,UACf;CACZ,MAAM,SAASA,MAAS,UAAU,gBAAgB;CAClD,MAAM,WACJ,SAAS,aACL,SAAS,WACT,SAAS,aACP,SAAS,WACT,SAAS;CACjB,OAAO,OAAO,gBACZ,MACA;EAAE;EAAY,MAAM;CAAS,GAC7B,OAAO,SAAS;EACd,IAAI;GACF,MAAM,SAAS,MAAM,UAAU;GAC/B,KAAK,IAAI;GACT,OAAO;EACT,SAAS,OAAO;GACd,IAAI,iBAAiB,OAAO;IAC1B,KAAK,gBAAgB,KAAK;IAC1B,KAAK,UAAU;KACb,MAAM,eAAe;KACrB,SAAS,MAAM;IACjB,CAAC;GACH,OACE,KAAK,UAAU;IACb,MAAM,eAAe;IACrB,SAAS,OAAO,KAAK;GACvB,CAAC;GAEH,KAAK,IAAI;GACT,MAAM;EACR;CACF,CACF;AACF"}
@@ -1,5 +1,4 @@
1
1
  import { Context, Layer } from "effect";
2
- //#region src/cloudflare/index.d.ts
3
2
  export interface Config {
4
3
  readonly apiKey?: string | undefined;
5
4
  readonly attributes?: Record<string, unknown> | undefined;
@@ -21,5 +20,5 @@ export declare const make: (config?: Config) => Telemetry;
21
20
  export declare const MxlCloudflareSDK: {
22
21
  make: typeof make;
23
22
  };
24
- //#endregion
23
+ export {};
25
24
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/cloudflare/index.ts"],"mappings":";;iBAWiB;WACN;WACA,aAAa;WACb;WACA;WACA;WACA;WACA;WACA;;;qBAGE,0BAA0B;iBAKtB;WACN,QAAQ,KAAK,4BAA4B;WACzC,OAAO,MAAM;WACb,cAAc,MAAM,oBAAoB;;qBAwBtC,OAAI,SAAY,WAAc;qBA4D9B;eAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/cloudflare/index.ts"],"mappings":";iBAMiB;WACN;WACA,aAAa;WACb;WACA;WACA;WACA;WACA;WACA;;;qBAGE,0BAA0B;iBAKtB;WACN,QAAQ,KAAK,4BAA4B;WACzC,OAAO,MAAM;WACb,cAAc,MAAM,oBAAoB;;qBAwBtC,OAAI,SAAY,WAAc;qBA4D9B"}