@savvagent/solid 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +201 -16
- package/dist/index.d.ts +201 -16
- package/dist/index.js +271 -19
- package/dist/index.mjs +268 -27
- package/package.json +13 -7
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
import * as solid_js from 'solid-js';
|
|
2
2
|
import { ParentProps, Accessor } from 'solid-js';
|
|
3
3
|
import { FlagClientConfig, FlagClient, FlagContext, FlagEvaluationResult } from '@savvagent/sdk';
|
|
4
|
-
export { ApiTypes, ErrorEvent, EvaluationEvent, FlagClient, FlagClientConfig, FlagContext, FlagEvaluationResult, FlagUpdateEvent, components } from '@savvagent/sdk';
|
|
4
|
+
export { ApiTypes, ErrorEvent, EvaluationEvent, FlagClient, FlagClientConfig, FlagContext, FlagDefinition, FlagEvaluationResult, FlagUpdateEvent, components } from '@savvagent/sdk';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Default context values that apply to all flag evaluations
|
|
8
|
+
* Per SDK Developer Guide: https://docs.savvagent.com/sdk-developer-guide
|
|
9
|
+
*/
|
|
10
|
+
interface DefaultFlagContext {
|
|
11
|
+
/** Application ID for application-scoped flags */
|
|
12
|
+
applicationId?: string;
|
|
13
|
+
/** Environment (development, staging, production) */
|
|
14
|
+
environment?: string;
|
|
15
|
+
/** Organization ID for multi-tenant apps */
|
|
16
|
+
organizationId?: string;
|
|
17
|
+
/** Default user ID (required for percentage rollouts) */
|
|
18
|
+
userId?: string;
|
|
19
|
+
/** Default anonymous ID (alternative to userId for anonymous users) */
|
|
20
|
+
anonymousId?: string;
|
|
21
|
+
/** Session ID as fallback identifier */
|
|
22
|
+
sessionId?: string;
|
|
23
|
+
/** User's language code (e.g., "en", "es") */
|
|
24
|
+
language?: string;
|
|
25
|
+
/** Default attributes for targeting */
|
|
26
|
+
attributes?: Record<string, any>;
|
|
27
|
+
}
|
|
28
|
+
interface SavvagentContextValue {
|
|
29
|
+
client: FlagClient;
|
|
30
|
+
isReady: Accessor<boolean>;
|
|
31
|
+
defaultContext: Accessor<FlagContext>;
|
|
32
|
+
}
|
|
6
33
|
interface SavvagentProviderProps extends ParentProps {
|
|
7
34
|
config: FlagClientConfig;
|
|
35
|
+
/** Default context values applied to all flag evaluations */
|
|
36
|
+
defaultContext?: DefaultFlagContext;
|
|
8
37
|
}
|
|
9
38
|
/**
|
|
10
39
|
* Provider component that initializes and provides the Savvagent client.
|
|
@@ -15,7 +44,15 @@ interface SavvagentProviderProps extends ParentProps {
|
|
|
15
44
|
*
|
|
16
45
|
* function App() {
|
|
17
46
|
* return (
|
|
18
|
-
* <SavvagentProvider
|
|
47
|
+
* <SavvagentProvider
|
|
48
|
+
* config={{ apiKey: 'sdk_...' }}
|
|
49
|
+
* defaultContext={{
|
|
50
|
+
* applicationId: 'my-app-id',
|
|
51
|
+
* environment: 'development',
|
|
52
|
+
* userId: 'user-123',
|
|
53
|
+
* attributes: { plan: 'pro' }
|
|
54
|
+
* }}
|
|
55
|
+
* >
|
|
19
56
|
* <MyApp />
|
|
20
57
|
* </SavvagentProvider>
|
|
21
58
|
* );
|
|
@@ -24,10 +61,10 @@ interface SavvagentProviderProps extends ParentProps {
|
|
|
24
61
|
*/
|
|
25
62
|
declare function SavvagentProvider(props: SavvagentProviderProps): solid_js.JSX.Element;
|
|
26
63
|
/**
|
|
27
|
-
* Get the Savvagent client
|
|
64
|
+
* Get the Savvagent context including client, ready state, and default context.
|
|
28
65
|
* Must be used within a SavvagentProvider.
|
|
29
66
|
*
|
|
30
|
-
* @returns The FlagClient instance
|
|
67
|
+
* @returns The FlagClient instance, ready state accessor, and default context accessor
|
|
31
68
|
* @throws Error if used outside of SavvagentProvider
|
|
32
69
|
*
|
|
33
70
|
* @example
|
|
@@ -35,13 +72,17 @@ declare function SavvagentProvider(props: SavvagentProviderProps): solid_js.JSX.
|
|
|
35
72
|
* import { useSavvagent } from '@savvagent/solid';
|
|
36
73
|
*
|
|
37
74
|
* function MyComponent() {
|
|
38
|
-
* const client = useSavvagent();
|
|
39
|
-
*
|
|
40
|
-
* return
|
|
75
|
+
* const { client, isReady, defaultContext } = useSavvagent();
|
|
76
|
+
*
|
|
77
|
+
* return (
|
|
78
|
+
* <Show when={isReady()}>
|
|
79
|
+
* <div>Client ready!</div>
|
|
80
|
+
* </Show>
|
|
81
|
+
* );
|
|
41
82
|
* }
|
|
42
83
|
* ```
|
|
43
84
|
*/
|
|
44
|
-
declare function useSavvagent():
|
|
85
|
+
declare function useSavvagent(): SavvagentContextValue;
|
|
45
86
|
interface CreateFlagOptions {
|
|
46
87
|
/** Context for flag evaluation */
|
|
47
88
|
context?: FlagContext;
|
|
@@ -118,34 +159,178 @@ declare function createFlag(flagKey: string, options?: CreateFlagOptions): Creat
|
|
|
118
159
|
* ```
|
|
119
160
|
*/
|
|
120
161
|
declare function createFlagValue(flagKey: string, options?: CreateFlagOptions): Accessor<boolean>;
|
|
162
|
+
interface CreateFlagsOptions {
|
|
163
|
+
/** Context for flag evaluation */
|
|
164
|
+
context?: FlagContext;
|
|
165
|
+
/** Default values for flags (keyed by flag key) */
|
|
166
|
+
defaultValues?: Record<string, boolean>;
|
|
167
|
+
/** Enable real-time updates */
|
|
168
|
+
realtime?: boolean;
|
|
169
|
+
/** Custom error handler */
|
|
170
|
+
onError?: (error: Error, flagKey: string) => void;
|
|
171
|
+
}
|
|
172
|
+
interface CreateFlagsReturn {
|
|
173
|
+
/** Map of flag keys to their current values */
|
|
174
|
+
values: Accessor<Record<string, boolean>>;
|
|
175
|
+
/** Whether any flag is currently being evaluated */
|
|
176
|
+
loading: Accessor<boolean>;
|
|
177
|
+
/** Map of flag keys to their errors (if any) */
|
|
178
|
+
errors: Accessor<Record<string, Error | null>>;
|
|
179
|
+
/** Map of flag keys to their detailed evaluation results */
|
|
180
|
+
results: Accessor<Record<string, FlagEvaluationResult | null>>;
|
|
181
|
+
/** Force re-evaluation of all flags */
|
|
182
|
+
refetch: () => void;
|
|
183
|
+
}
|
|
121
184
|
/**
|
|
122
|
-
* Create
|
|
185
|
+
* Create reactive flags for multiple flag keys with a single state update.
|
|
186
|
+
* This is more efficient than using multiple createFlag calls when you need
|
|
187
|
+
* several flags in the same component.
|
|
123
188
|
*
|
|
124
|
-
* @
|
|
189
|
+
* @param flagKeys - Array of feature flag keys to evaluate
|
|
190
|
+
* @param options - Configuration options
|
|
191
|
+
* @returns Reactive flag state for all flags
|
|
125
192
|
*
|
|
126
193
|
* @example
|
|
127
194
|
* ```tsx
|
|
128
|
-
* import {
|
|
195
|
+
* import { createFlags } from '@savvagent/solid';
|
|
196
|
+
* import { Show, For } from 'solid-js';
|
|
197
|
+
*
|
|
198
|
+
* function MyComponent() {
|
|
199
|
+
* const flags = createFlags(
|
|
200
|
+
* ['feature-a', 'feature-b', 'feature-c'],
|
|
201
|
+
* {
|
|
202
|
+
* context: { user_id: 'user-123' },
|
|
203
|
+
* defaultValues: { 'feature-a': false, 'feature-b': true },
|
|
204
|
+
* realtime: true
|
|
205
|
+
* }
|
|
206
|
+
* );
|
|
207
|
+
*
|
|
208
|
+
* return (
|
|
209
|
+
* <Show when={!flags.loading()} fallback={<Spinner />}>
|
|
210
|
+
* <div>
|
|
211
|
+
* <Show when={flags.values()['feature-a']}>
|
|
212
|
+
* <FeatureA />
|
|
213
|
+
* </Show>
|
|
214
|
+
* <Show when={flags.values()['feature-b']}>
|
|
215
|
+
* <FeatureB />
|
|
216
|
+
* </Show>
|
|
217
|
+
* </div>
|
|
218
|
+
* </Show>
|
|
219
|
+
* );
|
|
220
|
+
* }
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
declare function createFlags(flagKeys: string[], options?: CreateFlagsOptions): CreateFlagsReturn;
|
|
224
|
+
/**
|
|
225
|
+
* Execute a callback conditionally based on a flag value.
|
|
226
|
+
*
|
|
227
|
+
* @param flagKey - The feature flag key to check
|
|
228
|
+
* @param callback - Function to execute if flag is enabled
|
|
229
|
+
* @param options - Configuration options
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```tsx
|
|
233
|
+
* import { createWithFlag } from '@savvagent/solid';
|
|
234
|
+
*
|
|
235
|
+
* function MyComponent() {
|
|
236
|
+
* createWithFlag('analytics-enabled', async () => {
|
|
237
|
+
* await trackEvent('page_view');
|
|
238
|
+
* });
|
|
239
|
+
*
|
|
240
|
+
* return <div>Content</div>;
|
|
241
|
+
* }
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
declare function createWithFlag(flagKey: string, callback: () => void | Promise<void>, options?: CreateFlagOptions): void;
|
|
245
|
+
interface CreateUserReturn {
|
|
246
|
+
/** Current user ID */
|
|
247
|
+
userId: Accessor<string | null>;
|
|
248
|
+
/** Set user ID */
|
|
249
|
+
setUserId: (id: string | null) => void;
|
|
250
|
+
/** Get current user ID from client */
|
|
251
|
+
getUserId: () => string | null;
|
|
252
|
+
/** Current anonymous ID */
|
|
253
|
+
anonymousId: Accessor<string | null>;
|
|
254
|
+
/** Set anonymous ID */
|
|
255
|
+
setAnonymousId: (id: string) => void;
|
|
256
|
+
/** Get current anonymous ID from client */
|
|
257
|
+
getAnonymousId: () => string | null;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Create signals for user identification management.
|
|
261
|
+
*
|
|
262
|
+
* @returns User ID management functions and reactive signals
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```tsx
|
|
266
|
+
* import { createUser } from '@savvagent/solid';
|
|
129
267
|
* import { createEffect } from 'solid-js';
|
|
130
268
|
*
|
|
131
269
|
* function AuthHandler() {
|
|
132
|
-
* const
|
|
270
|
+
* const user = createUser();
|
|
133
271
|
*
|
|
134
272
|
* createEffect(() => {
|
|
135
273
|
* if (currentUser()) {
|
|
136
|
-
* setUserId(currentUser().id);
|
|
274
|
+
* user.setUserId(currentUser().id);
|
|
137
275
|
* } else {
|
|
138
|
-
* setUserId(null);
|
|
276
|
+
* user.setUserId(null);
|
|
139
277
|
* }
|
|
140
278
|
* });
|
|
141
279
|
*
|
|
142
|
-
* return
|
|
280
|
+
* return <div>User ID: {user.userId()}</div>;
|
|
281
|
+
* }
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
declare function createUser(): CreateUserReturn;
|
|
285
|
+
/**
|
|
286
|
+
* Legacy function - use createUser() instead.
|
|
287
|
+
* Create signals for user identification.
|
|
288
|
+
*
|
|
289
|
+
* @returns User ID signal tuple [userId, setUserId]
|
|
290
|
+
* @deprecated Use createUser() for full user management
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```tsx
|
|
294
|
+
* import { createUserSignals } from '@savvagent/solid';
|
|
295
|
+
*
|
|
296
|
+
* function AuthHandler() {
|
|
297
|
+
* const [userId, setUserId] = createUserSignals();
|
|
298
|
+
*
|
|
299
|
+
* return <div>User ID: {userId()}</div>;
|
|
143
300
|
* }
|
|
144
301
|
* ```
|
|
145
302
|
*/
|
|
146
303
|
declare function createUserSignals(): readonly [Accessor<string | null>, (id: string | null) => void];
|
|
304
|
+
/**
|
|
305
|
+
* Create an error tracking function for a specific flag.
|
|
306
|
+
*
|
|
307
|
+
* @param flagKey - The flag key associated with errors
|
|
308
|
+
* @param context - Optional context
|
|
309
|
+
* @returns Error tracking function
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```tsx
|
|
313
|
+
* import { createTrackError } from '@savvagent/solid';
|
|
314
|
+
*
|
|
315
|
+
* function FeatureComponent() {
|
|
316
|
+
* const trackError = createTrackError('new-feature');
|
|
317
|
+
*
|
|
318
|
+
* const handleAction = async () => {
|
|
319
|
+
* try {
|
|
320
|
+
* await doSomething();
|
|
321
|
+
* } catch (error) {
|
|
322
|
+
* trackError(error as Error);
|
|
323
|
+
* }
|
|
324
|
+
* };
|
|
325
|
+
*
|
|
326
|
+
* return <button onClick={handleAction}>Try Feature</button>;
|
|
327
|
+
* }
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
declare function createTrackError(flagKey: string, context?: FlagContext): (error: Error) => void;
|
|
147
331
|
/**
|
|
148
332
|
* Track an error with flag context.
|
|
333
|
+
* Direct function call (not reactive).
|
|
149
334
|
*
|
|
150
335
|
* @param flagKey - The flag key associated with the error
|
|
151
336
|
* @param error - The error that occurred
|
|
@@ -170,4 +355,4 @@ declare function createUserSignals(): readonly [Accessor<string | null>, (id: st
|
|
|
170
355
|
*/
|
|
171
356
|
declare function trackError(flagKey: string, error: Error, context?: FlagContext): void;
|
|
172
357
|
|
|
173
|
-
export { type CreateFlagOptions, type CreateFlagReturn, SavvagentProvider, type SavvagentProviderProps, createFlag, createFlagValue, createUserSignals, trackError, useSavvagent };
|
|
358
|
+
export { type CreateFlagOptions, type CreateFlagReturn, type CreateFlagsOptions, type CreateFlagsReturn, type CreateUserReturn, type DefaultFlagContext, SavvagentProvider, type SavvagentProviderProps, createFlag, createFlagValue, createFlags, createTrackError, createUser, createUserSignals, createWithFlag, trackError, useSavvagent };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
import * as solid_js from 'solid-js';
|
|
2
2
|
import { ParentProps, Accessor } from 'solid-js';
|
|
3
3
|
import { FlagClientConfig, FlagClient, FlagContext, FlagEvaluationResult } from '@savvagent/sdk';
|
|
4
|
-
export { ApiTypes, ErrorEvent, EvaluationEvent, FlagClient, FlagClientConfig, FlagContext, FlagEvaluationResult, FlagUpdateEvent, components } from '@savvagent/sdk';
|
|
4
|
+
export { ApiTypes, ErrorEvent, EvaluationEvent, FlagClient, FlagClientConfig, FlagContext, FlagDefinition, FlagEvaluationResult, FlagUpdateEvent, components } from '@savvagent/sdk';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Default context values that apply to all flag evaluations
|
|
8
|
+
* Per SDK Developer Guide: https://docs.savvagent.com/sdk-developer-guide
|
|
9
|
+
*/
|
|
10
|
+
interface DefaultFlagContext {
|
|
11
|
+
/** Application ID for application-scoped flags */
|
|
12
|
+
applicationId?: string;
|
|
13
|
+
/** Environment (development, staging, production) */
|
|
14
|
+
environment?: string;
|
|
15
|
+
/** Organization ID for multi-tenant apps */
|
|
16
|
+
organizationId?: string;
|
|
17
|
+
/** Default user ID (required for percentage rollouts) */
|
|
18
|
+
userId?: string;
|
|
19
|
+
/** Default anonymous ID (alternative to userId for anonymous users) */
|
|
20
|
+
anonymousId?: string;
|
|
21
|
+
/** Session ID as fallback identifier */
|
|
22
|
+
sessionId?: string;
|
|
23
|
+
/** User's language code (e.g., "en", "es") */
|
|
24
|
+
language?: string;
|
|
25
|
+
/** Default attributes for targeting */
|
|
26
|
+
attributes?: Record<string, any>;
|
|
27
|
+
}
|
|
28
|
+
interface SavvagentContextValue {
|
|
29
|
+
client: FlagClient;
|
|
30
|
+
isReady: Accessor<boolean>;
|
|
31
|
+
defaultContext: Accessor<FlagContext>;
|
|
32
|
+
}
|
|
6
33
|
interface SavvagentProviderProps extends ParentProps {
|
|
7
34
|
config: FlagClientConfig;
|
|
35
|
+
/** Default context values applied to all flag evaluations */
|
|
36
|
+
defaultContext?: DefaultFlagContext;
|
|
8
37
|
}
|
|
9
38
|
/**
|
|
10
39
|
* Provider component that initializes and provides the Savvagent client.
|
|
@@ -15,7 +44,15 @@ interface SavvagentProviderProps extends ParentProps {
|
|
|
15
44
|
*
|
|
16
45
|
* function App() {
|
|
17
46
|
* return (
|
|
18
|
-
* <SavvagentProvider
|
|
47
|
+
* <SavvagentProvider
|
|
48
|
+
* config={{ apiKey: 'sdk_...' }}
|
|
49
|
+
* defaultContext={{
|
|
50
|
+
* applicationId: 'my-app-id',
|
|
51
|
+
* environment: 'development',
|
|
52
|
+
* userId: 'user-123',
|
|
53
|
+
* attributes: { plan: 'pro' }
|
|
54
|
+
* }}
|
|
55
|
+
* >
|
|
19
56
|
* <MyApp />
|
|
20
57
|
* </SavvagentProvider>
|
|
21
58
|
* );
|
|
@@ -24,10 +61,10 @@ interface SavvagentProviderProps extends ParentProps {
|
|
|
24
61
|
*/
|
|
25
62
|
declare function SavvagentProvider(props: SavvagentProviderProps): solid_js.JSX.Element;
|
|
26
63
|
/**
|
|
27
|
-
* Get the Savvagent client
|
|
64
|
+
* Get the Savvagent context including client, ready state, and default context.
|
|
28
65
|
* Must be used within a SavvagentProvider.
|
|
29
66
|
*
|
|
30
|
-
* @returns The FlagClient instance
|
|
67
|
+
* @returns The FlagClient instance, ready state accessor, and default context accessor
|
|
31
68
|
* @throws Error if used outside of SavvagentProvider
|
|
32
69
|
*
|
|
33
70
|
* @example
|
|
@@ -35,13 +72,17 @@ declare function SavvagentProvider(props: SavvagentProviderProps): solid_js.JSX.
|
|
|
35
72
|
* import { useSavvagent } from '@savvagent/solid';
|
|
36
73
|
*
|
|
37
74
|
* function MyComponent() {
|
|
38
|
-
* const client = useSavvagent();
|
|
39
|
-
*
|
|
40
|
-
* return
|
|
75
|
+
* const { client, isReady, defaultContext } = useSavvagent();
|
|
76
|
+
*
|
|
77
|
+
* return (
|
|
78
|
+
* <Show when={isReady()}>
|
|
79
|
+
* <div>Client ready!</div>
|
|
80
|
+
* </Show>
|
|
81
|
+
* );
|
|
41
82
|
* }
|
|
42
83
|
* ```
|
|
43
84
|
*/
|
|
44
|
-
declare function useSavvagent():
|
|
85
|
+
declare function useSavvagent(): SavvagentContextValue;
|
|
45
86
|
interface CreateFlagOptions {
|
|
46
87
|
/** Context for flag evaluation */
|
|
47
88
|
context?: FlagContext;
|
|
@@ -118,34 +159,178 @@ declare function createFlag(flagKey: string, options?: CreateFlagOptions): Creat
|
|
|
118
159
|
* ```
|
|
119
160
|
*/
|
|
120
161
|
declare function createFlagValue(flagKey: string, options?: CreateFlagOptions): Accessor<boolean>;
|
|
162
|
+
interface CreateFlagsOptions {
|
|
163
|
+
/** Context for flag evaluation */
|
|
164
|
+
context?: FlagContext;
|
|
165
|
+
/** Default values for flags (keyed by flag key) */
|
|
166
|
+
defaultValues?: Record<string, boolean>;
|
|
167
|
+
/** Enable real-time updates */
|
|
168
|
+
realtime?: boolean;
|
|
169
|
+
/** Custom error handler */
|
|
170
|
+
onError?: (error: Error, flagKey: string) => void;
|
|
171
|
+
}
|
|
172
|
+
interface CreateFlagsReturn {
|
|
173
|
+
/** Map of flag keys to their current values */
|
|
174
|
+
values: Accessor<Record<string, boolean>>;
|
|
175
|
+
/** Whether any flag is currently being evaluated */
|
|
176
|
+
loading: Accessor<boolean>;
|
|
177
|
+
/** Map of flag keys to their errors (if any) */
|
|
178
|
+
errors: Accessor<Record<string, Error | null>>;
|
|
179
|
+
/** Map of flag keys to their detailed evaluation results */
|
|
180
|
+
results: Accessor<Record<string, FlagEvaluationResult | null>>;
|
|
181
|
+
/** Force re-evaluation of all flags */
|
|
182
|
+
refetch: () => void;
|
|
183
|
+
}
|
|
121
184
|
/**
|
|
122
|
-
* Create
|
|
185
|
+
* Create reactive flags for multiple flag keys with a single state update.
|
|
186
|
+
* This is more efficient than using multiple createFlag calls when you need
|
|
187
|
+
* several flags in the same component.
|
|
123
188
|
*
|
|
124
|
-
* @
|
|
189
|
+
* @param flagKeys - Array of feature flag keys to evaluate
|
|
190
|
+
* @param options - Configuration options
|
|
191
|
+
* @returns Reactive flag state for all flags
|
|
125
192
|
*
|
|
126
193
|
* @example
|
|
127
194
|
* ```tsx
|
|
128
|
-
* import {
|
|
195
|
+
* import { createFlags } from '@savvagent/solid';
|
|
196
|
+
* import { Show, For } from 'solid-js';
|
|
197
|
+
*
|
|
198
|
+
* function MyComponent() {
|
|
199
|
+
* const flags = createFlags(
|
|
200
|
+
* ['feature-a', 'feature-b', 'feature-c'],
|
|
201
|
+
* {
|
|
202
|
+
* context: { user_id: 'user-123' },
|
|
203
|
+
* defaultValues: { 'feature-a': false, 'feature-b': true },
|
|
204
|
+
* realtime: true
|
|
205
|
+
* }
|
|
206
|
+
* );
|
|
207
|
+
*
|
|
208
|
+
* return (
|
|
209
|
+
* <Show when={!flags.loading()} fallback={<Spinner />}>
|
|
210
|
+
* <div>
|
|
211
|
+
* <Show when={flags.values()['feature-a']}>
|
|
212
|
+
* <FeatureA />
|
|
213
|
+
* </Show>
|
|
214
|
+
* <Show when={flags.values()['feature-b']}>
|
|
215
|
+
* <FeatureB />
|
|
216
|
+
* </Show>
|
|
217
|
+
* </div>
|
|
218
|
+
* </Show>
|
|
219
|
+
* );
|
|
220
|
+
* }
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
declare function createFlags(flagKeys: string[], options?: CreateFlagsOptions): CreateFlagsReturn;
|
|
224
|
+
/**
|
|
225
|
+
* Execute a callback conditionally based on a flag value.
|
|
226
|
+
*
|
|
227
|
+
* @param flagKey - The feature flag key to check
|
|
228
|
+
* @param callback - Function to execute if flag is enabled
|
|
229
|
+
* @param options - Configuration options
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```tsx
|
|
233
|
+
* import { createWithFlag } from '@savvagent/solid';
|
|
234
|
+
*
|
|
235
|
+
* function MyComponent() {
|
|
236
|
+
* createWithFlag('analytics-enabled', async () => {
|
|
237
|
+
* await trackEvent('page_view');
|
|
238
|
+
* });
|
|
239
|
+
*
|
|
240
|
+
* return <div>Content</div>;
|
|
241
|
+
* }
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
declare function createWithFlag(flagKey: string, callback: () => void | Promise<void>, options?: CreateFlagOptions): void;
|
|
245
|
+
interface CreateUserReturn {
|
|
246
|
+
/** Current user ID */
|
|
247
|
+
userId: Accessor<string | null>;
|
|
248
|
+
/** Set user ID */
|
|
249
|
+
setUserId: (id: string | null) => void;
|
|
250
|
+
/** Get current user ID from client */
|
|
251
|
+
getUserId: () => string | null;
|
|
252
|
+
/** Current anonymous ID */
|
|
253
|
+
anonymousId: Accessor<string | null>;
|
|
254
|
+
/** Set anonymous ID */
|
|
255
|
+
setAnonymousId: (id: string) => void;
|
|
256
|
+
/** Get current anonymous ID from client */
|
|
257
|
+
getAnonymousId: () => string | null;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Create signals for user identification management.
|
|
261
|
+
*
|
|
262
|
+
* @returns User ID management functions and reactive signals
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```tsx
|
|
266
|
+
* import { createUser } from '@savvagent/solid';
|
|
129
267
|
* import { createEffect } from 'solid-js';
|
|
130
268
|
*
|
|
131
269
|
* function AuthHandler() {
|
|
132
|
-
* const
|
|
270
|
+
* const user = createUser();
|
|
133
271
|
*
|
|
134
272
|
* createEffect(() => {
|
|
135
273
|
* if (currentUser()) {
|
|
136
|
-
* setUserId(currentUser().id);
|
|
274
|
+
* user.setUserId(currentUser().id);
|
|
137
275
|
* } else {
|
|
138
|
-
* setUserId(null);
|
|
276
|
+
* user.setUserId(null);
|
|
139
277
|
* }
|
|
140
278
|
* });
|
|
141
279
|
*
|
|
142
|
-
* return
|
|
280
|
+
* return <div>User ID: {user.userId()}</div>;
|
|
281
|
+
* }
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
declare function createUser(): CreateUserReturn;
|
|
285
|
+
/**
|
|
286
|
+
* Legacy function - use createUser() instead.
|
|
287
|
+
* Create signals for user identification.
|
|
288
|
+
*
|
|
289
|
+
* @returns User ID signal tuple [userId, setUserId]
|
|
290
|
+
* @deprecated Use createUser() for full user management
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```tsx
|
|
294
|
+
* import { createUserSignals } from '@savvagent/solid';
|
|
295
|
+
*
|
|
296
|
+
* function AuthHandler() {
|
|
297
|
+
* const [userId, setUserId] = createUserSignals();
|
|
298
|
+
*
|
|
299
|
+
* return <div>User ID: {userId()}</div>;
|
|
143
300
|
* }
|
|
144
301
|
* ```
|
|
145
302
|
*/
|
|
146
303
|
declare function createUserSignals(): readonly [Accessor<string | null>, (id: string | null) => void];
|
|
304
|
+
/**
|
|
305
|
+
* Create an error tracking function for a specific flag.
|
|
306
|
+
*
|
|
307
|
+
* @param flagKey - The flag key associated with errors
|
|
308
|
+
* @param context - Optional context
|
|
309
|
+
* @returns Error tracking function
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```tsx
|
|
313
|
+
* import { createTrackError } from '@savvagent/solid';
|
|
314
|
+
*
|
|
315
|
+
* function FeatureComponent() {
|
|
316
|
+
* const trackError = createTrackError('new-feature');
|
|
317
|
+
*
|
|
318
|
+
* const handleAction = async () => {
|
|
319
|
+
* try {
|
|
320
|
+
* await doSomething();
|
|
321
|
+
* } catch (error) {
|
|
322
|
+
* trackError(error as Error);
|
|
323
|
+
* }
|
|
324
|
+
* };
|
|
325
|
+
*
|
|
326
|
+
* return <button onClick={handleAction}>Try Feature</button>;
|
|
327
|
+
* }
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
declare function createTrackError(flagKey: string, context?: FlagContext): (error: Error) => void;
|
|
147
331
|
/**
|
|
148
332
|
* Track an error with flag context.
|
|
333
|
+
* Direct function call (not reactive).
|
|
149
334
|
*
|
|
150
335
|
* @param flagKey - The flag key associated with the error
|
|
151
336
|
* @param error - The error that occurred
|
|
@@ -170,4 +355,4 @@ declare function createUserSignals(): readonly [Accessor<string | null>, (id: st
|
|
|
170
355
|
*/
|
|
171
356
|
declare function trackError(flagKey: string, error: Error, context?: FlagContext): void;
|
|
172
357
|
|
|
173
|
-
export { type CreateFlagOptions, type CreateFlagReturn, SavvagentProvider, type SavvagentProviderProps, createFlag, createFlagValue, createUserSignals, trackError, useSavvagent };
|
|
358
|
+
export { type CreateFlagOptions, type CreateFlagReturn, type CreateFlagsOptions, type CreateFlagsReturn, type CreateUserReturn, type DefaultFlagContext, SavvagentProvider, type SavvagentProviderProps, createFlag, createFlagValue, createFlags, createTrackError, createUser, createUserSignals, createWithFlag, trackError, useSavvagent };
|
package/dist/index.js
CHANGED
|
@@ -24,31 +24,87 @@ __export(index_exports, {
|
|
|
24
24
|
SavvagentProvider: () => SavvagentProvider,
|
|
25
25
|
createFlag: () => createFlag,
|
|
26
26
|
createFlagValue: () => createFlagValue,
|
|
27
|
+
createFlags: () => createFlags,
|
|
28
|
+
createTrackError: () => createTrackError,
|
|
29
|
+
createUser: () => createUser,
|
|
27
30
|
createUserSignals: () => createUserSignals,
|
|
31
|
+
createWithFlag: () => createWithFlag,
|
|
28
32
|
trackError: () => trackError,
|
|
29
33
|
useSavvagent: () => useSavvagent
|
|
30
34
|
});
|
|
31
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
var import_web = require("solid-js/web");
|
|
32
37
|
var import_solid_js = require("solid-js");
|
|
33
38
|
var import_sdk = require("@savvagent/sdk");
|
|
34
39
|
var import_sdk2 = require("@savvagent/sdk");
|
|
35
40
|
var SavvagentContext = (0, import_solid_js.createContext)();
|
|
36
41
|
function SavvagentProvider(props) {
|
|
37
|
-
const
|
|
42
|
+
const [isReady, setIsReady] = (0, import_solid_js.createSignal)(false);
|
|
43
|
+
let client;
|
|
44
|
+
try {
|
|
45
|
+
client = new import_sdk.FlagClient(props.config);
|
|
46
|
+
setIsReady(true);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error("[Savvagent] Failed to initialize client:", error);
|
|
49
|
+
props.config.onError?.(error);
|
|
50
|
+
client = new import_sdk.FlagClient({
|
|
51
|
+
...props.config,
|
|
52
|
+
apiKey: ""
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const normalizedDefaultContext = (0, import_solid_js.createMemo)(() => ({
|
|
56
|
+
application_id: props.defaultContext?.applicationId,
|
|
57
|
+
environment: props.defaultContext?.environment,
|
|
58
|
+
organization_id: props.defaultContext?.organizationId,
|
|
59
|
+
user_id: props.defaultContext?.userId,
|
|
60
|
+
anonymous_id: props.defaultContext?.anonymousId,
|
|
61
|
+
session_id: props.defaultContext?.sessionId,
|
|
62
|
+
language: props.defaultContext?.language,
|
|
63
|
+
attributes: props.defaultContext?.attributes
|
|
64
|
+
}));
|
|
38
65
|
(0, import_solid_js.onCleanup)(() => {
|
|
39
66
|
client.close();
|
|
40
67
|
});
|
|
41
|
-
|
|
68
|
+
const contextValue = {
|
|
69
|
+
client,
|
|
70
|
+
isReady,
|
|
71
|
+
defaultContext: normalizedDefaultContext
|
|
72
|
+
};
|
|
73
|
+
return (0, import_web.createComponent)(SavvagentContext.Provider, {
|
|
74
|
+
value: contextValue,
|
|
75
|
+
get children() {
|
|
76
|
+
return props.children;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
42
79
|
}
|
|
43
80
|
function useSavvagent() {
|
|
44
|
-
const
|
|
45
|
-
if (!
|
|
81
|
+
const context = (0, import_solid_js.useContext)(SavvagentContext);
|
|
82
|
+
if (!context) {
|
|
46
83
|
throw new Error("useSavvagent must be used within a SavvagentProvider");
|
|
47
84
|
}
|
|
48
|
-
return
|
|
85
|
+
return context;
|
|
86
|
+
}
|
|
87
|
+
function deepEqual(a, b) {
|
|
88
|
+
if (a === b) return true;
|
|
89
|
+
if (a === null || b === null) return a === b;
|
|
90
|
+
if (typeof a !== "object" || typeof b !== "object") return false;
|
|
91
|
+
const keysA = Object.keys(a);
|
|
92
|
+
const keysB = Object.keys(b);
|
|
93
|
+
if (keysA.length !== keysB.length) return false;
|
|
94
|
+
for (const key of keysA) {
|
|
95
|
+
if (!keysB.includes(key)) return false;
|
|
96
|
+
if (!deepEqual(a[key], b[key])) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
49
101
|
}
|
|
50
102
|
function createFlag(flagKey, options = {}) {
|
|
51
|
-
const
|
|
103
|
+
const {
|
|
104
|
+
client,
|
|
105
|
+
isReady,
|
|
106
|
+
defaultContext
|
|
107
|
+
} = useSavvagent();
|
|
52
108
|
const {
|
|
53
109
|
context,
|
|
54
110
|
defaultValue = false,
|
|
@@ -56,23 +112,39 @@ function createFlag(flagKey, options = {}) {
|
|
|
56
112
|
onError
|
|
57
113
|
} = options;
|
|
58
114
|
const [trigger, setTrigger] = (0, import_solid_js.createSignal)(0);
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
throw error2;
|
|
115
|
+
const mergedContext = (0, import_solid_js.createMemo)(() => {
|
|
116
|
+
const def = defaultContext();
|
|
117
|
+
return {
|
|
118
|
+
...def,
|
|
119
|
+
...context,
|
|
120
|
+
attributes: {
|
|
121
|
+
...def?.attributes,
|
|
122
|
+
...context?.attributes
|
|
68
123
|
}
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
let prevContext;
|
|
127
|
+
const [result] = (0, import_solid_js.createResource)(() => ({
|
|
128
|
+
trigger: trigger(),
|
|
129
|
+
context: mergedContext(),
|
|
130
|
+
ready: isReady()
|
|
131
|
+
}), async (source) => {
|
|
132
|
+
if (!source.ready) {
|
|
133
|
+
return null;
|
|
69
134
|
}
|
|
70
|
-
|
|
135
|
+
try {
|
|
136
|
+
return await client.evaluate(flagKey, source.context);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
const error2 = err;
|
|
139
|
+
onError?.(error2);
|
|
140
|
+
throw error2;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
71
143
|
const value = () => result()?.value ?? defaultValue;
|
|
72
144
|
const loading = () => result.loading;
|
|
73
145
|
const error = () => result.error ?? null;
|
|
74
146
|
(0, import_solid_js.createEffect)(() => {
|
|
75
|
-
if (!realtime) return;
|
|
147
|
+
if (!realtime || !isReady()) return;
|
|
76
148
|
const unsubscribe = client.subscribe(flagKey, () => {
|
|
77
149
|
setTrigger((t) => t + 1);
|
|
78
150
|
});
|
|
@@ -80,6 +152,22 @@ function createFlag(flagKey, options = {}) {
|
|
|
80
152
|
unsubscribe();
|
|
81
153
|
});
|
|
82
154
|
});
|
|
155
|
+
(0, import_solid_js.createEffect)(() => {
|
|
156
|
+
if (!isReady()) return;
|
|
157
|
+
const unsubscribe = client.onOverrideChange(() => {
|
|
158
|
+
setTrigger((t) => t + 1);
|
|
159
|
+
});
|
|
160
|
+
(0, import_solid_js.onCleanup)(() => {
|
|
161
|
+
unsubscribe();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
(0, import_solid_js.createEffect)(() => {
|
|
165
|
+
const currentContext = mergedContext();
|
|
166
|
+
if (prevContext !== void 0 && !deepEqual(prevContext, currentContext)) {
|
|
167
|
+
setTrigger((t) => t + 1);
|
|
168
|
+
}
|
|
169
|
+
prevContext = currentContext;
|
|
170
|
+
});
|
|
83
171
|
return {
|
|
84
172
|
value,
|
|
85
173
|
loading,
|
|
@@ -92,8 +180,158 @@ function createFlagValue(flagKey, options = {}) {
|
|
|
92
180
|
const flag = createFlag(flagKey, options);
|
|
93
181
|
return flag.value;
|
|
94
182
|
}
|
|
183
|
+
function createFlags(flagKeys, options = {}) {
|
|
184
|
+
const {
|
|
185
|
+
client,
|
|
186
|
+
isReady,
|
|
187
|
+
defaultContext
|
|
188
|
+
} = useSavvagent();
|
|
189
|
+
const {
|
|
190
|
+
context,
|
|
191
|
+
defaultValues = {},
|
|
192
|
+
realtime = true,
|
|
193
|
+
onError
|
|
194
|
+
} = options;
|
|
195
|
+
const [trigger, setTrigger] = (0, import_solid_js.createSignal)(0);
|
|
196
|
+
const mergedContext = (0, import_solid_js.createMemo)(() => {
|
|
197
|
+
const def = defaultContext();
|
|
198
|
+
return {
|
|
199
|
+
...def,
|
|
200
|
+
...context,
|
|
201
|
+
attributes: {
|
|
202
|
+
...def?.attributes,
|
|
203
|
+
...context?.attributes
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
const initialValues = {};
|
|
208
|
+
const initialErrors = {};
|
|
209
|
+
const initialResults = {};
|
|
210
|
+
for (const key of flagKeys) {
|
|
211
|
+
initialValues[key] = defaultValues[key] ?? false;
|
|
212
|
+
initialErrors[key] = null;
|
|
213
|
+
initialResults[key] = null;
|
|
214
|
+
}
|
|
215
|
+
const [values, setValues] = (0, import_solid_js.createSignal)(initialValues);
|
|
216
|
+
const [errors, setErrors] = (0, import_solid_js.createSignal)(initialErrors);
|
|
217
|
+
const [results, setResults] = (0, import_solid_js.createSignal)(initialResults);
|
|
218
|
+
const [loading, setLoading] = (0, import_solid_js.createSignal)(true);
|
|
219
|
+
let prevContext;
|
|
220
|
+
const evaluateFlags = async () => {
|
|
221
|
+
if (!isReady()) return;
|
|
222
|
+
setLoading(true);
|
|
223
|
+
const newValues = {};
|
|
224
|
+
const newErrors = {};
|
|
225
|
+
const newResults = {};
|
|
226
|
+
const ctx = mergedContext();
|
|
227
|
+
await Promise.all(flagKeys.map(async (flagKey) => {
|
|
228
|
+
try {
|
|
229
|
+
const evalResult = await client.evaluate(flagKey, ctx);
|
|
230
|
+
newValues[flagKey] = evalResult.value;
|
|
231
|
+
newErrors[flagKey] = null;
|
|
232
|
+
newResults[flagKey] = evalResult;
|
|
233
|
+
} catch (err) {
|
|
234
|
+
const error = err;
|
|
235
|
+
newValues[flagKey] = defaultValues[flagKey] ?? false;
|
|
236
|
+
newErrors[flagKey] = error;
|
|
237
|
+
newResults[flagKey] = null;
|
|
238
|
+
onError?.(error, flagKey);
|
|
239
|
+
}
|
|
240
|
+
}));
|
|
241
|
+
setValues(newValues);
|
|
242
|
+
setErrors(newErrors);
|
|
243
|
+
setResults(newResults);
|
|
244
|
+
setLoading(false);
|
|
245
|
+
};
|
|
246
|
+
(0, import_solid_js.createEffect)(() => {
|
|
247
|
+
trigger();
|
|
248
|
+
if (isReady()) {
|
|
249
|
+
evaluateFlags();
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
(0, import_solid_js.createEffect)(() => {
|
|
253
|
+
if (!realtime || !isReady()) return;
|
|
254
|
+
const unsubscribes = flagKeys.map((flagKey) => client.subscribe(flagKey, () => {
|
|
255
|
+
setTrigger((t) => t + 1);
|
|
256
|
+
}));
|
|
257
|
+
(0, import_solid_js.onCleanup)(() => {
|
|
258
|
+
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
(0, import_solid_js.createEffect)(() => {
|
|
262
|
+
if (!isReady()) return;
|
|
263
|
+
const unsubscribe = client.onOverrideChange(() => {
|
|
264
|
+
setTrigger((t) => t + 1);
|
|
265
|
+
});
|
|
266
|
+
(0, import_solid_js.onCleanup)(() => {
|
|
267
|
+
unsubscribe();
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
(0, import_solid_js.createEffect)(() => {
|
|
271
|
+
const currentContext = mergedContext();
|
|
272
|
+
if (prevContext !== void 0 && !deepEqual(prevContext, currentContext)) {
|
|
273
|
+
setTrigger((t) => t + 1);
|
|
274
|
+
}
|
|
275
|
+
prevContext = currentContext;
|
|
276
|
+
});
|
|
277
|
+
return {
|
|
278
|
+
values,
|
|
279
|
+
loading,
|
|
280
|
+
errors,
|
|
281
|
+
results,
|
|
282
|
+
refetch: () => setTrigger((t) => t + 1)
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function createWithFlag(flagKey, callback, options = {}) {
|
|
286
|
+
const {
|
|
287
|
+
client,
|
|
288
|
+
isReady
|
|
289
|
+
} = useSavvagent();
|
|
290
|
+
const {
|
|
291
|
+
context,
|
|
292
|
+
onError
|
|
293
|
+
} = options;
|
|
294
|
+
(0, import_solid_js.createEffect)(() => {
|
|
295
|
+
if (!isReady()) return;
|
|
296
|
+
client.withFlag(flagKey, callback, context).catch((error) => {
|
|
297
|
+
console.error(`[Savvagent] Error in withFlag callback for ${flagKey}:`, error);
|
|
298
|
+
onError?.(error);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
function createUser() {
|
|
303
|
+
const {
|
|
304
|
+
client
|
|
305
|
+
} = useSavvagent();
|
|
306
|
+
const [userId, setUserIdSignal] = (0, import_solid_js.createSignal)(client.getUserId());
|
|
307
|
+
const [anonymousId, setAnonymousIdSignal] = (0, import_solid_js.createSignal)(client.getAnonymousId());
|
|
308
|
+
const setUserId = (id) => {
|
|
309
|
+
client.setUserId(id);
|
|
310
|
+
setUserIdSignal(id);
|
|
311
|
+
};
|
|
312
|
+
const getUserId = () => {
|
|
313
|
+
return client.getUserId();
|
|
314
|
+
};
|
|
315
|
+
const setAnonymousId = (id) => {
|
|
316
|
+
client.setAnonymousId(id);
|
|
317
|
+
setAnonymousIdSignal(id);
|
|
318
|
+
};
|
|
319
|
+
const getAnonymousId = () => {
|
|
320
|
+
return client.getAnonymousId();
|
|
321
|
+
};
|
|
322
|
+
return {
|
|
323
|
+
userId,
|
|
324
|
+
setUserId,
|
|
325
|
+
getUserId,
|
|
326
|
+
anonymousId,
|
|
327
|
+
setAnonymousId,
|
|
328
|
+
getAnonymousId
|
|
329
|
+
};
|
|
330
|
+
}
|
|
95
331
|
function createUserSignals() {
|
|
96
|
-
const
|
|
332
|
+
const {
|
|
333
|
+
client
|
|
334
|
+
} = useSavvagent();
|
|
97
335
|
const [userId, setUserIdSignal] = (0, import_solid_js.createSignal)(client.getUserId());
|
|
98
336
|
const setUserId = (id) => {
|
|
99
337
|
client.setUserId(id);
|
|
@@ -101,8 +339,18 @@ function createUserSignals() {
|
|
|
101
339
|
};
|
|
102
340
|
return [userId, setUserId];
|
|
103
341
|
}
|
|
342
|
+
function createTrackError(flagKey, context) {
|
|
343
|
+
const {
|
|
344
|
+
client
|
|
345
|
+
} = useSavvagent();
|
|
346
|
+
return (error) => {
|
|
347
|
+
client.trackError(flagKey, error, context);
|
|
348
|
+
};
|
|
349
|
+
}
|
|
104
350
|
function trackError(flagKey, error, context) {
|
|
105
|
-
const
|
|
351
|
+
const {
|
|
352
|
+
client
|
|
353
|
+
} = useSavvagent();
|
|
106
354
|
client.trackError(flagKey, error, context);
|
|
107
355
|
}
|
|
108
356
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -111,7 +359,11 @@ function trackError(flagKey, error, context) {
|
|
|
111
359
|
SavvagentProvider,
|
|
112
360
|
createFlag,
|
|
113
361
|
createFlagValue,
|
|
362
|
+
createFlags,
|
|
363
|
+
createTrackError,
|
|
364
|
+
createUser,
|
|
114
365
|
createUserSignals,
|
|
366
|
+
createWithFlag,
|
|
115
367
|
trackError,
|
|
116
368
|
useSavvagent
|
|
117
369
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1,31 +1,76 @@
|
|
|
1
1
|
// src/index.tsx
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
useContext,
|
|
5
|
-
createSignal,
|
|
6
|
-
createResource,
|
|
7
|
-
createEffect,
|
|
8
|
-
onCleanup
|
|
9
|
-
} from "solid-js";
|
|
2
|
+
import { createComponent as _$createComponent } from "solid-js/web";
|
|
3
|
+
import { createContext, useContext, createSignal, createResource, createEffect, createMemo, onCleanup } from "solid-js";
|
|
10
4
|
import { FlagClient } from "@savvagent/sdk";
|
|
11
5
|
import { FlagClient as FlagClient2 } from "@savvagent/sdk";
|
|
12
6
|
var SavvagentContext = createContext();
|
|
13
7
|
function SavvagentProvider(props) {
|
|
14
|
-
const
|
|
8
|
+
const [isReady, setIsReady] = createSignal(false);
|
|
9
|
+
let client;
|
|
10
|
+
try {
|
|
11
|
+
client = new FlagClient(props.config);
|
|
12
|
+
setIsReady(true);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error("[Savvagent] Failed to initialize client:", error);
|
|
15
|
+
props.config.onError?.(error);
|
|
16
|
+
client = new FlagClient({
|
|
17
|
+
...props.config,
|
|
18
|
+
apiKey: ""
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const normalizedDefaultContext = createMemo(() => ({
|
|
22
|
+
application_id: props.defaultContext?.applicationId,
|
|
23
|
+
environment: props.defaultContext?.environment,
|
|
24
|
+
organization_id: props.defaultContext?.organizationId,
|
|
25
|
+
user_id: props.defaultContext?.userId,
|
|
26
|
+
anonymous_id: props.defaultContext?.anonymousId,
|
|
27
|
+
session_id: props.defaultContext?.sessionId,
|
|
28
|
+
language: props.defaultContext?.language,
|
|
29
|
+
attributes: props.defaultContext?.attributes
|
|
30
|
+
}));
|
|
15
31
|
onCleanup(() => {
|
|
16
32
|
client.close();
|
|
17
33
|
});
|
|
18
|
-
|
|
34
|
+
const contextValue = {
|
|
35
|
+
client,
|
|
36
|
+
isReady,
|
|
37
|
+
defaultContext: normalizedDefaultContext
|
|
38
|
+
};
|
|
39
|
+
return _$createComponent(SavvagentContext.Provider, {
|
|
40
|
+
value: contextValue,
|
|
41
|
+
get children() {
|
|
42
|
+
return props.children;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
19
45
|
}
|
|
20
46
|
function useSavvagent() {
|
|
21
|
-
const
|
|
22
|
-
if (!
|
|
47
|
+
const context = useContext(SavvagentContext);
|
|
48
|
+
if (!context) {
|
|
23
49
|
throw new Error("useSavvagent must be used within a SavvagentProvider");
|
|
24
50
|
}
|
|
25
|
-
return
|
|
51
|
+
return context;
|
|
52
|
+
}
|
|
53
|
+
function deepEqual(a, b) {
|
|
54
|
+
if (a === b) return true;
|
|
55
|
+
if (a === null || b === null) return a === b;
|
|
56
|
+
if (typeof a !== "object" || typeof b !== "object") return false;
|
|
57
|
+
const keysA = Object.keys(a);
|
|
58
|
+
const keysB = Object.keys(b);
|
|
59
|
+
if (keysA.length !== keysB.length) return false;
|
|
60
|
+
for (const key of keysA) {
|
|
61
|
+
if (!keysB.includes(key)) return false;
|
|
62
|
+
if (!deepEqual(a[key], b[key])) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
26
67
|
}
|
|
27
68
|
function createFlag(flagKey, options = {}) {
|
|
28
|
-
const
|
|
69
|
+
const {
|
|
70
|
+
client,
|
|
71
|
+
isReady,
|
|
72
|
+
defaultContext
|
|
73
|
+
} = useSavvagent();
|
|
29
74
|
const {
|
|
30
75
|
context,
|
|
31
76
|
defaultValue = false,
|
|
@@ -33,23 +78,39 @@ function createFlag(flagKey, options = {}) {
|
|
|
33
78
|
onError
|
|
34
79
|
} = options;
|
|
35
80
|
const [trigger, setTrigger] = createSignal(0);
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
throw error2;
|
|
81
|
+
const mergedContext = createMemo(() => {
|
|
82
|
+
const def = defaultContext();
|
|
83
|
+
return {
|
|
84
|
+
...def,
|
|
85
|
+
...context,
|
|
86
|
+
attributes: {
|
|
87
|
+
...def?.attributes,
|
|
88
|
+
...context?.attributes
|
|
45
89
|
}
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
let prevContext;
|
|
93
|
+
const [result] = createResource(() => ({
|
|
94
|
+
trigger: trigger(),
|
|
95
|
+
context: mergedContext(),
|
|
96
|
+
ready: isReady()
|
|
97
|
+
}), async (source) => {
|
|
98
|
+
if (!source.ready) {
|
|
99
|
+
return null;
|
|
46
100
|
}
|
|
47
|
-
|
|
101
|
+
try {
|
|
102
|
+
return await client.evaluate(flagKey, source.context);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
const error2 = err;
|
|
105
|
+
onError?.(error2);
|
|
106
|
+
throw error2;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
48
109
|
const value = () => result()?.value ?? defaultValue;
|
|
49
110
|
const loading = () => result.loading;
|
|
50
111
|
const error = () => result.error ?? null;
|
|
51
112
|
createEffect(() => {
|
|
52
|
-
if (!realtime) return;
|
|
113
|
+
if (!realtime || !isReady()) return;
|
|
53
114
|
const unsubscribe = client.subscribe(flagKey, () => {
|
|
54
115
|
setTrigger((t) => t + 1);
|
|
55
116
|
});
|
|
@@ -57,6 +118,22 @@ function createFlag(flagKey, options = {}) {
|
|
|
57
118
|
unsubscribe();
|
|
58
119
|
});
|
|
59
120
|
});
|
|
121
|
+
createEffect(() => {
|
|
122
|
+
if (!isReady()) return;
|
|
123
|
+
const unsubscribe = client.onOverrideChange(() => {
|
|
124
|
+
setTrigger((t) => t + 1);
|
|
125
|
+
});
|
|
126
|
+
onCleanup(() => {
|
|
127
|
+
unsubscribe();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
createEffect(() => {
|
|
131
|
+
const currentContext = mergedContext();
|
|
132
|
+
if (prevContext !== void 0 && !deepEqual(prevContext, currentContext)) {
|
|
133
|
+
setTrigger((t) => t + 1);
|
|
134
|
+
}
|
|
135
|
+
prevContext = currentContext;
|
|
136
|
+
});
|
|
60
137
|
return {
|
|
61
138
|
value,
|
|
62
139
|
loading,
|
|
@@ -69,8 +146,158 @@ function createFlagValue(flagKey, options = {}) {
|
|
|
69
146
|
const flag = createFlag(flagKey, options);
|
|
70
147
|
return flag.value;
|
|
71
148
|
}
|
|
149
|
+
function createFlags(flagKeys, options = {}) {
|
|
150
|
+
const {
|
|
151
|
+
client,
|
|
152
|
+
isReady,
|
|
153
|
+
defaultContext
|
|
154
|
+
} = useSavvagent();
|
|
155
|
+
const {
|
|
156
|
+
context,
|
|
157
|
+
defaultValues = {},
|
|
158
|
+
realtime = true,
|
|
159
|
+
onError
|
|
160
|
+
} = options;
|
|
161
|
+
const [trigger, setTrigger] = createSignal(0);
|
|
162
|
+
const mergedContext = createMemo(() => {
|
|
163
|
+
const def = defaultContext();
|
|
164
|
+
return {
|
|
165
|
+
...def,
|
|
166
|
+
...context,
|
|
167
|
+
attributes: {
|
|
168
|
+
...def?.attributes,
|
|
169
|
+
...context?.attributes
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
const initialValues = {};
|
|
174
|
+
const initialErrors = {};
|
|
175
|
+
const initialResults = {};
|
|
176
|
+
for (const key of flagKeys) {
|
|
177
|
+
initialValues[key] = defaultValues[key] ?? false;
|
|
178
|
+
initialErrors[key] = null;
|
|
179
|
+
initialResults[key] = null;
|
|
180
|
+
}
|
|
181
|
+
const [values, setValues] = createSignal(initialValues);
|
|
182
|
+
const [errors, setErrors] = createSignal(initialErrors);
|
|
183
|
+
const [results, setResults] = createSignal(initialResults);
|
|
184
|
+
const [loading, setLoading] = createSignal(true);
|
|
185
|
+
let prevContext;
|
|
186
|
+
const evaluateFlags = async () => {
|
|
187
|
+
if (!isReady()) return;
|
|
188
|
+
setLoading(true);
|
|
189
|
+
const newValues = {};
|
|
190
|
+
const newErrors = {};
|
|
191
|
+
const newResults = {};
|
|
192
|
+
const ctx = mergedContext();
|
|
193
|
+
await Promise.all(flagKeys.map(async (flagKey) => {
|
|
194
|
+
try {
|
|
195
|
+
const evalResult = await client.evaluate(flagKey, ctx);
|
|
196
|
+
newValues[flagKey] = evalResult.value;
|
|
197
|
+
newErrors[flagKey] = null;
|
|
198
|
+
newResults[flagKey] = evalResult;
|
|
199
|
+
} catch (err) {
|
|
200
|
+
const error = err;
|
|
201
|
+
newValues[flagKey] = defaultValues[flagKey] ?? false;
|
|
202
|
+
newErrors[flagKey] = error;
|
|
203
|
+
newResults[flagKey] = null;
|
|
204
|
+
onError?.(error, flagKey);
|
|
205
|
+
}
|
|
206
|
+
}));
|
|
207
|
+
setValues(newValues);
|
|
208
|
+
setErrors(newErrors);
|
|
209
|
+
setResults(newResults);
|
|
210
|
+
setLoading(false);
|
|
211
|
+
};
|
|
212
|
+
createEffect(() => {
|
|
213
|
+
trigger();
|
|
214
|
+
if (isReady()) {
|
|
215
|
+
evaluateFlags();
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
createEffect(() => {
|
|
219
|
+
if (!realtime || !isReady()) return;
|
|
220
|
+
const unsubscribes = flagKeys.map((flagKey) => client.subscribe(flagKey, () => {
|
|
221
|
+
setTrigger((t) => t + 1);
|
|
222
|
+
}));
|
|
223
|
+
onCleanup(() => {
|
|
224
|
+
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
createEffect(() => {
|
|
228
|
+
if (!isReady()) return;
|
|
229
|
+
const unsubscribe = client.onOverrideChange(() => {
|
|
230
|
+
setTrigger((t) => t + 1);
|
|
231
|
+
});
|
|
232
|
+
onCleanup(() => {
|
|
233
|
+
unsubscribe();
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
createEffect(() => {
|
|
237
|
+
const currentContext = mergedContext();
|
|
238
|
+
if (prevContext !== void 0 && !deepEqual(prevContext, currentContext)) {
|
|
239
|
+
setTrigger((t) => t + 1);
|
|
240
|
+
}
|
|
241
|
+
prevContext = currentContext;
|
|
242
|
+
});
|
|
243
|
+
return {
|
|
244
|
+
values,
|
|
245
|
+
loading,
|
|
246
|
+
errors,
|
|
247
|
+
results,
|
|
248
|
+
refetch: () => setTrigger((t) => t + 1)
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function createWithFlag(flagKey, callback, options = {}) {
|
|
252
|
+
const {
|
|
253
|
+
client,
|
|
254
|
+
isReady
|
|
255
|
+
} = useSavvagent();
|
|
256
|
+
const {
|
|
257
|
+
context,
|
|
258
|
+
onError
|
|
259
|
+
} = options;
|
|
260
|
+
createEffect(() => {
|
|
261
|
+
if (!isReady()) return;
|
|
262
|
+
client.withFlag(flagKey, callback, context).catch((error) => {
|
|
263
|
+
console.error(`[Savvagent] Error in withFlag callback for ${flagKey}:`, error);
|
|
264
|
+
onError?.(error);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
function createUser() {
|
|
269
|
+
const {
|
|
270
|
+
client
|
|
271
|
+
} = useSavvagent();
|
|
272
|
+
const [userId, setUserIdSignal] = createSignal(client.getUserId());
|
|
273
|
+
const [anonymousId, setAnonymousIdSignal] = createSignal(client.getAnonymousId());
|
|
274
|
+
const setUserId = (id) => {
|
|
275
|
+
client.setUserId(id);
|
|
276
|
+
setUserIdSignal(id);
|
|
277
|
+
};
|
|
278
|
+
const getUserId = () => {
|
|
279
|
+
return client.getUserId();
|
|
280
|
+
};
|
|
281
|
+
const setAnonymousId = (id) => {
|
|
282
|
+
client.setAnonymousId(id);
|
|
283
|
+
setAnonymousIdSignal(id);
|
|
284
|
+
};
|
|
285
|
+
const getAnonymousId = () => {
|
|
286
|
+
return client.getAnonymousId();
|
|
287
|
+
};
|
|
288
|
+
return {
|
|
289
|
+
userId,
|
|
290
|
+
setUserId,
|
|
291
|
+
getUserId,
|
|
292
|
+
anonymousId,
|
|
293
|
+
setAnonymousId,
|
|
294
|
+
getAnonymousId
|
|
295
|
+
};
|
|
296
|
+
}
|
|
72
297
|
function createUserSignals() {
|
|
73
|
-
const
|
|
298
|
+
const {
|
|
299
|
+
client
|
|
300
|
+
} = useSavvagent();
|
|
74
301
|
const [userId, setUserIdSignal] = createSignal(client.getUserId());
|
|
75
302
|
const setUserId = (id) => {
|
|
76
303
|
client.setUserId(id);
|
|
@@ -78,8 +305,18 @@ function createUserSignals() {
|
|
|
78
305
|
};
|
|
79
306
|
return [userId, setUserId];
|
|
80
307
|
}
|
|
308
|
+
function createTrackError(flagKey, context) {
|
|
309
|
+
const {
|
|
310
|
+
client
|
|
311
|
+
} = useSavvagent();
|
|
312
|
+
return (error) => {
|
|
313
|
+
client.trackError(flagKey, error, context);
|
|
314
|
+
};
|
|
315
|
+
}
|
|
81
316
|
function trackError(flagKey, error, context) {
|
|
82
|
-
const
|
|
317
|
+
const {
|
|
318
|
+
client
|
|
319
|
+
} = useSavvagent();
|
|
83
320
|
client.trackError(flagKey, error, context);
|
|
84
321
|
}
|
|
85
322
|
export {
|
|
@@ -87,7 +324,11 @@ export {
|
|
|
87
324
|
SavvagentProvider,
|
|
88
325
|
createFlag,
|
|
89
326
|
createFlagValue,
|
|
327
|
+
createFlags,
|
|
328
|
+
createTrackError,
|
|
329
|
+
createUser,
|
|
90
330
|
createUserSignals,
|
|
331
|
+
createWithFlag,
|
|
91
332
|
trackError,
|
|
92
333
|
useSavvagent
|
|
93
334
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvagent/solid",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "SolidJS SDK for Savvagent feature flags with reactive primitives",
|
|
5
5
|
"author": "Savvagent",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,12 +21,18 @@
|
|
|
21
21
|
"solid-js": ">=1.0.0"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@savvagent/sdk": "1.0.
|
|
24
|
+
"@savvagent/sdk": "1.0.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
27
|
+
"@solidjs/testing-library": "0.8.10",
|
|
28
|
+
"@testing-library/jest-dom": "6.9.1",
|
|
29
|
+
"esbuild-plugin-solid": "0.6.0",
|
|
30
|
+
"jsdom": "27.2.0",
|
|
31
|
+
"solid-js": "^1.9.10",
|
|
32
|
+
"tsup": "^8.5.1",
|
|
33
|
+
"typescript": "^5.9.3",
|
|
34
|
+
"vite-plugin-solid": "^2.11.10",
|
|
35
|
+
"vitest": "4.0.14"
|
|
30
36
|
},
|
|
31
37
|
"keywords": [
|
|
32
38
|
"savvagent",
|
|
@@ -50,8 +56,8 @@
|
|
|
50
56
|
"access": "public"
|
|
51
57
|
},
|
|
52
58
|
"scripts": {
|
|
53
|
-
"build": "tsup
|
|
54
|
-
"dev": "tsup
|
|
59
|
+
"build": "tsup",
|
|
60
|
+
"dev": "tsup --watch",
|
|
55
61
|
"test": "vitest",
|
|
56
62
|
"lint": "eslint src --ext .ts,.tsx",
|
|
57
63
|
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|