@od-oneapp/analytics 2026.2.1701 → 2026.2.2301-canary

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