flagmint-react-sdk 0.1.0 → 0.3.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 +609 -0
- package/dist/client.cjs +146 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +24 -0
- package/dist/client.d.ts +24 -0
- package/dist/client.js +138 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.cts +0 -2
- package/dist/index.d.ts +0 -2
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
# flagmint-react-sdk
|
|
2
|
+
|
|
3
|
+
A React wrapper for [flagmint-js-sdk](https://www.npmjs.com/package/flagmint-js-sdk) that provides React-specific hooks and context with fine-grained reactivity powered by Zustand.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Fine-grained reactivity** - Only components using specific flags re-render when those flags change
|
|
8
|
+
- 🚀 **SSR-safe** - No global state leakage between server requests
|
|
9
|
+
- 📦 **TypeScript support** - Full type safety with generics for flag values and context
|
|
10
|
+
- 🔀 **Dual export strategy** - Separate server-safe types and client-only hooks
|
|
11
|
+
- ⚡ **Auto-initialization** - Direct integration with FlagClient from flagmint-js-sdk
|
|
12
|
+
- 🔐 **Deferred initialization** - Perfect for authentication flows
|
|
13
|
+
- 📱 **Framework agnostic** - Works with Next.js, Remix, Vite, and more
|
|
14
|
+
|
|
15
|
+
## 📦 Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install react-flagmint flagmint-js-sdk
|
|
19
|
+
# or
|
|
20
|
+
pnpm add react-flagmint flagmint-js-sdk
|
|
21
|
+
# or
|
|
22
|
+
yarn add react-flagmint flagmint-js-sdk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🚀 Quick Start
|
|
26
|
+
|
|
27
|
+
### 1. Basic Setup
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
// app/providers.tsx (Next.js App Router)
|
|
31
|
+
'use client'
|
|
32
|
+
import { FlagmintProvider } from 'react-flagmint/client'
|
|
33
|
+
|
|
34
|
+
export default function Providers({ children }: { children: React.ReactNode }) {
|
|
35
|
+
return (
|
|
36
|
+
<FlagmintProvider
|
|
37
|
+
options={{
|
|
38
|
+
apiKey: process.env.NEXT_PUBLIC_FLAGMINT_API_KEY!,
|
|
39
|
+
transportMode: 'websocket',
|
|
40
|
+
context: {
|
|
41
|
+
userId: 'user-123',
|
|
42
|
+
plan: 'premium'
|
|
43
|
+
}
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
{children}
|
|
47
|
+
</FlagmintProvider>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// app/layout.tsx
|
|
54
|
+
import Providers from './providers'
|
|
55
|
+
|
|
56
|
+
export default function RootLayout({
|
|
57
|
+
children,
|
|
58
|
+
}: {
|
|
59
|
+
children: React.ReactNode
|
|
60
|
+
}) {
|
|
61
|
+
return (
|
|
62
|
+
<html>
|
|
63
|
+
<body>
|
|
64
|
+
<Providers>
|
|
65
|
+
{children}
|
|
66
|
+
</Providers>
|
|
67
|
+
</body>
|
|
68
|
+
</html>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Using Flags in Components
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
// components/FeatureComponent.tsx
|
|
77
|
+
'use client'
|
|
78
|
+
import { useFlag, useFlags, useFlagmintReady } from 'react-flagmint/client'
|
|
79
|
+
|
|
80
|
+
export default function FeatureComponent() {
|
|
81
|
+
const showNewFeature = useFlag('new-dashboard', false)
|
|
82
|
+
const userPlan = useFlag('user-plan', 'free')
|
|
83
|
+
const isReady = useFlagmintReady()
|
|
84
|
+
|
|
85
|
+
if (!isReady) {
|
|
86
|
+
return <div>Loading flags...</div>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div>
|
|
91
|
+
{showNewFeature && <NewDashboard />}
|
|
92
|
+
<div>Current plan: {userPlan}</div>
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 📚 API Reference
|
|
99
|
+
|
|
100
|
+
### FlagmintProvider
|
|
101
|
+
|
|
102
|
+
The provider component that initializes the FlagClient and provides flag context to your app.
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
<FlagmintProvider
|
|
106
|
+
options={FlagClientOptions}
|
|
107
|
+
initialFlags={{}}
|
|
108
|
+
deferInitialization={false}
|
|
109
|
+
>
|
|
110
|
+
{children}
|
|
111
|
+
</FlagmintProvider>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### Props
|
|
115
|
+
|
|
116
|
+
| Prop | Type | Default | Description |
|
|
117
|
+
|------|------|---------|-------------|
|
|
118
|
+
| `options` | `FlagClientOptions` | Required | Configuration for the FlagClient |
|
|
119
|
+
| `initialFlags` | `Flags` | `{}` | Initial flag values for SSR/hydration |
|
|
120
|
+
| `deferInitialization` | `boolean` | `false` | If true, wait for manual initialization |
|
|
121
|
+
|
|
122
|
+
#### FlagClientOptions
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
interface FlagClientOptions<C extends Record<string, any> = Record<string, any>> {
|
|
126
|
+
apiKey: string
|
|
127
|
+
context?: C
|
|
128
|
+
transportMode?: 'auto' | 'websocket' | 'long-polling'
|
|
129
|
+
enableOfflineCache?: boolean
|
|
130
|
+
persistContext?: boolean
|
|
131
|
+
onError?: (error: Error) => void
|
|
132
|
+
previewMode?: boolean
|
|
133
|
+
rawFlags?: Record<string, FlagValue>
|
|
134
|
+
deferInitialization?: boolean
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Hooks
|
|
139
|
+
|
|
140
|
+
#### useFlag(key, fallback?)
|
|
141
|
+
|
|
142
|
+
Get a specific flag value with fine-grained reactivity.
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
const showFeature = useFlag('feature-name', false)
|
|
146
|
+
const userTier = useFlag('user-tier', 'free')
|
|
147
|
+
const config = useFlag('app-config', { theme: 'light' })
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Parameters:**
|
|
151
|
+
- `key: string` - The flag key
|
|
152
|
+
- `fallback?: T` - Default value if flag is not found
|
|
153
|
+
|
|
154
|
+
**Returns:** The flag value or fallback
|
|
155
|
+
|
|
156
|
+
#### useFlags()
|
|
157
|
+
|
|
158
|
+
Get all currently loaded flags.
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
const allFlags = useFlags()
|
|
162
|
+
console.log(allFlags) // { 'feature-a': true, 'user-tier': 'pro' }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Returns:** Object containing all loaded flags
|
|
166
|
+
|
|
167
|
+
#### useFlagmint()
|
|
168
|
+
|
|
169
|
+
Get access to the FlagClient instance and utilities.
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
const { client, isReady, isInitialized, updateContext } = useFlagmint()
|
|
173
|
+
|
|
174
|
+
// Update user context
|
|
175
|
+
await updateContext({ userId: 'new-user', plan: 'enterprise' })
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Returns:**
|
|
179
|
+
```tsx
|
|
180
|
+
{
|
|
181
|
+
client: FlagClient | null
|
|
182
|
+
isReady: boolean
|
|
183
|
+
isInitialized: boolean
|
|
184
|
+
updateContext: (context: Record<string, any>) => Promise<void>
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### useFlagmintReady()
|
|
189
|
+
|
|
190
|
+
Check if the FlagClient is ready to serve flags.
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
const isReady = useFlagmintReady()
|
|
194
|
+
|
|
195
|
+
if (!isReady) {
|
|
196
|
+
return <LoadingSpinner />
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### useFlagClient()
|
|
201
|
+
|
|
202
|
+
Get direct access to the FlagClient instance.
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
const client = useFlagClient<MyFlagTypes>()
|
|
206
|
+
const specificFlag = client?.getFlag('feature-x', false)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## 🔧 Usage Patterns
|
|
210
|
+
|
|
211
|
+
### 1. Auto-initialization (Default)
|
|
212
|
+
|
|
213
|
+
Best when user context is available immediately:
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
function App() {
|
|
217
|
+
return (
|
|
218
|
+
<FlagmintProvider
|
|
219
|
+
options={{
|
|
220
|
+
apiKey: 'your-api-key',
|
|
221
|
+
context: {
|
|
222
|
+
userId: getCurrentUser().id,
|
|
223
|
+
plan: getCurrentUser().plan
|
|
224
|
+
}
|
|
225
|
+
}}
|
|
226
|
+
>
|
|
227
|
+
<Dashboard />
|
|
228
|
+
</FlagmintProvider>
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### 2. Deferred Initialization
|
|
234
|
+
|
|
235
|
+
Perfect for authentication flows:
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
// Provider with deferred initialization
|
|
239
|
+
<FlagmintProvider
|
|
240
|
+
options={{
|
|
241
|
+
apiKey: 'your-api-key',
|
|
242
|
+
context: {} // Empty initially
|
|
243
|
+
}}
|
|
244
|
+
deferInitialization={true}
|
|
245
|
+
>
|
|
246
|
+
<App />
|
|
247
|
+
</FlagmintProvider>
|
|
248
|
+
|
|
249
|
+
// Login component
|
|
250
|
+
function LoginPage() {
|
|
251
|
+
const { updateContext } = useFlagmint()
|
|
252
|
+
|
|
253
|
+
const handleLogin = async (user) => {
|
|
254
|
+
// First update context with user info
|
|
255
|
+
await updateContext({
|
|
256
|
+
userId: user.id,
|
|
257
|
+
plan: user.plan,
|
|
258
|
+
locale: user.locale
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// Flags are now available with user context
|
|
262
|
+
navigate('/dashboard')
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return <LoginForm onLogin={handleLogin} />
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 3. TypeScript Usage
|
|
270
|
+
|
|
271
|
+
Define your flag types for better type safety:
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
// types/flags.ts
|
|
275
|
+
export interface AppFlags {
|
|
276
|
+
'show-beta-feature': boolean
|
|
277
|
+
'user-plan': 'free' | 'pro' | 'enterprise'
|
|
278
|
+
'theme-config': {
|
|
279
|
+
primaryColor: string
|
|
280
|
+
darkMode: boolean
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export interface UserContext {
|
|
285
|
+
userId: string
|
|
286
|
+
plan: string
|
|
287
|
+
locale: string
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// components/TypedComponent.tsx
|
|
291
|
+
import { useFlag } from 'react-flagmint/client'
|
|
292
|
+
import type { AppFlags } from '../types/flags'
|
|
293
|
+
|
|
294
|
+
export default function TypedComponent() {
|
|
295
|
+
// TypeScript knows this returns boolean
|
|
296
|
+
const showBeta = useFlag<AppFlags['show-beta-feature']>('show-beta-feature', false)
|
|
297
|
+
|
|
298
|
+
// TypeScript knows this returns the union type
|
|
299
|
+
const plan = useFlag<AppFlags['user-plan']>('user-plan', 'free')
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<div>
|
|
303
|
+
{showBeta && <BetaFeature />}
|
|
304
|
+
<div>Plan: {plan}</div>
|
|
305
|
+
</div>
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### 4. Server-Side Rendering (SSR)
|
|
311
|
+
|
|
312
|
+
#### Next.js App Router
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
// app/providers.tsx
|
|
316
|
+
'use client'
|
|
317
|
+
import { FlagmintProvider } from 'react-flagmint/client'
|
|
318
|
+
|
|
319
|
+
export default function Providers({ children, initialFlags = {} }) {
|
|
320
|
+
return (
|
|
321
|
+
<FlagmintProvider
|
|
322
|
+
options={{
|
|
323
|
+
apiKey: process.env.NEXT_PUBLIC_FLAGMINT_API_KEY!,
|
|
324
|
+
transportMode: 'websocket'
|
|
325
|
+
}}
|
|
326
|
+
initialFlags={initialFlags}
|
|
327
|
+
>
|
|
328
|
+
{children}
|
|
329
|
+
</FlagmintProvider>
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// app/page.tsx
|
|
334
|
+
import { FlagClient } from 'flagmint-js-sdk'
|
|
335
|
+
import Providers from './providers'
|
|
336
|
+
import ClientComponent from './ClientComponent'
|
|
337
|
+
|
|
338
|
+
export default async function Page() {
|
|
339
|
+
// Optionally preload flags on server
|
|
340
|
+
let initialFlags = {}
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
const serverClient = new FlagClient({
|
|
344
|
+
apiKey: process.env.FLAGMINT_API_KEY!,
|
|
345
|
+
context: { server: true }
|
|
346
|
+
})
|
|
347
|
+
await serverClient.ready()
|
|
348
|
+
initialFlags = await serverClient.getFlags() // If this method exists
|
|
349
|
+
} catch (error) {
|
|
350
|
+
console.warn('Failed to load initial flags:', error)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return (
|
|
354
|
+
<Providers initialFlags={initialFlags}>
|
|
355
|
+
<ClientComponent />
|
|
356
|
+
</Providers>
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### 5. Context Updates
|
|
362
|
+
|
|
363
|
+
Update user context dynamically:
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
function UserSettings() {
|
|
367
|
+
const { updateContext } = useFlagmint()
|
|
368
|
+
const currentTheme = useFlag('user-theme', 'light')
|
|
369
|
+
|
|
370
|
+
const handleThemeChange = async (newTheme: string) => {
|
|
371
|
+
// Update context - flags will be re-evaluated
|
|
372
|
+
await updateContext({ theme: newTheme })
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const handlePlanUpgrade = async (newPlan: string) => {
|
|
376
|
+
await updateContext({ plan: newPlan })
|
|
377
|
+
// Component will re-render with new plan-based flags
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
<div>
|
|
382
|
+
<ThemeSelector onChange={handleThemeChange} />
|
|
383
|
+
<PlanUpgrade onUpgrade={handlePlanUpgrade} />
|
|
384
|
+
</div>
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### 6. Loading States
|
|
390
|
+
|
|
391
|
+
Handle loading states gracefully:
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
function FeaturePage() {
|
|
395
|
+
const isReady = useFlagmintReady()
|
|
396
|
+
const showPremiumFeature = useFlag('premium-feature', false)
|
|
397
|
+
|
|
398
|
+
if (!isReady) {
|
|
399
|
+
return (
|
|
400
|
+
<div className="loading-container">
|
|
401
|
+
<Spinner />
|
|
402
|
+
<p>Loading personalized features...</p>
|
|
403
|
+
</div>
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return (
|
|
408
|
+
<div>
|
|
409
|
+
<h1>Features</h1>
|
|
410
|
+
{showPremiumFeature ? (
|
|
411
|
+
<PremiumFeature />
|
|
412
|
+
) : (
|
|
413
|
+
<UpgradeBanner />
|
|
414
|
+
)}
|
|
415
|
+
</div>
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### 7. Error Handling
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
<FlagmintProvider
|
|
424
|
+
options={{
|
|
425
|
+
apiKey: 'your-api-key',
|
|
426
|
+
onError: (error) => {
|
|
427
|
+
console.error('Flagmint error:', error)
|
|
428
|
+
// Send to error reporting service
|
|
429
|
+
errorReporting.captureException(error)
|
|
430
|
+
}
|
|
431
|
+
}}
|
|
432
|
+
>
|
|
433
|
+
<App />
|
|
434
|
+
</FlagmintProvider>
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## 🏗️ Framework Examples
|
|
438
|
+
|
|
439
|
+
### Next.js (App Router)
|
|
440
|
+
|
|
441
|
+
```tsx
|
|
442
|
+
// app/layout.tsx
|
|
443
|
+
import Providers from './providers'
|
|
444
|
+
|
|
445
|
+
export default function RootLayout({ children }) {
|
|
446
|
+
return (
|
|
447
|
+
<html lang="en">
|
|
448
|
+
<body>
|
|
449
|
+
<Providers>{children}</Providers>
|
|
450
|
+
</body>
|
|
451
|
+
</html>
|
|
452
|
+
)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// app/providers.tsx
|
|
456
|
+
'use client'
|
|
457
|
+
import { FlagmintProvider } from 'react-flagmint/client'
|
|
458
|
+
|
|
459
|
+
export default function Providers({ children }) {
|
|
460
|
+
return (
|
|
461
|
+
<FlagmintProvider
|
|
462
|
+
options={{
|
|
463
|
+
apiKey: process.env.NEXT_PUBLIC_FLAGMINT_API_KEY!,
|
|
464
|
+
transportMode: 'long-polling'
|
|
465
|
+
}}
|
|
466
|
+
>
|
|
467
|
+
{children}
|
|
468
|
+
</FlagmintProvider>
|
|
469
|
+
)
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Vite + React
|
|
474
|
+
|
|
475
|
+
```tsx
|
|
476
|
+
// src/main.tsx
|
|
477
|
+
import React from 'react'
|
|
478
|
+
import ReactDOM from 'react-dom/client'
|
|
479
|
+
import { FlagmintProvider } from 'react-flagmint/client'
|
|
480
|
+
import App from './App'
|
|
481
|
+
|
|
482
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
483
|
+
<React.StrictMode>
|
|
484
|
+
<FlagmintProvider
|
|
485
|
+
options={{
|
|
486
|
+
apiKey: import.meta.env.VITE_FLAGMINT_API_KEY,
|
|
487
|
+
transportMode: 'websocket'
|
|
488
|
+
}}
|
|
489
|
+
>
|
|
490
|
+
<App />
|
|
491
|
+
</FlagmintProvider>
|
|
492
|
+
</React.StrictMode>
|
|
493
|
+
)
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Remix
|
|
497
|
+
|
|
498
|
+
```tsx
|
|
499
|
+
// app/root.tsx
|
|
500
|
+
import { FlagmintProvider } from 'react-flagmint/client'
|
|
501
|
+
|
|
502
|
+
export default function App() {
|
|
503
|
+
return (
|
|
504
|
+
<html>
|
|
505
|
+
<head>
|
|
506
|
+
<Meta />
|
|
507
|
+
<Links />
|
|
508
|
+
</head>
|
|
509
|
+
<body>
|
|
510
|
+
<FlagmintProvider
|
|
511
|
+
options={{
|
|
512
|
+
apiKey: process.env.FLAGMINT_API_KEY!,
|
|
513
|
+
transportMode: 'long-polling'
|
|
514
|
+
}}
|
|
515
|
+
>
|
|
516
|
+
<Outlet />
|
|
517
|
+
</FlagmintProvider>
|
|
518
|
+
<Scripts />
|
|
519
|
+
</body>
|
|
520
|
+
</html>
|
|
521
|
+
)
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
## ⚡ Performance
|
|
526
|
+
|
|
527
|
+
### Fine-grained Reactivity
|
|
528
|
+
|
|
529
|
+
Only components that use specific flags will re-render when those flags change:
|
|
530
|
+
|
|
531
|
+
```tsx
|
|
532
|
+
// ✅ Good: Only re-renders when 'feature-a' changes
|
|
533
|
+
function ComponentA() {
|
|
534
|
+
const featureA = useFlag('feature-a', false)
|
|
535
|
+
return <div>{featureA && <FeatureA />}</div>
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ✅ Good: Only re-renders when 'feature-b' changes
|
|
539
|
+
function ComponentB() {
|
|
540
|
+
const featureB = useFlag('feature-b', false)
|
|
541
|
+
return <div>{featureB && <FeatureB />}</div>
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// ❌ Avoid: Re-renders when ANY flag changes
|
|
545
|
+
function ComponentAll() {
|
|
546
|
+
const allFlags = useFlags()
|
|
547
|
+
return <div>{allFlags['feature-a'] && <FeatureA />}</div>
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Bundle Size
|
|
552
|
+
|
|
553
|
+
- **react-flagmint**: ~3KB gzipped
|
|
554
|
+
- **zustand**: ~2KB gzipped
|
|
555
|
+
- **Total overhead**: ~5KB gzipped
|
|
556
|
+
|
|
557
|
+
## 🔧 Development
|
|
558
|
+
|
|
559
|
+
### Local Development
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
# Clone the monorepo
|
|
563
|
+
git clone <repo-url>
|
|
564
|
+
cd flagmint-monorepo
|
|
565
|
+
|
|
566
|
+
# Install dependencies
|
|
567
|
+
pnpm install
|
|
568
|
+
|
|
569
|
+
# Build the library
|
|
570
|
+
pnpm --filter react-flagmint build
|
|
571
|
+
|
|
572
|
+
# Run the Next.js example
|
|
573
|
+
pnpm dev:next
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Testing
|
|
577
|
+
|
|
578
|
+
```bash
|
|
579
|
+
# Run tests
|
|
580
|
+
pnpm test
|
|
581
|
+
|
|
582
|
+
# Type checking
|
|
583
|
+
pnpm typecheck
|
|
584
|
+
|
|
585
|
+
# Lint
|
|
586
|
+
pnpm lint
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
## 📄 License
|
|
590
|
+
|
|
591
|
+
MIT
|
|
592
|
+
|
|
593
|
+
## 🤝 Contributing
|
|
594
|
+
|
|
595
|
+
1. Fork the repository
|
|
596
|
+
2. Create a feature branch
|
|
597
|
+
3. Make your changes
|
|
598
|
+
4. Add tests
|
|
599
|
+
5. Submit a pull request
|
|
600
|
+
|
|
601
|
+
## 📞 Support
|
|
602
|
+
|
|
603
|
+
- 📧 Email: support@flagmint.com
|
|
604
|
+
- 🐛 Issues: [GitHub Issues](https://github.com/jtad009/flagmint-react-sdk/issues)
|
|
605
|
+
- 📖 Docs: [Documentation](https://docs.flagmint.com)
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
**Made with ❤️ by the Flagmint team**
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var flagmintJsSdk = require('flagmint-js-sdk');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var vanilla = require('zustand/vanilla');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
var zustand = require('zustand');
|
|
8
|
+
|
|
9
|
+
function createFlagStore(initialFlags = {}) {
|
|
10
|
+
return vanilla.createStore((set, get) => ({
|
|
11
|
+
flags: initialFlags,
|
|
12
|
+
client: null,
|
|
13
|
+
isInitialized: false,
|
|
14
|
+
isReady: false,
|
|
15
|
+
setFlags: (flags) => set({ flags }),
|
|
16
|
+
setFlag: (key, value) => set((state) => ({
|
|
17
|
+
flags: { ...state.flags, [key]: value }
|
|
18
|
+
})),
|
|
19
|
+
setClient: (client) => set({ client }),
|
|
20
|
+
setInitialized: (initialized) => set({ isInitialized: initialized }),
|
|
21
|
+
setReady: (ready) => set({ isReady: ready })
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
var FlagmintStoreContext = react.createContext(null);
|
|
25
|
+
function FlagmintProvider({
|
|
26
|
+
children,
|
|
27
|
+
options,
|
|
28
|
+
initialFlags = {},
|
|
29
|
+
deferInitialization = false
|
|
30
|
+
}) {
|
|
31
|
+
const store = react.useMemo(() => createFlagStore(initialFlags), [initialFlags]);
|
|
32
|
+
const clientRef = react.useRef(null);
|
|
33
|
+
const initPromiseRef = react.useRef(null);
|
|
34
|
+
react.useRef(initialFlags);
|
|
35
|
+
const init = async () => {
|
|
36
|
+
if (clientRef.current) return clientRef.current;
|
|
37
|
+
if (initPromiseRef.current) return initPromiseRef.current;
|
|
38
|
+
const { setClient, setReady, setInitialized } = store.getState();
|
|
39
|
+
initPromiseRef.current = (async () => {
|
|
40
|
+
try {
|
|
41
|
+
const originalClient = new flagmintJsSdk.FlagClient(options);
|
|
42
|
+
await originalClient.ready();
|
|
43
|
+
const clientWrapper = {
|
|
44
|
+
...originalClient,
|
|
45
|
+
_flags: {},
|
|
46
|
+
getFlags: function() {
|
|
47
|
+
return this._flags;
|
|
48
|
+
},
|
|
49
|
+
// Override getFlag to track individual flags
|
|
50
|
+
getFlag: function(key, fallback) {
|
|
51
|
+
const value = originalClient.getFlag(key, fallback);
|
|
52
|
+
this._flags[key] = value;
|
|
53
|
+
store.getState().setFlag(key, value);
|
|
54
|
+
return value;
|
|
55
|
+
},
|
|
56
|
+
// Override updateContext to trigger flag refresh
|
|
57
|
+
updateContext: function(context) {
|
|
58
|
+
originalClient.updateContext(context);
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
Object.keys(this._flags).forEach((key) => {
|
|
61
|
+
const newValue = originalClient.getFlag(key);
|
|
62
|
+
if (this._flags[key] !== newValue) {
|
|
63
|
+
this._flags[key] = newValue;
|
|
64
|
+
store.getState().setFlag(key, newValue);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}, 100);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
clientRef.current = clientWrapper;
|
|
71
|
+
setClient(clientWrapper);
|
|
72
|
+
setReady(true);
|
|
73
|
+
setInitialized(true);
|
|
74
|
+
return clientWrapper;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error("Failed to initialize Flagmint client:", error);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
})();
|
|
80
|
+
return initPromiseRef.current;
|
|
81
|
+
};
|
|
82
|
+
react.useEffect(() => {
|
|
83
|
+
if (!deferInitialization) {
|
|
84
|
+
init().catch((error) => {
|
|
85
|
+
console.error("Auto-initialization failed:", error);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return () => {
|
|
89
|
+
var _a, _b;
|
|
90
|
+
if (clientRef.current) {
|
|
91
|
+
(_b = (_a = clientRef.current).destroy) == null ? void 0 : _b.call(_a);
|
|
92
|
+
clientRef.current = null;
|
|
93
|
+
initPromiseRef.current = null;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}, [deferInitialization]);
|
|
97
|
+
return /* @__PURE__ */ jsxRuntime.jsx(FlagmintStoreContext.Provider, { value: store, children });
|
|
98
|
+
}
|
|
99
|
+
function useFlagmintStore() {
|
|
100
|
+
const store = react.useContext(FlagmintStoreContext);
|
|
101
|
+
if (!store) {
|
|
102
|
+
throw new Error("useFlagmintStore must be used within a FlagmintProvider");
|
|
103
|
+
}
|
|
104
|
+
return store;
|
|
105
|
+
}
|
|
106
|
+
function useFlagmint() {
|
|
107
|
+
const store = useFlagmintStore();
|
|
108
|
+
const client = zustand.useStore(store, (state) => state.client);
|
|
109
|
+
const isInitialized = zustand.useStore(store, (state) => state.isInitialized);
|
|
110
|
+
const updateContext = async (context) => {
|
|
111
|
+
if (!client) {
|
|
112
|
+
throw new Error("Flagmint client not initialized");
|
|
113
|
+
}
|
|
114
|
+
await client.updateContext(context);
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
client,
|
|
118
|
+
isInitialized,
|
|
119
|
+
updateContext
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function useFlags() {
|
|
123
|
+
const store = useFlagmintStore();
|
|
124
|
+
return zustand.useStore(store, (state) => state.flags);
|
|
125
|
+
}
|
|
126
|
+
function useFlag(key, fallback) {
|
|
127
|
+
const store = useFlagmintStore();
|
|
128
|
+
return zustand.useStore(store, (state) => {
|
|
129
|
+
var _a;
|
|
130
|
+
return (_a = state.flags[key]) != null ? _a : fallback;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function useFlagmintReady() {
|
|
134
|
+
const store = useFlagmintStore();
|
|
135
|
+
return zustand.useStore(store, (state) => state.isInitialized && state.client !== null);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
exports.FlagmintProvider = FlagmintProvider;
|
|
139
|
+
exports.FlagmintStoreContext = FlagmintStoreContext;
|
|
140
|
+
exports.useFlag = useFlag;
|
|
141
|
+
exports.useFlagmint = useFlagmint;
|
|
142
|
+
exports.useFlagmintReady = useFlagmintReady;
|
|
143
|
+
exports.useFlagmintStore = useFlagmintStore;
|
|
144
|
+
exports.useFlags = useFlags;
|
|
145
|
+
//# sourceMappingURL=client.cjs.map
|
|
146
|
+
//# sourceMappingURL=client.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store/store.ts","../src/providers/FlagmintProvider.tsx","../src/hooks/index.ts"],"names":["createStore","createContext","useMemo","useRef","FlagClient","useEffect","useContext","useStore"],"mappings":"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","file":"client.cjs","sourcesContent":["'use client';\nimport { createStore, StoreApi } from \"zustand/vanilla\";\nimport { Flags, FlagStore } from \"@/types\";\n\n\nexport function createFlagStore(initialFlags: Flags = {}): StoreApi<FlagStore> {\n return createStore<FlagStore>((set, get) => ({\n flags: initialFlags,\n client: null,\n isInitialized: false,\n isReady: false,\n setFlags: (flags) => set({ flags }),\n setFlag: (key, value) => set((state) => ({\n flags: { ...state.flags, [key]: value }\n })),\n setClient: (client) => set({ client }),\n setInitialized: (initialized) => set({ isInitialized: initialized }),\n setReady: (ready) => set({ isReady: ready })\n }))\n}\n","'use client';\nimport { FlagClient } from \"flagmint-js-sdk\"\nimport { useMemo, useRef, useEffect, createContext } from \"react\"\nimport { createFlagStore } from \"@/store/store\"\nimport { FlagmintProviderProps, Flags, FlagStore } from \"@/types\"\nimport { StoreApi } from \"zustand\"\n\nexport const FlagmintStoreContext = createContext<StoreApi<FlagStore> | null>(null)\n\n\nexport function FlagmintProvider<\n T extends FlagClient<T, C> & { _flags: Flags; getFlags: () => Flags },\n C extends Record<string, any> = Record<string, any>\n>({\n children,\n options,\n initialFlags = {},\n deferInitialization = false\n}: FlagmintProviderProps<T, C>) {\n // Create a new store instance for each provider (per-request in SSR)\n const store = useMemo(() => createFlagStore(initialFlags), [initialFlags])\n \n // Use ref to avoid recreating client on every render\n const clientRef = useRef<FlagClient<T, C> | null>(null)\n const initPromiseRef = useRef<Promise<FlagClient<T, C>> | null>(null)\n const flagsRef = useRef<Flags>(initialFlags)\n \n // Initialization function similar to Vue plugin\n const init = async (): Promise<FlagClient<T, C>> => {\n if (clientRef.current) return clientRef.current // Avoid re-init\n \n // Return existing promise if init is already in progress\n if (initPromiseRef.current) return initPromiseRef.current\n \n const { setClient, setReady, setInitialized } = store.getState()\n \n initPromiseRef.current = (async () => {\n try {\n // Create a custom FlagClient that exposes flag updates\n const originalClient = new FlagClient<T, C>(options)\n await originalClient.ready()\n \n // Create a wrapper that tracks flags internally\n const clientWrapper = {\n ...originalClient,\n _flags: {} as Flags,\n getFlags: function() {\n return this._flags\n },\n // Override getFlag to track individual flags\n getFlag: function<K extends string>(key: K, fallback?: T) {\n const value = originalClient.getFlag(key as any, fallback)\n this._flags[key] = value\n store.getState().setFlag(key, value)\n return value\n },\n // Override updateContext to trigger flag refresh\n updateContext: function(context: C) {\n originalClient.updateContext(context)\n // After context update, we should refresh our tracked flags\n // This is a limitation of the current API\n setTimeout(() => {\n // Force a re-evaluation of flags we've previously requested\n Object.keys(this._flags).forEach(key => {\n const newValue = originalClient.getFlag(key as any)\n if (this._flags[key] !== newValue) {\n this._flags[key] = newValue\n store.getState().setFlag(key, newValue)\n }\n })\n }, 100)\n }\n } as FlagClient<T, C> & { _flags: Flags; getFlags: () => Flags }\n \n clientRef.current = clientWrapper\n setClient(clientWrapper)\n setReady(true)\n setInitialized(true)\n \n return clientWrapper\n } catch (error) {\n console.error('Failed to initialize Flagmint client:', error)\n throw error\n }\n })()\n \n return initPromiseRef.current\n }\n\n useEffect(() => {\n if (!deferInitialization) {\n init().catch(error => {\n console.error('Auto-initialization failed:', error)\n })\n }\n\n return () => {\n if (clientRef.current) {\n clientRef.current.destroy?.()\n clientRef.current = null\n initPromiseRef.current = null\n }\n }\n }, [deferInitialization])\n\n return (\n <FlagmintStoreContext.Provider value={store}>\n {children}\n </FlagmintStoreContext.Provider>\n )\n}\n","'use client';\nimport { FlagValue } from \"flagmint-js-sdk\"\nimport { useContext } from \"react\"\nimport { StoreApi, useStore } from \"zustand\"\nimport { FlagmintStoreContext } from \"@/providers/FlagmintProvider\"\nimport { FlagStore, Flags } from \"@/types\"\n\n// Helper to get the store from context\nexport function useFlagmintStore(): StoreApi<FlagStore> {\n const store = useContext(FlagmintStoreContext)\n if (!store) {\n throw new Error('useFlagmintStore must be used within a FlagmintProvider')\n }\n return store\n}\n\n// Hook to get the client instance and update context\nexport function useFlagmint() {\n const store = useFlagmintStore()\n const client = useStore(store, (state) => state.client)\n const isInitialized = useStore(store, (state) => state.isInitialized)\n\n const updateContext = async (context: Record<string, any>) => {\n if (!client) {\n throw new Error('Flagmint client not initialized')\n }\n await client.updateContext(context)\n }\n\n return {\n client,\n isInitialized,\n updateContext\n }\n}\n\n// Hook to get all flags - only re-renders when flags change\nexport function useFlags(): Flags {\n const store = useFlagmintStore()\n return useStore(store, (state) => state.flags)\n}\n\n// Hook to get a specific flag - only re-renders when this specific flag changes\nexport function useFlag<T extends FlagValue = FlagValue>(\n key: string,\n fallback?: T\n): T {\n const store = useFlagmintStore()\n return useStore(store, (state) => state.flags[key] ?? fallback) as T\n}\n\n// Hook to check if the client is ready\nexport function useFlagmintReady(): boolean {\n const store = useFlagmintStore()\n return useStore(store, (state) => state.isInitialized && state.client !== null)\n}"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import * as flagmint_js_sdk from 'flagmint-js-sdk';
|
|
4
|
+
import { FlagClient, FlagValue } from 'flagmint-js-sdk';
|
|
5
|
+
import { FlagStore, Flags, FlagmintProviderProps } from './index.cjs';
|
|
6
|
+
import { StoreApi } from 'zustand';
|
|
7
|
+
|
|
8
|
+
declare const FlagmintStoreContext: react.Context<StoreApi<FlagStore> | null>;
|
|
9
|
+
declare function FlagmintProvider<T extends FlagClient<T, C> & {
|
|
10
|
+
_flags: Flags;
|
|
11
|
+
getFlags: () => Flags;
|
|
12
|
+
}, C extends Record<string, any> = Record<string, any>>({ children, options, initialFlags, deferInitialization }: FlagmintProviderProps<T, C>): react_jsx_runtime.JSX.Element;
|
|
13
|
+
|
|
14
|
+
declare function useFlagmintStore(): StoreApi<FlagStore>;
|
|
15
|
+
declare function useFlagmint(): {
|
|
16
|
+
client: flagmint_js_sdk.FlagClient<any, any> | null;
|
|
17
|
+
isInitialized: boolean;
|
|
18
|
+
updateContext: (context: Record<string, any>) => Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
declare function useFlags(): Flags;
|
|
21
|
+
declare function useFlag<T extends FlagValue = FlagValue>(key: string, fallback?: T): T;
|
|
22
|
+
declare function useFlagmintReady(): boolean;
|
|
23
|
+
|
|
24
|
+
export { FlagmintProvider, FlagmintStoreContext, useFlag, useFlagmint, useFlagmintReady, useFlagmintStore, useFlags };
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import * as flagmint_js_sdk from 'flagmint-js-sdk';
|
|
4
|
+
import { FlagClient, FlagValue } from 'flagmint-js-sdk';
|
|
5
|
+
import { FlagStore, Flags, FlagmintProviderProps } from './index.js';
|
|
6
|
+
import { StoreApi } from 'zustand';
|
|
7
|
+
|
|
8
|
+
declare const FlagmintStoreContext: react.Context<StoreApi<FlagStore> | null>;
|
|
9
|
+
declare function FlagmintProvider<T extends FlagClient<T, C> & {
|
|
10
|
+
_flags: Flags;
|
|
11
|
+
getFlags: () => Flags;
|
|
12
|
+
}, C extends Record<string, any> = Record<string, any>>({ children, options, initialFlags, deferInitialization }: FlagmintProviderProps<T, C>): react_jsx_runtime.JSX.Element;
|
|
13
|
+
|
|
14
|
+
declare function useFlagmintStore(): StoreApi<FlagStore>;
|
|
15
|
+
declare function useFlagmint(): {
|
|
16
|
+
client: flagmint_js_sdk.FlagClient<any, any> | null;
|
|
17
|
+
isInitialized: boolean;
|
|
18
|
+
updateContext: (context: Record<string, any>) => Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
declare function useFlags(): Flags;
|
|
21
|
+
declare function useFlag<T extends FlagValue = FlagValue>(key: string, fallback?: T): T;
|
|
22
|
+
declare function useFlagmintReady(): boolean;
|
|
23
|
+
|
|
24
|
+
export { FlagmintProvider, FlagmintStoreContext, useFlag, useFlagmint, useFlagmintReady, useFlagmintStore, useFlags };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { FlagClient } from 'flagmint-js-sdk';
|
|
2
|
+
import { createContext, useMemo, useRef, useEffect, useContext } from 'react';
|
|
3
|
+
import { createStore } from 'zustand/vanilla';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
import { useStore } from 'zustand';
|
|
6
|
+
|
|
7
|
+
function createFlagStore(initialFlags = {}) {
|
|
8
|
+
return createStore((set, get) => ({
|
|
9
|
+
flags: initialFlags,
|
|
10
|
+
client: null,
|
|
11
|
+
isInitialized: false,
|
|
12
|
+
isReady: false,
|
|
13
|
+
setFlags: (flags) => set({ flags }),
|
|
14
|
+
setFlag: (key, value) => set((state) => ({
|
|
15
|
+
flags: { ...state.flags, [key]: value }
|
|
16
|
+
})),
|
|
17
|
+
setClient: (client) => set({ client }),
|
|
18
|
+
setInitialized: (initialized) => set({ isInitialized: initialized }),
|
|
19
|
+
setReady: (ready) => set({ isReady: ready })
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
var FlagmintStoreContext = createContext(null);
|
|
23
|
+
function FlagmintProvider({
|
|
24
|
+
children,
|
|
25
|
+
options,
|
|
26
|
+
initialFlags = {},
|
|
27
|
+
deferInitialization = false
|
|
28
|
+
}) {
|
|
29
|
+
const store = useMemo(() => createFlagStore(initialFlags), [initialFlags]);
|
|
30
|
+
const clientRef = useRef(null);
|
|
31
|
+
const initPromiseRef = useRef(null);
|
|
32
|
+
useRef(initialFlags);
|
|
33
|
+
const init = async () => {
|
|
34
|
+
if (clientRef.current) return clientRef.current;
|
|
35
|
+
if (initPromiseRef.current) return initPromiseRef.current;
|
|
36
|
+
const { setClient, setReady, setInitialized } = store.getState();
|
|
37
|
+
initPromiseRef.current = (async () => {
|
|
38
|
+
try {
|
|
39
|
+
const originalClient = new FlagClient(options);
|
|
40
|
+
await originalClient.ready();
|
|
41
|
+
const clientWrapper = {
|
|
42
|
+
...originalClient,
|
|
43
|
+
_flags: {},
|
|
44
|
+
getFlags: function() {
|
|
45
|
+
return this._flags;
|
|
46
|
+
},
|
|
47
|
+
// Override getFlag to track individual flags
|
|
48
|
+
getFlag: function(key, fallback) {
|
|
49
|
+
const value = originalClient.getFlag(key, fallback);
|
|
50
|
+
this._flags[key] = value;
|
|
51
|
+
store.getState().setFlag(key, value);
|
|
52
|
+
return value;
|
|
53
|
+
},
|
|
54
|
+
// Override updateContext to trigger flag refresh
|
|
55
|
+
updateContext: function(context) {
|
|
56
|
+
originalClient.updateContext(context);
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
Object.keys(this._flags).forEach((key) => {
|
|
59
|
+
const newValue = originalClient.getFlag(key);
|
|
60
|
+
if (this._flags[key] !== newValue) {
|
|
61
|
+
this._flags[key] = newValue;
|
|
62
|
+
store.getState().setFlag(key, newValue);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}, 100);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
clientRef.current = clientWrapper;
|
|
69
|
+
setClient(clientWrapper);
|
|
70
|
+
setReady(true);
|
|
71
|
+
setInitialized(true);
|
|
72
|
+
return clientWrapper;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error("Failed to initialize Flagmint client:", error);
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
return initPromiseRef.current;
|
|
79
|
+
};
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (!deferInitialization) {
|
|
82
|
+
init().catch((error) => {
|
|
83
|
+
console.error("Auto-initialization failed:", error);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return () => {
|
|
87
|
+
var _a, _b;
|
|
88
|
+
if (clientRef.current) {
|
|
89
|
+
(_b = (_a = clientRef.current).destroy) == null ? void 0 : _b.call(_a);
|
|
90
|
+
clientRef.current = null;
|
|
91
|
+
initPromiseRef.current = null;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}, [deferInitialization]);
|
|
95
|
+
return /* @__PURE__ */ jsx(FlagmintStoreContext.Provider, { value: store, children });
|
|
96
|
+
}
|
|
97
|
+
function useFlagmintStore() {
|
|
98
|
+
const store = useContext(FlagmintStoreContext);
|
|
99
|
+
if (!store) {
|
|
100
|
+
throw new Error("useFlagmintStore must be used within a FlagmintProvider");
|
|
101
|
+
}
|
|
102
|
+
return store;
|
|
103
|
+
}
|
|
104
|
+
function useFlagmint() {
|
|
105
|
+
const store = useFlagmintStore();
|
|
106
|
+
const client = useStore(store, (state) => state.client);
|
|
107
|
+
const isInitialized = useStore(store, (state) => state.isInitialized);
|
|
108
|
+
const updateContext = async (context) => {
|
|
109
|
+
if (!client) {
|
|
110
|
+
throw new Error("Flagmint client not initialized");
|
|
111
|
+
}
|
|
112
|
+
await client.updateContext(context);
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
client,
|
|
116
|
+
isInitialized,
|
|
117
|
+
updateContext
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function useFlags() {
|
|
121
|
+
const store = useFlagmintStore();
|
|
122
|
+
return useStore(store, (state) => state.flags);
|
|
123
|
+
}
|
|
124
|
+
function useFlag(key, fallback) {
|
|
125
|
+
const store = useFlagmintStore();
|
|
126
|
+
return useStore(store, (state) => {
|
|
127
|
+
var _a;
|
|
128
|
+
return (_a = state.flags[key]) != null ? _a : fallback;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function useFlagmintReady() {
|
|
132
|
+
const store = useFlagmintStore();
|
|
133
|
+
return useStore(store, (state) => state.isInitialized && state.client !== null);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { FlagmintProvider, FlagmintStoreContext, useFlag, useFlagmint, useFlagmintReady, useFlagmintStore, useFlags };
|
|
137
|
+
//# sourceMappingURL=client.js.map
|
|
138
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store/store.ts","../src/providers/FlagmintProvider.tsx","../src/hooks/index.ts"],"names":[],"mappings":"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","file":"client.js","sourcesContent":["'use client';\nimport { createStore, StoreApi } from \"zustand/vanilla\";\nimport { Flags, FlagStore } from \"@/types\";\n\n\nexport function createFlagStore(initialFlags: Flags = {}): StoreApi<FlagStore> {\n return createStore<FlagStore>((set, get) => ({\n flags: initialFlags,\n client: null,\n isInitialized: false,\n isReady: false,\n setFlags: (flags) => set({ flags }),\n setFlag: (key, value) => set((state) => ({\n flags: { ...state.flags, [key]: value }\n })),\n setClient: (client) => set({ client }),\n setInitialized: (initialized) => set({ isInitialized: initialized }),\n setReady: (ready) => set({ isReady: ready })\n }))\n}\n","'use client';\nimport { FlagClient } from \"flagmint-js-sdk\"\nimport { useMemo, useRef, useEffect, createContext } from \"react\"\nimport { createFlagStore } from \"@/store/store\"\nimport { FlagmintProviderProps, Flags, FlagStore } from \"@/types\"\nimport { StoreApi } from \"zustand\"\n\nexport const FlagmintStoreContext = createContext<StoreApi<FlagStore> | null>(null)\n\n\nexport function FlagmintProvider<\n T extends FlagClient<T, C> & { _flags: Flags; getFlags: () => Flags },\n C extends Record<string, any> = Record<string, any>\n>({\n children,\n options,\n initialFlags = {},\n deferInitialization = false\n}: FlagmintProviderProps<T, C>) {\n // Create a new store instance for each provider (per-request in SSR)\n const store = useMemo(() => createFlagStore(initialFlags), [initialFlags])\n \n // Use ref to avoid recreating client on every render\n const clientRef = useRef<FlagClient<T, C> | null>(null)\n const initPromiseRef = useRef<Promise<FlagClient<T, C>> | null>(null)\n const flagsRef = useRef<Flags>(initialFlags)\n \n // Initialization function similar to Vue plugin\n const init = async (): Promise<FlagClient<T, C>> => {\n if (clientRef.current) return clientRef.current // Avoid re-init\n \n // Return existing promise if init is already in progress\n if (initPromiseRef.current) return initPromiseRef.current\n \n const { setClient, setReady, setInitialized } = store.getState()\n \n initPromiseRef.current = (async () => {\n try {\n // Create a custom FlagClient that exposes flag updates\n const originalClient = new FlagClient<T, C>(options)\n await originalClient.ready()\n \n // Create a wrapper that tracks flags internally\n const clientWrapper = {\n ...originalClient,\n _flags: {} as Flags,\n getFlags: function() {\n return this._flags\n },\n // Override getFlag to track individual flags\n getFlag: function<K extends string>(key: K, fallback?: T) {\n const value = originalClient.getFlag(key as any, fallback)\n this._flags[key] = value\n store.getState().setFlag(key, value)\n return value\n },\n // Override updateContext to trigger flag refresh\n updateContext: function(context: C) {\n originalClient.updateContext(context)\n // After context update, we should refresh our tracked flags\n // This is a limitation of the current API\n setTimeout(() => {\n // Force a re-evaluation of flags we've previously requested\n Object.keys(this._flags).forEach(key => {\n const newValue = originalClient.getFlag(key as any)\n if (this._flags[key] !== newValue) {\n this._flags[key] = newValue\n store.getState().setFlag(key, newValue)\n }\n })\n }, 100)\n }\n } as FlagClient<T, C> & { _flags: Flags; getFlags: () => Flags }\n \n clientRef.current = clientWrapper\n setClient(clientWrapper)\n setReady(true)\n setInitialized(true)\n \n return clientWrapper\n } catch (error) {\n console.error('Failed to initialize Flagmint client:', error)\n throw error\n }\n })()\n \n return initPromiseRef.current\n }\n\n useEffect(() => {\n if (!deferInitialization) {\n init().catch(error => {\n console.error('Auto-initialization failed:', error)\n })\n }\n\n return () => {\n if (clientRef.current) {\n clientRef.current.destroy?.()\n clientRef.current = null\n initPromiseRef.current = null\n }\n }\n }, [deferInitialization])\n\n return (\n <FlagmintStoreContext.Provider value={store}>\n {children}\n </FlagmintStoreContext.Provider>\n )\n}\n","'use client';\nimport { FlagValue } from \"flagmint-js-sdk\"\nimport { useContext } from \"react\"\nimport { StoreApi, useStore } from \"zustand\"\nimport { FlagmintStoreContext } from \"@/providers/FlagmintProvider\"\nimport { FlagStore, Flags } from \"@/types\"\n\n// Helper to get the store from context\nexport function useFlagmintStore(): StoreApi<FlagStore> {\n const store = useContext(FlagmintStoreContext)\n if (!store) {\n throw new Error('useFlagmintStore must be used within a FlagmintProvider')\n }\n return store\n}\n\n// Hook to get the client instance and update context\nexport function useFlagmint() {\n const store = useFlagmintStore()\n const client = useStore(store, (state) => state.client)\n const isInitialized = useStore(store, (state) => state.isInitialized)\n\n const updateContext = async (context: Record<string, any>) => {\n if (!client) {\n throw new Error('Flagmint client not initialized')\n }\n await client.updateContext(context)\n }\n\n return {\n client,\n isInitialized,\n updateContext\n }\n}\n\n// Hook to get all flags - only re-renders when flags change\nexport function useFlags(): Flags {\n const store = useFlagmintStore()\n return useStore(store, (state) => state.flags)\n}\n\n// Hook to get a specific flag - only re-renders when this specific flag changes\nexport function useFlag<T extends FlagValue = FlagValue>(\n key: string,\n fallback?: T\n): T {\n const store = useFlagmintStore()\n return useStore(store, (state) => state.flags[key] ?? fallback) as T\n}\n\n// Hook to check if the client is ready\nexport function useFlagmintReady(): boolean {\n const store = useFlagmintStore()\n return useStore(store, (state) => state.isInitialized && state.client !== null)\n}"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -26,8 +26,6 @@ interface FlagStore {
|
|
|
26
26
|
}
|
|
27
27
|
interface FlagmintProviderProps {
|
|
28
28
|
children: ReactNode;
|
|
29
|
-
createClient?: () => CoreClientLike;
|
|
30
|
-
client: CoreClientLike;
|
|
31
29
|
initialFlags?: Flags;
|
|
32
30
|
}
|
|
33
31
|
interface FlagmintProviderProps<T = unknown, C extends Record<string, any> = Record<string, any>> {
|
package/dist/index.d.ts
CHANGED
|
@@ -26,8 +26,6 @@ interface FlagStore {
|
|
|
26
26
|
}
|
|
27
27
|
interface FlagmintProviderProps {
|
|
28
28
|
children: ReactNode;
|
|
29
|
-
createClient?: () => CoreClientLike;
|
|
30
|
-
client: CoreClientLike;
|
|
31
29
|
initialFlags?: Flags;
|
|
32
30
|
}
|
|
33
31
|
interface FlagmintProviderProps<T = unknown, C extends Record<string, any> = Record<string, any>> {
|