@supashiphq/javascript-sdk 0.7.7
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/LICENSE +21 -0
- package/README.md +656 -0
- package/dist/index.d.mts +261 -0
- package/dist/index.d.ts +261 -0
- package/dist/index.js +1072 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1044 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Supaship
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
# Supaship JavaScript SDK
|
|
2
|
+
|
|
3
|
+
A type-safe JavaScript SDK for Supaship that provides a simple way to manage feature flags in your JavaScript applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @supashiphq/javascript-sdk
|
|
9
|
+
# or
|
|
10
|
+
yarn add @supashiphq/javascript-sdk
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @supashiphq/javascript-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { SupaClient, FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'
|
|
19
|
+
|
|
20
|
+
// Define your features with fallback values
|
|
21
|
+
const features = {
|
|
22
|
+
'new-ui': false,
|
|
23
|
+
'premium-features': true,
|
|
24
|
+
'theme-config': {
|
|
25
|
+
primaryColor: '#007bff',
|
|
26
|
+
darkMode: false,
|
|
27
|
+
},
|
|
28
|
+
} satisfies FeaturesWithFallbacks
|
|
29
|
+
|
|
30
|
+
// Create client with features
|
|
31
|
+
const client = new SupaClient({
|
|
32
|
+
apiKey: 'your-api-key',
|
|
33
|
+
environment: 'production',
|
|
34
|
+
features,
|
|
35
|
+
context: {
|
|
36
|
+
userID: '123',
|
|
37
|
+
email: 'user@example.com',
|
|
38
|
+
plan: 'premium',
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Get a single feature flag (fully typed!)
|
|
43
|
+
const isNewUIEnabled = await client.getFeature('new-ui')
|
|
44
|
+
// Type: boolean
|
|
45
|
+
|
|
46
|
+
// Get theme configuration (fully typed!)
|
|
47
|
+
const themeConfig = await client.getFeature('theme-config')
|
|
48
|
+
// Type: { primaryColor: string; darkMode: boolean; }
|
|
49
|
+
|
|
50
|
+
// Get multiple features at once
|
|
51
|
+
const allFeatures = await client.getFeatures(['new-ui', 'premium-features'])
|
|
52
|
+
// Type: Record<string, FeatureValue>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Type-Safe Features
|
|
56
|
+
|
|
57
|
+
The SDK provides type safety through the `FeaturesWithFallbacks` type. This ensures:
|
|
58
|
+
|
|
59
|
+
- ✅ Features must be defined before use
|
|
60
|
+
- ✅ Feature names are validated at compile-time
|
|
61
|
+
- ✅ Feature values are properly typed
|
|
62
|
+
- ✅ No typos in feature names
|
|
63
|
+
|
|
64
|
+
### Defining Features
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'
|
|
68
|
+
|
|
69
|
+
// ✅ Recommended: satisfies (preserves exact literal types)
|
|
70
|
+
const features = {
|
|
71
|
+
// Boolean flags
|
|
72
|
+
'dark-mode': false,
|
|
73
|
+
'beta-access': true,
|
|
74
|
+
|
|
75
|
+
// Object configurations
|
|
76
|
+
'ui-config': {
|
|
77
|
+
theme: 'light' as const, // Preserves 'light' literal
|
|
78
|
+
maxItems: 100,
|
|
79
|
+
enableAnimations: true,
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// Arrays
|
|
83
|
+
'allowed-regions': ['us-east', 'eu-west'],
|
|
84
|
+
|
|
85
|
+
// Null for disabled/unset features
|
|
86
|
+
'experimental-feature': null,
|
|
87
|
+
} satisfies FeaturesWithFallbacks
|
|
88
|
+
|
|
89
|
+
// ⚠️ Avoid: Type annotation (widens types, loses precision)
|
|
90
|
+
const features: FeaturesWithFallbacks = {
|
|
91
|
+
'dark-mode': false,
|
|
92
|
+
'ui-config': {
|
|
93
|
+
theme: 'light', // Widened to string, not 'light' literal
|
|
94
|
+
maxItems: 100,
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const client = new SupaClient({
|
|
99
|
+
apiKey: 'your-api-key',
|
|
100
|
+
environment: 'production',
|
|
101
|
+
features,
|
|
102
|
+
context: {},
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// ✅ TypeScript knows 'dark-mode' exists and returns boolean
|
|
106
|
+
const darkMode = await client.getFeature('dark-mode')
|
|
107
|
+
|
|
108
|
+
// ❌ TypeScript error: 'unknown-feature' doesn't exist
|
|
109
|
+
const value = await client.getFeature('unknown-feature')
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## API Reference
|
|
113
|
+
|
|
114
|
+
### SupaClient
|
|
115
|
+
|
|
116
|
+
#### Constructor
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
new SupaClient(config: SupaClientConfig)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Configuration Options:**
|
|
123
|
+
|
|
124
|
+
| Option | Type | Required | Description |
|
|
125
|
+
| --------------- | ----------------------- | -------- | --------------------------------------------------------------- |
|
|
126
|
+
| `apiKey` | `string` | Yes | Your Supaship API key (Project Settings -> API Keys) |
|
|
127
|
+
| `environment` | `string` | Yes | Environment slug (e.g., `production`, `staging`, `development`) |
|
|
128
|
+
| `features` | `FeaturesWithFallbacks` | Yes | Feature definitions with fallback values |
|
|
129
|
+
| `context` | `FeatureContext` | Yes | Default context for feature evaluation |
|
|
130
|
+
| `networkConfig` | `NetworkConfig` | No | Network settings (endpoints, retry, timeout, custom fetch) |
|
|
131
|
+
| `plugins` | `SupaPlugin[]` | No | Plugins for observability, caching, etc. |
|
|
132
|
+
|
|
133
|
+
**Feature Context:**
|
|
134
|
+
|
|
135
|
+
| Field | Type | Description |
|
|
136
|
+
| --------------- | ---------------------------------- | ------------------------------------------- |
|
|
137
|
+
| `[key: string]` | `string` `number` `boolean` `null` | Key value pairs for feature flag evaluation |
|
|
138
|
+
|
|
139
|
+
**Network Configuration:**
|
|
140
|
+
|
|
141
|
+
| Field | Type | Required | Default | Description |
|
|
142
|
+
| ------------------ | ---------------------------------------------------------------------- | -------- | --------------------------------------- | ------------------------------------------------------------- |
|
|
143
|
+
| `featuresAPIUrl` | `string` | No | `https://edge.supaship.com/v1/features` | Override features API URL |
|
|
144
|
+
| `eventsAPIUrl` | `string` | No | `https://edge.supaship.com/v1/events` | Override events/analytics API URL |
|
|
145
|
+
| `requestTimeoutMs` | `number` | No | `10000` | Abort requests after N ms (uses AbortController if available) |
|
|
146
|
+
| `fetchFn` | `(input: RequestInfo \| URL, init?: RequestInit) => Promise<Response>` | No | — | Custom fetch (pass in Node < 18 or specialized runtimes) |
|
|
147
|
+
| `retry` | `RetryConfig` | No | see below | Retry behavior for network requests |
|
|
148
|
+
|
|
149
|
+
Retry (networkConfig.retry):
|
|
150
|
+
|
|
151
|
+
| Field | Type | Required | Default | Description |
|
|
152
|
+
| ------------- | --------- | -------- | ------- | -------------------------------------- |
|
|
153
|
+
| `enabled` | `boolean` | No | `true` | Enable/disable retries |
|
|
154
|
+
| `maxAttempts` | `number` | No | `3` | Maximum retry attempts |
|
|
155
|
+
| `backoff` | `number` | No | `1000` | Base backoff delay in ms (exponential) |
|
|
156
|
+
|
|
157
|
+
#### Methods
|
|
158
|
+
|
|
159
|
+
##### getFeature()
|
|
160
|
+
|
|
161
|
+
Retrieves a single feature flag value with full TypeScript type safety.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
getFeature<TKey extends keyof TFeatures>(
|
|
165
|
+
featureName: TKey,
|
|
166
|
+
options?: { context?: FeatureContext }
|
|
167
|
+
): Promise<TFeatures[TKey]>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Parameters:**
|
|
171
|
+
|
|
172
|
+
- `featureName`: The name of the feature flag (must be defined in your features config)
|
|
173
|
+
- `options.context`: Context override for this request
|
|
174
|
+
|
|
175
|
+
**Examples:**
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const features = {
|
|
179
|
+
'dark-mode': false,
|
|
180
|
+
'theme-config': { primary: '#007bff' },
|
|
181
|
+
} satisfies FeaturesWithFallbacks
|
|
182
|
+
|
|
183
|
+
const client = new SupaClient({
|
|
184
|
+
apiKey: 'key',
|
|
185
|
+
environment: 'production',
|
|
186
|
+
features,
|
|
187
|
+
context: {},
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// Get boolean feature
|
|
191
|
+
const darkMode = await client.getFeature('dark-mode')
|
|
192
|
+
// Type: boolean
|
|
193
|
+
|
|
194
|
+
// Get object feature
|
|
195
|
+
const theme = await client.getFeature('theme-config')
|
|
196
|
+
// Type: { primary: string }
|
|
197
|
+
|
|
198
|
+
// With context override
|
|
199
|
+
const darkMode = await client.getFeature('dark-mode', {
|
|
200
|
+
context: { userID: '123', plan: 'premium' },
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
##### getFeatures()
|
|
205
|
+
|
|
206
|
+
Retrieves multiple feature flags in a single request.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
getFeatures(
|
|
210
|
+
featureNames: (keyof TFeatures)[],
|
|
211
|
+
options?: { context?: FeatureContext }
|
|
212
|
+
): Promise<Record<string, FeatureValue>>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Parameters:**
|
|
216
|
+
|
|
217
|
+
- `featureNames`: Array of feature flag names (must be defined in your features config)
|
|
218
|
+
- `options.context`: Context override for this request
|
|
219
|
+
|
|
220
|
+
**Examples:**
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const features = {
|
|
224
|
+
'new-ui': false,
|
|
225
|
+
'premium-content': false,
|
|
226
|
+
'beta-mode': false,
|
|
227
|
+
} satisfies FeaturesWithFallbacks
|
|
228
|
+
|
|
229
|
+
const client = new SupaClient({
|
|
230
|
+
apiKey: 'key',
|
|
231
|
+
environment: 'prod',
|
|
232
|
+
features,
|
|
233
|
+
context: {},
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
// Get multiple features
|
|
237
|
+
const results = await client.getFeatures(['new-ui', 'premium-content'])
|
|
238
|
+
// { 'new-ui': true, 'premium-content': false }
|
|
239
|
+
|
|
240
|
+
// With context override
|
|
241
|
+
const results = await client.getFeatures(['new-ui', 'beta-mode'], {
|
|
242
|
+
context: { userID: '123', plan: 'premium' },
|
|
243
|
+
})
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
##### updateContext()
|
|
247
|
+
|
|
248
|
+
Updates the default context for the client.
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
updateContext(context: FeatureContext, mergeWithExisting?: boolean): void
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Parameters:**
|
|
255
|
+
|
|
256
|
+
- `context`: New context data
|
|
257
|
+
- `mergeWithExisting`: Whether to merge with existing context (default: `true`)
|
|
258
|
+
|
|
259
|
+
**Examples:**
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// Merge with existing context
|
|
263
|
+
client.updateContext({ userID: '456' })
|
|
264
|
+
|
|
265
|
+
// Replace entire context
|
|
266
|
+
client.updateContext({ userID: '456', newField: 'value' }, false)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
##### getContext()
|
|
270
|
+
|
|
271
|
+
Retrieves the current default context.
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
getContext(): FeatureContext | undefined
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Types
|
|
278
|
+
|
|
279
|
+
### FeatureValue
|
|
280
|
+
|
|
281
|
+
Supported feature flag value types:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
type FeatureValue = boolean | null | Record<string, unknown> | unknown[]
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
- **`boolean`** - Simple on/off flags
|
|
288
|
+
- **`object`** - Structured configuration data (e.g., `{ theme: 'dark', size: 'large' }`)
|
|
289
|
+
- **`array`** - Lists of values (e.g., `['feature-a', 'feature-b']`)
|
|
290
|
+
- **`null`** - Disabled or unset state
|
|
291
|
+
|
|
292
|
+
> **Note:** Strings and numbers are not supported as standalone feature values. Use objects or arrays for complex data.
|
|
293
|
+
|
|
294
|
+
### FeatureContext
|
|
295
|
+
|
|
296
|
+
Context object for feature evaluation:
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
interface FeatureContext {
|
|
300
|
+
[key: string]: string | number | boolean | null | undefined
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Common context properties:
|
|
305
|
+
|
|
306
|
+
- `userID`: User identifier
|
|
307
|
+
- `email`: User email
|
|
308
|
+
- `plan`: Membership plan (e.g., 'premium', 'free')
|
|
309
|
+
- `version`: Application version (e.g., 1.0.0)
|
|
310
|
+
|
|
311
|
+
> Note: The above are just common examples. You can use any properties in your context object that make sense for your application's feature targeting needs.
|
|
312
|
+
|
|
313
|
+
## Best Practices
|
|
314
|
+
|
|
315
|
+
### 1. Define Features Centrally
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
// features.ts
|
|
319
|
+
import { FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'
|
|
320
|
+
|
|
321
|
+
export const features = {
|
|
322
|
+
'new-dashboard': false,
|
|
323
|
+
'premium-features': false,
|
|
324
|
+
'ui-settings': {
|
|
325
|
+
theme: 'light' as const,
|
|
326
|
+
sidebarCollapsed: false,
|
|
327
|
+
},
|
|
328
|
+
'enabled-regions': ['us', 'eu'],
|
|
329
|
+
} satisfies FeaturesWithFallbacks
|
|
330
|
+
|
|
331
|
+
// client.ts
|
|
332
|
+
import { SupaClient } from '@supashiphq/javascript-sdk'
|
|
333
|
+
import { features } from './features'
|
|
334
|
+
|
|
335
|
+
export const client = new SupaClient({
|
|
336
|
+
apiKey: process.env.SUPASHIP_API_KEY!,
|
|
337
|
+
environment: process.env.ENVIRONMENT!,
|
|
338
|
+
features,
|
|
339
|
+
context: {},
|
|
340
|
+
})
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### 2. Use `satisfies` for Type Safety
|
|
344
|
+
|
|
345
|
+
Always use `satisfies` instead of type annotations to preserve literal types:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
// ✅ Good - preserves literal types
|
|
349
|
+
const features = {
|
|
350
|
+
'dark-mode': false,
|
|
351
|
+
config: {
|
|
352
|
+
maxItems: 50,
|
|
353
|
+
theme: 'light' as const,
|
|
354
|
+
},
|
|
355
|
+
} satisfies FeaturesWithFallbacks
|
|
356
|
+
|
|
357
|
+
// ❌ Bad - loses literal types
|
|
358
|
+
const features: FeaturesWithFallbacks = {
|
|
359
|
+
'dark-mode': false,
|
|
360
|
+
config: {
|
|
361
|
+
maxItems: 50,
|
|
362
|
+
theme: 'light', // Widened to string
|
|
363
|
+
},
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const client = new SupaClient({
|
|
367
|
+
apiKey: 'key',
|
|
368
|
+
environment: 'prod',
|
|
369
|
+
features,
|
|
370
|
+
context: {},
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
// With satisfies: TypeScript knows the exact literal type
|
|
374
|
+
const config = await client.getFeature('config')
|
|
375
|
+
// Type: { maxItems: number; theme: 'light'; }
|
|
376
|
+
|
|
377
|
+
const maxItems: number = config.maxItems // ✅ Type-safe
|
|
378
|
+
const theme = config.theme // ✅ Type-safe, inferred as 'light' literal
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### 3. Use Context for Targeting
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
const client = new SupaClient({
|
|
385
|
+
apiKey: 'your-api-key',
|
|
386
|
+
environment: 'production',
|
|
387
|
+
features,
|
|
388
|
+
context: {
|
|
389
|
+
userID: user.id,
|
|
390
|
+
email: user.email,
|
|
391
|
+
plan: user.subscriptionPlan,
|
|
392
|
+
version: process.env.APP_VERSION!,
|
|
393
|
+
},
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
// Update context when user state changes
|
|
397
|
+
function onUserLogin(user) {
|
|
398
|
+
client.updateContext({
|
|
399
|
+
userID: user.id,
|
|
400
|
+
email: user.email,
|
|
401
|
+
plan: user.plan,
|
|
402
|
+
})
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### 4. Batch Feature Requests
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
// ✅ Good - single API call
|
|
410
|
+
const results = await client.getFeatures(['feature-1', 'feature-2', 'feature-3'])
|
|
411
|
+
|
|
412
|
+
// ❌ Less efficient - multiple API calls
|
|
413
|
+
const feature1 = await client.getFeature('feature-1')
|
|
414
|
+
const feature2 = await client.getFeature('feature-2')
|
|
415
|
+
const feature3 = await client.getFeature('feature-3')
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Plugin System
|
|
419
|
+
|
|
420
|
+
The SDK supports plugins for observability, caching, logging, and more.
|
|
421
|
+
|
|
422
|
+
### Built-in Plugins
|
|
423
|
+
|
|
424
|
+
#### Toolbar Plugin
|
|
425
|
+
|
|
426
|
+
Visual toolbar for local development and testing.
|
|
427
|
+
|
|
428
|
+
**✨ Auto-enabled in browser environments!** The toolbar is automatically enabled in browsers with `enabled: 'auto'` (shows only on localhost). No manual configuration needed!
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
import { SupaClient, FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'
|
|
432
|
+
|
|
433
|
+
const features = {
|
|
434
|
+
'new-ui': false,
|
|
435
|
+
premium: false,
|
|
436
|
+
} satisfies FeaturesWithFallbacks
|
|
437
|
+
|
|
438
|
+
// ✅ Automatic (recommended) - Toolbar auto-enabled in browser with 'auto' mode
|
|
439
|
+
const client = new SupaClient({
|
|
440
|
+
apiKey: 'your-api-key',
|
|
441
|
+
environment: 'development',
|
|
442
|
+
features,
|
|
443
|
+
context: {},
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
// 🎨 Custom configuration
|
|
447
|
+
const client = new SupaClient({
|
|
448
|
+
apiKey: 'your-api-key',
|
|
449
|
+
environment: 'development',
|
|
450
|
+
features,
|
|
451
|
+
context: {},
|
|
452
|
+
toolbar: {
|
|
453
|
+
enabled: true, // Always show
|
|
454
|
+
position: {
|
|
455
|
+
placement: 'bottom-right',
|
|
456
|
+
offset: { x: '1rem', y: '1rem' },
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
// ❌ Opt-out (disable toolbar)
|
|
462
|
+
const client = new SupaClient({
|
|
463
|
+
apiKey: 'your-api-key',
|
|
464
|
+
environment: 'production',
|
|
465
|
+
features,
|
|
466
|
+
context: {},
|
|
467
|
+
toolbar: false,
|
|
468
|
+
})
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Features:**
|
|
472
|
+
|
|
473
|
+
- ✨ **Auto-enabled in browser** - No configuration needed!
|
|
474
|
+
- 🎯 Visual interface showing all configured feature flags
|
|
475
|
+
- 🔄 Override feature flag values locally
|
|
476
|
+
- 💾 Persistent storage in localStorage
|
|
477
|
+
- 🎨 Customizable position
|
|
478
|
+
- 🏠 Smart detection (shows only on localhost by default)
|
|
479
|
+
- 🚫 Easy opt-out with `toolbar: false`
|
|
480
|
+
|
|
481
|
+
## Examples
|
|
482
|
+
|
|
483
|
+
### React Integration
|
|
484
|
+
|
|
485
|
+
For React applications, use our dedicated React SDK which provides hooks and components optimized for React:
|
|
486
|
+
|
|
487
|
+
📦 **[@supashiphq/react-sdk](https://npmjs.com/package/@supashiphq/react-sdk)**
|
|
488
|
+
|
|
489
|
+
### Node.js Server
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
import express from 'express'
|
|
493
|
+
import { SupaClient, FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'
|
|
494
|
+
|
|
495
|
+
const features = {
|
|
496
|
+
'new-api': false,
|
|
497
|
+
'cache-enabled': true,
|
|
498
|
+
'rate-limit': { maxRequests: 100, windowMs: 60000 },
|
|
499
|
+
} satisfies FeaturesWithFallbacks
|
|
500
|
+
|
|
501
|
+
const featureClient = new SupaClient({
|
|
502
|
+
apiKey: process.env.SUPASHIP_API_KEY!,
|
|
503
|
+
environment: 'production',
|
|
504
|
+
features,
|
|
505
|
+
context: {},
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
const app = express()
|
|
509
|
+
|
|
510
|
+
app.get('/api/config', async (req, res) => {
|
|
511
|
+
const rateLimit = await featureClient.getFeature('rate-limit')
|
|
512
|
+
|
|
513
|
+
res.json({
|
|
514
|
+
rateLimit: rateLimit,
|
|
515
|
+
newApiEnabled: await featureClient.getFeature('new-api'),
|
|
516
|
+
})
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
app.get('/api/user-features/:userId', async (req, res) => {
|
|
520
|
+
const results = await featureClient.getFeatures(['new-api', 'cache-enabled'], {
|
|
521
|
+
context: {
|
|
522
|
+
userID: req.params.userId,
|
|
523
|
+
plan: req.user.plan,
|
|
524
|
+
region: req.headers['cloudfront-viewer-country'],
|
|
525
|
+
},
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
res.json(results)
|
|
529
|
+
})
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### Vue Integration
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
// plugins/feature-flags.ts
|
|
536
|
+
import { SupaClient, FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'
|
|
537
|
+
|
|
538
|
+
const features = {
|
|
539
|
+
'new-nav': false,
|
|
540
|
+
'dark-mode': false,
|
|
541
|
+
'premium-content': false,
|
|
542
|
+
} satisfies FeaturesWithFallbacks
|
|
543
|
+
|
|
544
|
+
const client = new SupaClient({
|
|
545
|
+
apiKey: import.meta.env.VITE_SUPASHIP_API_KEY,
|
|
546
|
+
environment: 'production',
|
|
547
|
+
features,
|
|
548
|
+
context: {},
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
export default {
|
|
552
|
+
install(app: App) {
|
|
553
|
+
app.config.globalProperties.$featureFlags = client
|
|
554
|
+
app.provide('featureFlags', client)
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// components/MyComponent.vue
|
|
559
|
+
<script setup lang="ts">
|
|
560
|
+
import { inject, ref, onMounted } from 'vue'
|
|
561
|
+
import type { SupaClient } from '@supashiphq/javascript-sdk'
|
|
562
|
+
|
|
563
|
+
const featureFlags = inject<SupaClient>('featureFlags')!
|
|
564
|
+
const showNewNav = ref(false)
|
|
565
|
+
const darkMode = ref(false)
|
|
566
|
+
|
|
567
|
+
onMounted(async () => {
|
|
568
|
+
showNewNav.value = await featureFlags.getFeature('new-nav')
|
|
569
|
+
darkMode.value = await featureFlags.getFeature('dark-mode')
|
|
570
|
+
})
|
|
571
|
+
</script>
|
|
572
|
+
|
|
573
|
+
<template>
|
|
574
|
+
<div :class="{ dark: darkMode }">
|
|
575
|
+
<NewNav v-if="showNewNav" />
|
|
576
|
+
<OldNav v-else />
|
|
577
|
+
</div>
|
|
578
|
+
</template>
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### Angular Integration
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
// feature-flag.service.ts
|
|
585
|
+
import { Injectable } from '@angular/core'
|
|
586
|
+
import { SupaClient, FeaturesWithFallbacks } from '@supashiphq/javascript-sdk'
|
|
587
|
+
import { environment } from '../environments/environment'
|
|
588
|
+
|
|
589
|
+
const features = {
|
|
590
|
+
'new-dashboard': false,
|
|
591
|
+
analytics: true,
|
|
592
|
+
'theme-config': { mode: 'light' as const },
|
|
593
|
+
} satisfies FeaturesWithFallbacks
|
|
594
|
+
|
|
595
|
+
@Injectable({
|
|
596
|
+
providedIn: 'root',
|
|
597
|
+
})
|
|
598
|
+
export class FeatureFlagService {
|
|
599
|
+
private client: SupaClient
|
|
600
|
+
|
|
601
|
+
constructor() {
|
|
602
|
+
this.client = new SupaClient({
|
|
603
|
+
apiKey: environment.SUPASHIP_API_KEY,
|
|
604
|
+
environment: 'production',
|
|
605
|
+
features,
|
|
606
|
+
context: {
|
|
607
|
+
userID: this.getCurrentUserId(),
|
|
608
|
+
version: environment.version,
|
|
609
|
+
},
|
|
610
|
+
})
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
async getFeature(featureName: string): Promise<any> {
|
|
614
|
+
return this.client.getFeature(featureName)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async getFeatures(featureNames: string[]): Promise<Record<string, any>> {
|
|
618
|
+
return this.client.getFeatures(featureNames)
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
updateContext(context: Record<string, any>) {
|
|
622
|
+
this.client.updateContext(context)
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
private getCurrentUserId(): string {
|
|
626
|
+
return localStorage.getItem('userID') || 'anonymous'
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// app.component.ts
|
|
631
|
+
import { Component, OnInit } from '@angular/core'
|
|
632
|
+
import { FeatureFlagService } from './feature-flag.service'
|
|
633
|
+
|
|
634
|
+
@Component({
|
|
635
|
+
selector: 'app-root',
|
|
636
|
+
template: `
|
|
637
|
+
<div>
|
|
638
|
+
<new-dashboard *ngIf="showNewDashboard"></new-dashboard>
|
|
639
|
+
<old-dashboard *ngIf="!showNewDashboard"></old-dashboard>
|
|
640
|
+
</div>
|
|
641
|
+
`,
|
|
642
|
+
})
|
|
643
|
+
export class AppComponent implements OnInit {
|
|
644
|
+
showNewDashboard = false
|
|
645
|
+
|
|
646
|
+
constructor(private featureFlags: FeatureFlagService) {}
|
|
647
|
+
|
|
648
|
+
async ngOnInit() {
|
|
649
|
+
this.showNewDashboard = await this.featureFlags.getFeature('new-dashboard')
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
## License
|
|
655
|
+
|
|
656
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|