@nexussdk/sdk 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.d.mts CHANGED
@@ -1,23 +1,34 @@
1
1
  import React from 'react';
2
- import { NexusFlagsOptions, NexusFlagsClient } from '@nexussdk/flags';
3
- import { NexusTrackerOptions, NexusTrackerClient } from '@nexussdk/tracker';
4
- import { UserContext, FlagEvaluationResult } from '@nexussdk/contracts';
2
+ import { NexusFlagsClient, NexusFlagsOptions } from '@nexussdk/flags';
3
+ import { NexusTrackerClient, NexusTrackerOptions } from '@nexussdk/tracker';
4
+ import { UserContext, SamplingConfig, TransportPlugin, PerformanceVitalsOptions, SeverityLevel, Breadcrumb, NexusErrorInfo, FlagEvaluationResult } from '@nexussdk/contracts';
5
5
 
6
6
  /**
7
7
  * @fileoverview Unified Nexus singleton facade wrapping Flags and Tracker clients.
8
8
  * @module @nexus/sdk/nexus
9
9
  */
10
10
 
11
+ /**
12
+ * Dev server routing configuration.
13
+ */
14
+ interface NexusDevServerOptions {
15
+ /** Enable local dev server ingestion mode. Auto-enabled in development if true. */
16
+ enabled?: boolean;
17
+ /** Port the dev server is listening on. Defaults to 4567. */
18
+ port?: number;
19
+ /** Host the dev server is bound to. Defaults to 'localhost'. */
20
+ host?: string;
21
+ }
11
22
  /**
12
23
  * Unified initialization options for the Nexus SDK umbrella.
13
24
  *
14
25
  * @example
15
26
  * Nexus.init({
16
27
  * apiKey: 'pk_live_...',
17
- * baseUrl: 'http://localhost:8080',
18
28
  * user: { id: 'usr_12345', country: 'VN' },
19
29
  * environment: 'production',
20
30
  * autoCapture: true,
31
+ * devServer: { enabled: process.env.NODE_ENV === 'development' },
21
32
  * });
22
33
  */
