@savvagent/sdk 1.0.0
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/README.md +443 -0
- package/dist/index.d.mts +599 -0
- package/dist/index.d.ts +599 -0
- package/dist/index.js +631 -0
- package/dist/index.mjs +601 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# @savvagent/typescript
|
|
2
|
+
|
|
3
|
+
Official TypeScript/JavaScript SDK for Savvagent - AI-powered feature flags with automatic error detection.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 **Fast Evaluation**: Local caching with configurable TTL
|
|
8
|
+
- 🔄 **Real-time Updates**: Automatic cache invalidation via Server-Sent Events
|
|
9
|
+
- 📊 **Telemetry**: Automatic tracking of flag evaluations and errors
|
|
10
|
+
- 🤖 **AI Error Detection**: Correlate errors with flag changes
|
|
11
|
+
- 📦 **TypeScript**: Full type safety with TypeScript definitions
|
|
12
|
+
- 🌐 **Universal**: Works in browsers and Node.js
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @savvagent/typescript
|
|
18
|
+
# or
|
|
19
|
+
yarn add @savvagent/typescript
|
|
20
|
+
# or
|
|
21
|
+
pnpm add @savvagent/typescript
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { FlagClient } from '@savvagent/typescript';
|
|
28
|
+
|
|
29
|
+
// Initialize the client
|
|
30
|
+
const client = new FlagClient({
|
|
31
|
+
apiKey: 'sdk_dev_your_api_key_here',
|
|
32
|
+
baseUrl: 'https://api.savvagent.com', // Optional: defaults to production
|
|
33
|
+
enableRealtime: true,
|
|
34
|
+
enableTelemetry: true,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Check if a flag is enabled
|
|
38
|
+
const isNewUIEnabled = await client.isEnabled('new-ui');
|
|
39
|
+
|
|
40
|
+
if (isNewUIEnabled) {
|
|
41
|
+
// Show new UI
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Execute code conditionally
|
|
45
|
+
await client.withFlag('experimental-feature', async () => {
|
|
46
|
+
// This code only runs if the flag is enabled
|
|
47
|
+
// Errors are automatically tracked with flag context
|
|
48
|
+
await experimentalFeature();
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
### Basic Flag Evaluation
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// Simple boolean check
|
|
58
|
+
const enabled = await client.isEnabled('my-feature');
|
|
59
|
+
|
|
60
|
+
// With user context for targeted rollouts
|
|
61
|
+
const enabled = await client.isEnabled('my-feature', {
|
|
62
|
+
userId: 'user-123',
|
|
63
|
+
attributes: {
|
|
64
|
+
plan: 'premium',
|
|
65
|
+
region: 'us-east',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Get detailed evaluation result
|
|
70
|
+
const result = await client.evaluate('my-feature', { userId: 'user-123' });
|
|
71
|
+
console.log(result);
|
|
72
|
+
// {
|
|
73
|
+
// key: 'my-feature',
|
|
74
|
+
// value: true,
|
|
75
|
+
// reason: 'evaluated', // or 'cached', 'default', 'error'
|
|
76
|
+
// metadata: {
|
|
77
|
+
// flagId: '...',
|
|
78
|
+
// description: 'My awesome feature'
|
|
79
|
+
// }
|
|
80
|
+
// }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Conditional Execution with Error Tracking
|
|
84
|
+
|
|
85
|
+
The `withFlag` method makes it easy to run code conditionally and automatically track errors:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Errors are automatically tracked with flag context
|
|
89
|
+
const result = await client.withFlag('new-algorithm', async () => {
|
|
90
|
+
return await complexCalculation();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (result) {
|
|
94
|
+
// Flag was enabled and code executed successfully
|
|
95
|
+
console.log('Result:', result);
|
|
96
|
+
} else {
|
|
97
|
+
// Flag was disabled
|
|
98
|
+
console.log('Feature not enabled');
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Manual Error Tracking
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
try {
|
|
106
|
+
await riskyOperation();
|
|
107
|
+
} catch (error) {
|
|
108
|
+
// Manually track error with flag context
|
|
109
|
+
client.trackError('my-feature', error, {
|
|
110
|
+
userId: 'user-123',
|
|
111
|
+
});
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Real-time Updates
|
|
117
|
+
|
|
118
|
+
Subscribe to flag changes and react in real-time:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// Subscribe to a specific flag
|
|
122
|
+
const unsubscribe = client.subscribe('my-feature', () => {
|
|
123
|
+
console.log('Flag was updated! Re-evaluating...');
|
|
124
|
+
// React to flag change (e.g., re-render UI)
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Subscribe to all flag changes
|
|
128
|
+
client.subscribe('*', () => {
|
|
129
|
+
console.log('Some flag was updated');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Unsubscribe when done
|
|
133
|
+
unsubscribe();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Cache Management
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// Get all cached flags
|
|
140
|
+
const cachedFlags = client.getCachedFlags();
|
|
141
|
+
console.log('Cached:', cachedFlags);
|
|
142
|
+
|
|
143
|
+
// Clear cache (forces re-evaluation)
|
|
144
|
+
client.clearCache();
|
|
145
|
+
|
|
146
|
+
// Check real-time connection status
|
|
147
|
+
if (client.isRealtimeConnected()) {
|
|
148
|
+
console.log('Connected to real-time updates');
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Configuration
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
interface FlagClientConfig {
|
|
156
|
+
/** SDK API key (required, starts with sdk_) */
|
|
157
|
+
apiKey: string;
|
|
158
|
+
|
|
159
|
+
/** Base URL for the Savvagent API (default: production) */
|
|
160
|
+
baseUrl?: string;
|
|
161
|
+
|
|
162
|
+
/** Enable real-time flag updates via SSE (default: true) */
|
|
163
|
+
enableRealtime?: boolean;
|
|
164
|
+
|
|
165
|
+
/** Cache TTL in milliseconds (default: 60000 = 1 minute) */
|
|
166
|
+
cacheTtl?: number;
|
|
167
|
+
|
|
168
|
+
/** Enable telemetry tracking (default: true) */
|
|
169
|
+
enableTelemetry?: boolean;
|
|
170
|
+
|
|
171
|
+
/** Default flag values when evaluation fails */
|
|
172
|
+
defaults?: Record<string, boolean>;
|
|
173
|
+
|
|
174
|
+
/** Custom error handler */
|
|
175
|
+
onError?: (error: Error) => void;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Example with All Options
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const client = new FlagClient({
|
|
183
|
+
apiKey: 'sdk_dev_abc123',
|
|
184
|
+
baseUrl: 'https://api.savvagent.com',
|
|
185
|
+
enableRealtime: true,
|
|
186
|
+
cacheTtl: 30000, // 30 seconds
|
|
187
|
+
enableTelemetry: true,
|
|
188
|
+
defaults: {
|
|
189
|
+
'new-ui': false, // Default to false if evaluation fails
|
|
190
|
+
'experimental-feature': false,
|
|
191
|
+
},
|
|
192
|
+
onError: (error) => {
|
|
193
|
+
console.error('Savvagent error:', error);
|
|
194
|
+
// Send to your error tracking service
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Framework Integration
|
|
200
|
+
|
|
201
|
+
### React
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { FlagClient } from '@savvagent/client-web';
|
|
205
|
+
import { createContext, useContext, useEffect, useState } from 'react';
|
|
206
|
+
|
|
207
|
+
// Create context
|
|
208
|
+
const FlagContext = createContext<FlagClient | null>(null);
|
|
209
|
+
|
|
210
|
+
// Provider component
|
|
211
|
+
export function FlagProvider({ children }: { children: React.ReactNode }) {
|
|
212
|
+
const [client] = useState(
|
|
213
|
+
() =>
|
|
214
|
+
new FlagClient({
|
|
215
|
+
apiKey: process.env.NEXT_PUBLIC_SAVVAGENT_KEY!,
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
return <FlagContext.Provider value={client}>{children}</FlagContext.Provider>;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Hook for using flags
|
|
223
|
+
export function useFlag(flagKey: string, context?: any) {
|
|
224
|
+
const client = useContext(FlagContext);
|
|
225
|
+
const [enabled, setEnabled] = useState(false);
|
|
226
|
+
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (!client) return;
|
|
229
|
+
|
|
230
|
+
// Initial evaluation
|
|
231
|
+
client.isEnabled(flagKey, context).then(setEnabled);
|
|
232
|
+
|
|
233
|
+
// Subscribe to updates
|
|
234
|
+
const unsubscribe = client.subscribe(flagKey, async () => {
|
|
235
|
+
const newValue = await client.isEnabled(flagKey, context);
|
|
236
|
+
setEnabled(newValue);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return unsubscribe;
|
|
240
|
+
}, [client, flagKey, context]);
|
|
241
|
+
|
|
242
|
+
return enabled;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Usage in component
|
|
246
|
+
function MyComponent() {
|
|
247
|
+
const isNewUIEnabled = useFlag('new-ui');
|
|
248
|
+
|
|
249
|
+
return <div>{isNewUIEnabled ? <NewUI /> : <OldUI />}</div>;
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Vue 3
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { FlagClient } from '@savvagent/client-web';
|
|
257
|
+
import { ref, onMounted, onUnmounted } from 'vue';
|
|
258
|
+
|
|
259
|
+
const client = new FlagClient({
|
|
260
|
+
apiKey: import.meta.env.VITE_SAVVAGENT_KEY,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
export function useFlag(flagKey: string, context?: any) {
|
|
264
|
+
const enabled = ref(false);
|
|
265
|
+
let unsubscribe: (() => void) | null = null;
|
|
266
|
+
|
|
267
|
+
onMounted(async () => {
|
|
268
|
+
enabled.value = await client.isEnabled(flagKey, context);
|
|
269
|
+
|
|
270
|
+
unsubscribe = client.subscribe(flagKey, async () => {
|
|
271
|
+
enabled.value = await client.isEnabled(flagKey, context);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
onUnmounted(() => {
|
|
276
|
+
unsubscribe?.();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
return enabled;
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Svelte
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
import { FlagClient } from '@savvagent/client-web';
|
|
287
|
+
import { writable } from 'svelte/store';
|
|
288
|
+
|
|
289
|
+
const client = new FlagClient({
|
|
290
|
+
apiKey: import.meta.env.VITE_SAVVAGENT_KEY,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
export function flagStore(flagKey: string, context?: any) {
|
|
294
|
+
const { subscribe, set } = writable(false);
|
|
295
|
+
|
|
296
|
+
// Initial evaluation
|
|
297
|
+
client.isEnabled(flagKey, context).then(set);
|
|
298
|
+
|
|
299
|
+
// Subscribe to updates
|
|
300
|
+
const unsubscribe = client.subscribe(flagKey, async () => {
|
|
301
|
+
const value = await client.isEnabled(flagKey, context);
|
|
302
|
+
set(value);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
subscribe,
|
|
307
|
+
unsubscribe,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Best Practices
|
|
313
|
+
|
|
314
|
+
### 1. Initialize Once
|
|
315
|
+
|
|
316
|
+
Create a single client instance and reuse it throughout your application:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// flags.ts
|
|
320
|
+
export const flagClient = new FlagClient({
|
|
321
|
+
apiKey: process.env.SAVVAGENT_API_KEY!,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// other-file.ts
|
|
325
|
+
import { flagClient } from './flags';
|
|
326
|
+
const enabled = await flagClient.isEnabled('my-feature');
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### 2. Use Context for Targeting
|
|
330
|
+
|
|
331
|
+
Always pass user context for consistent targeting:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
const context = {
|
|
335
|
+
userId: currentUser.id,
|
|
336
|
+
attributes: {
|
|
337
|
+
plan: currentUser.plan,
|
|
338
|
+
signupDate: currentUser.createdAt,
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const enabled = await client.isEnabled('premium-feature', context);
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 3. Set Defaults for Critical Flags
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
const client = new FlagClient({
|
|
349
|
+
apiKey: 'sdk_...',
|
|
350
|
+
defaults: {
|
|
351
|
+
'payment-enabled': true, // Default to enabled for critical features
|
|
352
|
+
'experimental-ui': false, // Default to disabled for experiments
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### 4. Clean Up
|
|
358
|
+
|
|
359
|
+
Always close the client when your application shuts down:
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
// In your cleanup logic
|
|
363
|
+
client.close();
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Telemetry
|
|
367
|
+
|
|
368
|
+
The SDK automatically sends telemetry data to help with AI error detection:
|
|
369
|
+
|
|
370
|
+
- **Evaluations**: Every flag evaluation is tracked (batched every 5 seconds)
|
|
371
|
+
- **Errors**: Errors in flagged code are tracked immediately
|
|
372
|
+
- **Privacy**: Only flag keys, results, and error metadata are sent (no sensitive data)
|
|
373
|
+
|
|
374
|
+
To disable telemetry:
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
const client = new FlagClient({
|
|
378
|
+
apiKey: 'sdk_...',
|
|
379
|
+
enableTelemetry: false,
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Real-time Updates
|
|
384
|
+
|
|
385
|
+
Real-time updates use Server-Sent Events (SSE) to push flag changes instantly:
|
|
386
|
+
|
|
387
|
+
- Automatic reconnection with exponential backoff
|
|
388
|
+
- Cache invalidation on flag updates
|
|
389
|
+
- Low overhead (single connection for all flags)
|
|
390
|
+
|
|
391
|
+
To disable real-time updates:
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
const client = new FlagClient({
|
|
395
|
+
apiKey: 'sdk_...',
|
|
396
|
+
enableRealtime: false,
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## API Reference
|
|
401
|
+
|
|
402
|
+
### FlagClient
|
|
403
|
+
|
|
404
|
+
#### Methods
|
|
405
|
+
|
|
406
|
+
- `isEnabled(flagKey, context?)`: Check if flag is enabled
|
|
407
|
+
- `evaluate(flagKey, context?)`: Get detailed evaluation result
|
|
408
|
+
- `withFlag(flagKey, callback, context?)`: Execute code conditionally
|
|
409
|
+
- `trackError(flagKey, error, context?)`: Manually track an error
|
|
410
|
+
- `subscribe(flagKey, callback)`: Subscribe to flag updates
|
|
411
|
+
- `getCachedFlags()`: Get all cached flag keys
|
|
412
|
+
- `clearCache()`: Clear the flag cache
|
|
413
|
+
- `isRealtimeConnected()`: Check real-time connection status
|
|
414
|
+
- `close()`: Close client and cleanup resources
|
|
415
|
+
|
|
416
|
+
## Troubleshooting
|
|
417
|
+
|
|
418
|
+
### Flags always return false
|
|
419
|
+
|
|
420
|
+
- Check your API key is correct and starts with `sdk_`
|
|
421
|
+
- Verify the baseUrl points to your Savvagent instance
|
|
422
|
+
- Check network requests in browser DevTools
|
|
423
|
+
|
|
424
|
+
### Real-time updates not working
|
|
425
|
+
|
|
426
|
+
- Ensure `enableRealtime: true` (default)
|
|
427
|
+
- Check if EventSource is supported in your environment
|
|
428
|
+
- Verify SSE endpoint is accessible
|
|
429
|
+
|
|
430
|
+
### TypeScript errors
|
|
431
|
+
|
|
432
|
+
- Ensure TypeScript version >= 5.0
|
|
433
|
+
- Check that `@savvagent/client-web` types are installed
|
|
434
|
+
|
|
435
|
+
## License
|
|
436
|
+
|
|
437
|
+
MIT
|
|
438
|
+
|
|
439
|
+
## Support
|
|
440
|
+
|
|
441
|
+
- Documentation: https://docs.savvagent.com
|
|
442
|
+
- Issues: https://github.com/yourusername/savvagent/issues
|
|
443
|
+
- Email: support@savvagent.com
|