@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 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