@od-oneapp/analytics 2026.2.1701 → 2026.2.2001-canary.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 (82) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/{ai-YMnynb-t.mjs → ai-BSaTnqoc.mjs} +2 -2
  3. package/{ai-YMnynb-t.mjs.map → ai-BSaTnqoc.mjs.map} +1 -1
  4. package/{client-D339NFJS.mjs → client-B0v807xN.mjs} +1 -1
  5. package/{client-D339NFJS.mjs.map → client-B0v807xN.mjs.map} +1 -1
  6. package/{client-CcFTauAh.mjs → client-BrLI6DF9.mjs} +1 -1
  7. package/{client-CcFTauAh.mjs.map → client-BrLI6DF9.mjs.map} +1 -1
  8. package/{client-CeOLjbac.mjs → client-ClA-Qeks.mjs} +21 -6
  9. package/client-ClA-Qeks.mjs.map +1 -0
  10. package/client-next.d.mts +7 -7
  11. package/client-next.d.mts.map +1 -1
  12. package/client-next.mjs +1616 -31
  13. package/client-next.mjs.map +1 -1
  14. package/{client-CTzJVFU5.mjs → client-uX7stpb0.mjs} +2 -2
  15. package/{client-CTzJVFU5.mjs.map → client-uX7stpb0.mjs.map} +1 -1
  16. package/client.d.mts +9 -9
  17. package/client.mjs +8 -116
  18. package/client.mjs.map +1 -1
  19. package/{config-DPS6bSYo.d.mts → config-BA1zbRTh.d.mts} +2 -2
  20. package/{config-DPS6bSYo.d.mts.map → config-BA1zbRTh.d.mts.map} +1 -1
  21. package/{config-P6P5adJg.mjs → config-DKN8G0TI.mjs} +1 -1
  22. package/{config-P6P5adJg.mjs.map → config-DKN8G0TI.mjs.map} +1 -1
  23. package/{console-8bND3mMU.mjs → console-CDP0gY0K.mjs} +2 -2
  24. package/console-CDP0gY0K.mjs.map +1 -0
  25. package/core-C3oFD9Vr.mjs +95 -0
  26. package/core-C3oFD9Vr.mjs.map +1 -0
  27. package/{ecommerce-Cgu4wlux.mjs → ecommerce-DAo98u3I.mjs} +2 -2
  28. package/{ecommerce-Cgu4wlux.mjs.map → ecommerce-DAo98u3I.mjs.map} +1 -1
  29. package/{emitters-DldkVSPp.d.mts → emitters-B2gDJtjd.d.mts} +2 -2
  30. package/{emitters-DldkVSPp.d.mts.map → emitters-B2gDJtjd.d.mts.map} +1 -1
  31. package/{emitters-6-nKo8i-.mjs → emitters-RheHbeX2.mjs} +1 -1
  32. package/{emitters-6-nKo8i-.mjs.map → emitters-RheHbeX2.mjs.map} +1 -1
  33. package/{index-jPzXRn52.d.mts → index-Bq051UsT.d.mts} +3 -3
  34. package/{index-jPzXRn52.d.mts.map → index-Bq051UsT.d.mts.map} +1 -1
  35. package/{index-BfNWgfa5.d.mts → index-CuDXrdLY.d.mts} +14 -2
  36. package/{index-BfNWgfa5.d.mts.map → index-CuDXrdLY.d.mts.map} +1 -1
  37. package/{index-BkIWe--N.d.mts → index-uQHwlV0P.d.mts} +2 -2
  38. package/{index-BkIWe--N.d.mts.map → index-uQHwlV0P.d.mts.map} +1 -1
  39. package/logs-8LsA--uG.mjs +112 -0
  40. package/logs-8LsA--uG.mjs.map +1 -0
  41. package/{manager-DvRRjza6.d.mts → manager-Dh9loalA.d.mts} +3 -2
  42. package/manager-Dh9loalA.d.mts.map +1 -0
  43. package/module-D5xR4CAy.mjs +5808 -0
  44. package/module-D5xR4CAy.mjs.map +1 -0
  45. package/package.json +21 -27
  46. package/{posthog-bootstrap-DWxFrxlt.d.mts → posthog-bootstrap-D7Ot--Qc.d.mts} +3 -3
  47. package/{posthog-bootstrap-DWxFrxlt.d.mts.map → posthog-bootstrap-D7Ot--Qc.d.mts.map} +1 -1
  48. package/{posthog-bootstrap-CYfIy_WS.mjs → posthog-bootstrap-DkDBbvMJ.mjs} +81 -46
  49. package/posthog-bootstrap-DkDBbvMJ.mjs.map +1 -0
  50. package/providers-http-client.d.mts +1 -1
  51. package/providers-http-client.d.mts.map +1 -1
  52. package/providers-http-client.mjs +35 -7
  53. package/providers-http-client.mjs.map +1 -1
  54. package/providers-http-server.d.mts +1 -1
  55. package/providers-http-server.d.mts.map +1 -1
  56. package/providers-http-server.mjs +19 -3
  57. package/providers-http-server.mjs.map +1 -1
  58. package/providers-http.d.mts +9 -1
  59. package/providers-http.d.mts.map +1 -1
  60. package/server-edge.d.mts +3 -3
  61. package/server-edge.mjs +4 -4
  62. package/server-edge.mjs.map +1 -1
  63. package/server-next.d.mts +9 -9
  64. package/server-next.mjs +5 -5
  65. package/server.d.mts +9 -9
  66. package/server.mjs +5 -5
  67. package/{service-Duqnlppl.mjs → service-tn0JFfVH.mjs} +49 -116
  68. package/service-tn0JFfVH.mjs.map +1 -0
  69. package/shared.d.mts +4 -4
  70. package/shared.mjs +3 -3
  71. package/{types-BxBnNQ0V.d.mts → types-BbXOa_UL.d.mts} +1 -1
  72. package/{types-BxBnNQ0V.d.mts.map → types-BbXOa_UL.d.mts.map} +1 -1
  73. package/{types-CBvxUEaF.d.mts → types-DC5uYgdR.d.mts} +1 -1
  74. package/{types-CBvxUEaF.d.mts.map → types-DC5uYgdR.d.mts.map} +1 -1
  75. package/types.d.mts +3 -3
  76. package/{vercel-types-lwakUfoI.d.mts → vercel-types-tUdlBxJ-.d.mts} +1 -1
  77. package/{vercel-types-lwakUfoI.d.mts.map → vercel-types-tUdlBxJ-.d.mts.map} +1 -1
  78. package/client-CeOLjbac.mjs.map +0 -1
  79. package/console-8bND3mMU.mjs.map +0 -1
  80. package/manager-DvRRjza6.d.mts.map +0 -1
  81. package/posthog-bootstrap-CYfIy_WS.mjs.map +0 -1
  82. package/service-Duqnlppl.mjs.map +0 -1
package/client-next.mjs CHANGED
@@ -1,12 +1,15 @@
1
1
  'use client';
2
2
 
3
- import { a as ContextBuilder, c as createAnonymousSession, d as isGroupPayload, f as isIdentifyPayload, g as withUTM, h as withMetadata, i as getDistinctIdFromCookies, l as createUserSession, m as isTrackPayload, n as createMinimalBootstrapData, o as EventBatch, p as isPagePayload, r as generateDistinctId, s as PayloadBuilder, t as createBootstrapData, u as isAliasPayload, v as createAnalyticsManager } from "./posthog-bootstrap-CYfIy_WS.mjs";
4
- import { a as screen, i as page, n as group, o as track, r as identify, t as alias } from "./emitters-6-nKo8i-.mjs";
5
- import { t as ecommerce_exports } from "./ecommerce-Cgu4wlux.mjs";
6
- import { a as createEmitterProcessor, i as validateConfig, n as createConfigBuilder, o as processEmitterPayload, r as getAnalyticsConfig, s as trackEcommerceEvent, t as PROVIDER_REQUIREMENTS } from "./config-P6P5adJg.mjs";
3
+ import { i as logWarn, n as logError, t as logDebug } from "./core-C3oFD9Vr.mjs";
4
+ import { i as logWarn$1, n as logError$1 } from "./logs-8LsA--uG.mjs";
5
+ import { a as ContextBuilder, c as createAnonymousSession, d as isGroupPayload, f as isIdentifyPayload, g as withUTM, h as withMetadata, i as getDistinctIdFromCookies, l as createUserSession, m as isTrackPayload, n as createMinimalBootstrapData, o as EventBatch, p as isPagePayload, r as generateDistinctId, s as PayloadBuilder, t as createBootstrapData, u as isAliasPayload, v as createAnalyticsManager } from "./posthog-bootstrap-DkDBbvMJ.mjs";
6
+ import { a as screen, i as page, n as group, o as track, r as identify, t as alias } from "./emitters-RheHbeX2.mjs";
7
+ import { t as ecommerce_exports } from "./ecommerce-DAo98u3I.mjs";
8
+ import { a as createEmitterProcessor, i as validateConfig, n as createConfigBuilder, o as processEmitterPayload, r as getAnalyticsConfig, s as trackEcommerceEvent, t as PROVIDER_REQUIREMENTS } from "./config-DKN8G0TI.mjs";
7
9
  import { useCallback, useEffect, useRef, useState } from "react";
8
10
  import { useParams, usePathname, useRouter, useSearchParams } from "next/navigation";
9
- import { createClientObservability } from "@od-oneapp/observability/client/next";
11
+ import { createEnv } from "@t3-oss/env-core";
12
+ import { z } from "zod/v4";
10
13
  import { jsx } from "react/jsx-runtime";
11
14
 
12
15
  //#region src/client/next/manager.ts
@@ -25,7 +28,6 @@ import { jsx } from "react/jsx-runtime";
25
28
  *
26
29
  * @module @od-oneapp/analytics/client/next/manager
27
30
  */
28
- const logWarn = (_message, _context) => {};
29
31
  /**
30
32
  * Create a Next.js client analytics instance with runtime provider loading
31
33
  * Only imports providers that are actually configured to avoid webpack bundling unused dependencies
@@ -45,7 +47,7 @@ async function createNextJSClientAnalytics(config) {
45
47
  case "console": {
46
48
  const module = await import(
47
49
  /* webpackChunkName: "analytics-console" */
48
- "./client-CTzJVFU5.mjs"
50
+ "./client-uX7stpb0.mjs"
49
51
  ).then((n) => n.t);
50
52
  CLIENT_PROVIDERS.console = (config) => new module.ConsoleProvider(config);
51
53
  break;