23
34
  interface NexusInitOptions {
@@ -33,6 +44,14 @@ interface NexusInitOptions {
33
44
  tags?: Record<string, string>;
34
45
  /** Toggle automated global error capture. Defaults to true. */
35
46
  autoCapture?: boolean;
47
+ /** Client-side rate limiting and deduplication sampling options. */
48
+ sampling?: SamplingConfig;
49
+ /** Pluggable transport adapter ('fetch', 'console', 'localStorage', custom fn). */
50
+ transport?: TransportPlugin;
51
+ /** Web Vitals performance observer options, or true for default observation. */
52
+ vitals?: boolean | PerformanceVitalsOptions;
53
+ /** Local dev server auto-routing configuration. */
54
+ devServer?: NexusDevServerOptions;
36
55
  /** Additional flags-specific options. */
37
56
  flags?: Partial<NexusFlagsOptions>;
38
57
  /** Additional tracker-specific options. */
@@ -59,6 +78,7 @@ interface NexusInitOptions {
59
78
  */
60
79
  declare class Nexus {
61
80
  private static instance;
81
+ private static vitalsCleanup;
62
82
  /** The underlying feature flags client instance. */
63
83
  readonly flags: NexusFlagsClient;
64
84
  /** The underlying error tracker client instance. */
@@ -71,9 +91,6 @@ declare class Nexus {
71
91
  *
72
92
  * @param options - SDK configuration options.
73
93
  * @returns The initialized Nexus singleton instance.
74
- *
75
- * @example
76
- * const nexus = Nexus.init({ apiKey: 'pk_live_...' });
77
94
  */
78
95
  static init(options?: NexusInitOptions): Nexus;
79
96
  /**
@@ -81,73 +98,52 @@ declare class Nexus {
81
98
  *
82
99
  * @returns The active Nexus instance.
83
100
  * @throws {Error} If `Nexus.init()` has not been called yet.
84
- *
85
- * @example
86
- * const nexus = Nexus.getInstance();
87
- * nexus.flags.isEnabled('checkout_v2');
88
101
  */
89
102
  static getInstance(): Nexus;
90
103
  /**
91
104
  * Convenience method: Check if a feature flag is enabled.
92
- *
93
- * @param key - Flag identifier.
94
- * @param defaultValue - Fallback if flag is missing.
95
- * @returns Boolean enabled state.
96
- *
97
- * @example
98
- * if (Nexus.isEnabled('checkout_redesign')) { ... }
99
105
  */
100
106
  static isEnabled(key: string, defaultValue?: boolean): boolean;
101
107
  /**
102
108
  * Convenience method: Get a flag variant value.
103
- *
104
- * @param key - Flag identifier.
105
- * @param variantKey - Variant property name.
106
- * @param defaultValue - Fallback value.
107
- * @returns Variant value cast to type T.
108
- *
109
- * @example
110
- * const rate = Nexus.getVariant<number>('promo_banner_v2', 'discount_rate', 10);
111
109
  */
112
110
  static getVariant<T = unknown>(key: string, variantKey: string, defaultValue?: T): T;
113
111
  /**
114
112
  * Convenience method: Capture an error manually.
115
- *
116
- * @param error - Error instance, string, or unknown value.
117
- * @param extra - Optional metadata tags.
118
- *
119
- * @example
120
- * Nexus.captureError(new TypeError('Cannot read properties of null'));
121
113
  */
122
114
  static captureError(error: unknown, extra?: Record<string, unknown>): void;
115
+ /**
116
+ * Convenience method: Capture an informational or warning message event.
117
+ */
118
+ static captureMessage(message: string, level?: SeverityLevel, extra?: Record<string, unknown>): void;
119
+ /**
120
+ * Convenience method: Add a breadcrumb manually.
121
+ */
122
+ static addBreadcrumb(breadcrumb: Breadcrumb): void;
123
+ /**
124
+ * Convenience method: Set extra contextual metadata.
125
+ */
126
+ static setExtra(key: string, value: unknown): void;
127
+ /**
128
+ * Convenience method: Attach Web Vitals observer to tracker.
129
+ */
130
+ static attachWebVitals(options?: PerformanceVitalsOptions): () => void;
123
131
  /**
124
132
  * Convenience method: Update user context for both flags and tracker.
125
- *
126
- * @param user - New user context (merged with existing).
127
- * @returns Promise resolving after flags refresh.
128
- *
129
- * @example
130
- * await Nexus.identify({ id: 'usr_12345', country: 'VN' });
131
133
  */
132
134
  static identify(user: UserContext): Promise<void>;
133
135
  /**
134
136
  * Resets user context to anonymous state (e.g. on logout).
135
- *
136
- * @example
137
- * Nexus.reset(); // called on user logout
138
137
  */
139
138
  static reset(): void;
140
139
  /**
141
140
  * Gracefully tears down both clients, closing SSE connections and flushing pending events.
142
- *
143
- * @example
144
- * await Nexus.destroy();
145
141
  */
146
142
  static destroy(): Promise<void>;
147
143
  }
148
144
 
149
145
  /**
150
- * @fileoverview React Context Provider and hooks for the Nexus SDK umbrella.
146
+ * @fileoverview React Context Provider, hooks, and Error Boundary for the Nexus SDK.
151
147
  * @module @nexus/sdk/react
152
148
  */
153
149
 
@@ -156,12 +152,13 @@ interface NexusContextValue {
156
152
  tracker: NexusTrackerClient;
157
153
  nexus: Nexus;
158
154
  }
155
+ declare const NexusContext: React.Context<NexusContextValue | null>;
159
156
  /**
160
157
  * Props for the NexusProvider component.
161
158
  */
162
159
  interface NexusProviderProps extends NexusInitOptions {
163
160
  /** Child components that will have access to Nexus context. */
164
- children?: React.ReactNode | any;
161
+ children?: React.ReactNode;
165
162
  }
166
163
  /**
167
164
  * React Context Provider that initializes the Nexus SDK and makes it available
@@ -194,11 +191,6 @@ declare function NexusProvider({ children, ...initOptions }: NexusProviderProps)
194
191
  *
195
192
  * @returns Object with `flags`, `tracker`, and `nexus` properties.
196
193
  * @throws {Error} If called outside of a `NexusProvider`.
197
- *
198
- * @example
199
- * const { flags, tracker } = useNexus();
200
- * const enabled = flags.isEnabled('checkout_v2');
201
- * tracker.captureError(new Error('something failed'));
202
194
  */
203
195
  declare function useNexus(): NexusContextValue;
204
196
  /**
@@ -209,14 +201,6 @@ interface UseFlagResult {
209
201
  enabled: boolean;
210
202
  /**
211
203
  * Retrieves a specific variant value from this flag.
212
- *
213
- * @param variantKey - Property name inside the variants object.
214
- * @param defaultValue - Fallback if variant is not found.
215
- * @returns Typed variant value.
216
- *
217
- * @example
218
- * const { enabled, getVariant } = useFlag('promo_banner_v2');
219
- * const rate = getVariant<number>('discount_rate', 10);
220
204
  */
221
205
  getVariant: <T = unknown>(variantKey: string, defaultValue?: T) => T;
222
206
  /** Full evaluation result including reason and version. */
@@ -224,26 +208,77 @@ interface UseFlagResult {
224
208
  }
225
209
  /**
226
210
  * React hook for subscribing to a feature flag's real-time state.
211
+ */
212
+ declare function useFlag(key: string, defaultEnabled?: boolean): UseFlagResult;
213
+ interface NexusGuardProps {
214
+ /** Component tree to wrap with error boundary protection. */
215
+ children?: React.ReactNode;
216
+ /**
217
+ * Custom fallback UI to display on crash.
218
+ * Can be a static ReactNode or a render function receiving `NexusErrorInfo`.
219
+ */
220
+ fallback?: React.ReactNode | ((info: NexusErrorInfo) => React.ReactNode);
221
+ /** Callback fired immediately when an error is caught. */
222
+ onError?: (error: Error, info: NexusErrorInfo) => void;
223
+ /** Automatically resets the error boundary on browser navigation / popstate. */
224
+ resetOnNavigation?: boolean;
225
+ /** Array of values that will trigger an automatic reset when changed. */
226
+ resetKeys?: unknown[];
227
+ /** Custom tags attached to errors captured by this guard. */
228
+ tags?: Record<string, string>;
229
+ /** Optional custom tracker instance. Defaults to context or singleton. */
230
+ tracker?: NexusTrackerClient;
231
+ }
232
+ interface NexusGuardState {
233
+ hasError: boolean;
234
+ errorInfo: NexusErrorInfo | null;
235
+ }
236
+ /**
237
+ * React Error Boundary component powered by Nexus telemetry.
238
+ * Prevents whole-app white-screen crashes, sends crash telemetry with component stack traces,
239
+ * and allows customized graceful degradation.
227
240
  *
228
- * Automatically re-renders when the flag changes via SSE updates.
229
- * Returns a stable result without triggering additional re-renders if unchanged.
230
- *
231
- * @param key - Flag programmatic identifier.
232
- * @param defaultEnabled - Fallback boolean if flag is not yet evaluated. Defaults to false.
233
- * @returns {@link UseFlagResult} with `enabled`, `getVariant`, and `result`.
241
+ * @example
242
+ * // Global app guard with custom render fallback
243
+ * <NexusGuard fallback={({ error, errorId, reset }) => (
244
+ * <div className="error-card">
245
+ * <h2>Something went wrong</h2>
246
+ * <p>Support Reference: {errorId}</p>
247
+ * <button onClick={reset}>Try Again</button>
248
+ * </div>
249
+ * )}>
250
+ * <App />
251
+ * </NexusGuard>
234
252
  *
235
253
  * @example
236
- * function CheckoutButton() {
237
- * const { enabled, getVariant } = useFlag('checkout_redesign');
238
- * const theme = getVariant<string>('theme', 'default');
254
+ * // Isolated widget guard (e.g. sidebar or checkout card)
255
+ * <NexusGuard fallback={<p>Sidebar temporarily unavailable.</p>}>
256
+ * <Sidebar />
257
+ * </NexusGuard>
258
+ */
259
+ declare class NexusGuard extends React.Component<NexusGuardProps, NexusGuardState> {
260
+ static contextType: React.Context<NexusContextValue | null>;
261
+ context: React.ContextType<typeof NexusContext>;
262
+ constructor(props: NexusGuardProps);
263
+ static getDerivedStateFromError(error: Error): Partial<NexusGuardState>;
264
+ componentDidCatch(error: Error, reactInfo: React.ErrorInfo): void;
265
+ componentDidMount(): void;
266
+ componentDidUpdate(prevProps: NexusGuardProps): void;
267
+ componentWillUnmount(): void;
268
+ private handleNavigation;
269
+ /** Resets the error boundary state back to healthy rendering. */
270
+ reset: () => void;
271
+ render(): React.ReactNode;
272
+ }
273
+ /**
274
+ * Higher-Order Component (HOC) variant of NexusGuard.
275
+ * Wraps any component with an isolated Error Boundary.
239
276
  *
240
- * return (
241
- * <button className={enabled ? `btn-${theme}` : 'btn-default'}>
242
- * {enabled ? 'New Checkout' : 'Checkout'}
243
- * </button>
244
- * );
245
- * }
277
+ * @example
278
+ * const SafeSidebar = withNexusGuard(Sidebar, {
279
+ * fallback: <div>Sidebar error</div>,
280
+ * });
246
281
  */
247
- declare function useFlag(key: string, defaultEnabled?: boolean): UseFlagResult;
282
+ declare function withNexusGuard<P extends object>(Component: React.ComponentType<P>, options?: Omit<NexusGuardProps, 'children'>): React.FC<P>;
248
283
 
249
- export { NexusProvider, type NexusProviderProps, type UseFlagResult, useFlag, useNexus };
284
+ export { NexusContext, type NexusContextValue, NexusGuard, type NexusGuardProps, type NexusGuardState, NexusProvider, type NexusProviderProps, type UseFlagResult, useFlag, useNexus, withNexusGuard };
package/dist/react.d.ts CHANGED
@@ -1,23 +1,34 @@
1
1
  import React from 'react';
2
- import { NexusFlagsOptions, NexusFlagsClient } from '@nexussdk/flags';
3
- import { NexusTrackerOptions, NexusTrackerClient } from '@nexussdk/tracker';
4
- import { UserContext, FlagEvaluationResult } from '@nexussdk/contracts';
2
+ import { NexusFlagsClient, NexusFlagsOptions } from '@nexussdk/flags';
3
+ import { NexusTrackerClient, NexusTrackerOptions } from '@nexussdk/tracker';
4
+ import { UserContext, SamplingConfig, TransportPlugin, PerformanceVitalsOptions, SeverityLevel, Breadcrumb, NexusErrorInfo, FlagEvaluationResult } from '@nexussdk/contracts';
5
5
 
6
6
  /**
7
7
  * @fileoverview Unified Nexus singleton facade wrapping Flags and Tracker clients.
8
8
  * @module @nexus/sdk/nexus
9
9
  */
10
10
 
11
+ /**
12
+ * Dev server routing configuration.
13
+ */
14
+ interface NexusDevServerOptions {
15
+ /** Enable local dev server ingestion mode. Auto-enabled in development if true. */
16
+ enabled?: boolean;
17
+ /** Port the dev server is listening on. Defaults to 4567. */
18
+ port?: number;
19
+ /** Host the dev server is bound to. Defaults to 'localhost'. */
20
+ host?: string;
21
+ }
11
22
  /**
12
23
  * Unified initialization options for the Nexus SDK umbrella.
13
24
  *
14
25
  * @example
15
26
  * Nexus.init({
16
27
  * apiKey: 'pk_live_...',
17
- * baseUrl: 'http://localhost:8080',
18
28
  * user: { id: 'usr_12345', country: 'VN' },
19
29
  * environment: 'production',
20
30
  * autoCapture: true,
31
+ * devServer: { enabled: process.env.NODE_ENV === 'development' },
21
32
  * });
22
33
  */
23
34
  interface NexusInitOptions {
@@ -33,6 +44,14 @@ interface NexusInitOptions {
33
44
  tags?: Record<string, string>;
34
45
  /** Toggle automated global error capture. Defaults to true. */
35
46
  autoCapture?: boolean;
47
+ /** Client-side rate limiting and deduplication sampling options. */
48
+ sampling?: SamplingConfig;
49
+ /** Pluggable transport adapter ('fetch', 'console', 'localStorage', custom fn). */
50
+ transport?: TransportPlugin;
51
+ /** Web Vitals performance observer options, or true for default observation. */
52
+ vitals?: boolean | PerformanceVitalsOptions;
53
+ /** Local dev server auto-routing configuration. */
54
+ devServer?: NexusDevServerOptions;
36
55
  /** Additional flags-specific options. */
37
56
  flags?: Partial<NexusFlagsOptions>;
38
57
  /** Additional tracker-specific options. */
@@ -59,6 +78,7 @@ interface NexusInitOptions {
59
78
  */
60
79
  declare class Nexus {
61
80
  private static instance;
81
+ private static vitalsCleanup;
62
82
  /** The underlying feature flags client instance. */
63
83
  readonly flags: NexusFlagsClient;
64
84
  /** The underlying error tracker client instance. */
@@ -71,9 +91,6 @@ declare class Nexus {
71
91
  *
72
92
  * @param options - SDK configuration options.
73
93
  * @returns The initialized Nexus singleton instance.
74
- *
75
- * @example
76
- * const nexus = Nexus.init({ apiKey: 'pk_live_...' });
77
94
  */
78
95
  static init(options?: NexusInitOptions): Nexus;
79
96
  /**
@@ -81,73 +98,52 @@ declare class Nexus {
81
98
  *
82
99
  * @returns The active Nexus instance.
83
100
  * @throws {Error} If `Nexus.init()` has not been called yet.
84
- *
85
- * @example
86
- * const nexus = Nexus.getInstance();
87
- * nexus.flags.isEnabled('checkout_v2');
88
101
  */
89
102
  static getInstance(): Nexus;
90
103
  /**
91
104
  * Convenience method: Check if a feature flag is enabled.
92
- *
93
- * @param key - Flag identifier.
94
- * @param defaultValue - Fallback if flag is missing.
95
- * @returns Boolean enabled state.
96
- *
97
- * @example
98
- * if (Nexus.isEnabled('checkout_redesign')) { ... }
99
105
  */
100
106
  static isEnabled(key: string, defaultValue?: boolean): boolean;
101
107
  /**
102
108
  * Convenience method: Get a flag variant value.
103
- *
104
- * @param key - Flag identifier.
105
- * @param variantKey - Variant property name.
106
- * @param defaultValue - Fallback value.
107
- * @returns Variant value cast to type T.
108
- *
109
- * @example
110
- * const rate = Nexus.getVariant<number>('promo_banner_v2', 'discount_rate', 10);
111
109
  */
112
110
  static getVariant<T = unknown>(key: string, variantKey: string, defaultValue?: T): T;
113
111
  /**
114
112
  * Convenience method: Capture an error manually.
115
- *
116
- * @param error - Error instance, string, or unknown value.
117
- * @param extra - Optional metadata tags.
118
- *
119
- * @example
120
- * Nexus.captureError(new TypeError('Cannot read properties of null'));
121
113
  */
122
114
  static captureError(error: unknown, extra?: Record<string, unknown>): void;
115
+ /**
116
+ * Convenience method: Capture an informational or warning message event.
117
+ */
118
+ static captureMessage(message: string, level?: SeverityLevel, extra?: Record<string, unknown>): void;
119
+ /**
120
+ * Convenience method: Add a breadcrumb manually.
121
+ */
122
+ static addBreadcrumb(breadcrumb: Breadcrumb): void;
123
+ /**
124
+ * Convenience method: Set extra contextual metadata.
125
+ */
126
+ static setExtra(key: string, value: unknown): void;
127
+ /**
128
+ * Convenience method: Attach Web Vitals observer to tracker.
129
+ */
130
+ static attachWebVitals(options?: PerformanceVitalsOptions): () => void;
123
131
  /**
124
132
  * Convenience method: Update user context for both flags and tracker.
125
- *
126
- * @param user - New user context (merged with existing).
127
- * @returns Promise resolving after flags refresh.
128
- *
129
- * @example
130
- * await Nexus.identify({ id: 'usr_12345', country: 'VN' });
131
133
  */
132
134
  static identify(user: UserContext): Promise<void>;
133
135
  /**
134
136
  * Resets user context to anonymous state (e.g. on logout).
135
- *
136
- * @example
137
- * Nexus.reset(); // called on user logout
138
137
  */
139
138
  static reset(): void;
140
139
  /**
141
140
  * Gracefully tears down both clients, closing SSE connections and flushing pending events.
142
- *
143
- * @example
144
- * await Nexus.destroy();
145
141
  */
146
142
  static destroy(): Promise<void>;
147
143
  }
148
144
 
149
145
  /**
150
- * @fileoverview React Context Provider and hooks for the Nexus SDK umbrella.
146
+ * @fileoverview React Context Provider, hooks, and Error Boundary for the Nexus SDK.
151
147
  * @module @nexus/sdk/react
152
148
  */
153
149
 
@@ -156,12 +152,13 @@ interface NexusContextValue {
156
152
  tracker: NexusTrackerClient;
157
153
  nexus: Nexus;
158
154
  }
155
+ declare const NexusContext: React.Context<NexusContextValue | null>;
159
156
  /**
160
157
  * Props for the NexusProvider component.
161
158
  */
162
159
  interface NexusProviderProps extends NexusInitOptions {
163
160
  /** Child components that will have access to Nexus context. */
164
- children?: React.ReactNode | any;
161
+ children?: React.ReactNode;
165
162
  }
166
163
  /**
167
164
  * React Context Provider that initializes the Nexus SDK and makes it available
@@ -194,11 +191,6 @@ declare function NexusProvider({ children, ...initOptions }: NexusProviderProps)
194
191
  *
195
192
  * @returns Object with `flags`, `tracker`, and `nexus` properties.
196
193
  * @throws {Error} If called outside of a `NexusProvider`.
197
- *
198
- * @example
199
- * const { flags, tracker } = useNexus();
200
- * const enabled = flags.isEnabled('checkout_v2');
201
- * tracker.captureError(new Error('something failed'));
202
194
  */
203
195
  declare function useNexus(): NexusContextValue;
204
196
  /**
@@ -209,14 +201,6 @@ interface UseFlagResult {
209
201
  enabled: boolean;
210
202
  /**
211
203
  * Retrieves a specific variant value from this flag.
212
- *
213
- * @param variantKey - Property name inside the variants object.
214
- * @param defaultValue - Fallback if variant is not found.
215
- * @returns Typed variant value.
216
- *
217
- * @example
218
- * const { enabled, getVariant } = useFlag('promo_banner_v2');
219
- * const rate = getVariant<number>('discount_rate', 10);
220
204
  */
221
205
  getVariant: <T = unknown>(variantKey: string, defaultValue?: T) => T;
222
206
  /** Full evaluation result including reason and version. */
@@ -224,26 +208,77 @@ interface UseFlagResult {
224
208
  }
225
209
  /**
226
210
  * React hook for subscribing to a feature flag's real-time state.
211
+ */
212
+ declare function useFlag(key: string, defaultEnabled?: boolean): UseFlagResult;
213
+ interface NexusGuardProps {
214
+ /** Component tree to wrap with error boundary protection. */
215
+ children?: React.ReactNode;
216
+ /**
217
+ * Custom fallback UI to display on crash.
218
+ * Can be a static ReactNode or a render function receiving `NexusErrorInfo`.
219
+ */
220
+ fallback?: React.ReactNode | ((info: NexusErrorInfo) => React.ReactNode);
221
+ /** Callback fired immediately when an error is caught. */
222
+ onError?: (error: Error, info: NexusErrorInfo) => void;
223
+ /** Automatically resets the error boundary on browser navigation / popstate. */
224
+ resetOnNavigation?: boolean;
225
+ /** Array of values that will trigger an automatic reset when changed. */
226
+ resetKeys?: unknown[];
227
+ /** Custom tags attached to errors captured by this guard. */
228
+ tags?: Record<string, string>;
229
+ /** Optional custom tracker instance. Defaults to context or singleton. */
230
+ tracker?: NexusTrackerClient;
231
+ }
232
+ interface NexusGuardState {
233
+ hasError: boolean;
234
+ errorInfo: NexusErrorInfo | null;
235
+ }
236
+ /**
237
+ * React Error Boundary component powered by Nexus telemetry.
238
+ * Prevents whole-app white-screen crashes, sends crash telemetry with component stack traces,
239
+ * and allows customized graceful degradation.
227
240
  *
228
- * Automatically re-renders when the flag changes via SSE updates.
229
- * Returns a stable result without triggering additional re-renders if unchanged.
230
- *
231
- * @param key - Flag programmatic identifier.
232
- * @param defaultEnabled - Fallback boolean if flag is not yet evaluated. Defaults to false.
233
- * @returns {@link UseFlagResult} with `enabled`, `getVariant`, and `result`.
241
+ * @example
242
+ * // Global app guard with custom render fallback
243
+ * <NexusGuard fallback={({ error, errorId, reset }) => (
244
+ * <div className="error-card">
245
+ * <h2>Something went wrong</h2>
246
+ * <p>Support Reference: {errorId}</p>
247
+ * <button onClick={reset}>Try Again</button>
248
+ * </div>
249
+ * )}>
250
+ * <App />
251
+ * </NexusGuard>
234
252
  *
235
253
  * @example
236
- * function CheckoutButton() {
237
- * const { enabled, getVariant } = useFlag('checkout_redesign');
238
- * const theme = getVariant<string>('theme', 'default');
254
+ * // Isolated widget guard (e.g. sidebar or checkout card)
255
+ * <NexusGuard fallback={<p>Sidebar temporarily unavailable.</p>}>
256
+ * <Sidebar />
257
+ * </NexusGuard>
258
+ */
259
+ declare class NexusGuard extends React.Component<NexusGuardProps, NexusGuardState> {
260
+ static contextType: React.Context<NexusContextValue | null>;
261
+ context: React.ContextType<typeof NexusContext>;
262
+ constructor(props: NexusGuardProps);
263
+ static getDerivedStateFromError(error: Error): Partial<NexusGuardState>;
264
+ componentDidCatch(error: Error, reactInfo: React.ErrorInfo): void;
265
+ componentDidMount(): void;
266
+ componentDidUpdate(prevProps: NexusGuardProps): void;
267
+ componentWillUnmount(): void;
268
+ private handleNavigation;
269
+ /** Resets the error boundary state back to healthy rendering. */
270
+ reset: () => void;
271
+ render(): React.ReactNode;
272
+ }
273
+ /**
274
+ * Higher-Order Component (HOC) variant of NexusGuard.
275
+ * Wraps any component with an isolated Error Boundary.
239
276
  *
240
- * return (
241
- * <button className={enabled ? `btn-${theme}` : 'btn-default'}>
242
- * {enabled ? 'New Checkout' : 'Checkout'}
243
- * </button>
244
- * );
245
- * }
277
+ * @example
278
+ * const SafeSidebar = withNexusGuard(Sidebar, {
279
+ * fallback: <div>Sidebar error</div>,
280
+ * });
246
281
  */
247
- declare function useFlag(key: string, defaultEnabled?: boolean): UseFlagResult;
282
+ declare function withNexusGuard<P extends object>(Component: React.ComponentType<P>, options?: Omit<NexusGuardProps, 'children'>): React.FC<P>;
248
283
 
249
- export { NexusProvider, type NexusProviderProps, type UseFlagResult, useFlag, useNexus };
284
+ export { NexusContext, type NexusContextValue, NexusGuard, type NexusGuardProps, type NexusGuardState, NexusProvider, type NexusProviderProps, type UseFlagResult, useFlag, useNexus, withNexusGuard };
package/dist/react.mjs CHANGED
@@ -1 +1,2 @@
1
- import N,{createContext,useRef,useMemo,useEffect,useContext,useState}from'react';import {NexusFlagsClient}from'@nexussdk/flags';import {NexusTrackerClient}from'@nexussdk/tracker';var a=class e{static instance=null;flags;tracker;constructor(n={}){let{apiKey:t,baseUrl:s,user:i,environment:r,tags:u,autoCapture:o,flags:p,tracker:g}=n;this.flags=new NexusFlagsClient({apiKey:t,baseUrl:s,user:i,...p}),this.tracker=new NexusTrackerClient({apiKey:t,baseUrl:s,environment:r,tags:u,autoCapture:o,...g});}static init(n={}){return e.instance||(e.instance=new e(n)),e.instance}static getInstance(){if(!e.instance)throw new Error('[Nexus SDK] Not initialized. Call Nexus.init({ apiKey: "..." }) first.');return e.instance}static isEnabled(n,t=false){return e.getInstance().flags.isEnabled(n,t)}static getVariant(n,t,s){return e.getInstance().flags.getVariant(n,t,s)}static captureError(n,t){e.getInstance().tracker.captureError(n,t);}static async identify(n){let t=e.getInstance();t.tracker.setUser(n),await t.flags.identify(n);}static reset(){let n=e.getInstance();n.tracker.setUser(null),n.flags.reset();}static async destroy(){e.instance&&(await e.instance.tracker.flush(),e.instance.tracker.destroy(),e.instance.flags.destroy(),e.instance=null);}};var x=createContext(null);function y({children:e,...n}){let t=useRef(null);t.current||(t.current=a.init(n));let s=useMemo(()=>({flags:t.current.flags,tracker:t.current.tracker,nexus:t.current}),[]);return useEffect(()=>()=>{a.destroy();},[]),N.createElement(x.Provider,{value:s},e)}function C(){let e=useContext(x);if(!e)throw new Error("[Nexus SDK] useNexus() must be called inside a <NexusProvider>.");return e}function I(e,n=false){let{flags:t}=C(),[s,i]=useState(()=>t.isEnabled(e)!==n?{key:e,enabled:t.isEnabled(e),variants:{},reason:"DEFAULT_ENABLED",version:0}:null);return useEffect(()=>{let r=t.isEnabled(e,n);return r!==(s?.enabled??n)&&i({key:e,enabled:r,variants:{},reason:"DEFAULT_ENABLED",version:0}),t.onFlagChange(e,o=>{i(o);})},[e]),useMemo(()=>({enabled:s?.enabled??t.isEnabled(e,n),getVariant:(r,u)=>t.getVariant(e,r,u),result:s}),[s,e,t,n])}export{y as NexusProvider,I as useFlag,C as useNexus};
1
+ import i,{createContext,useRef,useMemo,useEffect,useContext,useState}from'react';import {NexusFlagsClient}from'@nexussdk/flags';import {parseStackTrace,computeFingerprint,NexusTrackerClient,attachWebVitals}from'@nexussdk/tracker';var l=class t{static instance=null;static vitalsCleanup=null;flags;tracker;constructor(e={}){let{apiKey:r,baseUrl:n,user:s,environment:a,tags:o,autoCapture:u,sampling:m,transport:d,vitals:c,devServer:p,flags:y,tracker:C}=e;this.flags=new NexusFlagsClient({apiKey:r,baseUrl:n,user:s,...y});let N=n,R=d;if(p?.enabled){let f=p.port??4567;N=`http://${p.host??"localhost"}:${f}`;}if(this.tracker=new NexusTrackerClient({apiKey:r,baseUrl:N,environment:a,tags:o,autoCapture:u,sampling:m,transport:R,...C}),c){let f=typeof c=="object"?c:{};t.vitalsCleanup=attachWebVitals(this.tracker,f);}}static init(e={}){return t.instance||(t.instance=new t(e)),t.instance}static getInstance(){if(!t.instance)throw new Error('[Nexus SDK] Not initialized. Call Nexus.init({ apiKey: "..." }) first.');return t.instance}static isEnabled(e,r=false){return t.getInstance().flags.isEnabled(e,r)}static getVariant(e,r,n){return t.getInstance().flags.getVariant(e,r,n)}static captureError(e,r){t.getInstance().tracker.captureError(e,r);}static captureMessage(e,r="info",n){t.getInstance().tracker.captureMessage(e,r,n);}static addBreadcrumb(e){t.getInstance().tracker.addBreadcrumb(e);}static setExtra(e,r){t.getInstance().tracker.setExtra(e,r);}static attachWebVitals(e){return attachWebVitals(t.getInstance().tracker,e)}static async identify(e){let r=t.getInstance();r.tracker.setUser(e),await r.flags.identify(e);}static reset(){let e=t.getInstance();e.tracker.setUser(null),e.flags.reset();}static async destroy(){t.vitalsCleanup&&(t.vitalsCleanup(),t.vitalsCleanup=null),t.instance&&(await t.instance.tracker.flush(),t.instance.tracker.destroy(),t.instance.flags.destroy(),t.instance=null);}};var g=createContext(null);function M({children:t,...e}){let r=useRef(null);r.current||(r.current=l.init(e));let n=useMemo(()=>({flags:r.current.flags,tracker:r.current.tracker,nexus:r.current}),[]);return useEffect(()=>()=>{l.destroy();},[]),i.createElement(g.Provider,{value:n},t)}function O(){let t=useContext(g);if(!t)throw new Error("[Nexus SDK] useNexus() must be called inside a <NexusProvider>.");return t}function W(t,e=false){let{flags:r}=O(),[n,s]=useState(()=>r.isEnabled(t)!==e?{key:t,enabled:r.isEnabled(t),variants:{},reason:"DEFAULT_ENABLED",version:0}:null);return useEffect(()=>{let a=r.isEnabled(t,e);return a!==(n?.enabled??e)&&s({key:t,enabled:a,variants:{},reason:"DEFAULT_ENABLED",version:0}),r.onFlagChange(t,u=>{s(u);})},[t]),useMemo(()=>({enabled:n?.enabled??r.isEnabled(t,e),getVariant:(a,o)=>r.getVariant(t,a,o),result:n}),[n,t,r,e])}var x=class extends i.Component{static contextType=g;constructor(e){super(e),this.state={hasError:false,errorInfo:null};}static getDerivedStateFromError(e){let r=parseStackTrace(e.stack),s=`NX-${computeFingerprint(e.name,e.message,r[0]).replace(/^fp_/,"").slice(0,8).toUpperCase()}`;return {hasError:true,errorInfo:{error:e,errorId:s,reset:()=>{}}}}componentDidCatch(e,r){let{onError:n,tags:s,tracker:a}=this.props,o=a;if(!o&&this.context?.tracker&&(o=this.context.tracker),!o)try{o=l.getInstance().tracker;}catch{}let u=parseStackTrace(e.stack),d=`NX-${computeFingerprint(e.name,e.message,u[0]).replace(/^fp_/,"").slice(0,8).toUpperCase()}`,c={error:e,errorId:d,componentStack:r.componentStack??void 0,reset:()=>this.reset()};if(this.setState({errorInfo:c}),o&&o.captureError(e,{tags:{...s,guardErrorId:d},componentStack:r.componentStack??void 0}),n)try{n(e,c);}catch(p){typeof console<"u"&&console.warn("[NexusGuard] onError handler threw:",p);}}componentDidMount(){this.props.resetOnNavigation&&typeof window<"u"&&window.addEventListener("popstate",this.handleNavigation);}componentDidUpdate(e){let{resetKeys:r}=this.props;this.state.hasError&&r&&e.resetKeys&&r.some((s,a)=>s!==e.resetKeys[a])&&this.reset();}componentWillUnmount(){this.props.resetOnNavigation&&typeof window<"u"&&window.removeEventListener("popstate",this.handleNavigation);}handleNavigation=()=>{this.state.hasError&&this.reset();};reset=()=>{this.setState({hasError:false,errorInfo:null});};render(){let{hasError:e,errorInfo:r}=this.state,{fallback:n,children:s}=this.props;return e&&r?typeof n=="function"?n(r):n||i.createElement("div",{role:"alert",style:{padding:"16px 20px",margin:"12px 0",borderRadius:"8px",backgroundColor:"#1f1315",border:"1px solid #7f1d1d",color:"#fca5a5",fontFamily:"-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif",fontSize:"14px",lineHeight:1.5}},i.createElement("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:"8px"}},i.createElement("strong",{style:{color:"#ef4444"}},"Application Error Encountered"),i.createElement("span",{style:{fontSize:"12px",fontFamily:"monospace",background:"#450a0a",padding:"2px 8px",borderRadius:"4px"}},r.errorId)),i.createElement("p",{style:{margin:"0 0 12px 0",color:"#fecaca"}},r.error.message),i.createElement("button",{type:"button",onClick:()=>this.reset(),style:{background:"#b91c1c",border:"none",color:"#fff",padding:"6px 14px",borderRadius:"6px",fontSize:"13px",cursor:"pointer",fontWeight:500}},"Retry")):s}};function $(t,e={}){let r=s=>i.createElement(x,e,i.createElement(t,s)),n=t.displayName||t.name||"Component";return r.displayName=`withNexusGuard(${n})`,r}
2
+ export{g as NexusContext,x as NexusGuard,M as NexusProvider,W as useFlag,O as useNexus,$ as withNexusGuard};
package/dist/vue.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var vue=require('vue'),flags=require('@nexussdk/flags'),tracker=require('@nexussdk/tracker');var p=class e{static instance=null;static vitalsCleanup=null;flags;tracker;constructor(t={}){let{apiKey:n,baseUrl:r,user:o,environment:a,tags:s,autoCapture:i,sampling:f,transport:x,vitals:l,devServer:c,flags:g,tracker:C}=t;this.flags=new flags.NexusFlagsClient({apiKey:n,baseUrl:r,user:o,...g});let m=r,I=x;if(c?.enabled){let d=c.port??4567;m=`http://${c.host??"localhost"}:${d}`;}if(this.tracker=new tracker.NexusTrackerClient({apiKey:n,baseUrl:m,environment:a,tags:s,autoCapture:i,sampling:f,transport:I,...C}),l){let d=typeof l=="object"?l:{};e.vitalsCleanup=tracker.attachWebVitals(this.tracker,d);}}static init(t={}){return e.instance||(e.instance=new e(t)),e.instance}static getInstance(){if(!e.instance)throw new Error('[Nexus SDK] Not initialized. Call Nexus.init({ apiKey: "..." }) first.');return e.instance}static isEnabled(t,n=false){return e.getInstance().flags.isEnabled(t,n)}static getVariant(t,n,r){return e.getInstance().flags.getVariant(t,n,r)}static captureError(t,n){e.getInstance().tracker.captureError(t,n);}static captureMessage(t,n="info",r){e.getInstance().tracker.captureMessage(t,n,r);}static addBreadcrumb(t){e.getInstance().tracker.addBreadcrumb(t);}static setExtra(t,n){e.getInstance().tracker.setExtra(t,n);}static attachWebVitals(t){return tracker.attachWebVitals(e.getInstance().tracker,t)}static async identify(t){let n=e.getInstance();n.tracker.setUser(t),await n.flags.identify(t);}static reset(){let t=e.getInstance();t.tracker.setUser(null),t.flags.reset();}static async destroy(){e.vitalsCleanup&&(e.vitalsCleanup(),e.vitalsCleanup=null),e.instance&&(await e.instance.tracker.flush(),e.instance.tracker.destroy(),e.instance.flags.destroy(),e.instance=null);}};var y=Symbol("NexusSDK"),H={install(e,t={}){let n=p.init(t),r={flags:n.flags,tracker:n.tracker,nexus:n};e.provide(y,r);let o=e.config.errorHandler;e.config.errorHandler=(a,s,i)=>{n.tracker.captureError(a,{componentStack:i,tags:{framework:"vue3",component:s?.$options?.name||"AnonymousComponent"}}),o&&o(a,s,i);},e.component("NexusGuard",R);}};function N(){let e=vue.inject(y);if(e)return e;let t=p.getInstance();return {flags:t.flags,tracker:t.tracker,nexus:t}}function A(e,t=false){let{flags:n}=N(),r=vue.ref(n.isEnabled(e,t)),o=vue.shallowRef(null),a=n.onFlagChange(e,s=>{r.value=s.enabled,o.value=s;});return vue.onUnmounted(()=>{a();}),{enabled:r,getVariant:(s,i)=>n.getVariant(e,s,i),result:o}}var R=vue.defineComponent({name:"NexusGuard",props:{onError:{type:Function,default:void 0},tags:{type:Object,default:()=>({})}},setup(e,{slots:t}){let n=vue.ref(false),r=vue.shallowRef(null),o=()=>{n.value=false,r.value=null;};return vue.onErrorCaptured((a,s,i)=>{let f=tracker.parseStackTrace(a.stack),l=`NX-${tracker.computeFingerprint(a.name,a.message,f[0]).replace(/^fp_/,"").slice(0,8).toUpperCase()}`,c={error:a,errorId:l,componentStack:i,reset:o};n.value=true,r.value=c;try{let{tracker:g}=N();g.captureError(a,{componentStack:i,tags:{...e.tags,guardErrorId:l,framework:"vue3",component:s?.$options?.name||"AnonymousComponent"}});}catch{}if(e.onError)try{e.onError(a,c);}catch{}return false}),()=>n.value&&r.value?t.fallback?t.fallback(r.value):vue.h("div",{role:"alert",style:{padding:"16px 20px",margin:"12px 0",borderRadius:"8px",backgroundColor:"#1f1315",border:"1px solid #7f1d1d",color:"#fca5a5",fontFamily:"-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif",fontSize:"14px"}},[vue.h("div",{style:{display:"flex",justifyContent:"space-between",marginBottom:"8px"}},[vue.h("strong",{style:{color:"#ef4444"}},"Vue Component Crash Protected"),vue.h("span",{style:{fontFamily:"monospace",fontSize:"12px",background:"#450a0a",padding:"2px 8px",borderRadius:"4px"}},r.value.errorId)]),vue.h("p",{style:{margin:"0 0 12px 0",color:"#fecaca"}},r.value.error.message),vue.h("button",{type:"button",onClick:o,style:{background:"#b91c1c",border:"none",color:"#fff",padding:"6px 14px",borderRadius:"6px",fontSize:"13px",cursor:"pointer",fontWeight:"500"}},"Retry Component")]):t.default?t.default():null}});
2
+ exports.NEXUS_KEY=y;exports.NexusGuardVue=R;exports.NexusPlugin=H;exports.useFlag=A;exports.useNexus=N;