@flagify/node 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,246 @@
1
+ <p align="center">
2
+ <a href="https://flagify.dev">
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://flagify.dev/logo-white.svg" />
5
+ <source media="(prefers-color-scheme: light)" srcset="https://flagify.dev/logo-color.svg" />
6
+ <img alt="Flagify" src="https://flagify.dev/logo-color.svg" width="280" />
7
+ </picture>
8
+ </a>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <strong>Feature flags for modern teams</strong>
13
+ </p>
14
+
15
+ <p align="center">
16
+ <a href="https://www.npmjs.com/package/@flagify/node"><img src="https://img.shields.io/npm/v/@flagify/node.svg?style=flat-square&color=0D80F9" alt="npm version" /></a>
17
+ <a href="https://www.npmjs.com/package/@flagify/node"><img src="https://img.shields.io/npm/dm/@flagify/node.svg?style=flat-square&color=0D80F9" alt="npm downloads" /></a>
18
+ <a href="https://github.com/flagifyhq/node-sdk/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@flagify/node.svg?style=flat-square&color=0D80F9" alt="license" /></a>
19
+ <a href="https://github.com/flagifyhq/node-sdk"><img src="https://img.shields.io/github/stars/flagifyhq/node-sdk?style=flat-square&color=0D80F9" alt="github stars" /></a>
20
+ </p>
21
+
22
+ <p align="center">
23
+ <a href="https://flagify.dev/docs">Documentation</a> &middot;
24
+ <a href="https://flagify.dev/docs/sdks/node">SDK Reference</a> &middot;
25
+ <a href="https://github.com/flagifyhq/node-sdk/issues">Issues</a> &middot;
26
+ <a href="https://flagify.dev">Website</a>
27
+ </p>
28
+
29
+ ---
30
+
31
+ ## Overview
32
+
33
+ `@flagify/node` is the official Node.js SDK for [Flagify](https://flagify.dev). TypeScript-first, with in-memory caching and sub-millisecond flag evaluation.
34
+
35
+ - **TypeScript-first** -- Full type safety with generics support
36
+ - **In-memory cache** -- Sub-millisecond evaluations after initial sync
37
+ - **Stale-while-revalidate** -- Serves cached values while refreshing in the background
38
+ - **Lightweight** -- Zero runtime dependencies (except `dotenv`)
39
+ - **Isomorphic** -- ESM and CommonJS output
40
+
41
+ ## Table of contents
42
+
43
+ - [Installation](#installation)
44
+ - [Quick start](#quick-start)
45
+ - [Configuration](#configuration)
46
+ - [API reference](#api-reference)
47
+ - [How it works](#how-it-works)
48
+ - [Environment variables](#environment-variables)
49
+ - [Contributing](#contributing)
50
+ - [License](#license)
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ # pnpm
56
+ pnpm add @flagify/node
57
+
58
+ # npm
59
+ npm install @flagify/node
60
+
61
+ # yarn
62
+ yarn add @flagify/node
63
+ ```
64
+
65
+ ## Quick start
66
+
67
+ ```typescript
68
+ import { Flagify } from '@flagify/node'
69
+
70
+ const flagify = new Flagify({
71
+ projectKey: 'proj_xxx',
72
+ publicKey: 'pk_xxx',
73
+ })
74
+
75
+ // Boolean flag
76
+ if (flagify.isEnabled('new-checkout')) {
77
+ showNewCheckout()
78
+ }
79
+
80
+ // Typed value
81
+ const limit = flagify.getValue<number>('rate-limit')
82
+ ```
83
+
84
+ ## Configuration
85
+
86
+ ```typescript
87
+ import { Flagify } from '@flagify/node'
88
+
89
+ const flagify = new Flagify({
90
+ // Required
91
+ projectKey: 'proj_xxx',
92
+ publicKey: 'pk_xxx',
93
+
94
+ // Optional -- server-side only, never expose in client bundles
95
+ secretKey: 'sk_xxx',
96
+
97
+ options: {
98
+ // Custom API endpoint (defaults to https://api.flagify.dev)
99
+ apiUrl: 'https://api.flagify.dev',
100
+
101
+ // Cache TTL in ms (default: 5 minutes)
102
+ staleTimeMs: 300_000,
103
+
104
+ // Real-time updates via SSE (coming soon)
105
+ realtime: false,
106
+
107
+ // User context for targeting rules
108
+ user: {
109
+ id: 'user_123',
110
+ email: 'mario@example.com',
111
+ role: 'admin',
112
+ group: 'engineering',
113
+ geolocation: {
114
+ country: 'US',
115
+ region: 'CA',
116
+ city: 'San Francisco',
117
+ },
118
+ // Custom attributes
119
+ plan: 'enterprise',
120
+ companySize: 50,
121
+ },
122
+ },
123
+ })
124
+ ```
125
+
126
+ ### Configuration options
127
+
128
+ | Option | Type | Required | Default | Description |
129
+ |--------|------|----------|---------|-------------|
130
+ | `projectKey` | `string` | Yes | -- | Project identifier from your Flagify workspace |
131
+ | `publicKey` | `string` | Yes | -- | Client-safe publishable API key |
132
+ | `secretKey` | `string` | No | -- | Server-side secret key |
133
+ | `options.apiUrl` | `string` | No | `https://api.flagify.dev` | Custom API base URL |
134
+ | `options.staleTimeMs` | `number` | No | `300000` | Cache staleness threshold in ms |
135
+ | `options.realtime` | `boolean` | No | `false` | Enable real-time SSE updates |
136
+ | `options.user` | `FlagifyUser` | No | -- | User context for targeting |
137
+
138
+ ## API reference
139
+
140
+ ### `new Flagify(config: FlagifyOptions)`
141
+
142
+ Creates a new Flagify client. Immediately fetches all flags and populates the local cache.
143
+
144
+ ```typescript
145
+ const flagify = new Flagify({
146
+ projectKey: 'proj_xxx',
147
+ publicKey: 'pk_xxx',
148
+ })
149
+ ```
150
+
151
+ ---
152
+
153
+ ### `flagify.isEnabled(flagKey: string): boolean`
154
+
155
+ Evaluates a boolean feature flag.
156
+
157
+ Returns `false` when:
158
+ - The flag does not exist
159
+ - The flag is disabled
160
+ - The flag type is not `boolean`
161
+
162
+ ```typescript
163
+ if (flagify.isEnabled('dark-mode')) {
164
+ applyDarkTheme()
165
+ }
166
+ ```
167
+
168
+ ---
169
+
170
+ ### `flagify.getValue<T>(flagKey: string): T`
171
+
172
+ Returns the resolved value of a feature flag with TypeScript generics.
173
+
174
+ ```typescript
175
+ // String variant
176
+ const variant = flagify.getValue<string>('checkout-flow')
177
+
178
+ // Number
179
+ const limit = flagify.getValue<number>('rate-limit')
180
+
181
+ // JSON object
182
+ const config = flagify.getValue<{ maxRetries: number; timeout: number }>('api-config')
183
+ ```
184
+
185
+ ## How it works
186
+
187
+ ```
188
+ Init Evaluate Stale?
189
+ ---- -------- ------
190
+ GET /v1/flags --> Read from cache --> Background refetch
191
+ Cache all flags Sub-ms response GET /v1/flags/:key
192
+ Return stale value immediately
193
+ ```
194
+
195
+ 1. On initialization, the client syncs all flags from `GET /v1/flags`
196
+ 2. All evaluations read from the in-memory `Map` cache -- sub-millisecond
197
+ 3. When a flag exceeds `staleTimeMs`, the stale value is returned immediately while a background `GET /v1/flags/:key` refreshes the cache
198
+ 4. If the API is unreachable, the client falls back to cached defaults
199
+
200
+ ## Environment variables
201
+
202
+ | Variable | Description |
203
+ |----------|-------------|
204
+ | `FLAGIFY_API_URL` | Override the default API base URL |
205
+
206
+ ## Types
207
+
208
+ All types are exported for convenience:
209
+
210
+ ```typescript
211
+ import type {
212
+ FlagifyOptions,
213
+ FlagifyUser,
214
+ FlagifyFlaggy,
215
+ IFlagifyClient,
216
+ } from '@flagify/node'
217
+ ```
218
+
219
+ ## Contributing
220
+
221
+ We welcome contributions. Please open an issue first to discuss what you'd like to change.
222
+
223
+ ```bash
224
+ # Clone
225
+ git clone https://github.com/flagifyhq/node-sdk.git
226
+ cd node-sdk
227
+
228
+ # Install
229
+ pnpm install
230
+
231
+ # Development
232
+ pnpm run dev
233
+
234
+ # Build
235
+ pnpm run build
236
+ ```
237
+
238
+ ## License
239
+
240
+ MIT -- see [LICENSE](./LICENSE) for details.
241
+
242
+ ---
243
+
244
+ <p align="center">
245
+ <sub>Built with care by the <a href="https://flagify.dev">Flagify</a> team</sub>
246
+ </p>
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Represents a user context object used for feature flag targeting.
3
+ *
4
+ * You can define standard properties like `id`, `email`, `role`, and `group`,
5
+ * as well as any number of custom attributes for segmentation purposes.
6
+ */
7
+ interface FlagifyUser {
8
+ /**
9
+ * Unique identifier for the user.
10
+ * This is typically required for targeting, experimentation, or auditing.
11
+ */
12
+ id: string;
13
+ /**
14
+ * Optional email address of the user.
15
+ */
16
+ email?: string;
17
+ /**
18
+ * Optional role or access level of the user (e.g., "admin", "editor", "viewer").
19
+ */
20
+ role?: string;
21
+ /**
22
+ * Optional group or organization to which the user belongs.
23
+ */
24
+ group?: string;
25
+ /**
26
+ * Optional geolocation details used for region-based targeting.
27
+ */
28
+ geolocation?: {
29
+ /**
30
+ * ISO country code (e.g., "US", "MX", "DE").
31
+ */
32
+ country?: string;
33
+ /**
34
+ * Optional region or state within the country.
35
+ */
36
+ region?: string;
37
+ /**
38
+ * Optional city or locality.
39
+ */
40
+ city?: string;
41
+ };
42
+ /**
43
+ * Any other custom attributes for advanced targeting rules.
44
+ */
45
+ [key: string]: unknown;
46
+ }
47
+
48
+ /**
49
+ * Configuration options required to initialize the Flagify client.
50
+ */
51
+ interface FlagifyOptions {
52
+ /**
53
+ * The key identifying the project within your Flagify workspace.
54
+ */
55
+ projectKey: string;
56
+ /**
57
+ * Public API key used to identify the client or application.
58
+ * This is safe to expose in client-side environments (e.g., browser, mobile).
59
+ */
60
+ publicKey: string;
61
+ /**
62
+ * Optional private key for secure server-side communication.
63
+ * Never expose this in frontend environments.
64
+ */
65
+ secretKey?: string;
66
+ /**
67
+ * Additional optional configuration for advanced use cases.
68
+ */
69
+ options?: {
70
+ /**
71
+ * Contextual user data for targeting rules and segmentation.
72
+ * You may provide built-in fields like `id`, `email`, `role`, or custom traits.
73
+ */
74
+ user?: FlagifyUser;
75
+ /**
76
+ * Custom base URL for the Flagify API (e.g., for self-hosted instances or testing).
77
+ * Defaults to "https://api.flagify.app" if not provided.
78
+ */
79
+ apiUrl?: string;
80
+ /**
81
+ * Optional cache stale time in milliseconds for local flag resolution.
82
+ * Defaults to 5 minutes.
83
+ */
84
+ staleTimeMs?: number;
85
+ /**
86
+ * Enables real-time flag updates via Server-Sent Events.
87
+ */
88
+ realtime?: boolean;
89
+ /**
90
+ * Interval in milliseconds to periodically re-sync all flags.
91
+ * Useful as a fallback when realtime is unavailable.
92
+ * Example: 30000 (every 30 seconds).
93
+ */
94
+ pollIntervalMs?: number;
95
+ };
96
+ }
97
+
98
+ interface FlagifyHttpClient {
99
+ get<T = unknown>(path: string): Promise<T>;
100
+ post<T = unknown, B = unknown>(path: string, body: B): Promise<T>;
101
+ baseUrl: string;
102
+ headers: Record<string, string>;
103
+ }
104
+
105
+ interface RealtimeEvents {
106
+ onFlagChange: (event: FlagChangeEvent) => void;
107
+ onConnected: () => void;
108
+ onReconnected: () => void;
109
+ onError: (error: Error) => void;
110
+ }
111
+ interface FlagChangeEvent {
112
+ environmentId: string;
113
+ flagKey: string;
114
+ action: "updated" | "created" | "archived";
115
+ }
116
+ declare class RealtimeListener {
117
+ private readonly httpClient;
118
+ private readonly events;
119
+ private controller;
120
+ private reconnectAttempts;
121
+ private reconnectTimer;
122
+ private hasConnectedBefore;
123
+ constructor(httpClient: FlagifyHttpClient, events: RealtimeEvents);
124
+ connect(): void;
125
+ disconnect(): void;
126
+ private stream;
127
+ private parseSSEFrame;
128
+ private scheduleReconnect;
129
+ }
130
+
131
+ /**
132
+ * Interface defining the core methods for the Flaggy client.
133
+ */
134
+ interface IFlagifyClient {
135
+ /**
136
+ * Retrieves the resolved value of a feature flag.
137
+ * Falls back to defaultValue if evaluation fails or flag is not found.
138
+ *
139
+ * @param flagKey - The key of the feature flag to retrieve.
140
+ * @returns The resolved value of the feature flag.
141
+ */
142
+ getValue<T>(flagKey: string, fallback: T): T;
143
+ /**
144
+ * Checks if a boolean feature flag is enabled.
145
+ * Returns false if flag is not found or default is false.
146
+ *
147
+ * @param flagKey - The key of the feature flag to check.
148
+ * @returns True if the flag is enabled, false otherwise.
149
+ */
150
+ isEnabled(flagKey: string): boolean;
151
+ /**
152
+ * Returns the variant key with the highest weight for a multivariate flag.
153
+ * Returns fallback if the flag is missing, disabled, or has no variants.
154
+ *
155
+ * @param flagKey - The key of the feature flag.
156
+ * @param fallback - The default variant key.
157
+ * @returns The winning variant key.
158
+ */
159
+ getVariant(flagKey: string, fallback: string): string;
160
+ /**
161
+ * Evaluates a flag with user context for targeting rules.
162
+ * Calls the API's evaluate endpoint directly (not cached).
163
+ */
164
+ evaluate(flagKey: string, user: FlagifyUser): Promise<EvaluateResult>;
165
+ }
166
+
167
+ interface EvaluateResult {
168
+ key: string;
169
+ value: unknown;
170
+ reason: "targeting_rule" | "rollout" | "default" | "disabled";
171
+ }
172
+ declare class Flagify implements IFlagifyClient {
173
+ private readonly config;
174
+ private flagCache;
175
+ private httpClient;
176
+ private realtime;
177
+ private readyPromise;
178
+ private pollTimer;
179
+ /** Called when a flag changes via SSE. Useful for triggering React re-renders. */
180
+ onFlagChange: ((event: FlagChangeEvent) => void) | null;
181
+ constructor(config: FlagifyOptions);
182
+ /** Resolves when the initial flag sync is complete. */
183
+ ready(): Promise<void>;
184
+ getValue<T>(flagKey: string, fallback: T): T;
185
+ isEnabled(flagKey: string): boolean;
186
+ getVariant(flagKey: string, fallback: string): string;
187
+ evaluate(flagKey: string, user: FlagifyUser): Promise<EvaluateResult>;
188
+ /**
189
+ * Disconnects the realtime listener and cleans up resources.
190
+ */
191
+ destroy(): void;
192
+ private isStale;
193
+ private refetchFlag;
194
+ private syncFlags;
195
+ private evaluateWithUser;
196
+ private validateConfig;
197
+ private setupPolling;
198
+ private setupRealtimeListener;
199
+ }
200
+
201
+ /**
202
+ * Represents a feature flag within the Flagify system.
203
+ */
204
+ interface FlagifyFlaggy {
205
+ /**
206
+ * Unique identifier for the flag (e.g., "new-dashboard").
207
+ */
208
+ key: string;
209
+ /**
210
+ * Human-readable name of the flag.
211
+ */
212
+ name: string;
213
+ /**
214
+ * The current value of the flag, which can be a boolean, string, number, or JSON object.
215
+ * This is the value that will be returned when the flag is evaluated.
216
+ */
217
+ value: boolean | string | number | Record<string, unknown>;
218
+ /**
219
+ * Detailed description of the flag's purpose.
220
+ */
221
+ description?: string;
222
+ /**
223
+ * Data type of the flag's value (e.g., "boolean", "string", "number", "json").
224
+ */
225
+ type: 'boolean' | 'string' | 'number' | 'json';
226
+ /**
227
+ * Default value returned when the flag is enabled but no targeting rules match.
228
+ */
229
+ defaultValue: boolean | string | number | Record<string, unknown>;
230
+ /**
231
+ * Value returned when the flag is disabled (enabled = false).
232
+ */
233
+ offValue: boolean | string | number | Record<string, unknown>;
234
+ /**
235
+ * Indicates whether the flag is currently active.
236
+ */
237
+ enabled: boolean;
238
+ /**
239
+ * Optional rollout percentage (0 to 100) for gradual feature releases.
240
+ */
241
+ rolloutPercentage?: number;
242
+ /**
243
+ * Optional targeting rules for user segmentation.
244
+ */
245
+ targetingRules?: Array<{
246
+ priority: number;
247
+ segmentId?: string;
248
+ valueOverride?: unknown;
249
+ rolloutPercentage?: number;
250
+ enabled: boolean;
251
+ conditions?: Array<{
252
+ attribute: string;
253
+ operator: 'equals' | 'not_equals' | 'contains' | 'not_contains' | 'starts_with' | 'ends_with' | 'in' | 'not_in' | 'gt' | 'lt';
254
+ value: unknown;
255
+ }>;
256
+ }>;
257
+ /**
258
+ * Optional multivariate variants for A/B testing.
259
+ */
260
+ variants?: Array<{
261
+ key: string;
262
+ value: boolean | string | number | Record<string, unknown>;
263
+ weight: number;
264
+ }>;
265
+ /**
266
+ * Timestamp of when the flag was created.
267
+ */
268
+ createdAt: string;
269
+ /**
270
+ * Timestamp of the last update to the flag.
271
+ */
272
+ updatedAt: string;
273
+ }
274
+
275
+ export { type EvaluateResult, type FlagChangeEvent, Flagify, type FlagifyFlaggy, type FlagifyOptions, type FlagifyUser, type IFlagifyClient, type RealtimeEvents, RealtimeListener };