@teracrafts/flagkit 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 +2048 -0
- package/dist/index.d.ts +2048 -0
- package/dist/index.global.js +3 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# @teracrafts/flagkit
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for [FlagKit](https://flagkit.dev) - Feature flag management made simple.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Zero runtime dependencies** - No external packages required
|
|
8
|
+
- **Universal** - Browser and Node.js support
|
|
9
|
+
- **TypeScript-first** - Full type safety with comprehensive type definitions
|
|
10
|
+
- **Tiny bundle** - < 20KB gzipped
|
|
11
|
+
- **Resilient** - Automatic retry with exponential backoff, circuit breaker
|
|
12
|
+
- **Event tracking** - Analytics with batching
|
|
13
|
+
- **Offline mode** - Works without network
|
|
14
|
+
- **Security** - PII detection, bootstrap verification, timing attack protection, error sanitization
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @teracrafts/flagkit
|
|
20
|
+
# or
|
|
21
|
+
yarn add @teracrafts/flagkit
|
|
22
|
+
# or
|
|
23
|
+
pnpm add @teracrafts/flagkit
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import FlagKit from '@teracrafts/flagkit';
|
|
30
|
+
|
|
31
|
+
// Initialize the SDK
|
|
32
|
+
const client = await FlagKit.initialize({
|
|
33
|
+
apiKey: 'sdk_your_api_key',
|
|
34
|
+
onReady: () => console.log('FlagKit ready!'),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Wait for SDK to be ready
|
|
38
|
+
await client.waitForReady();
|
|
39
|
+
|
|
40
|
+
// Identify user
|
|
41
|
+
client.identify('user-123', {
|
|
42
|
+
email: 'user@example.com',
|
|
43
|
+
custom: { plan: 'premium' },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Evaluate flags
|
|
47
|
+
const darkMode = client.getBooleanValue('dark-mode', false);
|
|
48
|
+
const welcomeMsg = client.getStringValue('welcome-message', 'Hello!');
|
|
49
|
+
const maxItems = client.getNumberValue('max-items', 10);
|
|
50
|
+
const config = client.getJsonValue('feature-config', { enabled: false });
|
|
51
|
+
|
|
52
|
+
// Track events
|
|
53
|
+
client.track('button_clicked', { button: 'signup' });
|
|
54
|
+
|
|
55
|
+
// Cleanup when done
|
|
56
|
+
await client.close();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Architecture
|
|
60
|
+
|
|
61
|
+
The SDK is organized into clean, modular components:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
@teracrafts/flagkit/
|
|
65
|
+
├── index.ts # Public exports
|
|
66
|
+
├── flagkit.ts # Static FlagKit factory
|
|
67
|
+
├── client.ts # FlagKitClient implementation
|
|
68
|
+
├── types/ # Type definitions
|
|
69
|
+
│ ├── config.ts # FlagKitOptions, BootstrapConfig
|
|
70
|
+
│ ├── context.ts # EvaluationContext
|
|
71
|
+
│ ├── flag.ts # FlagState, EvaluationResult
|
|
72
|
+
│ └── events.ts # Event types
|
|
73
|
+
├── errors/ # Error types and codes
|
|
74
|
+
│ ├── flagkit-error.ts
|
|
75
|
+
│ ├── error-codes.ts
|
|
76
|
+
│ └── sanitizer.ts # Error message sanitization
|
|
77
|
+
├── http/ # HTTP client, circuit breaker, retry
|
|
78
|
+
│ ├── http-client.ts
|
|
79
|
+
│ └── circuit-breaker.ts
|
|
80
|
+
├── core/ # Core components
|
|
81
|
+
│ ├── cache.ts # In-memory cache with TTL
|
|
82
|
+
│ ├── context-manager.ts
|
|
83
|
+
│ ├── polling-manager.ts
|
|
84
|
+
│ └── event-queue.ts # Event batching
|
|
85
|
+
├── storage/ # Storage implementations
|
|
86
|
+
│ └── storage.ts
|
|
87
|
+
└── utils/ # Utilities
|
|
88
|
+
├── logger.ts
|
|
89
|
+
└── security.ts # PII detection, HMAC signing
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Configuration Options
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
interface FlagKitOptions {
|
|
96
|
+
// Required
|
|
97
|
+
apiKey: string; // Your FlagKit API key
|
|
98
|
+
|
|
99
|
+
// Optional
|
|
100
|
+
pollingInterval?: number; // Default: 30000 (ms)
|
|
101
|
+
enablePolling?: boolean; // Default: true
|
|
102
|
+
cacheEnabled?: boolean; // Default: true
|
|
103
|
+
cacheTTL?: number; // Default: 300000 (ms)
|
|
104
|
+
offline?: boolean; // Default: false
|
|
105
|
+
timeout?: number; // Default: 5000 (ms)
|
|
106
|
+
retries?: number; // Default: 3
|
|
107
|
+
debug?: boolean; // Default: false
|
|
108
|
+
localPort?: number; // Uses http://localhost:{port}/api/v1
|
|
109
|
+
bootstrap?: BootstrapData; // Fallback values (with optional signature)
|
|
110
|
+
|
|
111
|
+
// Callbacks
|
|
112
|
+
onReady?: () => void;
|
|
113
|
+
onError?: (error: FlagKitError) => void;
|
|
114
|
+
onUpdate?: (flags: FlagState[]) => void;
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## API Reference
|
|
119
|
+
|
|
120
|
+
### Flag Evaluation
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// Boolean flags
|
|
124
|
+
getBooleanValue(key: string, defaultValue: boolean, context?: EvaluationContext): boolean
|
|
125
|
+
|
|
126
|
+
// String flags
|
|
127
|
+
getStringValue(key: string, defaultValue: string, context?: EvaluationContext): string
|
|
128
|
+
|
|
129
|
+
// Number flags
|
|
130
|
+
getNumberValue(key: string, defaultValue: number, context?: EvaluationContext): number
|
|
131
|
+
|
|
132
|
+
// JSON flags
|
|
133
|
+
getJsonValue<T>(key: string, defaultValue: T, context?: EvaluationContext): T
|
|
134
|
+
|
|
135
|
+
// Full evaluation result
|
|
136
|
+
evaluate(key: string, context?: EvaluationContext): EvaluationResult
|
|
137
|
+
|
|
138
|
+
// Evaluate all flags
|
|
139
|
+
evaluateAll(context?: EvaluationContext): Record<string, EvaluationResult>
|
|
140
|
+
|
|
141
|
+
// Check if flag exists
|
|
142
|
+
hasFlag(key: string): boolean
|
|
143
|
+
|
|
144
|
+
// Get all flag keys
|
|
145
|
+
getAllFlagKeys(): string[]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Context Management
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// Set global context
|
|
152
|
+
setContext(context: EvaluationContext): void
|
|
153
|
+
|
|
154
|
+
// Get current context
|
|
155
|
+
getContext(): EvaluationContext | null
|
|
156
|
+
|
|
157
|
+
// Clear context
|
|
158
|
+
clearContext(): void
|
|
159
|
+
|
|
160
|
+
// Identify user
|
|
161
|
+
identify(userId: string, attributes?: Partial<EvaluationContext>): void
|
|
162
|
+
|
|
163
|
+
// Reset to anonymous
|
|
164
|
+
reset(): void
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Event Tracking
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// Track custom event
|
|
171
|
+
track(eventType: string, eventData?: Record<string, unknown>): void
|
|
172
|
+
|
|
173
|
+
// Flush events immediately
|
|
174
|
+
flush(): Promise<void>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Lifecycle
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// Check if ready
|
|
181
|
+
isReady(): boolean
|
|
182
|
+
|
|
183
|
+
// Wait for ready
|
|
184
|
+
waitForReady(): Promise<void>
|
|
185
|
+
|
|
186
|
+
// Force refresh flags
|
|
187
|
+
refresh(): Promise<void>
|
|
188
|
+
|
|
189
|
+
// Close and cleanup
|
|
190
|
+
close(): Promise<void>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Evaluation Context
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
interface EvaluationContext {
|
|
197
|
+
userId?: string;
|
|
198
|
+
userKey?: string;
|
|
199
|
+
email?: string;
|
|
200
|
+
name?: string;
|
|
201
|
+
anonymous?: boolean;
|
|
202
|
+
country?: string;
|
|
203
|
+
ip?: string;
|
|
204
|
+
userAgent?: string;
|
|
205
|
+
custom?: Record<string, unknown>;
|
|
206
|
+
privateAttributes?: string[]; // Attributes not sent to server
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Error Handling
|
|
211
|
+
|
|
212
|
+
The SDK uses specific error classes for different error types:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import {
|
|
216
|
+
FlagKitError,
|
|
217
|
+
InitializationError,
|
|
218
|
+
AuthenticationError,
|
|
219
|
+
NetworkError,
|
|
220
|
+
EvaluationError,
|
|
221
|
+
} from '@teracrafts/flagkit';
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
await FlagKit.initialize({ apiKey: 'invalid' });
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if (error instanceof AuthenticationError) {
|
|
227
|
+
console.error('Invalid API key');
|
|
228
|
+
} else if (error instanceof NetworkError) {
|
|
229
|
+
console.error('Network issue:', error.message);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
All errors include:
|
|
235
|
+
- `code`: Error code string (e.g., "NETWORK_TIMEOUT")
|
|
236
|
+
- `numericCode`: Numeric error code (e.g., 1301)
|
|
237
|
+
- `recoverable`: Whether the operation can be retried
|
|
238
|
+
- `retryAfter`: Seconds to wait before retrying (if applicable)
|
|
239
|
+
|
|
240
|
+
## Offline Mode
|
|
241
|
+
|
|
242
|
+
Enable offline mode for environments without network access:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
const client = await FlagKit.initialize({
|
|
246
|
+
apiKey: 'sdk_your_api_key',
|
|
247
|
+
offline: true,
|
|
248
|
+
bootstrap: {
|
|
249
|
+
'dark-mode': false,
|
|
250
|
+
'max-items': 10,
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Local Development
|
|
256
|
+
|
|
257
|
+
Enable local development mode to connect to a local FlagKit server:
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
const client = await FlagKit.initialize({
|
|
261
|
+
apiKey: 'sdk_your_api_key',
|
|
262
|
+
localPort: 8200,
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Browser Usage
|
|
267
|
+
|
|
268
|
+
The SDK includes a browser bundle that exposes `FlagKit` globally:
|
|
269
|
+
|
|
270
|
+
```html
|
|
271
|
+
<script src="https://unpkg.com/@teracrafts/flagkit"></script>
|
|
272
|
+
<script>
|
|
273
|
+
FlagKit.initialize({ apiKey: 'sdk_your_api_key' })
|
|
274
|
+
.then(client => {
|
|
275
|
+
const darkMode = client.getBooleanValue('dark-mode', false);
|
|
276
|
+
});
|
|
277
|
+
</script>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Advanced: Direct Client Access
|
|
281
|
+
|
|
282
|
+
For more control, you can use the client directly:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
import { FlagKitClient, HttpClient, FlagCache } from '@teracrafts/flagkit';
|
|
286
|
+
|
|
287
|
+
const client = new FlagKitClient({
|
|
288
|
+
apiKey: 'sdk_your_api_key',
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
await client.initialize();
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## TypeScript Support
|
|
295
|
+
|
|
296
|
+
The SDK is written in TypeScript and includes full type definitions:
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import type {
|
|
300
|
+
FlagKitOptions,
|
|
301
|
+
EvaluationContext,
|
|
302
|
+
EvaluationResult,
|
|
303
|
+
FlagState,
|
|
304
|
+
FlagKitError,
|
|
305
|
+
} from '@teracrafts/flagkit';
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Security Features
|
|
309
|
+
|
|
310
|
+
### PII Detection
|
|
311
|
+
|
|
312
|
+
The SDK can detect and warn about potential PII (Personally Identifiable Information) in contexts and events:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// Enable strict PII mode - throws errors instead of warnings
|
|
316
|
+
const client = await FlagKit.initialize({
|
|
317
|
+
apiKey: 'sdk_...',
|
|
318
|
+
strictPIIMode: true,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Attributes containing PII will throw SecurityError
|
|
322
|
+
try {
|
|
323
|
+
client.identify('user-123', {
|
|
324
|
+
email: 'user@example.com', // PII detected!
|
|
325
|
+
});
|
|
326
|
+
} catch (error) {
|
|
327
|
+
if (error instanceof SecurityError) {
|
|
328
|
+
console.error('PII error:', error.message);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Use privateAttributes to mark fields as intentionally containing PII
|
|
333
|
+
client.setContext({
|
|
334
|
+
userId: 'user-123',
|
|
335
|
+
email: 'user@example.com',
|
|
336
|
+
privateAttributes: ['email'], // Marks email as intentionally private
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Bootstrap Signature Verification
|
|
341
|
+
|
|
342
|
+
Verify bootstrap data integrity using HMAC signatures:
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
import { createBootstrapSignature } from '@teracrafts/flagkit';
|
|
346
|
+
|
|
347
|
+
// Create signed bootstrap data
|
|
348
|
+
const bootstrap = createBootstrapSignature(
|
|
349
|
+
{ 'feature-a': true, 'feature-b': 'value' },
|
|
350
|
+
'sdk_your_api_key'
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// Use signed bootstrap with verification
|
|
354
|
+
const client = await FlagKit.initialize({
|
|
355
|
+
apiKey: 'sdk_...',
|
|
356
|
+
bootstrap,
|
|
357
|
+
bootstrapVerification: {
|
|
358
|
+
enabled: true,
|
|
359
|
+
maxAge: 86400000, // 24 hours in milliseconds
|
|
360
|
+
onFailure: 'error', // 'warn' (default), 'error', or 'ignore'
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Evaluation Jitter (Timing Attack Protection)
|
|
366
|
+
|
|
367
|
+
Add random delays to flag evaluations to prevent cache timing attacks:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
const client = await FlagKit.initialize({
|
|
371
|
+
apiKey: 'sdk_...',
|
|
372
|
+
evaluationJitter: {
|
|
373
|
+
enabled: true,
|
|
374
|
+
minMs: 5,
|
|
375
|
+
maxMs: 15,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Error Sanitization
|
|
381
|
+
|
|
382
|
+
Automatically redact sensitive information from error messages:
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
const client = await FlagKit.initialize({
|
|
386
|
+
apiKey: 'sdk_...',
|
|
387
|
+
errorSanitization: {
|
|
388
|
+
enabled: true,
|
|
389
|
+
preserveOriginal: false, // Set true for debugging
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
// Errors will have paths, IPs, API keys, and emails redacted
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Key Rotation
|
|
396
|
+
|
|
397
|
+
Support seamless API key rotation:
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
const client = await FlagKit.initialize({
|
|
401
|
+
apiKey: 'sdk_primary_key',
|
|
402
|
+
secondaryApiKey: 'sdk_secondary_key',
|
|
403
|
+
keyRotationGracePeriod: 300000, // 5 minutes
|
|
404
|
+
});
|
|
405
|
+
// SDK will automatically failover to secondary key on 401 errors
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## All Configuration Options
|
|
409
|
+
|
|
410
|
+
| Option | Type | Default | Description |
|
|
411
|
+
|--------|------|---------|-------------|
|
|
412
|
+
| `apiKey` | string | Required | API key for authentication |
|
|
413
|
+
| `secondaryApiKey` | string | - | Secondary key for rotation |
|
|
414
|
+
| `keyRotationGracePeriod` | number | 300000 | Grace period in ms |
|
|
415
|
+
| `pollingInterval` | number | 30000 | Polling interval in ms |
|
|
416
|
+
| `enablePolling` | boolean | true | Enable background polling |
|
|
417
|
+
| `cacheEnabled` | boolean | true | Enable local caching |
|
|
418
|
+
| `cacheTTL` | number | 300000 | Cache TTL in ms |
|
|
419
|
+
| `persistCache` | boolean | false | Persist cache to storage |
|
|
420
|
+
| `cacheStorageKey` | string | 'flagkit_cache' | Storage key for cache |
|
|
421
|
+
| `offline` | boolean | false | Offline mode |
|
|
422
|
+
| `timeout` | number | 5000 | Request timeout in ms |
|
|
423
|
+
| `retries` | number | 3 | Number of retry attempts |
|
|
424
|
+
| `bootstrap` | BootstrapData | {} | Initial flag values |
|
|
425
|
+
| `bootstrapVerification` | object | enabled | Bootstrap verification settings |
|
|
426
|
+
| `localPort` | number | - | Local development port |
|
|
427
|
+
| `debug` | boolean | false | Enable debug logging |
|
|
428
|
+
| `logger` | Logger | - | Custom logger |
|
|
429
|
+
| `onReady` | function | - | Ready callback |
|
|
430
|
+
| `onError` | function | - | Error callback |
|
|
431
|
+
| `onUpdate` | function | - | Update callback |
|
|
432
|
+
| `strictPIIMode` | boolean | false | Error on PII detection |
|
|
433
|
+
| `evaluationJitter` | object | disabled | Timing attack protection |
|
|
434
|
+
| `errorSanitization` | object | enabled | Sanitize error messages |
|
|
435
|
+
|
|
436
|
+
## Requirements
|
|
437
|
+
|
|
438
|
+
- Node.js 18+ (for server-side)
|
|
439
|
+
- Modern browser with `fetch` support (for client-side)
|
|
440
|
+
|
|
441
|
+
## License
|
|
442
|
+
|
|
443
|
+
MIT
|