chub-dev 0.1.0 → 0.1.2-beta.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 +55 -0
- package/bin/chub-mcp +2 -0
- package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
- package/dist/airtable/docs/database/python/DOC.md +1735 -0
- package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
- package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
- package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
- package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
- package/dist/asana/docs/tasks/DOC.md +1396 -0
- package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
- package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
- package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
- package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
- package/dist/auth0/docs/identity/python/DOC.md +1199 -0
- package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
- package/dist/aws/docs/s3/python/DOC.md +1807 -0
- package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
- package/dist/binance/docs/trading/python/DOC.md +1454 -0
- package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
- package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
- package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
- package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
- package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
- package/dist/clerk/docs/auth/python/DOC.md +274 -0
- package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
- package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
- package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
- package/dist/cohere/docs/llm/DOC.md +1335 -0
- package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
- package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
- package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
- package/dist/deepgram/docs/speech/python/DOC.md +685 -0
- package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
- package/dist/deepl/docs/translation/python/DOC.md +944 -0
- package/dist/deepseek/docs/llm/DOC.md +1220 -0
- package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
- package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
- package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
- package/dist/discord/docs/bot/python/DOC.md +1130 -0
- package/dist/elasticsearch/docs/search/DOC.md +1634 -0
- package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
- package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
- package/dist/firebase/docs/auth/DOC.md +1015 -0
- package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
- package/dist/gemini/docs/genai/python/DOC.md +555 -0
- package/dist/github/docs/octokit/DOC.md +1560 -0
- package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
- package/dist/google/docs/bigquery/python/DOC.md +1503 -0
- package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
- package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
- package/dist/huggingface/docs/transformers/DOC.md +948 -0
- package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
- package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
- package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
- package/dist/jira/docs/issues/python/DOC.md +1492 -0
- package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
- package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
- package/dist/landingai-ade/docs/api/DOC.md +620 -0
- package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
- package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
- package/dist/landingai-ade/skills/SKILL.md +489 -0
- package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
- package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
- package/dist/linear/docs/tracker/DOC.md +1554 -0
- package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
- package/dist/livekit/docs/realtime/python/DOC.md +163 -0
- package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
- package/dist/meilisearch/docs/search/DOC.md +1241 -0
- package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
- package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
- package/dist/mongodb/docs/atlas/DOC.md +2041 -0
- package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
- package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
- package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
- package/dist/okta/docs/identity/python/DOC.md +1401 -0
- package/dist/openai/docs/chat/javascript/DOC.md +407 -0
- package/dist/openai/docs/chat/python/DOC.md +568 -0
- package/dist/paypal/docs/checkout/DOC.md +278 -0
- package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
- package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
- package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
- package/dist/plaid/docs/banking/python/DOC.md +1203 -0
- package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
- package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
- package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
- package/dist/prisma/docs/orm/python/DOC.md +1317 -0
- package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
- package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
- package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
- package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
- package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
- package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
- package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
- package/dist/redis/docs/key-value/python/DOC.md +2054 -0
- package/dist/registry.json +2817 -0
- package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
- package/dist/resend/docs/email/DOC.md +1271 -0
- package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
- package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
- package/dist/search-index.json +1 -0
- package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
- package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
- package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
- package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
- package/dist/shopify/docs/storefront/DOC.md +457 -0
- package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
- package/dist/slack/docs/workspace/python/DOC.md +271 -0
- package/dist/square/docs/payments/javascript/DOC.md +1855 -0
- package/dist/square/docs/payments/python/DOC.md +1728 -0
- package/dist/stripe/docs/api/DOC.md +1727 -0
- package/dist/stripe/docs/payments/DOC.md +1726 -0
- package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
- package/dist/stytch/docs/auth/python/DOC.md +1962 -0
- package/dist/supabase/docs/client/DOC.md +1606 -0
- package/dist/twilio/docs/messaging/python/DOC.md +469 -0
- package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
- package/dist/vercel/docs/platform/DOC.md +1940 -0
- package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
- package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
- package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
- package/dist/zendesk/docs/support/python/DOC.md +2297 -0
- package/package.json +22 -6
- package/skills/get-api-docs/SKILL.md +84 -0
- package/src/commands/annotate.js +83 -0
- package/src/commands/build.js +12 -1
- package/src/commands/feedback.js +150 -0
- package/src/commands/get.js +83 -42
- package/src/commands/search.js +7 -0
- package/src/index.js +43 -17
- package/src/lib/analytics.js +90 -0
- package/src/lib/annotations.js +57 -0
- package/src/lib/bm25.js +170 -0
- package/src/lib/cache.js +69 -6
- package/src/lib/config.js +8 -3
- package/src/lib/identity.js +99 -0
- package/src/lib/registry.js +103 -20
- package/src/lib/telemetry.js +86 -0
- package/src/mcp/server.js +177 -0
- package/src/mcp/tools.js +251 -0
|
@@ -0,0 +1,1191 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: feature-flags
|
|
3
|
+
description: "LaunchDarkly Node.js Server SDK for feature flag management and experimentation"
|
|
4
|
+
metadata:
|
|
5
|
+
languages: "javascript"
|
|
6
|
+
versions: "9.10.2"
|
|
7
|
+
updated-on: "2026-03-02"
|
|
8
|
+
source: maintainer
|
|
9
|
+
tags: "launchdarkly,feature-flags,toggles,experimentation,rollout"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# LaunchDarkly Node.js Server SDK
|
|
13
|
+
|
|
14
|
+
## Golden Rule
|
|
15
|
+
|
|
16
|
+
**Always use `@launchdarkly/node-server-sdk` for server-side Node.js applications.**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @launchdarkly/node-server-sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Do NOT use:
|
|
23
|
+
- `launchdarkly-node-server-sdk` (deprecated, last updated 2021)
|
|
24
|
+
- `launchdarkly-node-client-sdk` (client-side only, different use case)
|
|
25
|
+
- Any unofficial or third-party LaunchDarkly packages
|
|
26
|
+
|
|
27
|
+
The official package is `@launchdarkly/node-server-sdk` maintained by LaunchDarkly.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @launchdarkly/node-server-sdk
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Optional observability plugin (requires v9.10+):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @launchdarkly/observability-node
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Environment Variables
|
|
46
|
+
|
|
47
|
+
Set your SDK key as an environment variable:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
export LAUNCHDARKLY_SDK_KEY="sdk-key-123abc"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Initialization
|
|
56
|
+
|
|
57
|
+
### Basic Initialization
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
import { init } from '@launchdarkly/node-server-sdk';
|
|
61
|
+
|
|
62
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY);
|
|
63
|
+
|
|
64
|
+
// Wait for initialization before evaluating flags
|
|
65
|
+
await client.waitForInitialization({ timeout: 10 });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### With Configuration Options
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
import { init } from '@launchdarkly/node-server-sdk';
|
|
72
|
+
|
|
73
|
+
const options = {
|
|
74
|
+
timeout: 10,
|
|
75
|
+
capacity: 1000,
|
|
76
|
+
flushInterval: 30,
|
|
77
|
+
stream: true,
|
|
78
|
+
allAttributesPrivate: false,
|
|
79
|
+
privateAttributes: ['email', 'ssn'],
|
|
80
|
+
offline: false,
|
|
81
|
+
diagnosticOptOut: false,
|
|
82
|
+
wrapperName: 'my-wrapper',
|
|
83
|
+
wrapperVersion: '1.0.0'
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, options);
|
|
87
|
+
await client.waitForInitialization({ timeout: 10 });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### With Observability Plugin
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
import { init } from '@launchdarkly/node-server-sdk';
|
|
94
|
+
import { Observability } from '@launchdarkly/observability-node';
|
|
95
|
+
|
|
96
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
97
|
+
plugins: [new Observability()]
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await client.waitForInitialization({ timeout: 10 });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Singleton Pattern
|
|
104
|
+
|
|
105
|
+
**Critical:** Make the LDClient a singleton. Do NOT create multiple instances per request.
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
// app.js or server initialization
|
|
109
|
+
import { init } from '@launchdarkly/node-server-sdk';
|
|
110
|
+
|
|
111
|
+
let ldClient;
|
|
112
|
+
|
|
113
|
+
async function initializeLaunchDarkly() {
|
|
114
|
+
ldClient = init(process.env.LAUNCHDARKLY_SDK_KEY);
|
|
115
|
+
await ldClient.waitForInitialization({ timeout: 10 });
|
|
116
|
+
return ldClient;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getLDClient() {
|
|
120
|
+
if (!ldClient) {
|
|
121
|
+
throw new Error('LaunchDarkly client not initialized');
|
|
122
|
+
}
|
|
123
|
+
return ldClient;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Initialize once at app startup
|
|
127
|
+
await initializeLaunchDarkly();
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Error Handling During Initialization
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
import { init } from '@launchdarkly/node-server-sdk';
|
|
134
|
+
|
|
135
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY);
|
|
136
|
+
|
|
137
|
+
client.once('ready', () => {
|
|
138
|
+
console.log('LaunchDarkly client initialized successfully');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
client.once('failed', () => {
|
|
142
|
+
console.error('LaunchDarkly client failed to initialize');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
await client.waitForInitialization({ timeout: 10 });
|
|
147
|
+
console.log('Client ready');
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('Initialization timeout:', error);
|
|
150
|
+
// Client will still work but may return default values
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Contexts
|
|
157
|
+
|
|
158
|
+
### Simple User Context
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
const context = {
|
|
162
|
+
kind: 'user',
|
|
163
|
+
key: 'user-key-123abc',
|
|
164
|
+
name: 'Sandy Smith',
|
|
165
|
+
email: 'sandy@example.com'
|
|
166
|
+
};
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Context with Custom Attributes
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
const context = {
|
|
173
|
+
kind: 'user',
|
|
174
|
+
key: 'user-key-123abc',
|
|
175
|
+
name: 'Sandy Smith',
|
|
176
|
+
email: 'sandy@example.com',
|
|
177
|
+
plan: 'premium',
|
|
178
|
+
betaTester: true,
|
|
179
|
+
customAttribute: 'custom-value'
|
|
180
|
+
};
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Anonymous Context
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
const context = {
|
|
187
|
+
kind: 'user',
|
|
188
|
+
key: 'anonymous-user-123',
|
|
189
|
+
anonymous: true
|
|
190
|
+
};
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Multi-Context (Multiple Kinds)
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
const multiContext = {
|
|
197
|
+
kind: 'multi',
|
|
198
|
+
user: {
|
|
199
|
+
key: 'user-key-123',
|
|
200
|
+
name: 'Sandy'
|
|
201
|
+
},
|
|
202
|
+
organization: {
|
|
203
|
+
key: 'org-key-456',
|
|
204
|
+
name: 'Acme Corp'
|
|
205
|
+
},
|
|
206
|
+
device: {
|
|
207
|
+
key: 'device-key-789',
|
|
208
|
+
platform: 'iOS'
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Private Attributes
|
|
214
|
+
|
|
215
|
+
Mark specific attributes as private (not sent to LaunchDarkly):
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
const context = {
|
|
219
|
+
kind: 'user',
|
|
220
|
+
key: 'user-key-123abc',
|
|
221
|
+
name: 'Sandy Smith',
|
|
222
|
+
email: 'sandy@example.com',
|
|
223
|
+
ssn: '123-45-6789',
|
|
224
|
+
_meta: {
|
|
225
|
+
privateAttributes: ['email', 'ssn']
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Flag Evaluation
|
|
233
|
+
|
|
234
|
+
### Boolean Flag
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
const showFeature = await client.variation('flag-key-123abc', context, false);
|
|
238
|
+
|
|
239
|
+
if (showFeature) {
|
|
240
|
+
// Feature enabled
|
|
241
|
+
} else {
|
|
242
|
+
// Feature disabled
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### String Flag
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
const theme = await client.variation('theme-flag', context, 'light');
|
|
250
|
+
|
|
251
|
+
console.log(`User theme: ${theme}`); // 'dark' or 'light'
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Number Flag
|
|
255
|
+
|
|
256
|
+
```javascript
|
|
257
|
+
const maxItems = await client.variation('max-items', context, 10);
|
|
258
|
+
|
|
259
|
+
console.log(`Max items: ${maxItems}`); // e.g., 50
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### JSON Flag
|
|
263
|
+
|
|
264
|
+
```javascript
|
|
265
|
+
const config = await client.variation('config-flag', context, {
|
|
266
|
+
timeout: 30,
|
|
267
|
+
retries: 3
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
console.log(`Timeout: ${config.timeout}, Retries: ${config.retries}`);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Callback Style (Legacy)
|
|
274
|
+
|
|
275
|
+
```javascript
|
|
276
|
+
client.variation('flag-key-123abc', context, false, (err, showFeature) => {
|
|
277
|
+
if (err) {
|
|
278
|
+
console.error('Error evaluating flag:', err);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (showFeature) {
|
|
283
|
+
// Feature enabled
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Synchronous Variation (After Initialization)
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
// Only use after client is fully initialized
|
|
292
|
+
const showFeature = client.variation('flag-key-123abc', context, false);
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Flag Evaluation with Details
|
|
298
|
+
|
|
299
|
+
### Get Evaluation Reason
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
const result = await client.variationDetail('flag-key-123abc', context, false);
|
|
303
|
+
|
|
304
|
+
console.log('Value:', result.value);
|
|
305
|
+
console.log('Variation Index:', result.variationIndex);
|
|
306
|
+
console.log('Reason:', result.reason);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Reason Object Structure
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
const result = await client.variationDetail('flag-key-123abc', context, false);
|
|
313
|
+
|
|
314
|
+
// result.reason can be:
|
|
315
|
+
// { kind: 'OFF' }
|
|
316
|
+
// { kind: 'FALLTHROUGH' }
|
|
317
|
+
// { kind: 'TARGET_MATCH' }
|
|
318
|
+
// { kind: 'RULE_MATCH', ruleIndex: 0, ruleId: 'rule-id' }
|
|
319
|
+
// { kind: 'PREREQUISITE_FAILED', prerequisiteKey: 'other-flag' }
|
|
320
|
+
// { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' }
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Using Evaluation Details for Debugging
|
|
324
|
+
|
|
325
|
+
```javascript
|
|
326
|
+
const result = await client.variationDetail('experiment-flag', context, 'control');
|
|
327
|
+
|
|
328
|
+
if (result.reason.kind === 'ERROR') {
|
|
329
|
+
console.error('Flag evaluation error:', result.reason.errorKind);
|
|
330
|
+
} else if (result.reason.kind === 'RULE_MATCH') {
|
|
331
|
+
console.log('Matched rule:', result.reason.ruleIndex);
|
|
332
|
+
} else if (result.reason.kind === 'FALLTHROUGH') {
|
|
333
|
+
console.log('Using fallthrough variation');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
console.log('Serving variation:', result.value);
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## All Flags State
|
|
342
|
+
|
|
343
|
+
### Get All Flags for a Context
|
|
344
|
+
|
|
345
|
+
```javascript
|
|
346
|
+
const state = await client.allFlagsState(context);
|
|
347
|
+
|
|
348
|
+
const allFlags = state.allValues();
|
|
349
|
+
console.log('All flags:', allFlags);
|
|
350
|
+
// { 'flag-1': true, 'flag-2': 'value', 'flag-3': 42 }
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### For Client-Side Bootstrapping
|
|
354
|
+
|
|
355
|
+
```javascript
|
|
356
|
+
const state = await client.allFlagsState(context, {
|
|
357
|
+
clientSideOnly: true,
|
|
358
|
+
withReasons: true,
|
|
359
|
+
detailsOnlyForTrackedFlags: false
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const bootstrapData = state.toJSON();
|
|
363
|
+
|
|
364
|
+
// Send to client-side
|
|
365
|
+
res.json({
|
|
366
|
+
flags: bootstrapData
|
|
367
|
+
});
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Check If State Is Valid
|
|
371
|
+
|
|
372
|
+
```javascript
|
|
373
|
+
const state = await client.allFlagsState(context);
|
|
374
|
+
|
|
375
|
+
if (state.valid) {
|
|
376
|
+
console.log('Successfully retrieved all flags');
|
|
377
|
+
const flags = state.allValues();
|
|
378
|
+
} else {
|
|
379
|
+
console.error('Failed to retrieve flags');
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Event Tracking
|
|
386
|
+
|
|
387
|
+
### Track Custom Event
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
client.track('button-clicked', context);
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Track Event with Data
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
client.track('purchase-completed', context, {
|
|
397
|
+
itemId: 'item-123',
|
|
398
|
+
price: 29.99,
|
|
399
|
+
currency: 'USD'
|
|
400
|
+
});
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Track Event with Numeric Metric
|
|
404
|
+
|
|
405
|
+
```javascript
|
|
406
|
+
client.track('purchase-completed', context, {
|
|
407
|
+
itemId: 'item-123'
|
|
408
|
+
}, 29.99);
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Identify Context
|
|
412
|
+
|
|
413
|
+
Send context attributes to LaunchDarkly for targeting:
|
|
414
|
+
|
|
415
|
+
```javascript
|
|
416
|
+
client.identify(context);
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Flush Events
|
|
420
|
+
|
|
421
|
+
Force immediate delivery of pending events:
|
|
422
|
+
|
|
423
|
+
```javascript
|
|
424
|
+
await client.flush();
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Auto-Flush on Shutdown
|
|
428
|
+
|
|
429
|
+
```javascript
|
|
430
|
+
process.on('SIGTERM', async () => {
|
|
431
|
+
await client.flush();
|
|
432
|
+
await client.close();
|
|
433
|
+
process.exit(0);
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Data Modes
|
|
440
|
+
|
|
441
|
+
### Streaming Mode (Default)
|
|
442
|
+
|
|
443
|
+
```javascript
|
|
444
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
445
|
+
stream: true
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Polling Mode
|
|
450
|
+
|
|
451
|
+
```javascript
|
|
452
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
453
|
+
stream: false,
|
|
454
|
+
pollInterval: 60 // seconds
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Offline Mode
|
|
459
|
+
|
|
460
|
+
```javascript
|
|
461
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
462
|
+
offline: true
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// All flags return default values
|
|
466
|
+
const result = await client.variation('flag-key', context, false);
|
|
467
|
+
// Will always return false (the default)
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## Relay Proxy
|
|
473
|
+
|
|
474
|
+
### Using LaunchDarkly Relay Proxy
|
|
475
|
+
|
|
476
|
+
```javascript
|
|
477
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
478
|
+
baseUri: 'http://relay-proxy.example.com',
|
|
479
|
+
streamUri: 'http://relay-proxy.example.com',
|
|
480
|
+
eventsUri: 'http://relay-proxy.example.com'
|
|
481
|
+
});
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Daemon Mode
|
|
485
|
+
|
|
486
|
+
```javascript
|
|
487
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
488
|
+
useLdd: true,
|
|
489
|
+
featureStore: myFeatureStore
|
|
490
|
+
});
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Feature Stores
|
|
496
|
+
|
|
497
|
+
### Redis Feature Store
|
|
498
|
+
|
|
499
|
+
```javascript
|
|
500
|
+
import { init } from '@launchdarkly/node-server-sdk';
|
|
501
|
+
import { RedisFeatureStore } from '@launchdarkly/node-server-sdk-redis';
|
|
502
|
+
|
|
503
|
+
const redisStore = RedisFeatureStore({
|
|
504
|
+
redisOpts: {
|
|
505
|
+
host: 'localhost',
|
|
506
|
+
port: 6379
|
|
507
|
+
},
|
|
508
|
+
prefix: 'ld',
|
|
509
|
+
cacheTTL: 30
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
513
|
+
featureStore: redisStore
|
|
514
|
+
});
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### DynamoDB Feature Store
|
|
518
|
+
|
|
519
|
+
```javascript
|
|
520
|
+
import { DynamoDBFeatureStore } from '@launchdarkly/node-server-sdk-dynamodb';
|
|
521
|
+
|
|
522
|
+
const dynamoStore = DynamoDBFeatureStore('feature-flags-table', {
|
|
523
|
+
cacheTTL: 30
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
527
|
+
featureStore: dynamoStore
|
|
528
|
+
});
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## Bootstrapping
|
|
534
|
+
|
|
535
|
+
### File-Based Data Source
|
|
536
|
+
|
|
537
|
+
```javascript
|
|
538
|
+
import { init } from '@launchdarkly/node-server-sdk';
|
|
539
|
+
import { FileDataSource } from '@launchdarkly/node-server-sdk-file';
|
|
540
|
+
|
|
541
|
+
const fileSource = FileDataSource({
|
|
542
|
+
paths: ['./flags.json']
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
546
|
+
updateProcessor: fileSource
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### flags.json Format
|
|
551
|
+
|
|
552
|
+
```json
|
|
553
|
+
{
|
|
554
|
+
"flags": {
|
|
555
|
+
"flag-key-123abc": {
|
|
556
|
+
"key": "flag-key-123abc",
|
|
557
|
+
"on": true,
|
|
558
|
+
"variations": [true, false],
|
|
559
|
+
"fallthrough": {
|
|
560
|
+
"variation": 0
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
"segments": {}
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## Subscribing to Flag Changes
|
|
571
|
+
|
|
572
|
+
### Listen for Specific Flag Changes
|
|
573
|
+
|
|
574
|
+
```javascript
|
|
575
|
+
client.on('update:flag-key-123abc', (newValue, oldValue) => {
|
|
576
|
+
console.log(`Flag changed from ${oldValue} to ${newValue}`);
|
|
577
|
+
});
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### Listen for All Flag Updates
|
|
581
|
+
|
|
582
|
+
```javascript
|
|
583
|
+
client.on('update', (settings) => {
|
|
584
|
+
console.log('Flags updated');
|
|
585
|
+
});
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
## Big Segments
|
|
591
|
+
|
|
592
|
+
### Configure Big Segments Store
|
|
593
|
+
|
|
594
|
+
```javascript
|
|
595
|
+
import { init } from '@launchdarkly/node-server-sdk';
|
|
596
|
+
import { RedisBigSegmentStore } from '@launchdarkly/node-server-sdk-redis';
|
|
597
|
+
|
|
598
|
+
const bigSegmentStore = RedisBigSegmentStore({
|
|
599
|
+
redisOpts: {
|
|
600
|
+
host: 'localhost',
|
|
601
|
+
port: 6379
|
|
602
|
+
},
|
|
603
|
+
prefix: 'big-segments'
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
607
|
+
bigSegments: {
|
|
608
|
+
store: bigSegmentStore,
|
|
609
|
+
contextCacheSize: 1000,
|
|
610
|
+
contextCacheTime: 5,
|
|
611
|
+
statusPollInterval: 5,
|
|
612
|
+
staleAfter: 120
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### Check Big Segments Status
|
|
618
|
+
|
|
619
|
+
```javascript
|
|
620
|
+
const result = await client.variationDetail('segment-flag', context, false);
|
|
621
|
+
|
|
622
|
+
if (result.reason.bigSegmentsStatus === 'STALE') {
|
|
623
|
+
console.warn('Big segments data is stale');
|
|
624
|
+
}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## Hooks
|
|
630
|
+
|
|
631
|
+
### Create a Hook
|
|
632
|
+
|
|
633
|
+
```javascript
|
|
634
|
+
class LoggingHook {
|
|
635
|
+
getMetadata() {
|
|
636
|
+
return { name: 'logging-hook' };
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
beforeEvaluation(hookContext, data) {
|
|
640
|
+
console.log('Evaluating flag:', hookContext.flagKey);
|
|
641
|
+
return data;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
afterEvaluation(hookContext, data, detail) {
|
|
645
|
+
console.log('Flag result:', detail.value);
|
|
646
|
+
return data;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Register Hook via Configuration
|
|
652
|
+
|
|
653
|
+
```javascript
|
|
654
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
655
|
+
hooks: [new LoggingHook()]
|
|
656
|
+
});
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
### Add Hook at Runtime
|
|
660
|
+
|
|
661
|
+
```javascript
|
|
662
|
+
const hook = new LoggingHook();
|
|
663
|
+
client.addHook(hook);
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## Migrations
|
|
669
|
+
|
|
670
|
+
### Create Migration
|
|
671
|
+
|
|
672
|
+
```javascript
|
|
673
|
+
import { createMigration } from '@launchdarkly/node-server-sdk';
|
|
674
|
+
|
|
675
|
+
const migration = createMigration(client, {
|
|
676
|
+
execution: 'parallel',
|
|
677
|
+
latencyTracking: true,
|
|
678
|
+
errorTracking: true,
|
|
679
|
+
|
|
680
|
+
readOld: async (payload) => {
|
|
681
|
+
return await oldDatabase.read(payload.id);
|
|
682
|
+
},
|
|
683
|
+
|
|
684
|
+
readNew: async (payload) => {
|
|
685
|
+
return await newDatabase.read(payload.id);
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
writeOld: async (payload) => {
|
|
689
|
+
await oldDatabase.write(payload.id, payload.data);
|
|
690
|
+
},
|
|
691
|
+
|
|
692
|
+
writeNew: async (payload) => {
|
|
693
|
+
await newDatabase.write(payload.id, payload.data);
|
|
694
|
+
},
|
|
695
|
+
|
|
696
|
+
check: (oldValue, newValue) => {
|
|
697
|
+
return JSON.stringify(oldValue) === JSON.stringify(newValue);
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
### Execute Migration Read
|
|
703
|
+
|
|
704
|
+
```javascript
|
|
705
|
+
const stage = await client.variation('migration-flag', context, 'off');
|
|
706
|
+
|
|
707
|
+
const result = await migration.read('migration-flag', context, stage, {
|
|
708
|
+
id: 'record-123'
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
if (result.isSuccessful()) {
|
|
712
|
+
console.log('Data:', result.getValue());
|
|
713
|
+
} else {
|
|
714
|
+
console.error('Migration read failed');
|
|
715
|
+
}
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
### Execute Migration Write
|
|
719
|
+
|
|
720
|
+
```javascript
|
|
721
|
+
const stage = await client.variation('migration-flag', context, 'off');
|
|
722
|
+
|
|
723
|
+
const result = await migration.write('migration-flag', context, stage, {
|
|
724
|
+
id: 'record-123',
|
|
725
|
+
data: { name: 'Example' }
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
if (result.isSuccessful()) {
|
|
729
|
+
console.log('Write completed');
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### Migration Stages
|
|
734
|
+
|
|
735
|
+
- `'off'` - Use old implementation only
|
|
736
|
+
- `'dualwrite'` - Write to both, read from old
|
|
737
|
+
- `'shadow'` - Read from both, write to both, use old for responses
|
|
738
|
+
- `'live'` - Read from both, write to both, use new for responses
|
|
739
|
+
- `'rampdown'` - Write to both, read from new
|
|
740
|
+
- `'complete'` - Use new implementation only
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
744
|
+
## HTTP Configuration
|
|
745
|
+
|
|
746
|
+
### Configure Proxy
|
|
747
|
+
|
|
748
|
+
```javascript
|
|
749
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
750
|
+
proxyOptions: {
|
|
751
|
+
host: 'proxy.example.com',
|
|
752
|
+
port: 8080,
|
|
753
|
+
auth: 'username:password'
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
### Custom TLS Options
|
|
759
|
+
|
|
760
|
+
```javascript
|
|
761
|
+
import * as fs from 'fs';
|
|
762
|
+
|
|
763
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
764
|
+
tlsOptions: {
|
|
765
|
+
ca: fs.readFileSync('./ca-cert.pem'),
|
|
766
|
+
cert: fs.readFileSync('./client-cert.pem'),
|
|
767
|
+
key: fs.readFileSync('./client-key.pem')
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
### Custom HTTP Headers
|
|
773
|
+
|
|
774
|
+
```javascript
|
|
775
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
776
|
+
requestHeaderTransform: (headers) => {
|
|
777
|
+
headers['X-Custom-Header'] = 'custom-value';
|
|
778
|
+
return headers;
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
---
|
|
784
|
+
|
|
785
|
+
## Application Metadata
|
|
786
|
+
|
|
787
|
+
### Set Application Info
|
|
788
|
+
|
|
789
|
+
```javascript
|
|
790
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
791
|
+
application: {
|
|
792
|
+
id: 'my-app',
|
|
793
|
+
version: '1.2.3',
|
|
794
|
+
name: 'My Application',
|
|
795
|
+
versionName: 'v1.2.3-beta'
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
---
|
|
801
|
+
|
|
802
|
+
## Secure Mode
|
|
803
|
+
|
|
804
|
+
### Generate Secure Mode Hash
|
|
805
|
+
|
|
806
|
+
```javascript
|
|
807
|
+
import { createHmac } from 'crypto';
|
|
808
|
+
|
|
809
|
+
function generateSecureModeHash(sdkKey, contextKey) {
|
|
810
|
+
return createHmac('sha256', sdkKey)
|
|
811
|
+
.update(contextKey)
|
|
812
|
+
.digest('hex');
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const hash = generateSecureModeHash(
|
|
816
|
+
process.env.LAUNCHDARKLY_SDK_KEY,
|
|
817
|
+
context.key
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
// Send hash to client-side
|
|
821
|
+
res.json({ hash });
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
---
|
|
825
|
+
|
|
826
|
+
## Testing
|
|
827
|
+
|
|
828
|
+
### Using Test Data Source
|
|
829
|
+
|
|
830
|
+
```javascript
|
|
831
|
+
import { init, TestData } from '@launchdarkly/node-server-sdk';
|
|
832
|
+
|
|
833
|
+
const td = TestData();
|
|
834
|
+
|
|
835
|
+
td.update(td.flag('flag-key-123abc')
|
|
836
|
+
.variations(true, false)
|
|
837
|
+
.variationForAll(true)
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
const client = init('sdk-key', {
|
|
841
|
+
updateProcessor: td.getFactory()
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
// Update flag during test
|
|
845
|
+
td.update(td.flag('flag-key-123abc').variationForAll(false));
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
### Mock Client for Unit Tests
|
|
849
|
+
|
|
850
|
+
```javascript
|
|
851
|
+
const mockClient = {
|
|
852
|
+
variation: jest.fn().mockResolvedValue(true),
|
|
853
|
+
variationDetail: jest.fn().mockResolvedValue({
|
|
854
|
+
value: true,
|
|
855
|
+
variationIndex: 0,
|
|
856
|
+
reason: { kind: 'OFF' }
|
|
857
|
+
}),
|
|
858
|
+
track: jest.fn(),
|
|
859
|
+
identify: jest.fn(),
|
|
860
|
+
flush: jest.fn().mockResolvedValue(undefined),
|
|
861
|
+
close: jest.fn().mockResolvedValue(undefined)
|
|
862
|
+
};
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
---
|
|
866
|
+
|
|
867
|
+
## Error Handling
|
|
868
|
+
|
|
869
|
+
### Handle Initialization Errors
|
|
870
|
+
|
|
871
|
+
```javascript
|
|
872
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY);
|
|
873
|
+
|
|
874
|
+
client.on('error', (error) => {
|
|
875
|
+
console.error('LaunchDarkly error:', error);
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
try {
|
|
879
|
+
await client.waitForInitialization({ timeout: 10 });
|
|
880
|
+
} catch (error) {
|
|
881
|
+
console.error('Initialization timeout');
|
|
882
|
+
// Client will still work with default values
|
|
883
|
+
}
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
### Handle Evaluation Errors
|
|
887
|
+
|
|
888
|
+
```javascript
|
|
889
|
+
const result = await client.variationDetail('flag-key', context, false);
|
|
890
|
+
|
|
891
|
+
if (result.reason.kind === 'ERROR') {
|
|
892
|
+
switch (result.reason.errorKind) {
|
|
893
|
+
case 'MALFORMED_FLAG':
|
|
894
|
+
console.error('Flag configuration is invalid');
|
|
895
|
+
break;
|
|
896
|
+
case 'FLAG_NOT_FOUND':
|
|
897
|
+
console.error('Flag does not exist');
|
|
898
|
+
break;
|
|
899
|
+
case 'USER_NOT_SPECIFIED':
|
|
900
|
+
console.error('Context is invalid');
|
|
901
|
+
break;
|
|
902
|
+
case 'WRONG_TYPE':
|
|
903
|
+
console.error('Flag type mismatch');
|
|
904
|
+
break;
|
|
905
|
+
default:
|
|
906
|
+
console.error('Unknown error:', result.reason.errorKind);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
---
|
|
912
|
+
|
|
913
|
+
## Logging
|
|
914
|
+
|
|
915
|
+
### Custom Logger
|
|
916
|
+
|
|
917
|
+
```javascript
|
|
918
|
+
import { BasicLogger } from '@launchdarkly/node-server-sdk';
|
|
919
|
+
|
|
920
|
+
const logger = BasicLogger.get();
|
|
921
|
+
|
|
922
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
923
|
+
logger: logger
|
|
924
|
+
});
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
### Set Log Level
|
|
928
|
+
|
|
929
|
+
```javascript
|
|
930
|
+
import { BasicLogger } from '@launchdarkly/node-server-sdk';
|
|
931
|
+
|
|
932
|
+
const logger = BasicLogger.get();
|
|
933
|
+
logger.setLevel('debug'); // 'debug', 'info', 'warn', 'error'
|
|
934
|
+
|
|
935
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
936
|
+
logger: logger
|
|
937
|
+
});
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
### Custom Logger Implementation
|
|
941
|
+
|
|
942
|
+
```javascript
|
|
943
|
+
class CustomLogger {
|
|
944
|
+
debug(message) {
|
|
945
|
+
console.log('[DEBUG]', message);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
info(message) {
|
|
949
|
+
console.log('[INFO]', message);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
warn(message) {
|
|
953
|
+
console.warn('[WARN]', message);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
error(message) {
|
|
957
|
+
console.error('[ERROR]', message);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
962
|
+
logger: new CustomLogger()
|
|
963
|
+
});
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
---
|
|
967
|
+
|
|
968
|
+
## Complete Express.js Example
|
|
969
|
+
|
|
970
|
+
```javascript
|
|
971
|
+
import express from 'express';
|
|
972
|
+
import { init } from '@launchdarkly/node-server-sdk';
|
|
973
|
+
|
|
974
|
+
const app = express();
|
|
975
|
+
let ldClient;
|
|
976
|
+
|
|
977
|
+
// Initialize LaunchDarkly client
|
|
978
|
+
async function initLaunchDarkly() {
|
|
979
|
+
ldClient = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
980
|
+
timeout: 10,
|
|
981
|
+
stream: true
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
await ldClient.waitForInitialization({ timeout: 10 });
|
|
985
|
+
console.log('LaunchDarkly initialized');
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Middleware to add LD client to request
|
|
989
|
+
app.use((req, res, next) => {
|
|
990
|
+
req.ldClient = ldClient;
|
|
991
|
+
next();
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
// Route using feature flag
|
|
995
|
+
app.get('/feature', async (req, res) => {
|
|
996
|
+
const context = {
|
|
997
|
+
kind: 'user',
|
|
998
|
+
key: req.headers['x-user-id'] || 'anonymous',
|
|
999
|
+
email: req.headers['x-user-email']
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
const showNewFeature = await ldClient.variation(
|
|
1003
|
+
'new-feature-flag',
|
|
1004
|
+
context,
|
|
1005
|
+
false
|
|
1006
|
+
);
|
|
1007
|
+
|
|
1008
|
+
if (showNewFeature) {
|
|
1009
|
+
res.json({ feature: 'new', message: 'Welcome to the new feature!' });
|
|
1010
|
+
} else {
|
|
1011
|
+
res.json({ feature: 'old', message: 'Using legacy feature' });
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
// Track custom event
|
|
1016
|
+
app.post('/track-event', async (req, res) => {
|
|
1017
|
+
const context = {
|
|
1018
|
+
kind: 'user',
|
|
1019
|
+
key: req.body.userId
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
ldClient.track('button-clicked', context, {
|
|
1023
|
+
buttonId: req.body.buttonId
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
res.json({ success: true });
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
// Graceful shutdown
|
|
1030
|
+
process.on('SIGTERM', async () => {
|
|
1031
|
+
console.log('Shutting down...');
|
|
1032
|
+
await ldClient.flush();
|
|
1033
|
+
await ldClient.close();
|
|
1034
|
+
process.exit(0);
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
// Start server
|
|
1038
|
+
const PORT = process.env.PORT || 3000;
|
|
1039
|
+
|
|
1040
|
+
initLaunchDarkly()
|
|
1041
|
+
.then(() => {
|
|
1042
|
+
app.listen(PORT, () => {
|
|
1043
|
+
console.log(`Server running on port ${PORT}`);
|
|
1044
|
+
});
|
|
1045
|
+
})
|
|
1046
|
+
.catch((error) => {
|
|
1047
|
+
console.error('Failed to initialize LaunchDarkly:', error);
|
|
1048
|
+
process.exit(1);
|
|
1049
|
+
});
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
---
|
|
1053
|
+
|
|
1054
|
+
## TypeScript Support
|
|
1055
|
+
|
|
1056
|
+
### Type Definitions
|
|
1057
|
+
|
|
1058
|
+
```typescript
|
|
1059
|
+
import {
|
|
1060
|
+
init,
|
|
1061
|
+
LDClient,
|
|
1062
|
+
LDContext,
|
|
1063
|
+
LDOptions,
|
|
1064
|
+
LDFlagValue,
|
|
1065
|
+
LDEvaluationDetail
|
|
1066
|
+
} from '@launchdarkly/node-server-sdk';
|
|
1067
|
+
|
|
1068
|
+
const options: LDOptions = {
|
|
1069
|
+
timeout: 10,
|
|
1070
|
+
stream: true
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
const client: LDClient = init(process.env.LAUNCHDARKLY_SDK_KEY!, options);
|
|
1074
|
+
|
|
1075
|
+
const context: LDContext = {
|
|
1076
|
+
kind: 'user',
|
|
1077
|
+
key: 'user-123',
|
|
1078
|
+
name: 'Sandy'
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
const value: LDFlagValue = await client.variation('flag-key', context, false);
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
### Typed Flag Values
|
|
1085
|
+
|
|
1086
|
+
```typescript
|
|
1087
|
+
interface FeatureConfig {
|
|
1088
|
+
maxItems: number;
|
|
1089
|
+
enabled: boolean;
|
|
1090
|
+
theme: string;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const config = await client.variation<FeatureConfig>(
|
|
1094
|
+
'config-flag',
|
|
1095
|
+
context,
|
|
1096
|
+
{ maxItems: 10, enabled: false, theme: 'light' }
|
|
1097
|
+
);
|
|
1098
|
+
|
|
1099
|
+
console.log(config.maxItems); // Type-safe
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
---
|
|
1103
|
+
|
|
1104
|
+
## OpenTelemetry Integration
|
|
1105
|
+
|
|
1106
|
+
### Setup OpenTelemetry with LaunchDarkly
|
|
1107
|
+
|
|
1108
|
+
```javascript
|
|
1109
|
+
import { init } from '@launchdarkly/node-server-sdk';
|
|
1110
|
+
import { Observability } from '@launchdarkly/observability-node';
|
|
1111
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
1112
|
+
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
|
1113
|
+
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
|
|
1114
|
+
|
|
1115
|
+
const sdk = new NodeSDK({
|
|
1116
|
+
metricReader: new PrometheusExporter({
|
|
1117
|
+
port: 9464
|
|
1118
|
+
}),
|
|
1119
|
+
instrumentations: [getNodeAutoInstrumentations()]
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
sdk.start();
|
|
1123
|
+
|
|
1124
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
1125
|
+
plugins: [new Observability()]
|
|
1126
|
+
});
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
---
|
|
1130
|
+
|
|
1131
|
+
## Private Attributes Configuration
|
|
1132
|
+
|
|
1133
|
+
### Global Private Attributes
|
|
1134
|
+
|
|
1135
|
+
```javascript
|
|
1136
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
1137
|
+
privateAttributes: ['email', 'ssn', 'address'],
|
|
1138
|
+
allAttributesPrivate: false
|
|
1139
|
+
});
|
|
1140
|
+
```
|
|
1141
|
+
|
|
1142
|
+
### All Attributes Private
|
|
1143
|
+
|
|
1144
|
+
```javascript
|
|
1145
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
1146
|
+
allAttributesPrivate: true
|
|
1147
|
+
});
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
---
|
|
1151
|
+
|
|
1152
|
+
## Diagnostics
|
|
1153
|
+
|
|
1154
|
+
### Disable Diagnostic Events
|
|
1155
|
+
|
|
1156
|
+
```javascript
|
|
1157
|
+
const client = init(process.env.LAUNCHDARKLY_SDK_KEY, {
|
|
1158
|
+
diagnosticOptOut: true
|
|
1159
|
+
});
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
---
|
|
1163
|
+
|
|
1164
|
+
## Shutdown and Cleanup
|
|
1165
|
+
|
|
1166
|
+
### Graceful Shutdown
|
|
1167
|
+
|
|
1168
|
+
```javascript
|
|
1169
|
+
async function shutdown() {
|
|
1170
|
+
console.log('Flushing events...');
|
|
1171
|
+
await client.flush();
|
|
1172
|
+
|
|
1173
|
+
console.log('Closing client...');
|
|
1174
|
+
await client.close();
|
|
1175
|
+
|
|
1176
|
+
console.log('Shutdown complete');
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
process.on('SIGINT', shutdown);
|
|
1180
|
+
process.on('SIGTERM', shutdown);
|
|
1181
|
+
```
|
|
1182
|
+
|
|
1183
|
+
### Check Initialization Status
|
|
1184
|
+
|
|
1185
|
+
```javascript
|
|
1186
|
+
if (client.initialized()) {
|
|
1187
|
+
console.log('Client is ready');
|
|
1188
|
+
} else {
|
|
1189
|
+
console.log('Client is not initialized yet');
|
|
1190
|
+
}
|
|
1191
|
+
```
|