@@ -53,7 +55,7 @@ async function createNextJSClientAnalytics(config) {
53
55
  case "posthog": {
54
56
  const module = await import(
55
57
  /* webpackChunkName: "analytics-posthog" */
56
- "./client-CeOLjbac.mjs"
58
+ "./client-ClA-Qeks.mjs"
57
59
  );
58
60
  CLIENT_PROVIDERS.posthog = (config) => new module.PostHogClientProvider(config);
59
61
  break;
@@ -61,15 +63,23 @@ async function createNextJSClientAnalytics(config) {
61
63
  case "segment": {
62
64
  const module = await import(
63
65
  /* webpackChunkName: "analytics-segment" */
64
- "./client-D339NFJS.mjs"
66
+ "./client-B0v807xN.mjs"
65
67
  );
66
68
  CLIENT_PROVIDERS.segment = (config) => new module.SegmentClientProvider(config);
67
69
  break;
68
70
  }
71
+ case "http": {
72
+ const module = await import(
73
+ /* webpackChunkName: "analytics-http" */
74
+ "./providers-http-client.mjs"
75
+ );
76
+ CLIENT_PROVIDERS.http = (config) => new module.HttpClientProvider(config);
77
+ break;
78
+ }
69
79
  case "vercel": {
70
80
  const module = await import(
71
81
  /* webpackChunkName: "analytics-vercel" */
72
- "./client-CcFTauAh.mjs"
82
+ "./client-BrLI6DF9.mjs"
73
83
  );
74
84
  CLIENT_PROVIDERS.vercel = (config) => new module.VercelClientProvider(config);
75
85
  break;
@@ -99,22 +109,27 @@ async function createNextJSClientAnalyticsUninitialized(config) {
99
109
  const configuredProviders = Object.keys(config.providers);
100
110
  for (const providerName of configuredProviders) switch (providerName) {
101
111
  case "console": {
102
- const { ConsoleProvider } = await import("./client-CTzJVFU5.mjs").then((n) => n.t);
112
+ const { ConsoleProvider } = await import("./client-uX7stpb0.mjs").then((n) => n.t);
103
113
  CLIENT_PROVIDERS.console = (config) => new ConsoleProvider(config);
104
114
  break;
105
115
  }
106
116
  case "posthog": {
107
- const { PostHogClientProvider } = await import("./client-CeOLjbac.mjs");
117
+ const { PostHogClientProvider } = await import("./client-ClA-Qeks.mjs");
108
118
  CLIENT_PROVIDERS.posthog = (config) => new PostHogClientProvider(config);
109
119
  break;
110
120
  }
111
121
  case "segment": {
112
- const { SegmentClientProvider } = await import("./client-D339NFJS.mjs");
122
+ const { SegmentClientProvider } = await import("./client-B0v807xN.mjs");
113
123
  CLIENT_PROVIDERS.segment = (config) => new SegmentClientProvider(config);
114
124
  break;
115
125
  }
126
+ case "http": {
127
+ const { HttpClientProvider } = await import("./providers-http-client.mjs");
128
+ CLIENT_PROVIDERS.http = (config) => new HttpClientProvider(config);
129
+ break;
130
+ }
116
131
  case "vercel": {
117
- const { VercelClientProvider } = await import("./client-CcFTauAh.mjs");
132
+ const { VercelClientProvider } = await import("./client-BrLI6DF9.mjs");
118
133
  CLIENT_PROVIDERS.vercel = (config) => new VercelClientProvider(config);
119
134
  break;
120
135
  }
@@ -125,6 +140,16 @@ async function createNextJSClientAnalyticsUninitialized(config) {
125
140
  return createAnalyticsManager(config, CLIENT_PROVIDERS);
126
141
  }
127
142
 
143
+ //#endregion
144
+ //#region src/client/next/state.ts
145
+ let globalAnalytics = null;
146
+ function getGlobalAnalytics() {
147
+ return globalAnalytics;
148
+ }
149
+ function setGlobalAnalytics(analytics) {
150
+ globalAnalytics = analytics;
151
+ }
152
+
128
153
  //#endregion
129
154
  //#region src/client/next/hooks.ts
130
155
  /**
@@ -146,7 +171,6 @@ async function createNextJSClientAnalyticsUninitialized(config) {
146
171
  *
147
172
  * @module @od-oneapp/analytics/client/next/hooks
148
173
  */
149
- let globalAnalytics$1 = null;
150
174
  /**
151
175
  * Hook to get or create analytics instance.
152
176
  *
@@ -170,19 +194,25 @@ let globalAnalytics$1 = null;
170
194
  function useAnalytics(config) {
171
195
  const [analytics, setAnalytics] = useState(null);
172
196
  useEffect(() => {
173
- if (!globalAnalytics$1 && config) {
197
+ const currentAnalytics = getGlobalAnalytics();
198
+ if (!currentAnalytics && config) {
174
199
  const initAnalytics = async () => {
175
200
  try {
176
201
  const instance = await createNextJSClientAnalytics(config);
177
- globalAnalytics$1 = instance;
202
+ setGlobalAnalytics(instance);
178
203
  setAnalytics(instance);
179
- } catch {}
204
+ } catch (error) {
205
+ logError("Failed to initialize Next.js analytics client", {
206
+ error: error instanceof Error ? error.message : String(error),
207
+ configProviders: Object.keys(config.providers)
208
+ });
209
+ }
180
210
  };
181
211
  initAnalytics();
182
- } else if (globalAnalytics$1 && analytics !== globalAnalytics$1) setTimeout(() => {
183
- setAnalytics(globalAnalytics$1);
212
+ } else if (currentAnalytics && analytics !== currentAnalytics) setTimeout(() => {
213
+ setAnalytics(currentAnalytics);
184
214
  }, 0);
185
- }, [config]);
215
+ }, [analytics, config]);
186
216
  return analytics;
187
217
  }
188
218
  /**
@@ -209,7 +239,8 @@ function usePageTracking(options) {
209
239
  const params = useParams();
210
240
  const tracked = useRef("");
211
241
  useEffect(() => {
212
- if (options?.skip || !globalAnalytics$1) return;
242
+ const analyticsInstance = getGlobalAnalytics();
243
+ if (options?.skip || !analyticsInstance) return;
213
244
  const searchString = options?.trackSearch ? searchParams.toString() : "";
214
245
  const pageKey = `${pathname}${searchString}`;
215
246
  if (tracked.current === pageKey) return;
@@ -226,7 +257,7 @@ function usePageTracking(options) {
226
257
  properties.search_params = Object.fromEntries(searchParams.entries());
227
258
  }
228
259
  if (options?.trackParams && params) properties.route_params = params;
229
- globalAnalytics$1.page(pathname, properties);
260
+ analyticsInstance.page(pathname, properties);
230
261
  }, [
231
262
  pathname,
232
263
  searchParams,
@@ -254,8 +285,9 @@ function usePageTracking(options) {
254
285
  */
255
286
  function useTrackEvent() {
256
287
  return useCallback((event, properties, options) => {
257
- if (!globalAnalytics$1) return;
258
- globalAnalytics$1.track(event, properties, options);
288
+ const analyticsInstance = getGlobalAnalytics();
289
+ if (!analyticsInstance) return;
290
+ analyticsInstance.track(event, properties, options);
259
291
  }, []);
260
292
  }
261
293
  /**
@@ -275,8 +307,9 @@ function useTrackEvent() {
275
307
  */
276
308
  function useIdentifyUser() {
277
309
  return useCallback((userId, traits, options) => {
278
- if (!globalAnalytics$1) return;
279
- globalAnalytics$1.identify(userId, traits, options);
310
+ const analyticsInstance = getGlobalAnalytics();
311
+ if (!analyticsInstance) return;
312
+ analyticsInstance.identify(userId, traits, options);
280
313
  }, []);
281
314
  }
282
315
  /**
@@ -294,7 +327,1560 @@ function useIdentifyUser() {
294
327
  * ```
295
328
  */
296
329
  function resetAnalytics() {
297
- globalAnalytics$1 = null;
330
+ setGlobalAnalytics(null);
331
+ }
332
+
333
+ //#endregion
334
+ //#region ../observability/env.ts
335
+ /**
336
+ * @fileoverview Core environment configuration for the observability package
337
+ * Core environment configuration for the observability package
338
+ * Provider-specific configurations are handled by their respective plugins
339
+ */
340
+ const env$2 = createEnv({
341
+ server: {},
342
+ clientPrefix: "NEXT_PUBLIC_",
343
+ client: {
344
+ NEXT_PUBLIC_NODE_ENV: z.enum([
345
+ "development",
346
+ "test",
347
+ "production"
348
+ ]).default("development"),
349
+ NEXT_PUBLIC_OBSERVABILITY_DEBUG: z.string().optional().transform((val) => val === "true").default(false),
350
+ NEXT_PUBLIC_OBSERVABILITY_CONSOLE_ENABLED: z.string().optional().transform((val) => val === "true" ? true : val === "false" ? false : void 0)
351
+ },
352
+ runtimeEnv: process.env,
353
+ emptyStringAsUndefined: true,
354
+ onInvalidAccess: (variable) => {
355
+ throw new Error(`❌ Attempted to access a server-side environment variable on the client: ${variable}`);
356
+ },
357
+ onValidationError: (error) => {
358
+ logWarn("Observability package environment validation failed", { error });
359
+ }
360
+ });
361
+
362
+ //#endregion
363
+ //#region ../observability/src/core/manager.ts
364
+ /**
365
+ * @fileoverview ObservabilityManager - Core orchestrator for multiple observability providers
366
+ * ObservabilityManager - Core orchestrator for multiple observability providers
367
+ */
368
+ /**
369
+ * Manager that orchestrates multiple observability plugins
370
+ * Broadcasts all method calls to enabled plugins
371
+ */
372
+ var ObservabilityManager = class {
373
+ plugins = /* @__PURE__ */ new Map();
374
+ initialized = false;
375
+ initializationPromise = null;
376
+ lifecycle = {};
377
+ initializationError = null;
378
+ /**
379
+ * Create a new ObservabilityManager instance.
380
+ *
381
+ * @param lifecycle - Optional lifecycle callbacks for plugin events
382
+ */
383
+ constructor(lifecycle) {
384
+ if (lifecycle) this.lifecycle = lifecycle;
385
+ }
386
+ /**
387
+ * Add a plugin to the manager
388
+ */
389
+ addPlugin(plugin) {
390
+ this.plugins.set(plugin.name, plugin);
391
+ return this;
392
+ }
393
+ /**
394
+ * Get a specific plugin by name.
395
+ *
396
+ * @param name - Name of the plugin to retrieve
397
+ * @returns Plugin instance if found, undefined otherwise
398
+ */
399
+ getPlugin(name) {
400
+ return this.plugins.get(name);
401
+ }
402
+ /**
403
+ * Get all registered plugins.
404
+ *
405
+ * @returns Array of all registered plugins
406
+ */
407
+ getPlugins() {
408
+ return Array.from(this.plugins.values());
409
+ }
410
+ /**
411
+ * List all registered plugins (alias for getPlugins).
412
+ *
413
+ * @returns Array of all registered plugins
414
+ */
415
+ listPlugins() {
416
+ return this.getPlugins();
417
+ }
418
+ /**
419
+ * Initialize all plugins
420
+ * Note: Failed initialization is retryable - subsequent calls will re-attempt initialization.
421
+ * This allows recovery from transient failures (network issues, temporary misconfigurations).
422
+ */
423
+ async initialize() {
424
+ if (this.initializationPromise) return this.initializationPromise;
425
+ if (this.initialized) return;
426
+ if (this.initializationError) {
427
+ logWarn$1("ObservabilityManager: Retrying failed initialization");
428
+ this.initializationError = null;
429
+ }
430
+ this.initializationPromise = this.doInitialize();
431
+ try {
432
+ await this.initializationPromise;
433
+ } finally {
434
+ this.initializationPromise = null;
435
+ }
436
+ }
437
+ async doInitialize() {
438
+ const initPromises = Array.from(this.plugins.values()).filter((plugin) => plugin.enabled && plugin.initialize).map(async (plugin) => {
439
+ try {
440
+ if (plugin.initialize) await plugin.initialize();
441
+ this.lifecycle.onInitialized?.(plugin);
442
+ } catch (error) {
443
+ logError$1(`Failed to initialize plugin ${plugin.name}`, {
444
+ error,
445
+ pluginName: plugin.name
446
+ });
447
+ this.lifecycle.onError?.(error, plugin);
448
+ throw error;
449
+ }
450
+ });
451
+ try {
452
+ await Promise.all(initPromises);
453
+ this.initialized = true;
454
+ } catch (error) {
455
+ this.initializationError = error instanceof Error ? error : new Error(String(error));
456
+ throw this.initializationError;
457
+ }
458
+ }
459
+ /**
460
+ * Check if initialization had an error
461
+ * @returns `true` if initialization failed, `false` otherwise
462
+ */
463
+ hasInitializationError() {
464
+ return this.initializationError !== null;
465
+ }
466
+ /**
467
+ * Get the initialization error if one occurred
468
+ * @returns The initialization error or `null` if no error
469
+ */
470
+ getInitializationError() {
471
+ return this.initializationError;
472
+ }
473
+ /**
474
+ * Shutdown all plugins
475
+ */
476
+ async shutdown() {
477
+ const shutdownPromises = Array.from(this.plugins.values()).filter((plugin) => plugin.shutdown).map(async (plugin) => {
478
+ try {
479
+ if (plugin.shutdown) await plugin.shutdown();
480
+ this.lifecycle.onShutdown?.(plugin);
481
+ } catch (error) {
482
+ logError$1(`Failed to shutdown plugin ${plugin.name}`, {
483
+ error,
484
+ pluginName: plugin.name
485
+ });
486
+ this.lifecycle.onError?.(error, plugin);
487
+ }
488
+ });
489
+ await Promise.allSettled(shutdownPromises);
490
+ this.initialized = false;
491
+ }
492
+ /**
493
+ * Broadcast exception to all enabled plugins.
494
+ *
495
+ * @param error - Error object or unknown error value
496
+ * @param context - Optional additional context data
497
+ */
498
+ captureException(error, context) {
499
+ this.broadcast((plugin) => plugin.captureException(error, context));
500
+ }
501
+ /**
502
+ * Broadcast message to all enabled plugins.
503
+ *
504
+ * @param message - Message to log
505
+ * @param level - Log level (default: 'info')
506
+ * @param context - Optional additional context data
507
+ */
508
+ captureMessage(message, level = "info", context) {
509
+ this.broadcast((plugin) => plugin.captureMessage(message, level, context));
510
+ }
511
+ /**
512
+ * Validate and sanitize user data to prevent injection and DoS attacks
513
+ * @param user - User data to validate
514
+ * @returns Validated user data with length limits and format validation applied
515
+ */
516
+ validateUser(user) {
517
+ if (!user) return null;
518
+ const id = String(user.id).trim().slice(0, 255);
519
+ if (!id) {
520
+ logWarn$1("ObservabilityManager: User ID is empty, ignoring user data");
521
+ return null;
522
+ }
523
+ const validated = { id };
524
+ if ("email" in user && user.email) {
525
+ const email = String(user.email).trim().slice(0, 255);
526
+ if (email && /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(email)) validated.email = email;
527
+ }
528
+ if ("username" in user && user.username) {
529
+ const username = String(user.username).trim().slice(0, 255);
530
+ if (username) validated.username = username;
531
+ }
532
+ if ("ip_address" in user && user.ip_address) {
533
+ const ip = String(user.ip_address).trim();
534
+ const isValidIPv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ip);
535
+ const looksLikeIPv6 = /^[0-9a-fA-F:]+$/.test(ip) && ip.includes(":") && ip.split(":").length <= 8;
536
+ if (isValidIPv4 || looksLikeIPv6) validated.ip_address = ip.slice(0, 45);
537
+ }
538
+ return validated;
539
+ }
540
+ /**
541
+ * Set user on all enabled plugins
542
+ * User data is validated and sanitized before being set
543
+ * @param user - User data to set (will be validated)
544
+ */
545
+ setUser(user) {
546
+ const validatedUser = this.validateUser(user);
547
+ this.broadcast((plugin) => plugin.setUser(validatedUser));
548
+ }
549
+ /**
550
+ * Add breadcrumb to all enabled plugins.
551
+ *
552
+ * @param breadcrumb - Breadcrumb data to add
553
+ */
554
+ addBreadcrumb(breadcrumb) {
555
+ this.broadcast((plugin) => plugin.addBreadcrumb(breadcrumb));
556
+ }
557
+ /**
558
+ * Execute callback within scope for all enabled plugins.
559
+ *
560
+ * @param callback - Callback that receives the scope
561
+ */
562
+ withScope(callback) {
563
+ this.broadcast((plugin) => plugin.withScope(callback));
564
+ }
565
+ /**
566
+ * Flush all plugins that support it.
567
+ *
568
+ * Waits for all enabled plugins with flush capability to send pending events.
569
+ *
570
+ * @param timeout - Maximum time to wait in milliseconds
571
+ * @returns Promise resolving to true if all plugins flushed successfully
572
+ */
573
+ async flush(timeout) {
574
+ const flushPromises = Array.from(this.plugins.values()).filter((plugin) => {
575
+ return plugin.enabled && "flush" in plugin && typeof plugin.flush === "function";
576
+ }).map((plugin) => plugin.flush(timeout));
577
+ if (flushPromises.length === 0) return true;
578
+ return (await Promise.allSettled(flushPromises)).every((result) => result.status === "fulfilled" && result.value === true);
579
+ }
580
+ /**
581
+ * Helper to broadcast a method call to all enabled plugins
582
+ * Uses nested try-catch to ensure errors in error handling don't prevent other plugins from executing
583
+ */
584
+ broadcast(fn) {
585
+ this.plugins.forEach((plugin) => {
586
+ if (plugin.enabled) try {
587
+ fn(plugin);
588
+ } catch (error) {
589
+ try {
590
+ logError$1(`Plugin ${plugin.name} error`, {
591
+ error,
592
+ pluginName: plugin.name
593
+ });
594
+ } catch {}
595
+ try {
596
+ this.lifecycle.onError?.(error, plugin);
597
+ } catch {}
598
+ }
599
+ });
600
+ }
601
+ /**
602
+ * Check if manager has any enabled plugins.
603
+ *
604
+ * @returns True if at least one plugin is enabled, false otherwise
605
+ */
606
+ hasEnabledPlugins() {
607
+ return Array.from(this.plugins.values()).some((plugin) => plugin.enabled);
608
+ }
609
+ /**
610
+ * Get names of all enabled plugins.
611
+ *
612
+ * @returns Array of plugin names that are currently enabled
613
+ */
614
+ getEnabledPluginNames() {
615
+ return Array.from(this.plugins.values()).filter((plugin) => plugin.enabled).map((plugin) => plugin.name);
616
+ }
617
+ };
618
+
619
+ //#endregion
620
+ //#region ../observability/src/factory/builder.ts
621
+ /**
622
+ * @fileoverview ObservabilityBuilder - Fluent API for building observability instances
623
+ * ObservabilityBuilder - Fluent API for building observability instances
624
+ */
625
+ /**
626
+ * Builder for creating configured ObservabilityManager instances
627
+ */
628
+ var ObservabilityBuilder = class ObservabilityBuilder {
629
+ plugins = [];
630
+ lifecycle = {};
631
+ autoInitialize = true;
632
+ /**
633
+ * Add a plugin to the observability stack
634
+ * @param plugin - Observability plugin to add
635
+ * @returns Builder instance for chaining
636
+ */
637
+ withPlugin(plugin) {
638
+ if (plugin) this.plugins.push(plugin);
639
+ return this;
640
+ }
641
+ /**
642
+ * Add multiple plugins at once
643
+ * @param plugins - Array of observability plugins to add
644
+ * @returns Builder instance for chaining
645
+ */
646
+ withPlugins(plugins) {
647
+ if (plugins && Array.isArray(plugins)) {
648
+ const validPlugins = plugins.filter((plugin) => plugin != null);
649
+ this.plugins.push(...validPlugins);
650
+ }
651
+ return this;
652
+ }
653
+ /**
654
+ * Set lifecycle callbacks for plugin management
655
+ * @param lifecycle - Lifecycle callback configuration
656
+ * @returns Builder instance for chaining
657
+ */
658
+ withLifecycle(lifecycle) {
659
+ this.lifecycle = {
660
+ ...this.lifecycle,
661
+ ...lifecycle
662
+ };
663
+ return this;
664
+ }
665
+ /**
666
+ * Configure whether to auto-initialize plugins (default: true)
667
+ */
668
+ withAutoInitialize(autoInitialize) {
669
+ this.autoInitialize = autoInitialize;
670
+ return this;
671
+ }
672
+ /**
673
+ * Build the ObservabilityManager instance
674
+ * @returns Configured ObservabilityManager instance
675
+ */
676
+ build() {
677
+ const manager = new ObservabilityManager(this.lifecycle);
678
+ this.plugins.forEach((plugin) => manager.addPlugin(plugin));
679
+ if (this.autoInitialize && typeof process !== "undefined" && process.env.NEXT_RUNTIME !== "edge") (async () => {
680
+ try {
681
+ await manager.initialize();
682
+ } catch (error) {
683
+ try {
684
+ logError$1("Failed to initialize observability", { error });
685
+ } catch {}
686
+ }
687
+ })();
688
+ return manager;
689
+ }
690
+ /**
691
+ * Build and initialize the ObservabilityManager instance
692
+ * @returns Promise resolving to initialized ObservabilityManager
693
+ */
694
+ async buildWithAutoInit() {
695
+ const manager = new ObservabilityManager(this.lifecycle);
696
+ this.plugins.forEach((plugin) => manager.addPlugin(plugin));
697
+ try {
698
+ await manager.initialize();
699
+ } catch {}
700
+ return manager;
701
+ }
702
+ /**
703
+ * Create a new builder instance
704
+ * @returns New ObservabilityBuilder instance
705
+ */
706
+ static create() {
707
+ return new ObservabilityBuilder();
708
+ }
709
+ };
710
+
711
+ //#endregion
712
+ //#region ../observability/src/plugins/betterstack/env.ts
713
+ /**
714
+ * @fileoverview Better Stack (Logtail) environment configuration
715
+ * Better Stack (Logtail) environment configuration
716
+ * Updated to support official @logtail packages
717
+ */
718
+ const env$1 = createEnv({
719
+ server: {
720
+ LOGTAIL_SOURCE_TOKEN: z.string().optional(),
721
+ BETTERSTACK_SOURCE_TOKEN: z.string().optional(),
722
+ BETTER_STACK_SOURCE_TOKEN: z.string().optional(),
723
+ BETTER_STACK_INGESTING_URL: z.string().url().optional(),
724
+ BETTER_STACK_LOG_LEVEL: z.enum([
725
+ "debug",
726
+ "info",
727
+ "warn",
728
+ "error",
729
+ "off"
730
+ ]).optional()
731
+ },
732
+ client: {
733
+ NEXT_PUBLIC_LOGTAIL_TOKEN: z.string().optional(),
734
+ NEXT_PUBLIC_BETTERSTACK_TOKEN: z.string().optional(),
735
+ NEXT_PUBLIC_BETTER_STACK_SOURCE_TOKEN: z.string().optional(),
736
+ NEXT_PUBLIC_BETTER_STACK_INGESTING_URL: z.string().url().optional(),
737
+ NEXT_PUBLIC_BETTER_STACK_LOG_LEVEL: z.enum([
738
+ "debug",
739
+ "info",
740
+ "warn",
741
+ "error",
742
+ "off"
743
+ ]).optional()
744
+ },
745
+ clientPrefix: "NEXT_PUBLIC_",
746
+ runtimeEnv: process.env,
747
+ emptyStringAsUndefined: true,
748
+ onInvalidAccess: (variable) => {
749
+ throw new Error(`❌ Attempted to access a server-side environment variable on the client: ${variable}`);
750
+ },
751
+ onValidationError: (error) => {
752
+ logWarn("Better Stack environment validation failed", { error });
753
+ }
754
+ });
755
+ /**
756
+ * Safe environment access for non-Next.js contexts.
757
+ *
758
+ * Provides fallback environment variable access for Node.js, workers, and test environments
759
+ * where the validated env object may not be available. Returns fallback values with proper
760
+ * priority handling for legacy and official Better Stack variable names.
761
+ *
762
+ * @returns Environment object with Better Stack configuration values
763
+ */
764
+ function safeEnv$1() {
765
+ if (env$1) return env$1;
766
+ return {
767
+ LOGTAIL_SOURCE_TOKEN: process.env.LOGTAIL_SOURCE_TOKEN ?? process.env.BETTERSTACK_SOURCE_TOKEN ?? "",
768
+ BETTERSTACK_SOURCE_TOKEN: process.env.BETTERSTACK_SOURCE_TOKEN ?? process.env.LOGTAIL_SOURCE_TOKEN ?? "",
769
+ NEXT_PUBLIC_LOGTAIL_TOKEN: process.env.NEXT_PUBLIC_LOGTAIL_TOKEN ?? process.env.NEXT_PUBLIC_BETTERSTACK_TOKEN ?? "",
770
+ NEXT_PUBLIC_BETTERSTACK_TOKEN: process.env.NEXT_PUBLIC_BETTERSTACK_TOKEN ?? process.env.NEXT_PUBLIC_LOGTAIL_TOKEN ?? "",
771
+ BETTER_STACK_SOURCE_TOKEN: process.env.BETTER_STACK_SOURCE_TOKEN ?? process.env.LOGTAIL_SOURCE_TOKEN ?? process.env.BETTERSTACK_SOURCE_TOKEN ?? "",
772
+ BETTER_STACK_INGESTING_URL: process.env.BETTER_STACK_INGESTING_URL ?? "",
773
+ BETTER_STACK_LOG_LEVEL: process.env.BETTER_STACK_LOG_LEVEL ?? "info",
774
+ NEXT_PUBLIC_BETTER_STACK_SOURCE_TOKEN: process.env.NEXT_PUBLIC_BETTER_STACK_SOURCE_TOKEN ?? process.env.NEXT_PUBLIC_LOGTAIL_TOKEN ?? process.env.NEXT_PUBLIC_BETTERSTACK_TOKEN ?? "",
775
+ NEXT_PUBLIC_BETTER_STACK_INGESTING_URL: process.env.NEXT_PUBLIC_BETTER_STACK_INGESTING_URL ?? "",
776
+ NEXT_PUBLIC_BETTER_STACK_LOG_LEVEL: process.env.NEXT_PUBLIC_BETTER_STACK_LOG_LEVEL ?? "info"
777
+ };
778
+ }
779
+
780
+ //#endregion
781
+ //#region ../observability/src/plugins/betterstack/plugin.ts
782
+ /**
783
+ * @fileoverview Better Stack (Logtail) plugin implementation
784
+ * Better Stack (Logtail) plugin implementation
785
+ * Updated to use official @logtail packages
786
+ */
787
+ /**
788
+ * Maximum number of breadcrumbs to include in error/message context
789
+ * Limits context size to prevent excessive data transmission
790
+ */
791
+ const MAX_BREADCRUMBS_IN_CONTEXT = 10;
792
+ /**
793
+ * Maximum number of breadcrumbs to store in memory
794
+ * Prevents unbounded memory growth while maintaining useful history
795
+ */
796
+ const MAX_BREADCRUMBS_STORED = 100;
797
+ /**
798
+ * Better Stack plugin implementation using official @logtail packages
799
+ */
800
+ /**
801
+ * Better Stack (Logtail) plugin implementation.
802
+ *
803
+ * Integrates Better Stack logging service into the observability system.
804
+ * Uses official @logtail packages (@logtail/js or @logtail/next) for unified
805
+ * logging across environments.
806
+ */
807
+ var BetterStackPlugin = class {
808
+ name = "betterstack";
809
+ enabled;
810
+ client;
811
+ initialized = false;
812
+ logtailPackage;
813
+ currentUser = null;
814
+ breadcrumbs = [];
815
+ breadcrumbIndex = 0;
816
+ config;
817
+ /**
818
+ * Create a new BetterStackPlugin instance.
819
+ *
820
+ * @param config - Better Stack plugin configuration
821
+ */
822
+ constructor(config = {}) {
823
+ this.config = config;
824
+ const env = safeEnv$1();
825
+ const hasToken = config.sourceToken ?? env.BETTER_STACK_SOURCE_TOKEN ?? env.BETTERSTACK_SOURCE_TOKEN ?? env.LOGTAIL_SOURCE_TOKEN ?? env.NEXT_PUBLIC_BETTER_STACK_SOURCE_TOKEN ?? env.NEXT_PUBLIC_BETTERSTACK_TOKEN ?? env.NEXT_PUBLIC_LOGTAIL_TOKEN;
826
+ this.enabled = config.enabled ?? Boolean(hasToken);
827
+ this.logtailPackage = config.logtailPackage ?? this.detectLogtailPackage();
828
+ }
829
+ /**
830
+ * Detect which Logtail package to use based on the runtime environment.
831
+ *
832
+ * @returns Package name ('@logtail/next' for Next.js, '@logtail/js' otherwise)
833
+ */
834
+ detectLogtailPackage() {
835
+ if (process.env.NEXT_RUNTIME) return "@logtail/next";
836
+ return "@logtail/js";
837
+ }
838
+ getClient() {
839
+ return this.client;
840
+ }
841
+ /**
842
+ * Get safe environment access (for testing/mocking).
843
+ *
844
+ * @returns Environment configuration object
845
+ */
846
+ getSafeEnv() {
847
+ return safeEnv$1();
848
+ }
849
+ async initialize(config) {
850
+ if (this.initialized || !this.enabled) return;
851
+ const env = safeEnv$1();
852
+ const mergedConfig = {
853
+ ...this.config,
854
+ ...config
855
+ };
856
+ const sourceToken = mergedConfig.sourceToken ?? env.BETTER_STACK_SOURCE_TOKEN ?? env.NEXT_PUBLIC_BETTER_STACK_SOURCE_TOKEN ?? env.LOGTAIL_SOURCE_TOKEN ?? env.BETTERSTACK_SOURCE_TOKEN ?? env.NEXT_PUBLIC_LOGTAIL_TOKEN ?? env.NEXT_PUBLIC_BETTERSTACK_TOKEN;
857
+ if (!sourceToken) {
858
+ logWarn("Better Stack plugin: No source token provided, skipping initialization");
859
+ this.enabled = false;
860
+ return;
861
+ }
862
+ try {
863
+ const LogtailModule = await import(
864
+ /* webpackIgnore: true */
865
+ this.logtailPackage
866
+ );
867
+ const LogtailClass = LogtailModule.default ?? LogtailModule.Logtail ?? LogtailModule;
868
+ if (!LogtailClass) {
869
+ logError(`Better Stack plugin: Could not find Logtail class in ${this.logtailPackage}`, { logtailPackage: this.logtailPackage });
870
+ this.enabled = false;
871
+ return;
872
+ }
873
+ const logtailConfig = { sourceToken };
874
+ if (mergedConfig.endpoint ?? env.BETTER_STACK_INGESTING_URL ?? env.NEXT_PUBLIC_BETTER_STACK_INGESTING_URL) logtailConfig.endpoint = mergedConfig.endpoint ?? env.BETTER_STACK_INGESTING_URL ?? env.NEXT_PUBLIC_BETTER_STACK_INGESTING_URL;
875
+ this.client = new LogtailClass(logtailConfig);
876
+ this.initialized = true;
877
+ } catch (error) {
878
+ logError(`Failed to import Logtail package '${this.logtailPackage}'`, {
879
+ error,
880
+ logtailPackage: this.logtailPackage
881
+ });
882
+ this.enabled = false;
883
+ }
884
+ }
885
+ async shutdown() {
886
+ if (this.client && this.initialized) {
887
+ await this.client.flush();
888
+ this.initialized = false;
889
+ }
890
+ }
891
+ captureException(error, context) {
892
+ if (!this.enabled || !this.client) return;
893
+ const errorObj = error instanceof Error ? error : new Error(String(error));
894
+ const data = {
895
+ error: {
896
+ name: errorObj.name,
897
+ message: errorObj.message,
898
+ stack: errorObj.stack
899
+ },
900
+ context: context?.extra,
901
+ tags: context?.tags,
902
+ user: context?.user ?? this.currentUser,
903
+ breadcrumbs: this.getRecentBreadcrumbs(MAX_BREADCRUMBS_IN_CONTEXT)
904
+ };
905
+ this.client.error(errorObj.message, data);
906
+ }
907
+ captureMessage(message, level = "info", context) {
908
+ if (!this.enabled || !this.client) return;
909
+ const data = {
910
+ level,
911
+ context: context?.extra,
912
+ tags: context?.tags,
913
+ user: context?.user ?? this.currentUser,
914
+ breadcrumbs: this.getRecentBreadcrumbs(MAX_BREADCRUMBS_IN_CONTEXT)
915
+ };
916
+ switch (level) {
917
+ case "debug":
918
+ this.client.debug(message, data);
919
+ break;
920
+ case "warning":
921
+ this.client.warn(message, data);
922
+ break;
923
+ case "error":
924
+ this.client.error(message, data);
925
+ break;
926
+ default:
927
+ this.client.info(message, data);
928
+ break;
929
+ }
930
+ }
931
+ setUser(user) {
932
+ if (!this.enabled) return;
933
+ this.currentUser = user;
934
+ }
935
+ addBreadcrumb(breadcrumb) {
936
+ if (!this.enabled) return;
937
+ const enriched = {
938
+ ...breadcrumb,
939
+ timestamp: breadcrumb.timestamp ?? Date.now() / 1e3
940
+ };
941
+ if (this.breadcrumbs.length < MAX_BREADCRUMBS_STORED) this.breadcrumbs.push(enriched);
942
+ else {
943
+ this.breadcrumbs[this.breadcrumbIndex] = enriched;
944
+ this.breadcrumbIndex = (this.breadcrumbIndex + 1) % MAX_BREADCRUMBS_STORED;
945
+ }
946
+ }
947
+ /**
948
+ * Get the most recent breadcrumbs in chronological order
949
+ * @param count - Number of breadcrumbs to retrieve
950
+ * @returns Array of recent breadcrumbs
951
+ */
952
+ getRecentBreadcrumbs(count) {
953
+ const { length } = this.breadcrumbs;
954
+ if (length === 0) return [];
955
+ if (length <= count) return this.breadcrumbs.slice();
956
+ if (length < MAX_BREADCRUMBS_STORED) return this.breadcrumbs.slice(length - count);
957
+ const result = [];
958
+ const start = (this.breadcrumbIndex - count + length) % length;
959
+ for (let i = 0; i < count; i++) {
960
+ const index = (start + i) % length;
961
+ const breadcrumb = this.breadcrumbs[index];
962
+ if (breadcrumb) result.push(breadcrumb);
963
+ }
964
+ return result;
965
+ }
966
+ withScope(callback) {
967
+ if (!this.enabled) return;
968
+ try {
969
+ callback({
970
+ setContext: (_key, _context) => {},
971
+ setUser: (user) => {
972
+ this.setUser(user);
973
+ }
974
+ });
975
+ } catch {}
976
+ }
977
+ async flush(_timeout) {
978
+ if (!this.enabled || !this.client) return true;
979
+ try {
980
+ await this.client.flush();
981
+ return true;
982
+ } catch (error) {
983
+ logError("Better Stack flush error", { error });
984
+ return false;
985
+ }
986
+ }
987
+ };
988
+ /**
989
+ * Factory function to create a Better Stack plugin.
990
+ *
991
+ * Creates a configured BetterStackPlugin instance with optional configuration.
992
+ * Auto-detects the appropriate @logtail package based on the environment.
993
+ *
994
+ * @param config - Optional Better Stack plugin configuration
995
+ * @returns BetterStackPlugin instance
996
+ *
997
+ * @example
998
+ * ```typescript
999
+ * const betterstack = createBetterStackPlugin({
1000
+ * sourceToken: '...',
1001
+ * logLevel: 'info',
1002
+ * });
1003
+ * ```
1004
+ */
1005
+ const createBetterStackPlugin = (config) => {
1006
+ return new BetterStackPlugin(config);
1007
+ };
1008
+
1009
+ //#endregion
1010
+ //#region ../observability/src/shared.ts
1011
+ /**
1012
+ * @fileoverview Shared observability utilities and types
1013
+ * Shared observability utilities and types
1014
+ */
1015
+ /**
1016
+ * Get runtime environment information.
1017
+ *
1018
+ * Detects the current runtime environment (browser, Node.js, edge, Bun) and returns
1019
+ * detailed information about the environment type, version, and capabilities.
1020
+ *
1021
+ * @returns Runtime information object with type, version, and environment-specific details
1022
+ *
1023
+ * @example
1024
+ * ```typescript
1025
+ * const runtime = getRuntimeInfo();
1026
+ * // Returns: { type: 'node', version: '22.0.0', major: 22, minor: 0, isNode22Plus: true, isNextJs: true }
1027
+ * // or: { type: 'browser', isNextJs: true }
1028
+ * // or: { type: 'edge', variant: 'vercel' }
1029
+ * ```
1030
+ */
1031
+ const getRuntimeInfo = () => {
1032
+ if (typeof globalThis !== "undefined" && globalThis.EdgeRuntime) return {
1033
+ type: "edge",
1034
+ variant: "vercel"
1035
+ };
1036
+ if (typeof globalThis !== "undefined" && globalThis.caches && typeof globalThis.caches !== "undefined") return {
1037
+ type: "edge",
1038
+ variant: "cloudflare"
1039
+ };
1040
+ if (typeof process !== "undefined" && process.versions?.bun) return {
1041
+ type: "bun",
1042
+ version: process.versions.bun
1043
+ };
1044
+ if (typeof globalThis !== "undefined" && "window" in globalThis && typeof globalThis.window !== "undefined" && typeof globalThis.window.document !== "undefined" && typeof globalThis.window.navigator !== "undefined") return {
1045
+ type: "browser",
1046
+ isNextJs: Boolean(globalThis.window.__NEXT_DATA__)
1047
+ };
1048
+ if (typeof process !== "undefined" && process.versions?.node) {
1049
+ const nodeVersion = parseInt(process.versions.node.split(".")[0] ?? "0");
1050
+ const nodeVersionMinor = parseInt(process.versions.node.split(".")[1] ?? "0");
1051
+ if (nodeVersion < 22) logWarn(`[Observability] Node ${process.versions.node} detected. Node 22+ is required for optimal performance and latest features.`);
1052
+ return {
1053
+ type: "node",
1054
+ version: process.versions.node,
1055
+ major: nodeVersion,
1056
+ minor: nodeVersionMinor,
1057
+ isNode22Plus: nodeVersion >= 22,
1058
+ isNextJs: Boolean(process.env.NEXT_RUNTIME) || Boolean(process.env.__NEXT_RUNTIME) || Boolean(process.env.NEXT_PUBLIC_VERCEL_ENV)
1059
+ };
1060
+ }
1061
+ return { type: "unknown" };
1062
+ };
1063
+ const runtimeInfo = getRuntimeInfo();
1064
+ /**
1065
+ * Detect if code is running in a browser environment
1066
+ * Extracted to shared utility to avoid duplication
1067
+ * @returns true if running in browser, false otherwise
1068
+ */
1069
+ function isBrowser() {
1070
+ return typeof globalThis !== "undefined" && "window" in globalThis && typeof globalThis.window !== "undefined" && typeof globalThis.window.document !== "undefined" && typeof globalThis.window.navigator !== "undefined";
1071
+ }
1072
+ /**
1073
+ * Determine if console logging should be enabled based on environment
1074
+ * Centralized logic to avoid duplication across entry points
1075
+ * @param envNodeEnv - The NEXT_PUBLIC_NODE_ENV value
1076
+ * @param consoleEnabled - The NEXT_PUBLIC_OBSERVABILITY_CONSOLE_ENABLED value (explicit control)
1077
+ * @param debugEnabled - The NEXT_PUBLIC_OBSERVABILITY_DEBUG value
1078
+ * @returns boolean indicating if console should be enabled
1079
+ */
1080
+ function shouldEnableConsole(envNodeEnv, consoleEnabled, debugEnabled) {
1081
+ const isDevelopment = envNodeEnv === "development" || true;
1082
+ if (consoleEnabled !== void 0) return consoleEnabled;
1083
+ if (isDevelopment) return true;
1084
+ return debugEnabled ?? false;
1085
+ }
1086
+
1087
+ //#endregion
1088
+ //#region ../observability/src/plugins/console/index.ts
1089
+ /**
1090
+ * @fileoverview Console plugin for observability
1091
+ * Console plugin for observability
1092
+ * Simple logging implementation for development
1093
+ */
1094
+ /**
1095
+ * Console plugin implementation for browser environments
1096
+ */
1097
+ /**
1098
+ * Console plugin implementation for browser environments.
1099
+ *
1100
+ * Provides simple console logging with color support for development.
1101
+ * Supports both browser (CSS colors) and Node.js (ANSI colors) environments.
1102
+ */
1103
+ var ConsolePlugin = class {
1104
+ name = "console";
1105
+ enabled;
1106
+ prefix;
1107
+ colors;
1108
+ isBrowser;
1109
+ /**
1110
+ * Create a new ConsolePlugin instance.
1111
+ *
1112
+ * @param config - Console plugin configuration
1113
+ */
1114
+ constructor(config = {}) {
1115
+ this.enabled = config.enabled ?? true;
1116
+ this.prefix = config.prefix ?? "[Console]";
1117
+ this.colors = config.colors ?? true;
1118
+ this.isBrowser = isBrowser();
1119
+ }
1120
+ async initialize() {}
1121
+ async shutdown() {}
1122
+ getClient() {}
1123
+ captureException(error, context) {
1124
+ if (!this.enabled) return;
1125
+ try {
1126
+ const contextData = this.formatContext(context);
1127
+ if (this.colors && this.isBrowser) console.error(`%c${this.prefix} Error:`, "color: red; font-weight: bold", error, contextData);
1128
+ else if (this.colors) console.error(`\x1b[31m${this.prefix} Error:\x1b[0m`, error, contextData);
1129
+ else console.error(`${this.prefix} Error:`, error, contextData);
1130
+ } catch {}
1131
+ }
1132
+ captureMessage(message, level = "info", context) {
1133
+ if (!this.enabled) return;
1134
+ try {
1135
+ const contextData = this.formatContext(context);
1136
+ const logMethod = this.getLogMethod(level);
1137
+ const levelLabel = this.getLevelLabel(level);
1138
+ if (this.colors && this.isBrowser) {
1139
+ const cssColor = this.getBrowserColorCSS(level);
1140
+ console[logMethod](`%c${this.prefix} ${levelLabel}:`, cssColor, message, contextData);
1141
+ } else if (this.colors) {
1142
+ const ansiColor = this.getAnsiColorCode(level);
1143
+ console[logMethod](`${ansiColor}${this.prefix} ${levelLabel}:\x1b[0m`, message, contextData);
1144
+ } else console[logMethod](`${this.prefix} ${levelLabel}:`, message, contextData);
1145
+ } catch {}
1146
+ }
1147
+ setUser(user) {
1148
+ if (!this.enabled) return;
1149
+ try {
1150
+ if (user === null) console.info(this.prefix, "User cleared");
1151
+ else console.info(this.prefix, "User set:", JSON.stringify(user));
1152
+ } catch {}
1153
+ }
1154
+ addBreadcrumb(breadcrumb) {
1155
+ if (!this.enabled) return;
1156
+ try {
1157
+ const enrichedBreadcrumb = {
1158
+ ...breadcrumb,
1159
+ timestamp: breadcrumb.timestamp ?? Date.now() / 1e3
1160
+ };
1161
+ console.log(this.prefix, "Breadcrumb:", JSON.stringify(enrichedBreadcrumb));
1162
+ } catch {}
1163
+ }
1164
+ withScope(callback) {
1165
+ if (!this.enabled) return;
1166
+ try {
1167
+ callback({
1168
+ setContext: (key, context) => {
1169
+ console.log(this.prefix, "Context set:", key, JSON.stringify(context));
1170
+ },
1171
+ setUser: (user) => {
1172
+ this.setUser(user);
1173
+ }
1174
+ });
1175
+ } catch {}
1176
+ }
1177
+ async flush() {
1178
+ if (this.enabled) console.log(this.prefix, "Flushed");
1179
+ return true;
1180
+ }
1181
+ formatContext(context) {
1182
+ if (!context) return {};
1183
+ if (context.extra !== void 0 || context.tags !== void 0) return {
1184
+ context: context.extra,
1185
+ tags: context.tags
1186
+ };
1187
+ if ("operation" in context || "metadata" in context) return {
1188
+ context: context.metadata ?? {},
1189
+ tags: { operation: context.operation }
1190
+ };
1191
+ return {
1192
+ context,
1193
+ tags: {}
1194
+ };
1195
+ }
1196
+ /**
1197
+ * Get CSS color style for browser console
1198
+ * @param level - Log level
1199
+ * @returns CSS style string
1200
+ */
1201
+ getBrowserColorCSS(level) {
1202
+ return {
1203
+ debug: "color: #00CED1; font-weight: bold",
1204
+ info: "color: #32CD32; font-weight: bold",
1205
+ warning: "color: #FFA500; font-weight: bold",
1206
+ error: "color: #DC143C; font-weight: bold"
1207
+ }[level] ?? "color: inherit";
1208
+ }
1209
+ /**
1210
+ * Get ANSI color code for Node.js terminal
1211
+ * @param level - Log level
1212
+ * @returns ANSI escape code
1213
+ */
1214
+ getAnsiColorCode(level) {
1215
+ return {
1216
+ debug: "\x1B[36m",
1217
+ info: "\x1B[32m",
1218
+ warning: "\x1B[33m",
1219
+ error: "\x1B[31m"
1220
+ }[level] ?? "";
1221
+ }
1222
+ /**
1223
+ * Get the console method name for a log level.
1224
+ *
1225
+ * @param level - Log level
1226
+ * @returns Console method name ('debug', 'info', 'warn', or 'error')
1227
+ */
1228
+ getLogMethod(level) {
1229
+ switch (level) {
1230
+ case "debug": return "debug";
1231
+ case "info": return "info";
1232
+ case "warning": return "warn";
1233
+ case "error": return "error";
1234
+ default: return "info";
1235
+ }
1236
+ }
1237
+ /**
1238
+ * Get a human-readable label for a log level.
1239
+ *
1240
+ * @param level - Log level
1241
+ * @returns Capitalized level label ('Debug', 'Info', 'Warning', 'Error')
1242
+ */
1243
+ getLevelLabel(level) {
1244
+ switch (level) {
1245
+ case "debug": return "Debug";
1246
+ case "info": return "Info";
1247
+ case "warning": return "Warning";
1248
+ case "error": return "Error";
1249
+ default: return "Info";
1250
+ }
1251
+ }
1252
+ };
1253
+ /**
1254
+ * Factory function to create a console plugin for browser environments.
1255
+ *
1256
+ * @param config - Console plugin configuration
1257
+ * @returns ConsolePlugin instance configured for browser
1258
+ */
1259
+ const createConsolePlugin = (config) => {
1260
+ return new ConsolePlugin(config);
1261
+ };
1262
+
1263
+ //#endregion
1264
+ //#region ../observability/src/plugins/sentry/env.ts
1265
+ /**
1266
+ * @fileoverview Sentry-specific environment configuration
1267
+ * Sentry-specific environment configuration
1268
+ * Apps can extend this configuration to inherit Sentry environment variables
1269
+ */
1270
+ const env = createEnv({
1271
+ server: {
1272
+ SENTRY_DSN: z.string().url().optional(),
1273
+ SENTRY_AUTH_TOKEN: z.string().optional(),
1274
+ SENTRY_ORG: z.string().optional(),
1275
+ SENTRY_PROJECT: z.string().optional(),
1276
+ SENTRY_ENVIRONMENT: z.enum([
1277
+ "development",
1278
+ "preview",
1279
+ "production"
1280
+ ]).optional(),
1281
+ SENTRY_RELEASE: z.string().optional(),
1282
+ SENTRY_TRACES_SAMPLE_RATE: z.coerce.number().min(0).max(1).optional(),
1283
+ SENTRY_PROFILES_SAMPLE_RATE: z.coerce.number().min(0).max(1).optional(),
1284
+ SENTRY_REPLAYS_SESSION_SAMPLE_RATE: z.coerce.number().min(0).max(1).optional(),
1285
+ SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE: z.coerce.number().min(0).max(1).optional(),
1286
+ SENTRY_DEBUG: z.string().optional().transform((val) => val === "true").default(false)
1287
+ },
1288
+ clientPrefix: "NEXT_PUBLIC_",
1289
+ client: {
1290
+ NEXT_PUBLIC_SENTRY_DSN: z.string().url().optional(),
1291
+ NEXT_PUBLIC_SENTRY_ENVIRONMENT: z.enum([
1292
+ "development",
1293
+ "preview",
1294
+ "production"
1295
+ ]).optional(),
1296
+ NEXT_PUBLIC_SENTRY_RELEASE: z.string().optional()
1297
+ },
1298
+ runtimeEnv: process.env,
1299
+ emptyStringAsUndefined: true,
1300
+ onInvalidAccess: (variable) => {
1301
+ throw new Error(`❌ Attempted to access a server-side environment variable on the client: ${variable}`);
1302
+ },
1303
+ onValidationError: (error) => {
1304
+ logWarn("Sentry environment validation failed", { error });
1305
+ }
1306
+ });
1307
+ /**
1308
+ * Safe environment access for non-Next.js contexts.
1309
+ *
1310
+ * Provides fallback environment variable access for Node.js, workers, and test environments
1311
+ * where the validated env object may not be available. Returns fallback values with proper
1312
+ * type coercion and defaults.
1313
+ *
1314
+ * @returns Environment object with Sentry configuration values
1315
+ */
1316
+ function safeEnv() {
1317
+ if (env) return env;
1318
+ return {
1319
+ SENTRY_DSN: process.env.SENTRY_DSN ?? "",
1320
+ SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN ?? "",
1321
+ SENTRY_ORG: process.env.SENTRY_ORG ?? "",
1322
+ SENTRY_PROJECT: process.env.SENTRY_PROJECT ?? "",
1323
+ SENTRY_ENVIRONMENT: process.env.SENTRY_ENVIRONMENT ?? "development",
1324
+ SENTRY_RELEASE: process.env.SENTRY_RELEASE ?? "",
1325
+ SENTRY_TRACES_SAMPLE_RATE: Number(process.env.SENTRY_TRACES_SAMPLE_RATE) || 1,
1326
+ SENTRY_PROFILES_SAMPLE_RATE: Number(process.env.SENTRY_PROFILES_SAMPLE_RATE) ?? 0,
1327
+ SENTRY_REPLAYS_SESSION_SAMPLE_RATE: Number(process.env.SENTRY_REPLAYS_SESSION_SAMPLE_RATE) || 0,
1328
+ SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE: Number(process.env.SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE) || 1,
1329
+ SENTRY_DEBUG: process.env.SENTRY_DEBUG === "true",
1330
+ NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN ?? "",
1331
+ NEXT_PUBLIC_SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT ?? "development",
1332
+ NEXT_PUBLIC_SENTRY_RELEASE: process.env.NEXT_PUBLIC_SENTRY_RELEASE ?? ""
1333
+ };
1334
+ }
1335
+
1336
+ //#endregion
1337
+ //#region ../observability/src/plugins/sentry/plugin.ts
1338
+ /**
1339
+ * @fileoverview Sentry plugin implementation
1340
+ * Sentry plugin implementation
1341
+ */
1342
+ /**
1343
+ * Default log filter function that filters out noisy development logs.
1344
+ * Filters out:
1345
+ * - Next.js Fast Refresh messages
1346
+ * - HMR (Hot Module Replacement) messages
1347
+ * - Prisma query logs (server-only)
1348
+ * - Prisma engine/info logs
1349
+ * - SQL queries matching Prisma's format
1350
+ *
1351
+ * Handles ANSI color codes, console formatting (%c, %s), and CSS styling.
1352
+ *
1353
+ * @param log - The log entry from Sentry
1354
+ * @param isServer - Whether this is running on the server (defaults to true)
1355
+ * @returns The log entry if it should be sent, or null to filter it out
1356
+ */
1357
+ function defaultBeforeSendLog(log, isServer = true) {
1358
+ const messageToCheck = log.message || log.formatted || "";
1359
+ if (!messageToCheck) return log;
1360
+ const messageWithoutAnsi = String(messageToCheck).replace(/\u001b\[[0-9;]*m/g, "").replace(/%[csdfiO]/g, "").replace(/background:[^;]+;?/g, "").replace(/color:[^;]+;?/g, "").replace(/border-radius:[^;]+;?/g, "").replace(/light-dark\([^)]+\)/g, "").replace(/rgba?\([^)]+\)/g, "").replace(/#[0-9a-fA-F]{3,8}/g, "").toLowerCase();
1361
+ if (messageWithoutAnsi.includes("[fast refresh]") || messageWithoutAnsi.includes("[hmr]")) return null;
1362
+ if (isServer) {
1363
+ if (messageWithoutAnsi.includes("prisma:query") || messageWithoutAnsi.includes("prisma:engine") || messageWithoutAnsi.includes("prisma:info") || messageWithoutAnsi.includes("select") && messageWithoutAnsi.includes("from") && (messageWithoutAnsi.includes("\"public\".") || messageWithoutAnsi.includes("public."))) return null;
1364
+ }
1365
+ return log;
1366
+ }
1367
+ /**
1368
+ * Sentry plugin implementation
1369
+ */
1370
+ /**
1371
+ * Sentry plugin implementation.
1372
+ *
1373
+ * Integrates Sentry error tracking and performance monitoring into the observability system.
1374
+ * Auto-detects the appropriate Sentry package (@sentry/node, @sentry/browser, @sentry/nextjs)
1375
+ * based on the runtime environment.
1376
+ */
1377
+ var SentryPlugin = class {
1378
+ name = "sentry";
1379
+ enabled;
1380
+ client;
1381
+ initialized = false;
1382
+ sentryPackage;
1383
+ config;
1384
+ /**
1385
+ * Create a new SentryPlugin instance.
1386
+ *
1387
+ * @param config - Sentry plugin configuration
1388
+ */
1389
+ constructor(config = {}) {
1390
+ this.config = config;
1391
+ const env = safeEnv();
1392
+ const hasDSN = config.dsn ?? env.SENTRY_DSN ?? env.NEXT_PUBLIC_SENTRY_DSN;
1393
+ this.enabled = config.enabled ?? Boolean(hasDSN);
1394
+ this.sentryPackage = config.sentryPackage ?? this.detectSentryPackage();
1395
+ }
1396
+ /**
1397
+ * Detect which Sentry package to use based on the runtime environment.
1398
+ *
1399
+ * @returns Package name ('@sentry/node', '@sentry/browser', or '@sentry/nextjs')
1400
+ */
1401
+ detectSentryPackage() {
1402
+ if (isBrowser()) return "@sentry/browser";
1403
+ else if (process.env.NEXT_RUNTIME === "edge") return "@sentry/nextjs";
1404
+ else if (process.env.NEXT_RUNTIME) return "@sentry/nextjs";
1405
+ else return "@sentry/node";
1406
+ }
1407
+ getClient() {
1408
+ return this.client;
1409
+ }
1410
+ /**
1411
+ * Get safe environment access (for testing/mocking).
1412
+ *
1413
+ * @returns Environment configuration object
1414
+ */
1415
+ getSafeEnv() {
1416
+ return safeEnv();
1417
+ }
1418
+ async initialize(config) {
1419
+ if (this.initialized || !this.enabled) return;
1420
+ const env = safeEnv();
1421
+ const mergedConfig = {
1422
+ ...this.config,
1423
+ ...config
1424
+ };
1425
+ const dsn = mergedConfig.dsn ?? env.SENTRY_DSN ?? env.NEXT_PUBLIC_SENTRY_DSN;
1426
+ if (!dsn) {
1427
+ logWarn("Sentry plugin: No DSN provided, skipping initialization");
1428
+ this.enabled = false;
1429
+ return;
1430
+ }
1431
+ try {
1432
+ this.client = await import(
1433
+ /* webpackIgnore: true */
1434
+ this.sentryPackage
1435
+ );
1436
+ if (this.client && this.enabled) {
1437
+ let { integrations } = mergedConfig;
1438
+ if (!integrations && this.client.browserTracingIntegration) {
1439
+ integrations = [];
1440
+ if (mergedConfig.tracesSampleRate !== void 0 || env.SENTRY_TRACES_SAMPLE_RATE !== void 0) integrations.push(this.client.browserTracingIntegration());
1441
+ if (this.client.replayIntegration && (mergedConfig.replaysSessionSampleRate !== void 0 || env.SENTRY_REPLAYS_SESSION_SAMPLE_RATE !== void 0)) integrations.push(this.client.replayIntegration());
1442
+ if (this.client.profilesIntegration && (mergedConfig.profilesSampleRate !== void 0 || env.SENTRY_PROFILES_SAMPLE_RATE !== void 0)) integrations.push(this.client.profilesIntegration());
1443
+ }
1444
+ const enableLogs = mergedConfig.enableLogs ?? true;
1445
+ if (enableLogs && this.client.consoleLoggingIntegration) {
1446
+ const consoleLoggingConfig = mergedConfig.consoleLoggingIntegration || { levels: [
1447
+ "log",
1448
+ "warn",
1449
+ "error"
1450
+ ] };
1451
+ if (!integrations) integrations = [];
1452
+ integrations.push(this.client.consoleLoggingIntegration(consoleLoggingConfig));
1453
+ }
1454
+ this.client.init({
1455
+ dsn,
1456
+ environment: mergedConfig.environment ?? env.SENTRY_ENVIRONMENT ?? "development",
1457
+ release: mergedConfig.release ?? env.SENTRY_RELEASE,
1458
+ debug: mergedConfig.debug ?? env.SENTRY_DEBUG,
1459
+ enableLogs,
1460
+ tracesSampleRate: mergedConfig.tracesSampleRate ?? env.SENTRY_TRACES_SAMPLE_RATE,
1461
+ profilesSampleRate: mergedConfig.profilesSampleRate ?? env.SENTRY_PROFILES_SAMPLE_RATE,
1462
+ replaysSessionSampleRate: mergedConfig.replaysSessionSampleRate ?? env.SENTRY_REPLAYS_SESSION_SAMPLE_RATE,
1463
+ replaysOnErrorSampleRate: mergedConfig.replaysOnErrorSampleRate ?? env.SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE,
1464
+ integrations,
1465
+ beforeSend: mergedConfig.beforeSend,
1466
+ beforeSendTransaction: mergedConfig.beforeSendTransaction,
1467
+ beforeSendLog: mergedConfig.beforeSendLog || (enableLogs ? (log) => defaultBeforeSendLog(log, !isBrowser()) : void 0),
1468
+ tracePropagationTargets: mergedConfig.tracePropagationTargets,
1469
+ initialScope: mergedConfig.initialScope,
1470
+ maxBreadcrumbs: mergedConfig.maxBreadcrumbs,
1471
+ attachStacktrace: mergedConfig.attachStacktrace,
1472
+ autoSessionTracking: mergedConfig.autoSessionTracking
1473
+ });
1474
+ this.initialized = true;
1475
+ }
1476
+ } catch (error) {
1477
+ logError(`Failed to import Sentry package '${this.sentryPackage}'`, { error });
1478
+ this.enabled = false;
1479
+ }
1480
+ }
1481
+ async shutdown() {
1482
+ if (this.client && this.initialized) {
1483
+ if (this.client.close) await this.client.close();
1484
+ else if (this.client.flush) await this.client.flush();
1485
+ this.initialized = false;
1486
+ }
1487
+ }
1488
+ /**
1489
+ * Clean up the plugin (alias for shutdown)
1490
+ */
1491
+ async cleanup() {
1492
+ await this.shutdown();
1493
+ }
1494
+ captureException(error, context) {
1495
+ if (!this.enabled || !this.client) return;
1496
+ if (typeof this.client.captureException !== "function") {
1497
+ logError("[Sentry Fallback] Exception", { error });
1498
+ return;
1499
+ }
1500
+ try {
1501
+ this.client.captureException(error, context);
1502
+ } catch (captureError) {
1503
+ logWarn("[Sentry Plugin] Failed to capture exception", { error: captureError });
1504
+ logError("[Sentry Fallback] Exception", { error });
1505
+ }
1506
+ }
1507
+ captureMessage(message, level = "info", context) {
1508
+ if (!this.enabled || !this.client) return;
1509
+ if (this.client.logger) try {
1510
+ const attributes = context ? context.extra ?? context : void 0;
1511
+ switch (level) {
1512
+ case "debug":
1513
+ this.client.logger.debug(message, attributes);
1514
+ return;
1515
+ case "info":
1516
+ this.client.logger.info(message, attributes);
1517
+ return;
1518
+ case "warning":
1519
+ this.client.logger.warn(message, attributes);
1520
+ return;
1521
+ case "error":
1522
+ this.client.logger.error(message, attributes);
1523
+ return;
1524
+ }
1525
+ } catch (error) {
1526
+ logWarn("[Sentry Plugin] Failed to log via logger", { error });
1527
+ }
1528
+ if (typeof this.client.captureMessage !== "function") {
1529
+ if (level === "error") logError(`[Sentry Fallback] ${message}`);
1530
+ else if (level === "warning") logWarn(`[Sentry Fallback] ${message}`);
1531
+ else logDebug(`[Sentry Fallback] ${message}`);
1532
+ return;
1533
+ }
1534
+ const sentryLevel = level === "warning" ? "warning" : level === "error" ? "error" : level === "debug" ? "debug" : "info";
1535
+ try {
1536
+ const captureContext = context ? {
1537
+ level: sentryLevel,
1538
+ extra: context.extra ?? context,
1539
+ tags: context.tags
1540
+ } : sentryLevel;
1541
+ this.client.captureMessage(message, captureContext);
1542
+ } catch (error) {
1543
+ logWarn("[Sentry Plugin] Failed to capture message", { error });
1544
+ if (level === "error") logError(`[Sentry Fallback] ${message}`);
1545
+ else if (level === "warning") logWarn(`[Sentry Fallback] ${message}`);
1546
+ else logDebug(`[Sentry Fallback] ${message}`);
1547
+ }
1548
+ }
1549
+ setUser(user) {
1550
+ if (!this.enabled || !this.client) return;
1551
+ this.client.setUser(user);
1552
+ }
1553
+ addBreadcrumb(breadcrumb) {
1554
+ if (!this.enabled || !this.client) return;
1555
+ this.client.addBreadcrumb({
1556
+ ...breadcrumb,
1557
+ timestamp: breadcrumb.timestamp ?? Date.now() / 1e3
1558
+ });
1559
+ }
1560
+ withScope(callback) {
1561
+ if (!this.enabled || !this.client) return;
1562
+ this.client.withScope(callback);
1563
+ }
1564
+ /**
1565
+ * Start a new transaction
1566
+ */
1567
+ startTransaction(context, customSamplingContext) {
1568
+ if (!this.enabled || !this.client) return void 0;
1569
+ if (this.client.startTransaction) return this.client.startTransaction(context, customSamplingContext);
1570
+ logWarn("startTransaction not available in this Sentry version");
1571
+ }
1572
+ /**
1573
+ * Start a new span
1574
+ */
1575
+ startSpan(context) {
1576
+ if (!this.enabled || !this.client) return void 0;
1577
+ if (this.client.startSpan) return this.client.startSpan(context);
1578
+ const transaction = this.getActiveTransaction();
1579
+ if (transaction?.startChild) return transaction.startChild(context);
1580
+ }
1581
+ /**
1582
+ * Get the currently active transaction
1583
+ */
1584
+ getActiveTransaction() {
1585
+ if (!this.enabled || !this.client) return void 0;
1586
+ if (this.client.getActiveTransaction) return this.client.getActiveTransaction();
1587
+ if (this.client.getCurrentScope) {
1588
+ const scope = this.client.getCurrentScope();
1589
+ if (scope?.getTransaction) return scope.getTransaction();
1590
+ }
1591
+ }
1592
+ /**
1593
+ * Configure the current scope
1594
+ */
1595
+ configureScope(callback) {
1596
+ if (!this.enabled || !this.client) return;
1597
+ if (this.client.configureScope) this.client.configureScope(callback);
1598
+ else if (this.client.withScope) this.client.withScope(callback);
1599
+ }
1600
+ /**
1601
+ * Get the current hub instance
1602
+ */
1603
+ getCurrentHub() {
1604
+ if (!this.enabled || !this.client) return void 0;
1605
+ if (this.client.getCurrentHub) return this.client.getCurrentHub();
1606
+ }
1607
+ /**
1608
+ * Set a measurement on the active transaction
1609
+ */
1610
+ setMeasurement(name, value, unit) {
1611
+ if (!this.enabled || !this.client) return;
1612
+ const transaction = this.getActiveTransaction();
1613
+ if (transaction?.setMeasurement) transaction.setMeasurement(name, value, unit);
1614
+ }
1615
+ /**
1616
+ * Add performance entries as breadcrumbs and measurements
1617
+ */
1618
+ addPerformanceEntries(entries) {
1619
+ if (!this.enabled || !this.client) return;
1620
+ const transaction = this.getActiveTransaction();
1621
+ entries.forEach((entry) => {
1622
+ this.addBreadcrumb({
1623
+ category: "performance",
1624
+ message: `${entry.entryType}: ${entry.name}`,
1625
+ data: {
1626
+ name: entry.name,
1627
+ duration: entry.duration,
1628
+ startTime: entry.startTime,
1629
+ ...entry.entryType === "navigation" && {
1630
+ transferSize: entry.transferSize,
1631
+ encodedBodySize: entry.encodedBodySize,
1632
+ decodedBodySize: entry.decodedBodySize
1633
+ },
1634
+ ...entry.entryType === "resource" && {
1635
+ initiatorType: entry.initiatorType,
1636
+ transferSize: entry.transferSize
1637
+ }
1638
+ }
1639
+ });
1640
+ if (transaction && entry.entryType === "navigation") {
1641
+ const navEntry = entry;
1642
+ const fetchStart = navEntry.fetchStart ?? 0;
1643
+ if (navEntry.responseStart !== void 0) this.setMeasurement("fcp", navEntry.responseStart - fetchStart, "millisecond");
1644
+ if (navEntry.domInteractive !== void 0) this.setMeasurement("dom_interactive", navEntry.domInteractive - fetchStart, "millisecond");
1645
+ if (navEntry.domComplete !== void 0) this.setMeasurement("dom_complete", navEntry.domComplete - fetchStart, "millisecond");
1646
+ if (navEntry.loadEventEnd !== void 0) this.setMeasurement("load_event_end", navEntry.loadEventEnd - fetchStart, "millisecond");
1647
+ }
1648
+ });
1649
+ }
1650
+ /**
1651
+ * Record a Web Vital measurement
1652
+ */
1653
+ recordWebVital(name, value, options) {
1654
+ if (!this.enabled || !this.client) return;
1655
+ const { unit = "millisecond", rating } = options ?? {};
1656
+ const transaction = this.getActiveTransaction();
1657
+ if (transaction) {
1658
+ transaction.setMeasurement(name.toLowerCase(), value, unit);
1659
+ if (rating) transaction.setTag(`webvital.${name.toLowerCase()}.rating`, rating);
1660
+ this.client.captureMessage(`Web Vital: ${name}`, {
1661
+ level: "info",
1662
+ tags: {
1663
+ "webvital.name": name,
1664
+ "webvital.value": value,
1665
+ "webvital.unit": unit,
1666
+ ...rating && { "webvital.rating": rating }
1667
+ },
1668
+ contexts: { trace: {
1669
+ trace_id: transaction.traceId,
1670
+ span_id: transaction.spanId
1671
+ } }
1672
+ });
1673
+ }
1674
+ }
1675
+ /**
1676
+ * Create a custom performance mark
1677
+ */
1678
+ mark(name, options) {
1679
+ if (!this.enabled) return;
1680
+ if (typeof performance !== "undefined" && performance.mark) performance.mark(name, options);
1681
+ this.addBreadcrumb({
1682
+ category: "performance.mark",
1683
+ message: name,
1684
+ data: options?.detail,
1685
+ timestamp: Date.now() / 1e3
1686
+ });
1687
+ }
1688
+ /**
1689
+ * Create a custom performance measure
1690
+ */
1691
+ measure(name, startMarkOrOptions, endMark) {
1692
+ if (!this.enabled) return;
1693
+ if (typeof performance !== "undefined" && performance.measure) {
1694
+ if (typeof startMarkOrOptions === "string") performance.measure(name, startMarkOrOptions, endMark);
1695
+ else if (startMarkOrOptions) performance.measure(name, startMarkOrOptions);
1696
+ else performance.measure(name);
1697
+ const measures = performance.getEntriesByName(name, "measure");
1698
+ const measure = measures[measures.length - 1];
1699
+ if (measure) {
1700
+ this.setMeasurement(name, measure.duration, "millisecond");
1701
+ this.addBreadcrumb({
1702
+ category: "performance.measure",
1703
+ message: name,
1704
+ data: {
1705
+ duration: measure.duration,
1706
+ startTime: measure.startTime
1707
+ },
1708
+ timestamp: measure.startTime / 1e3
1709
+ });
1710
+ }
1711
+ }
1712
+ }
1713
+ /**
1714
+ * Log a trace message using Sentry.logger (if available).
1715
+ * Falls back to captureMessage if logger is not available.
1716
+ *
1717
+ * @param message - Trace message to log
1718
+ * @param attributes - Optional attributes to include with the log
1719
+ */
1720
+ logTrace(message, attributes) {
1721
+ if (!this.enabled || !this.client) return;
1722
+ if (this.client.logger?.trace) try {
1723
+ this.client.logger.trace(message, attributes);
1724
+ return;
1725
+ } catch (error) {
1726
+ logWarn("[Sentry Plugin] Failed to log trace", { error });
1727
+ }
1728
+ this.captureMessage(message, "debug", attributes);
1729
+ }
1730
+ /**
1731
+ * Log a debug message using Sentry.logger (if available).
1732
+ * Falls back to captureMessage if logger is not available.
1733
+ *
1734
+ * @param message - Debug message to log
1735
+ * @param attributes - Optional attributes to include with the log
1736
+ */
1737
+ logDebug(message, attributes) {
1738
+ if (!this.enabled || !this.client) return;
1739
+ if (this.client.logger?.debug) try {
1740
+ this.client.logger.debug(message, attributes);
1741
+ return;
1742
+ } catch (error) {
1743
+ logWarn("[Sentry Plugin] Failed to log debug", { error });
1744
+ }
1745
+ this.captureMessage(message, "debug", attributes);
1746
+ }
1747
+ /**
1748
+ * Log an info message using Sentry.logger (if available).
1749
+ * Falls back to captureMessage if logger is not available.
1750
+ *
1751
+ * @param message - Info message to log
1752
+ * @param attributes - Optional attributes to include with the log
1753
+ */
1754
+ logInfo(message, attributes) {
1755
+ if (!this.enabled || !this.client) return;
1756
+ if (this.client.logger?.info) try {
1757
+ this.client.logger.info(message, attributes);
1758
+ return;
1759
+ } catch (error) {
1760
+ logWarn("[Sentry Plugin] Failed to log info", { error });
1761
+ }
1762
+ this.captureMessage(message, "info", attributes);
1763
+ }
1764
+ /**
1765
+ * Log a warning message using Sentry.logger (if available).
1766
+ * Falls back to captureMessage if logger is not available.
1767
+ *
1768
+ * @param message - Warning message to log
1769
+ * @param attributes - Optional attributes to include with the log
1770
+ */
1771
+ logWarn(message, attributes) {
1772
+ if (!this.enabled || !this.client) return;
1773
+ if (this.client.logger?.warn) try {
1774
+ this.client.logger.warn(message, attributes);
1775
+ return;
1776
+ } catch (error) {
1777
+ logWarn("[Sentry Plugin] Failed to log warn", { error });
1778
+ }
1779
+ this.captureMessage(message, "warning", attributes);
1780
+ }
1781
+ /**
1782
+ * Log an error message using Sentry.logger (if available).
1783
+ * Falls back to captureMessage if logger is not available.
1784
+ *
1785
+ * @param message - Error message to log
1786
+ * @param attributes - Optional attributes to include with the log
1787
+ */
1788
+ logError(message, attributes) {
1789
+ if (!this.enabled || !this.client) return;
1790
+ if (this.client.logger?.error) try {
1791
+ this.client.logger.error(message, attributes);
1792
+ return;
1793
+ } catch (error) {
1794
+ logWarn("[Sentry Plugin] Failed to log error", { error });
1795
+ }
1796
+ this.captureMessage(message, "error", attributes);
1797
+ }
1798
+ /**
1799
+ * Log a fatal message using Sentry.logger (if available).
1800
+ * Falls back to captureMessage if logger is not available.
1801
+ *
1802
+ * @param message - Fatal message to log
1803
+ * @param attributes - Optional attributes to include with the log
1804
+ */
1805
+ logFatal(message, attributes) {
1806
+ if (!this.enabled || !this.client) return;
1807
+ if (this.client.logger?.fatal) try {
1808
+ this.client.logger.fatal(message, attributes);
1809
+ return;
1810
+ } catch (error) {
1811
+ logWarn("[Sentry Plugin] Failed to log fatal", { error });
1812
+ }
1813
+ this.captureMessage(message, "error", attributes);
1814
+ }
1815
+ /**
1816
+ * Get the Sentry.logger instance if available.
1817
+ * This allows direct access to Sentry.logger APIs including fmt.
1818
+ *
1819
+ * @returns Sentry.logger instance or undefined if not available
1820
+ *
1821
+ * @example
1822
+ * ```typescript
1823
+ * const logger = sentryPlugin.getLogger();
1824
+ * if (logger) {
1825
+ * logger.info(logger.fmt`User ${user.id} logged in`);
1826
+ * }
1827
+ * ```
1828
+ */
1829
+ getLogger() {
1830
+ if (!this.enabled || !this.client) return void 0;
1831
+ return this.client.logger;
1832
+ }
1833
+ async flush(timeout) {
1834
+ if (!this.enabled || !this.client || !this.client.flush) return true;
1835
+ try {
1836
+ return await this.client.flush(timeout);
1837
+ } catch (_error) {
1838
+ return false;
1839
+ }
1840
+ }
1841
+ };
1842
+ /**
1843
+ * Factory function to create a Sentry plugin.
1844
+ *
1845
+ * Creates a configured SentryPlugin instance with optional configuration.
1846
+ * Auto-detects the appropriate Sentry package based on the environment.
1847
+ *
1848
+ * @param config - Optional Sentry plugin configuration
1849
+ * @returns SentryPlugin instance
1850
+ *
1851
+ * @example
1852
+ * ```typescript
1853
+ * const sentry = createSentryPlugin({
1854
+ * dsn: 'https://...',
1855
+ * environment: 'production',
1856
+ * tracesSampleRate: 0.1,
1857
+ * });
1858
+ * ```
1859
+ */
1860
+ const createSentryPlugin = (config) => {
1861
+ return new SentryPlugin(config);
1862
+ };
1863
+
1864
+ //#endregion
1865
+ //#region ../observability/src/client-next.ts
1866
+ /**
1867
+ * @fileoverview Next.js client-specific observability export
1868
+ * Next.js client-specific observability export
1869
+ */
1870
+ /**
1871
+ * Create auto-configured observability for Next.js client
1872
+ */
1873
+ async function createClientObservability() {
1874
+ const builder = ObservabilityBuilder.create();
1875
+ const enableConsole = shouldEnableConsole(env$2.NEXT_PUBLIC_NODE_ENV, env$2.NEXT_PUBLIC_OBSERVABILITY_CONSOLE_ENABLED, env$2.NEXT_PUBLIC_OBSERVABILITY_DEBUG);
1876
+ builder.withPlugin(createConsolePlugin({
1877
+ prefix: "[Next.js Client]",
1878
+ enabled: enableConsole
1879
+ }));
1880
+ if (env.NEXT_PUBLIC_SENTRY_DSN) builder.withPlugin(createSentryPlugin({ dsn: env.NEXT_PUBLIC_SENTRY_DSN }));
1881
+ const clientBetterStackToken = env$1.NEXT_PUBLIC_BETTER_STACK_SOURCE_TOKEN ?? env$1.NEXT_PUBLIC_BETTERSTACK_TOKEN ?? env$1.NEXT_PUBLIC_LOGTAIL_TOKEN;
1882
+ if (clientBetterStackToken) builder.withPlugin(createBetterStackPlugin({ sourceToken: clientBetterStackToken }));
1883
+ return builder.build();
298
1884
  }
299
1885
 
300
1886
  //#endregion
@@ -317,7 +1903,6 @@ function resetAnalytics() {
317
1903
  *
318
1904
  * @module @od-oneapp/analytics/client/next/components
319
1905
  */
320
- let globalAnalytics = null;
321
1906
  let logger = null;
322
1907
  /**
323
1908
  * Analytics provider component for App Router.
@@ -352,11 +1937,11 @@ let logger = null;
352
1937
  */
353
1938
  function AnalyticsProvider({ autoPageTracking = true, children, config, pageTrackingOptions }) {
354
1939
  useEffect(() => {
355
- if (!globalAnalytics) {
1940
+ if (!getGlobalAnalytics()) {
356
1941
  const initAnalytics = async () => {
357
1942
  try {
358
1943
  logger ??= await createClientObservability();
359
- globalAnalytics = await createNextJSClientAnalytics(config);
1944
+ setGlobalAnalytics(await createNextJSClientAnalytics(config));
360
1945
  } catch (error) {
361
1946
  if (logger) logger.captureException(error, { message: "Failed to initialize analytics" });
362
1947
  }