featurefly 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arrua Platform Team
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,687 @@
1
+ # FeatureFly πŸš€
2
+
3
+ **Lightweight, universal Feature Flags SDK for Node.js and the browser.**
4
+ One package. Backend and frontend. Zero config to start.
5
+
6
+ [![npm version](https://img.shields.io/npm/v/featurefly.svg)](https://www.npmjs.com/package/featurefly)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-first-blue.svg)](https://www.typescriptlang.org/)
9
+ [![Bundle Size](https://img.shields.io/badge/gzipped-~21KB-green.svg)](#-feature-comparison)
10
+
11
+ ---
12
+
13
+ ## πŸ“‘ Table of Contents
14
+
15
+ - [Installation](#-installation)
16
+ - [Quick Start](#-quick-start)
17
+ - [Usage by Environment](#-usage-by-environment)
18
+ - [Backend (Node.js / NestJS / Express)](#-backend-nodejs--nestjs--express)
19
+ - [Frontend (Vanilla JS / Any bundler)](#-frontend-vanilla-js--any-bundler)
20
+ - [React](#%EF%B8%8F-react)
21
+ - [Vue 3](#-vue-3)
22
+ - [Configuration](#-configuration)
23
+ - [API Reference](#-api-reference)
24
+ - [Flag Evaluation](#flag-evaluation)
25
+ - [Flag Management (CRUD)](#flag-management-crud)
26
+ - [Workspace Flags](#workspace-flags)
27
+ - [Real-time Streaming (SSE)](#real-time-streaming-sse)
28
+ - [Edge Evaluation (Offline)](#edge-evaluation-offline-mode)
29
+ - [Impact Metrics](#impact-metrics)
30
+ - [Event System](#event-system)
31
+ - [Local Overrides](#local-overrides)
32
+ - [Utilities](#utilities)
33
+ - [Considerations](#-considerations)
34
+ - [Evaluation Flow](#-evaluation-flow)
35
+ - [Resilience](#-resilience)
36
+ - [Feature Comparison](#-feature-comparison)
37
+ - [Roadmap](#-roadmap)
38
+ - [Contributing](#-contributing)
39
+ - [License](#-license)
40
+
41
+ ---
42
+
43
+ ## πŸ“¦ Installation
44
+
45
+ ```bash
46
+ npm install featurefly
47
+ ```
48
+
49
+ > **Peer dependencies (optional):** If you plan to use the React hooks or Vue composables, install the corresponding framework alongside:
50
+ >
51
+ > ```bash
52
+ > # React projects
53
+ > npm install featurefly react
54
+ >
55
+ > # Vue 3 projects
56
+ > npm install featurefly vue
57
+ > ```
58
+
59
+ ---
60
+
61
+ ## πŸš€ Quick Start
62
+
63
+ ```typescript
64
+ import { FeatureFlagsClient } from "featurefly";
65
+
66
+ const client = new FeatureFlagsClient({
67
+ baseUrl: "https://your-api.com",
68
+ apiKey: "your-api-key",
69
+ });
70
+
71
+ const isEnabled = await client.evaluateFlag("new-checkout-flow");
72
+
73
+ if (isEnabled) {
74
+ // New checkout
75
+ } else {
76
+ // Legacy checkout
77
+ }
78
+
79
+ // Always dispose when done (servers: on shutdown, SPAs: on unmount)
80
+ client.dispose();
81
+ ```
82
+
83
+ That's it. The same code works in a NestJS service, an Express middleware, a Vite frontend, or a Next.js API route.
84
+
85
+ ---
86
+
87
+ ## 🌍 Usage by Environment
88
+
89
+ FeatureFly is **universal** β€” the same npm package runs on the server and in the browser. The only difference is _how_ you integrate it.
90
+
91
+ ### πŸ–₯️ Backend (Node.js / NestJS / Express)
92
+
93
+ Use the client directly to evaluate flags on the server side, typically in middleware, guards, or services.
94
+
95
+ ```typescript
96
+ // services/feature-flags.service.ts
97
+ import { FeatureFlagsClient } from "featurefly";
98
+
99
+ const client = new FeatureFlagsClient({
100
+ baseUrl: process.env.FEATURE_FLAGS_API_URL,
101
+ apiKey: process.env.FEATURE_FLAGS_API_KEY,
102
+ cacheTtlMs: 30_000, // Cache flags for 30s on the server
103
+ });
104
+
105
+ export async function isFeatureEnabled(
106
+ slug: string,
107
+ userId?: string,
108
+ workspaceId?: string,
109
+ ): Promise<boolean> {
110
+ return client.evaluateFlag(slug, { userId, workspaceId });
111
+ }
112
+ ```
113
+
114
+ ```typescript
115
+ // Example: Express middleware
116
+ app.get("/dashboard", async (req, res) => {
117
+ const showNewDashboard = await isFeatureEnabled(
118
+ "new-dashboard",
119
+ req.user.id,
120
+ req.user.workspaceId,
121
+ );
122
+
123
+ if (showNewDashboard) {
124
+ return res.render("dashboard-v2");
125
+ }
126
+ return res.render("dashboard");
127
+ });
128
+ ```
129
+
130
+ ```typescript
131
+ // Example: NestJS guard
132
+ @Injectable()
133
+ export class FeatureFlagGuard implements CanActivate {
134
+ constructor(private readonly flags: FeatureFlagsClient) {}
135
+
136
+ async canActivate(context: ExecutionContext): Promise<boolean> {
137
+ const request = context.switchToHttp().getRequest();
138
+ return this.flags.evaluateFlag("beta-api", {
139
+ userId: request.user.id,
140
+ });
141
+ }
142
+ }
143
+ ```
144
+
145
+ > πŸ’‘ **Tip:** On the server, create a **single instance** of `FeatureFlagsClient` and reuse it across requests. Call `client.dispose()` during graceful shutdown.
146
+
147
+ ---
148
+
149
+ ### 🌐 Frontend (Vanilla JS / Any bundler)
150
+
151
+ Works with any bundler (Vite, Webpack, esbuild, Rollup) or even a plain `<script>` tag.
152
+
153
+ ```typescript
154
+ import { FeatureFlagsClient } from "featurefly";
155
+
156
+ const client = new FeatureFlagsClient({
157
+ baseUrl: "https://your-api.com",
158
+ apiKey: "pk_live_xxx", // Use a public/client key
159
+ streaming: true, // Auto-receive flag updates via SSE
160
+ });
161
+
162
+ // Evaluate and render
163
+ const showBanner = await client.evaluateFlag("promo-banner", {
164
+ userId: currentUser.id,
165
+ attributes: { plan: currentUser.plan, country: "AR" },
166
+ });
167
+
168
+ if (showBanner) {
169
+ document.getElementById("promo")!.style.display = "block";
170
+ }
171
+
172
+ // React to live flag changes
173
+ client.on("flagsUpdated", async () => {
174
+ const updated = await client.evaluateFlag("promo-banner");
175
+ document.getElementById("promo")!.style.display = updated ? "block" : "none";
176
+ });
177
+ ```
178
+
179
+ ---
180
+
181
+ ### βš›οΈ React
182
+
183
+ Import from `featurefly/react`. Hooks auto-re-evaluate when flags change via streaming.
184
+
185
+ ```tsx
186
+ import { FeatureFlagsClient } from "featurefly";
187
+ import {
188
+ FeatureFlyProvider,
189
+ useFeatureFlag,
190
+ useAllFlags,
191
+ } from "featurefly/react";
192
+
193
+ // Create your client (once, outside the component tree)
194
+ const client = new FeatureFlagsClient({
195
+ baseUrl: "https://your-api.com",
196
+ apiKey: "pk_live_xxx",
197
+ streaming: true,
198
+ });
199
+
200
+ // 1. Wrap your app
201
+ function App() {
202
+ return (
203
+ <FeatureFlyProvider client={client}>
204
+ <MyComponent />
205
+ </FeatureFlyProvider>
206
+ );
207
+ }
208
+
209
+ // 2. Use hooks
210
+ function MyComponent() {
211
+ const { value: darkMode, loading } = useFeatureFlag("dark-mode", false);
212
+ const { flags } = useAllFlags({ workspaceId: "ws-123" });
213
+
214
+ if (loading) return <Spinner />;
215
+
216
+ return (
217
+ <div className={darkMode ? "dark" : "light"}>
218
+ {flags["new-feature"] && <NewFeature />}
219
+ </div>
220
+ );
221
+ }
222
+ ```
223
+
224
+ | Hook | Returns | Description |
225
+ | ---------------------------------------------- | -------------------- | ----------------------------------------------- |
226
+ | `useFeatureFlag(slug, defaultValue, context?)` | `{ value, loading }` | Evaluates a single flag. Re-renders on changes. |
227
+ | `useAllFlags(context?)` | `{ flags, loading }` | Returns all flags as a key-value object. |
228
+
229
+ ---
230
+
231
+ ### πŸ’š Vue 3
232
+
233
+ Import from `featurefly/vue`. Composables return reactive `Ref` values that update automatically.
234
+
235
+ ```typescript
236
+ // main.ts
237
+ import { createApp } from "vue";
238
+ import { FeatureFlagsClient } from "featurefly";
239
+ import { FeatureFlyPlugin } from "featurefly/vue";
240
+
241
+ const client = new FeatureFlagsClient({
242
+ baseUrl: "https://your-api.com",
243
+ apiKey: "pk_live_xxx",
244
+ streaming: true,
245
+ });
246
+
247
+ const app = createApp(App);
248
+ app.use(FeatureFlyPlugin, { client });
249
+ app.mount("#app");
250
+ ```
251
+
252
+ ```vue
253
+ <!-- MyComponent.vue -->
254
+ <script setup>
255
+ import { useFeatureFlag, useAllFlags } from "featurefly/vue";
256
+
257
+ const darkMode = useFeatureFlag("dark-mode", false);
258
+ const flags = useAllFlags({ workspaceId: "ws-123" });
259
+ </script>
260
+
261
+ <template>
262
+ <div :class="{ dark: darkMode }">
263
+ <NewFeature v-if="flags['new-feature']" />
264
+ </div>
265
+ </template>
266
+ ```
267
+
268
+ | Composable | Returns | Description |
269
+ | ---------------------------------------------- | -------------------------------- | ------------------------------------------ |
270
+ | `useFeatureFlag(slug, defaultValue, context?)` | `Ref<T>` | Reactive ref that updates on flag changes. |
271
+ | `useAllFlags(context?)` | `Ref<Record<string, FlagValue>>` | Reactive ref with all flags. |
272
+
273
+ ---
274
+
275
+ ## πŸ”§ Configuration
276
+
277
+ All options are optional except `baseUrl`.
278
+
279
+ ```typescript
280
+ const client = new FeatureFlagsClient({
281
+ // Required β€” your feature flags API endpoint
282
+ baseUrl: "https://your-api.com",
283
+
284
+ // Authentication
285
+ apiKey: "your-api-key",
286
+
287
+ // HTTP timeout (default: 10000ms)
288
+ timeout: 10_000,
289
+
290
+ // Cache (default: enabled, 60s TTL)
291
+ cacheEnabled: true,
292
+ cacheTtlMs: 60_000,
293
+
294
+ // Retry (default: 3 attempts, 1s base delay)
295
+ retry: {
296
+ maxAttempts: 3,
297
+ baseDelayMs: 1_000,
298
+ maxDelayMs: 10_000,
299
+ },
300
+
301
+ // Circuit Breaker (default: 5 failures, 30s reset)
302
+ circuitBreaker: {
303
+ failureThreshold: 5,
304
+ resetTimeoutMs: 30_000,
305
+ },
306
+
307
+ // Logging β€” 'debug' | 'info' | 'warn' | 'error' | 'silent' (default: 'warn')
308
+ logLevel: "warn",
309
+
310
+ // Or provide your own logger (pino, winston, etc.)
311
+ logger: myLogger,
312
+
313
+ // Local overrides β€” flags evaluated instantly without HTTP
314
+ localOverrides: {
315
+ "feature-x": true,
316
+ "variant-test": "blue",
317
+ },
318
+
319
+ // Fallback defaults when the API is unreachable
320
+ fallbackDefaults: {
321
+ "critical-feature": false,
322
+ },
323
+
324
+ // Real-time updates via SSE (default: false)
325
+ streaming: true, // or { reconnectDelayMs: 2000 }
326
+
327
+ // A/B testing callback
328
+ trackingCallback: (assignment) => {
329
+ analytics.track("Experiment Viewed", assignment);
330
+ },
331
+
332
+ // Edge evaluation β€” pass a pre-fetched document for fully offline mode
333
+ // edgeDocument: myPreFetchedDoc,
334
+ });
335
+ ```
336
+
337
+ ---
338
+
339
+ ## πŸ“š API Reference
340
+
341
+ ### Flag Evaluation
342
+
343
+ ```typescript
344
+ // Boolean flag
345
+ const isEnabled = await client.evaluateFlag("my-flag");
346
+
347
+ // With user context (targeting, rollout, experiments)
348
+ const isEnabled = await client.evaluateFlag("my-flag", {
349
+ workspaceId: "ws-123",
350
+ userId: "user-456",
351
+ attributes: { plan: "pro", country: "AR" },
352
+ });
353
+
354
+ // Typed flag values
355
+ const variant = await client.evaluateFlag<string>("ab-test");
356
+ const limit = await client.evaluateFlag<number>("rate-limit");
357
+ const config = await client.evaluateFlag<Record<string, unknown>>("ui-config");
358
+
359
+ // All flags at once (single HTTP request)
360
+ const allFlags = await client.evaluateAllFlags({ workspaceId: "ws-123" });
361
+ ```
362
+
363
+ ### Flag Management (CRUD)
364
+
365
+ ```typescript
366
+ // Create
367
+ const flag = await client.createFlag({
368
+ slug: "new-feature",
369
+ name: "New Feature",
370
+ category: "both",
371
+ valueType: "boolean",
372
+ defaultValue: false,
373
+ tags: ["v2", "beta"],
374
+ });
375
+
376
+ // Read
377
+ const allFlags = await client.getAllFlags();
378
+ const byId = await client.getFlagById("flag-id");
379
+ const bySlug = await client.getFlagBySlug("my-flag");
380
+
381
+ // Update
382
+ await client.updateFlag("flag-id", { name: "Updated Name" });
383
+
384
+ // Delete
385
+ await client.deleteFlag("flag-id");
386
+ ```
387
+
388
+ ### Workspace Flags
389
+
390
+ ```typescript
391
+ // Set a workspace-level override
392
+ await client.setWorkspaceFlag("feature-x", "workspace-123", true);
393
+
394
+ // Get all flags for a workspace
395
+ const flags = await client.getWorkspaceFlags("workspace-123");
396
+
397
+ // Remove an override
398
+ await client.removeWorkspaceFlag("feature-x", "workspace-123");
399
+ ```
400
+
401
+ ### Real-time Streaming (SSE)
402
+
403
+ ```typescript
404
+ // Option A: Auto-start via config
405
+ const client = new FeatureFlagsClient({
406
+ baseUrl: "https://your-api.com",
407
+ streaming: true,
408
+ });
409
+
410
+ // Option B: Manual control
411
+ client.startStreaming();
412
+ client.stopStreaming();
413
+
414
+ // React to flag updates (cache is auto-invalidated)
415
+ client.on("flagsUpdated", () => {
416
+ console.log("Flags refreshed from server!");
417
+ });
418
+ ```
419
+
420
+ ### Edge Evaluation (Offline Mode)
421
+
422
+ Evaluate flags with 0ms latency by pre-fetching the entire flag document.
423
+
424
+ ```typescript
425
+ // 1. Fetch the document (e.g., at server startup or on app boot)
426
+ const doc = await fetch("https://your-api.com/feature-flags/document").then(
427
+ (r) => r.json(),
428
+ );
429
+
430
+ // 2. Create a fully offline client
431
+ const edgeClient = new FeatureFlagsClient({
432
+ baseUrl: "https://your-api.com",
433
+ edgeDocument: doc,
434
+ });
435
+
436
+ // 3. Evaluate β€” zero network calls, zero latency
437
+ const value = await edgeClient.evaluateFlag("my-flag", {
438
+ userId: "user-123",
439
+ });
440
+ ```
441
+
442
+ ### Impact Metrics
443
+
444
+ Client-side telemetry collected passively, no external calls.
445
+
446
+ ```typescript
447
+ const metrics = client.getImpactMetrics();
448
+
449
+ console.log(metrics.totalEvaluations); // 1523
450
+ console.log(metrics.cacheHitRate); // 0.87
451
+ console.log(metrics.latency.p50); // 2ms
452
+ console.log(metrics.latency.p95); // 12ms
453
+ console.log(metrics.latency.p99); // 45ms
454
+
455
+ // Per-flag detail
456
+ console.log(metrics.flags["my-flag"].evaluations); // 42
457
+
458
+ // Experiment exposures
459
+ console.log(metrics.experiments["checkout-exp"].exposures); // 300
460
+
461
+ // Reset all counters
462
+ client.resetMetrics();
463
+ ```
464
+
465
+ ### Event System
466
+
467
+ ```typescript
468
+ // Flag evaluated (with timing)
469
+ client.on("flagEvaluated", ({ slug, value, reason, durationMs }) => {
470
+ analytics.track("flag_check", { slug, value, reason, durationMs });
471
+ });
472
+
473
+ // Flag value changed (detect drift)
474
+ client.on("flagChanged", ({ slug, previousValue, newValue }) => {
475
+ console.log(`${slug}: ${previousValue} β†’ ${newValue}`);
476
+ });
477
+
478
+ // Circuit breaker
479
+ client.on("circuitOpen", ({ state, failures }) => {
480
+ alerting.send(`Circuit opened after ${failures} failures`);
481
+ });
482
+
483
+ // Cache
484
+ client.on("cacheHit", ({ key }) => monitor.increment("cache.hit"));
485
+ client.on("cacheMiss", ({ key }) => monitor.increment("cache.miss"));
486
+
487
+ // Streaming
488
+ client.on("streamConnected", () => console.log("SSE connected"));
489
+ client.on("streamDisconnected", () => console.log("SSE lost"));
490
+
491
+ // Unsubscribe
492
+ const unsubscribe = client.on("flagEvaluated", handler);
493
+ unsubscribe();
494
+ ```
495
+
496
+ ### Local Overrides
497
+
498
+ Useful for development and testing β€” evaluated instantly, no HTTP.
499
+
500
+ ```typescript
501
+ client.setLocalOverride("experimental-ui", true);
502
+ client.setLocalOverride("theme", "dark");
503
+
504
+ const overrides = client.getLocalOverrides();
505
+
506
+ client.removeLocalOverride("experimental-ui");
507
+ client.clearLocalOverrides();
508
+ ```
509
+
510
+ ### Utilities
511
+
512
+ ```typescript
513
+ // Cache
514
+ client.clearCache();
515
+ client.getCacheStats(); // { size, keys, enabled }
516
+
517
+ // Circuit breaker
518
+ client.getCircuitBreakerState(); // { state, failures }
519
+ client.resetCircuitBreaker();
520
+
521
+ // Lifecycle
522
+ client.dispose(); // Release timers, listeners, metrics, SSE
523
+ client.isDisposed(); // true
524
+ ```
525
+
526
+ ---
527
+
528
+ ## ⚠️ Considerations
529
+
530
+ ### Universal Package (Backend + Frontend)
531
+
532
+ FeatureFly ships as a **single universal package** that works on both the server and the browser:
533
+
534
+ | Concern | How it's handled |
535
+ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
536
+ | **HTTP client** | Uses `axios`, which auto-detects the environment (Node.js `http` module vs browser `XMLHttpRequest`). |
537
+ | **Hashing (MurmurHash3)** | Implemented in pure TypeScript β€” no dependency on Node.js `crypto`. |
538
+ | **SSE Streaming** | Uses the native browser `EventSource` API. On Node.js < 22, streaming is disabled unless you provide a polyfill. Node.js 22+ includes native `EventSource`. |
539
+ | **Build format** | Ships both CJS (`require()`) and ESM (`import`). Your bundler picks the right one. |
540
+
541
+ ### API Key Security
542
+
543
+ - On the **backend**, use a secret API key stored in environment variables.
544
+ - On the **frontend**, use a **public/read-only** key that only has permission to evaluate flags, not manage them. Never expose your secret key in client-side code.
545
+
546
+ ### Single Instance Pattern
547
+
548
+ Create **one** `FeatureFlagsClient` instance and share it:
549
+
550
+ - **Backend:** Create at app startup, dispose on `SIGTERM` / `SIGINT`.
551
+ - **React:** Create outside the component tree, pass via `<FeatureFlyProvider>`.
552
+ - **Vue:** Create before `app.mount()`, install via `app.use(FeatureFlyPlugin, { client })`.
553
+
554
+ ### Cache Strategy
555
+
556
+ | Environment | Recommended `cacheTtlMs` | Why |
557
+ | ----------------- | ------------------------ | -------------------------------------------------------------------------- |
558
+ | Backend (API) | `30_000` – `60_000` | Flags change infrequently; caching reduces load on the flags API. |
559
+ | Frontend (SPA) | `60_000` – `120_000` | Combined with `streaming: true`, the cache is auto-invalidated on changes. |
560
+ | Edge / Serverless | `0` (disabled) | Use `edgeDocument` for fully offline evaluation instead. |
561
+
562
+ ### Disposing Resources
563
+
564
+ Always call `client.dispose()` when you're done. This cleans up:
565
+
566
+ - SSE connections
567
+ - Cache timers
568
+ - Metrics collectors
569
+ - Event listeners
570
+
571
+ ```typescript
572
+ // Express / NestJS
573
+ process.on("SIGTERM", () => client.dispose());
574
+
575
+ // React
576
+ useEffect(() => () => client.dispose(), []);
577
+
578
+ // Vue
579
+ onUnmounted(() => client.dispose());
580
+ ```
581
+
582
+ ### Node.js Version
583
+
584
+ Requires **Node.js >= 18**. For SSE streaming on the server, Node.js 22+ is recommended (native `EventSource`), or install a polyfill like `eventsource` for older versions.
585
+
586
+ ---
587
+
588
+ ## βš™οΈ Evaluation Flow
589
+
590
+ When you call `evaluateFlag()`, the SDK follows this priority chain:
591
+
592
+ ```
593
+ evaluateFlag('slug', context)
594
+ β”‚
595
+ β”œβ”€ 1. Local Overrides β†’ instant return (no HTTP)
596
+ β”œβ”€ 2. Edge Evaluator β†’ offline return if document loaded
597
+ β”œβ”€ 3. Cache hit β†’ instant return (no HTTP)
598
+ β”œβ”€ 4. Circuit Breaker β†’ reject if circuit is open
599
+ β”œβ”€ 5. Retry w/ Backoff β†’ exponential backoff + jitter
600
+ β”œβ”€ 6. HTTP Request β†’ GET /feature-flags/:slug/evaluate
601
+ β”œβ”€ 7. Cache store β†’ persist result with TTL
602
+ └─ 8. Fallback β†’ predefined defaults if everything fails
603
+ ```
604
+
605
+ ---
606
+
607
+ ## πŸ”’ Resilience
608
+
609
+ Built-in, zero-config resilience. No plugins needed.
610
+
611
+ | Layer | What it does |
612
+ | --------------------- | ----------------------------------------------------------------------- |
613
+ | **Retry** | Retries failed requests with exponential backoff + jitter |
614
+ | **Circuit Breaker** | Stops calling a failing API after N consecutive failures, auto-recovers |
615
+ | **Cache** | Serves stale data while the API is down |
616
+ | **Fallback Defaults** | Returns predefined safe values when nothing else works |
617
+ | **Local Overrides** | Flags work completely offline |
618
+ | **Edge Evaluator** | Full offline evaluation using a pre-fetched document |
619
+
620
+ ---
621
+
622
+ ## πŸ“Š Feature Comparison
623
+
624
+ | Capability | FeatureFly | LaunchDarkly | Unleash | GrowthBook | Flagsmith |
625
+ | ---------------------------------------- | :--------: | :-----------: | :-----------: | :--------: | :-------: |
626
+ | Boolean flags | βœ… | βœ… | βœ… | βœ… | βœ… |
627
+ | Multi-type values (string, number, JSON) | βœ… | βœ… | βœ… | βœ… | βœ… |
628
+ | In-memory cache | βœ… | βœ… | βœ… | βœ… | βœ… |
629
+ | Retry with backoff | βœ… | βœ… | βœ… | βœ… | βœ… |
630
+ | Circuit breaker | βœ… | βœ… | ❌ | ❌ | ❌ |
631
+ | Typed event system | βœ… | ⚠️ | ❌ | ❌ | ❌ |
632
+ | Local overrides | βœ… | ⚠️ | βœ… | βœ… | ❌ |
633
+ | Fallback defaults | βœ… | βœ… | βœ… | βœ… | βœ… |
634
+ | Injectable logger | βœ… | βœ… | βœ… | ❌ | ❌ |
635
+ | Dispose / cleanup | βœ… | βœ… | βœ… | ❌ | ❌ |
636
+ | Workspace-level overrides | βœ… | ⚠️ | ❌ | ❌ | βœ… |
637
+ | Streaming (SSE) | βœ… | βœ… | βœ… | βœ… | βœ… |
638
+ | Targeting / segmentation | βœ… | βœ… | βœ… | βœ… | βœ… |
639
+ | Percentage rollout | βœ… | βœ… | βœ… | βœ… | βœ… |
640
+ | A/B testing | βœ… | βœ… | ❌ | βœ… | βœ… |
641
+ | Edge evaluation (offline) | βœ… | βœ… | βœ… | βœ… | ❌ |
642
+ | Impact metrics | βœ… | βœ… | βœ… | βœ… | ❌ |
643
+ | React hooks | βœ… | βœ… | βœ… | βœ… | βœ… |
644
+ | Vue composables | βœ… | ❌ | ❌ | ❌ | ❌ |
645
+ | Self-hosted | βœ… | ❌ | βœ… | βœ… | βœ… |
646
+ | TypeScript first | βœ… | βœ… | ⚠️ | βœ… | ⚠️ |
647
+ | Open source | βœ… MIT | βœ… Apache 2.0 | βœ… Apache 2.0 | βœ… MIT | βœ… BSD-3 |
648
+ | | | | | | |
649
+ | **Bundle size (gzipped)** | **~21 KB** | ~100 KB+ | ~336 KB | ~9 KB | ~50 KB+ |
650
+ | **Runtime dependencies** | **1** | 3+ | 5+ | **0** | 2+ |
651
+ | **Pricing** | **Free** | Paid | Free/Paid | **Free** | Free/Paid |
652
+
653
+ > βœ… Supported Β· ⚠️ Partial Β· ❌ Not available
654
+
655
+ ---
656
+
657
+ ## πŸ—ΊοΈ Roadmap
658
+
659
+ | Feature | Status | Description |
660
+ | ------------------- | ------------ | -------------------------------------------- |
661
+ | Multi-language SDKs | πŸ”œ Planned | Go, Python, Ruby, PHP server-side SDKs |
662
+ | Encrypted payloads | πŸ’‘ Exploring | End-to-end encryption of flag configurations |
663
+ | Audit log | πŸ’‘ Exploring | Track who changed what and when |
664
+
665
+ ---
666
+
667
+ ## πŸ§ͺ Testing
668
+
669
+ ```bash
670
+ npm test # Run tests with coverage
671
+ npm run test:watch # Watch mode
672
+ ```
673
+
674
+ ---
675
+
676
+ ## 🀝 Contributing
677
+
678
+ 1. Clone the repo
679
+ 2. `npm install`
680
+ 3. `npm test`
681
+ 4. `npm run build`
682
+
683
+ ---
684
+
685
+ ## πŸ“„ License
686
+
687
+ MIT Β© Arrua Platform Team