anmo 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 +243 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +224 -0
- package/dist/react.d.ts +18 -0
- package/dist/react.js +65 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Anmo SDK
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for Anmo Feature Flags - a modern, real-time feature flag management service.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install anmo
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { AnmoClient } from "anmo";
|
|
15
|
+
|
|
16
|
+
const client = new AnmoClient({
|
|
17
|
+
apiKey: "anmo_your_api_key_here",
|
|
18
|
+
autoFetch: true, // Automatically fetch flags on init
|
|
19
|
+
autoStream: true, // Automatically start streaming updates
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Wait for initial flags to load
|
|
23
|
+
await client.ready();
|
|
24
|
+
|
|
25
|
+
// Check if a flag is enabled
|
|
26
|
+
if (client.isEnabled("new-dashboard")) {
|
|
27
|
+
console.log("New dashboard is enabled!");
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- 🚀 **Real-time Updates** - Automatically sync flag changes via Server-Sent Events
|
|
34
|
+
- 🔒 **Type-safe** - Full TypeScript support with type definitions
|
|
35
|
+
- 📦 **Lightweight** - Minimal dependencies
|
|
36
|
+
- 🎯 **Simple API** - Easy to use, hard to misuse
|
|
37
|
+
- ⚡ **Fast** - Built for performance
|
|
38
|
+
- ⚛️ **React Hooks** - First-class React support with custom hooks
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install anmo
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For React applications, React 16.8+ is required (for hooks support).
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### Basic Example
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { AnmoClient } from "anmo";
|
|
54
|
+
|
|
55
|
+
const client = new AnmoClient({
|
|
56
|
+
apiKey: "anmo_your_api_key_here",
|
|
57
|
+
baseUrl: "https://your-anmo-instance.com",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Wait for initial flags to load
|
|
61
|
+
await client.ready();
|
|
62
|
+
|
|
63
|
+
// Check individual flag
|
|
64
|
+
if (client.isEnabled("new-dashboard")) {
|
|
65
|
+
console.log("New dashboard is enabled!");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Get a specific flag with a default value
|
|
69
|
+
const darkMode = client.getFlag("dark-mode", false);
|
|
70
|
+
|
|
71
|
+
// Get all flags
|
|
72
|
+
const allFlags = client.getAllFlags();
|
|
73
|
+
console.log("All flags:", allFlags);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Real-time Updates
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
const client = new AnmoClient({
|
|
80
|
+
apiKey: "anmo_your_api_key_here",
|
|
81
|
+
baseUrl: "https://your-anmo-instance.com",
|
|
82
|
+
autoStream: true, // Start streaming immediately
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Listen for flag updates
|
|
86
|
+
client.onUpdate((flags) => {
|
|
87
|
+
console.log("Flags updated:", flags);
|
|
88
|
+
|
|
89
|
+
// React to changes
|
|
90
|
+
if (flags["maintenance-mode"]) {
|
|
91
|
+
showMaintenanceBanner();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Clean up when done
|
|
96
|
+
process.on("SIGINT", () => {
|
|
97
|
+
client.destroy();
|
|
98
|
+
process.exit();
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Manual Streaming Control
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const client = new AnmoClient({
|
|
106
|
+
apiKey: "anmo_your_api_key_here",
|
|
107
|
+
baseUrl: "https://your-anmo-instance.com",
|
|
108
|
+
autoStream: false, // Don't auto-start
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Start streaming manually
|
|
112
|
+
client.startStream();
|
|
113
|
+
|
|
114
|
+
// Stop streaming when needed
|
|
115
|
+
client.stopStream();
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### React Usage
|
|
119
|
+
|
|
120
|
+
The SDK provides React hooks for easy integration:
|
|
121
|
+
|
|
122
|
+
#### Basic Hook
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { useFeatureFlag } from "anmo/react";
|
|
126
|
+
|
|
127
|
+
function App() {
|
|
128
|
+
const showNewDashboard = useFeatureFlag("new-dashboard", {
|
|
129
|
+
apiKey: process.env.REACT_APP_ANMO_API_KEY!,
|
|
130
|
+
});
|
|
131
|
+
const useDarkMode = useFeatureFlag("use-dark-mode", {
|
|
132
|
+
apiKey: process.env.REACT_APP_ANMO_API_KEY!,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (loading) return <div>Loading...</div>;
|
|
136
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div>
|
|
140
|
+
{showNewDashboard && <NewDashboard />}
|
|
141
|
+
{useDarkMode && <DarkModeToggle />}
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## React API Reference
|
|
148
|
+
|
|
149
|
+
### `useFeatureFlag(flagKey, options, defaultValue?)`
|
|
150
|
+
|
|
151
|
+
Hook for a single feature flag. Returns boolean indicating if the flag is enabled.
|
|
152
|
+
|
|
153
|
+
## Node.js API Reference
|
|
154
|
+
|
|
155
|
+
### Constructor Options
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
interface AnmoClientOptions {
|
|
159
|
+
apiKey: string; // Required: Your Anmo API key
|
|
160
|
+
baseUrl?: string; // Optional: Base URL (default: 'http://localhost:3000')
|
|
161
|
+
autoFetch?: boolean; // Optional: Auto-fetch on init (default: true)
|
|
162
|
+
autoStream?: boolean; // Optional: Auto-start streaming (default: false)
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Methods
|
|
167
|
+
|
|
168
|
+
#### `ready(): Promise<void>`
|
|
169
|
+
|
|
170
|
+
Wait for the client to be ready (initial flags fetched).
|
|
171
|
+
|
|
172
|
+
#### `getFlags(): Promise<FlagMap>`
|
|
173
|
+
|
|
174
|
+
Manually fetch all feature flags. Usually not needed if `autoFetch` is enabled.
|
|
175
|
+
|
|
176
|
+
#### `isEnabled(key: string, defaultValue?: boolean): boolean`
|
|
177
|
+
|
|
178
|
+
Check if a feature flag is enabled. Returns `defaultValue` (default: `false`) if flag doesn't exist.
|
|
179
|
+
|
|
180
|
+
#### `getFlag(key: string, defaultValue?: boolean): boolean`
|
|
181
|
+
|
|
182
|
+
Alias for `isEnabled()`.
|
|
183
|
+
|
|
184
|
+
#### `getAllFlags(): FlagMap`
|
|
185
|
+
|
|
186
|
+
Get a copy of all current flags as an object.
|
|
187
|
+
|
|
188
|
+
#### `onUpdate(listener: (flags: FlagMap) => void): () => void`
|
|
189
|
+
|
|
190
|
+
Subscribe to flag updates. Returns an unsubscribe function.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
const unsubscribe = client.onUpdate((flags) => {
|
|
194
|
+
console.log("Updated flags:", flags);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Later, to unsubscribe:
|
|
198
|
+
unsubscribe();
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### `startStream(): void`
|
|
202
|
+
|
|
203
|
+
Start streaming real-time updates via Server-Sent Events.
|
|
204
|
+
|
|
205
|
+
#### `stopStream(): void`
|
|
206
|
+
|
|
207
|
+
Stop streaming updates and close the connection.
|
|
208
|
+
|
|
209
|
+
#### `destroy(): void`
|
|
210
|
+
|
|
211
|
+
Clean up all resources, close connections, and remove all listeners.
|
|
212
|
+
|
|
213
|
+
## Publishing to npm
|
|
214
|
+
|
|
215
|
+
To publish this package to npm:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
cd sdks/javascript
|
|
219
|
+
npm install
|
|
220
|
+
npm run build
|
|
221
|
+
npm publish --access public
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Development
|
|
225
|
+
|
|
226
|
+
To work on the SDK locally:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
cd sdks/javascript
|
|
230
|
+
npm install
|
|
231
|
+
npm run build
|
|
232
|
+
npm link
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Then in your project:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
npm link anmo
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type representing a map of feature flag keys to their enabled state
|
|
3
|
+
*/
|
|
4
|
+
export type FlagMap = Record<string, boolean>;
|
|
5
|
+
/**
|
|
6
|
+
* Configuration options for the Anmo client
|
|
7
|
+
*/
|
|
8
|
+
export interface AnmoClientOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Your Anmo API key (starts with 'anmo_')
|
|
11
|
+
*/
|
|
12
|
+
apiKey: string;
|
|
13
|
+
/**
|
|
14
|
+
* Base URL of your Anmo instance
|
|
15
|
+
* @default 'https://anmo.io'
|
|
16
|
+
*/
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Whether to automatically fetch flags on initialization
|
|
20
|
+
* @default true
|
|
21
|
+
*/
|
|
22
|
+
autoFetch?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Whether to automatically start streaming updates
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
autoStream?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Anmo Feature Flag Client
|
|
31
|
+
*
|
|
32
|
+
* This client provides methods to interact with the Anmo feature flag service,
|
|
33
|
+
* including fetching flags, checking if flags are enabled, and subscribing to
|
|
34
|
+
* real-time updates via Server-Sent Events (SSE).
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const client = new AnmoClient({
|
|
39
|
+
* apiKey: 'anmo_your_api_key_here',
|
|
40
|
+
* baseUrl: 'https://your-anmo-instance.com',
|
|
41
|
+
* autoFetch: true,
|
|
42
|
+
* autoStream: true
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* // Wait for initial flags to load
|
|
46
|
+
* await client.ready();
|
|
47
|
+
*
|
|
48
|
+
* // Check if a flag is enabled
|
|
49
|
+
* if (client.isEnabled('new-dashboard')) {
|
|
50
|
+
* console.log('New dashboard is enabled!');
|
|
51
|
+
* }
|
|
52
|
+
*
|
|
53
|
+
* // Listen for flag updates
|
|
54
|
+
* client.onUpdate((flags) => {
|
|
55
|
+
* console.log('Flags updated:', flags);
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare class AnmoClient {
|
|
60
|
+
private apiKey;
|
|
61
|
+
private baseUrl;
|
|
62
|
+
private flags;
|
|
63
|
+
private eventSource;
|
|
64
|
+
private updateListeners;
|
|
65
|
+
private readyPromise;
|
|
66
|
+
private readyResolve;
|
|
67
|
+
private isReady;
|
|
68
|
+
constructor(options: AnmoClientOptions);
|
|
69
|
+
/**
|
|
70
|
+
* Wait for the client to be ready (initial flags fetched)
|
|
71
|
+
* @returns Promise that resolves when flags are ready
|
|
72
|
+
*/
|
|
73
|
+
ready(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Fetch all feature flags for the environment
|
|
76
|
+
* @returns Promise resolving to the flag map
|
|
77
|
+
*/
|
|
78
|
+
getFlags(): Promise<FlagMap>;
|
|
79
|
+
/**
|
|
80
|
+
* Check if a feature flag is enabled
|
|
81
|
+
* @param key - The flag key to check
|
|
82
|
+
* @param defaultValue - Default value if flag is not found (default: false)
|
|
83
|
+
* @returns Whether the flag is enabled
|
|
84
|
+
*/
|
|
85
|
+
isEnabled(key: string, defaultValue?: boolean): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Get the value of a specific flag
|
|
88
|
+
* @param key - The flag key
|
|
89
|
+
* @param defaultValue - Default value if flag is not found
|
|
90
|
+
* @returns The flag value
|
|
91
|
+
*/
|
|
92
|
+
getFlag(key: string, defaultValue?: boolean): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Get all current flags
|
|
95
|
+
* @returns A copy of all flags
|
|
96
|
+
*/
|
|
97
|
+
getAllFlags(): FlagMap;
|
|
98
|
+
/**
|
|
99
|
+
* Subscribe to flag updates
|
|
100
|
+
* @param listener - Callback function to call when flags update
|
|
101
|
+
* @returns Unsubscribe function
|
|
102
|
+
*/
|
|
103
|
+
onUpdate(listener: (flags: FlagMap) => void): () => void;
|
|
104
|
+
/**
|
|
105
|
+
* Start streaming real-time updates via Server-Sent Events
|
|
106
|
+
*/
|
|
107
|
+
startStream(): void;
|
|
108
|
+
/**
|
|
109
|
+
* Stop streaming updates
|
|
110
|
+
*/
|
|
111
|
+
stopStream(): void;
|
|
112
|
+
/**
|
|
113
|
+
* Notify all listeners of flag updates
|
|
114
|
+
*/
|
|
115
|
+
private notifyListeners;
|
|
116
|
+
/**
|
|
117
|
+
* Clean up resources and close connections
|
|
118
|
+
*/
|
|
119
|
+
destroy(): void;
|
|
120
|
+
}
|
|
121
|
+
export default AnmoClient;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AnmoClient = void 0;
|
|
7
|
+
const eventsource_1 = __importDefault(require("eventsource"));
|
|
8
|
+
/**
|
|
9
|
+
* Anmo Feature Flag Client
|
|
10
|
+
*
|
|
11
|
+
* This client provides methods to interact with the Anmo feature flag service,
|
|
12
|
+
* including fetching flags, checking if flags are enabled, and subscribing to
|
|
13
|
+
* real-time updates via Server-Sent Events (SSE).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const client = new AnmoClient({
|
|
18
|
+
* apiKey: 'anmo_your_api_key_here',
|
|
19
|
+
* baseUrl: 'https://your-anmo-instance.com',
|
|
20
|
+
* autoFetch: true,
|
|
21
|
+
* autoStream: true
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Wait for initial flags to load
|
|
25
|
+
* await client.ready();
|
|
26
|
+
*
|
|
27
|
+
* // Check if a flag is enabled
|
|
28
|
+
* if (client.isEnabled('new-dashboard')) {
|
|
29
|
+
* console.log('New dashboard is enabled!');
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* // Listen for flag updates
|
|
33
|
+
* client.onUpdate((flags) => {
|
|
34
|
+
* console.log('Flags updated:', flags);
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
class AnmoClient {
|
|
39
|
+
constructor(options) {
|
|
40
|
+
this.flags = {};
|
|
41
|
+
this.eventSource = null;
|
|
42
|
+
this.updateListeners = [];
|
|
43
|
+
this.readyPromise = null;
|
|
44
|
+
this.readyResolve = null;
|
|
45
|
+
this.isReady = false;
|
|
46
|
+
if (!options.apiKey) {
|
|
47
|
+
throw new Error("API key is required");
|
|
48
|
+
}
|
|
49
|
+
if (!options.apiKey.startsWith("anmo_")) {
|
|
50
|
+
console.warn('API key should start with "anmo_"');
|
|
51
|
+
}
|
|
52
|
+
this.apiKey = options.apiKey;
|
|
53
|
+
this.baseUrl = options.baseUrl || "https://anmo.io";
|
|
54
|
+
// Create ready promise
|
|
55
|
+
this.readyPromise = new Promise((resolve) => {
|
|
56
|
+
this.readyResolve = resolve;
|
|
57
|
+
});
|
|
58
|
+
// Auto-fetch flags if enabled
|
|
59
|
+
if (options.autoFetch !== false) {
|
|
60
|
+
this.getFlags().catch((error) => {
|
|
61
|
+
console.error("Failed to fetch initial flags:", error);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
// Auto-start streaming if enabled
|
|
65
|
+
if (options.autoStream === true) {
|
|
66
|
+
this.startStream();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Wait for the client to be ready (initial flags fetched)
|
|
71
|
+
* @returns Promise that resolves when flags are ready
|
|
72
|
+
*/
|
|
73
|
+
async ready() {
|
|
74
|
+
if (this.isReady)
|
|
75
|
+
return;
|
|
76
|
+
return this.readyPromise;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Fetch all feature flags for the environment
|
|
80
|
+
* @returns Promise resolving to the flag map
|
|
81
|
+
*/
|
|
82
|
+
async getFlags() {
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch(`${this.baseUrl}/api/client/flags`, {
|
|
85
|
+
headers: {
|
|
86
|
+
"x-api-key": this.apiKey,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
const error = (await response
|
|
91
|
+
.json()
|
|
92
|
+
.catch(() => ({ error: "Unknown error" })));
|
|
93
|
+
throw new Error(`Failed to fetch flags: ${response.statusText} - ${error.error || ""}`);
|
|
94
|
+
}
|
|
95
|
+
const data = (await response.json());
|
|
96
|
+
this.flags = data.flags;
|
|
97
|
+
if (!this.isReady) {
|
|
98
|
+
this.isReady = true;
|
|
99
|
+
this.readyResolve?.();
|
|
100
|
+
}
|
|
101
|
+
return this.flags;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error("Error fetching flags:", error);
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if a feature flag is enabled
|
|
110
|
+
* @param key - The flag key to check
|
|
111
|
+
* @param defaultValue - Default value if flag is not found (default: false)
|
|
112
|
+
* @returns Whether the flag is enabled
|
|
113
|
+
*/
|
|
114
|
+
isEnabled(key, defaultValue = false) {
|
|
115
|
+
if (!(key in this.flags)) {
|
|
116
|
+
return defaultValue;
|
|
117
|
+
}
|
|
118
|
+
return this.flags[key] === true;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get the value of a specific flag
|
|
122
|
+
* @param key - The flag key
|
|
123
|
+
* @param defaultValue - Default value if flag is not found
|
|
124
|
+
* @returns The flag value
|
|
125
|
+
*/
|
|
126
|
+
getFlag(key, defaultValue = false) {
|
|
127
|
+
return this.isEnabled(key, defaultValue);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get all current flags
|
|
131
|
+
* @returns A copy of all flags
|
|
132
|
+
*/
|
|
133
|
+
getAllFlags() {
|
|
134
|
+
return { ...this.flags };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Subscribe to flag updates
|
|
138
|
+
* @param listener - Callback function to call when flags update
|
|
139
|
+
* @returns Unsubscribe function
|
|
140
|
+
*/
|
|
141
|
+
onUpdate(listener) {
|
|
142
|
+
this.updateListeners.push(listener);
|
|
143
|
+
// Return unsubscribe function
|
|
144
|
+
return () => {
|
|
145
|
+
const index = this.updateListeners.indexOf(listener);
|
|
146
|
+
if (index > -1) {
|
|
147
|
+
this.updateListeners.splice(index, 1);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Start streaming real-time updates via Server-Sent Events
|
|
153
|
+
*/
|
|
154
|
+
startStream() {
|
|
155
|
+
if (this.eventSource) {
|
|
156
|
+
console.warn("Stream already started");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const url = `${this.baseUrl}/api/client/stream`;
|
|
160
|
+
this.eventSource = new eventsource_1.default(url, {
|
|
161
|
+
headers: {
|
|
162
|
+
"x-api-key": this.apiKey,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
this.eventSource.onmessage = (event) => {
|
|
166
|
+
try {
|
|
167
|
+
const data = JSON.parse(event.data);
|
|
168
|
+
if (data.type === "initial" || data.type === "update") {
|
|
169
|
+
this.flags = data.flags;
|
|
170
|
+
if (!this.isReady) {
|
|
171
|
+
this.isReady = true;
|
|
172
|
+
this.readyResolve?.();
|
|
173
|
+
}
|
|
174
|
+
this.notifyListeners();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
console.error("Error parsing SSE message:", error);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
this.eventSource.onerror = (error) => {
|
|
182
|
+
console.error("SSE error:", error);
|
|
183
|
+
this.stopStream();
|
|
184
|
+
// Attempt to reconnect after 5 seconds
|
|
185
|
+
setTimeout(() => {
|
|
186
|
+
console.log("Attempting to reconnect...");
|
|
187
|
+
this.startStream();
|
|
188
|
+
}, 5000);
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Stop streaming updates
|
|
193
|
+
*/
|
|
194
|
+
stopStream() {
|
|
195
|
+
if (this.eventSource) {
|
|
196
|
+
this.eventSource.close();
|
|
197
|
+
this.eventSource = null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Notify all listeners of flag updates
|
|
202
|
+
*/
|
|
203
|
+
notifyListeners() {
|
|
204
|
+
const currentFlags = this.getAllFlags();
|
|
205
|
+
this.updateListeners.forEach((listener) => {
|
|
206
|
+
try {
|
|
207
|
+
listener(currentFlags);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
console.error("Error in update listener:", error);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Clean up resources and close connections
|
|
216
|
+
*/
|
|
217
|
+
destroy() {
|
|
218
|
+
this.stopStream();
|
|
219
|
+
this.updateListeners = [];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
exports.AnmoClient = AnmoClient;
|
|
223
|
+
// Re-export for convenience
|
|
224
|
+
exports.default = AnmoClient;
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FlagMap, AnmoClientOptions } from "./index";
|
|
2
|
+
export type { FlagMap, AnmoClientOptions };
|
|
3
|
+
/**
|
|
4
|
+
* React hook for a single feature flag with real-time updates
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* function MyComponent() {
|
|
9
|
+
* const isNewDashboard = useFeatureFlag('new-dashboard', {
|
|
10
|
+
* apiKey: 'anmo_your_api_key',
|
|
11
|
+
* baseUrl: 'https://anmo.io'
|
|
12
|
+
* }, false);
|
|
13
|
+
*
|
|
14
|
+
* return isNewDashboard ? <NewDashboard /> : <OldDashboard />;
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function useFeatureFlag(flagKey: string, options: AnmoClientOptions, defaultValue?: boolean): boolean;
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useFeatureFlag = useFeatureFlag;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const index_1 = require("./index");
|
|
6
|
+
/**
|
|
7
|
+
* React hook for a single feature flag with real-time updates
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* function MyComponent() {
|
|
12
|
+
* const isNewDashboard = useFeatureFlag('new-dashboard', {
|
|
13
|
+
* apiKey: 'anmo_your_api_key',
|
|
14
|
+
* baseUrl: 'https://anmo.io'
|
|
15
|
+
* }, false);
|
|
16
|
+
*
|
|
17
|
+
* return isNewDashboard ? <NewDashboard /> : <OldDashboard />;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function useFeatureFlag(flagKey, options, defaultValue = false) {
|
|
22
|
+
const [value, setValue] = (0, react_1.useState)(defaultValue);
|
|
23
|
+
// Create client instance (memoized by API key and URL)
|
|
24
|
+
const client = (0, react_1.useMemo)(() => {
|
|
25
|
+
return new index_1.AnmoClient({
|
|
26
|
+
...options,
|
|
27
|
+
autoFetch: false,
|
|
28
|
+
autoStream: false,
|
|
29
|
+
});
|
|
30
|
+
}, [options.apiKey, options.baseUrl]);
|
|
31
|
+
(0, react_1.useEffect)(() => {
|
|
32
|
+
let mounted = true;
|
|
33
|
+
// Fetch initial flags
|
|
34
|
+
const fetchFlags = async () => {
|
|
35
|
+
try {
|
|
36
|
+
const flags = await client.getFlags();
|
|
37
|
+
if (mounted) {
|
|
38
|
+
setValue(flags[flagKey] ?? defaultValue);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
console.error("Failed to fetch flags:", err);
|
|
43
|
+
if (mounted) {
|
|
44
|
+
setValue(defaultValue);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
fetchFlags();
|
|
49
|
+
// Subscribe to updates
|
|
50
|
+
const unsubscribe = client.onUpdate((updatedFlags) => {
|
|
51
|
+
if (mounted) {
|
|
52
|
+
setValue(updatedFlags[flagKey] ?? defaultValue);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
// Start streaming
|
|
56
|
+
client.startStream();
|
|
57
|
+
// Cleanup
|
|
58
|
+
return () => {
|
|
59
|
+
mounted = false;
|
|
60
|
+
unsubscribe();
|
|
61
|
+
client.destroy();
|
|
62
|
+
};
|
|
63
|
+
}, [client, flagKey, defaultValue]);
|
|
64
|
+
return value;
|
|
65
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "anmo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official Node.js SDK for Anmo Feature Flags",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./react": {
|
|
13
|
+
"types": "./dist/react.d.ts",
|
|
14
|
+
"default": "./dist/react.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"prepublishOnly": "npm run build",
|
|
24
|
+
"clean": "rm -rf dist",
|
|
25
|
+
"test": "npm run build && node test.js"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"feature-flags",
|
|
29
|
+
"feature-toggles",
|
|
30
|
+
"anmo",
|
|
31
|
+
"sdk",
|
|
32
|
+
"nodejs"
|
|
33
|
+
],
|
|
34
|
+
"author": "",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"eventsource": "^2.0.2"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": ">=16.8.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"react": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/eventsource": "^1.1.15",
|
|
49
|
+
"@types/node": "^22.0.0",
|
|
50
|
+
"@types/react": "^18.0.0",
|
|
51
|
+
"typescript": "^5.0.0"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18.0.0"
|
|
55
|
+
},
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "https://github.com/AndrewVos/anmo"
|
|
59
|
+
}
|
|
60
|
+
}
|