@flagbearer/node 0.1.3 → 0.1.5
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 +5 -115
- package/dist/index.js +103 -35
- package/dist/version.d.ts +1 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -10,83 +10,10 @@ Requires Node.js 22 or later.
|
|
|
10
10
|
npm install @flagbearer/node
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Docs
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
keys, which start with `fbc_`, through `@flagbearer/web`.
|
|
18
|
-
|
|
19
|
-
## Usage
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
import { init } from "@flagbearer/node";
|
|
23
|
-
|
|
24
|
-
const flags = init({
|
|
25
|
-
key: process.env.FLAGBEARER_SERVER_KEY!,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
await flags.sync(); // Optional: wait for fresh rules before accepting requests.
|
|
30
|
-
} catch (error) {
|
|
31
|
-
console.error("Initial Flagbearer sync failed", error);
|
|
32
|
-
// Continue using defaults; the SDK retries transient failures in the background.
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const enabled = flags.isEnabled(
|
|
36
|
-
"advanced-search",
|
|
37
|
-
{ key: "123", email: "jane@example.com" },
|
|
38
|
-
false, // Application default when the rule is unknown or can't be evaluated.
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
// Keep one client per process, pass each request's user explicitly, and close it
|
|
42
|
-
// during application shutdown rather than after each request.
|
|
43
|
-
flags.close();
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
`url` is optional and defaults to `https://sdk.flagbearer.dev`, the SDK API
|
|
47
|
-
origin. Override it with the SDK URL printed by `bun run dev` (by default
|
|
48
|
-
`http://127.0.0.1:8081`) for local development, or with a self-hosted
|
|
49
|
-
deployment's SDK origin; use HTTPS for remote production servers. Secret server keys are sent in
|
|
50
|
-
`Authorization: Bearer ...` headers for both HTTP requests and streaming, never
|
|
51
|
-
in URLs. Do not include server keys in browser code. Requests reject redirects
|
|
52
|
-
rather than following a changed API location.
|
|
53
|
-
|
|
54
|
-
## Refreshing and outages
|
|
55
|
-
|
|
56
|
-
Pass `debug: true` to `init` to log stream connections, received versions, refresh
|
|
57
|
-
attempts, retries, and applied snapshot versions using `console.debug`. Debugging
|
|
58
|
-
is off by default; existing error logs are independent of this option. Logs never
|
|
59
|
-
include SDK keys, user identities, flag names, or ruleset contents.
|
|
60
|
-
|
|
61
|
-
For a change delivered over SSE, look for both `Stream version received` and
|
|
62
|
-
`Snapshot applied` with the same version. A notification alone doesn't mean the
|
|
63
|
-
SDK has downloaded the new rules yet. `Snapshot applied` also appears for initial,
|
|
64
|
-
manual, and polling refreshes; it doesn't imply flag values actually changed.
|
|
65
|
-
|
|
66
|
-
- Initialization opens an authenticated SSE stream and starts fetching rules.
|
|
67
|
-
- The internal `@flagbearer/sdk-core` coordinator handles version tracking,
|
|
68
|
-
request coalescing, five-second request timeouts, retries, and fallback polling.
|
|
69
|
-
- Valid rules are indexed by flag key and replaced together. `isEnabled` is
|
|
70
|
-
synchronous and uses only the current in-memory rules.
|
|
71
|
-
- A newer streamed version triggers a ruleset refresh within two seconds. Older responses cannot
|
|
72
|
-
replace a newer ruleset. Reconnecting also triggers a refresh.
|
|
73
|
-
- The Node stream uses `fetch`, supports chunked SSE data and heartbeats, and
|
|
74
|
-
reconnects with jittered backoff capped at 30 seconds. Connection setup times
|
|
75
|
-
out after five seconds; 45 seconds without stream data triggers reconnection.
|
|
76
|
-
- While streaming is disconnected, rules are polled about every 30 seconds. HTTP 408,
|
|
77
|
-
429, and server errors are retried, honoring longer `Retry-After` delays.
|
|
78
|
-
- Invalid rules or permanent ruleset errors stop automatic refreshes and close
|
|
79
|
-
the stream. Existing rules remain usable. An explicit `sync()` can retry;
|
|
80
|
-
successful recovery resumes polling, not the closed stream.
|
|
81
|
-
- A permanent stream error stops stream reconnects but leaves polling available.
|
|
82
|
-
- Explicit `sync()` failures reject; background refresh failures are logged.
|
|
83
|
-
- `close()` aborts requests and streaming and clears timers. It is idempotent;
|
|
84
|
-
later `sync()` calls reject. Existing in-memory rules can still be evaluated.
|
|
85
|
-
|
|
86
|
-
Until the first valid ruleset arrives, checks use the supplied default. During an
|
|
87
|
-
outage, the last valid ruleset stays in memory. There is **no disk cache yet**:
|
|
88
|
-
restarting the application during an outage means using defaults until a fetch
|
|
89
|
-
succeeds. This SDK requires a Node runtime with built-in `fetch` and web streams.
|
|
15
|
+
Keys, syncing at startup, per-request checks, shutdown and exposures are covered
|
|
16
|
+
at [docs.flagbearer.dev/sdks/node](https://docs.flagbearer.dev/sdks/node).
|
|
90
17
|
|
|
91
18
|
## Development
|
|
92
19
|
|
|
@@ -107,42 +34,5 @@ Bun is used as the build tool; the output targets Node.js.
|
|
|
107
34
|
|
|
108
35
|
## Integration tests
|
|
109
36
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
```sh
|
|
114
|
-
bun run test:integration
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
See [the integration README](../../tests/integration/README.md) for setup and coverage.
|
|
118
|
-
|
|
119
|
-
## Exposure reporting
|
|
120
|
-
|
|
121
|
-
Flag checks automatically report the boolean actually returned, its evaluation
|
|
122
|
-
reason (`off`, `tester`, `rollout`, or `fallback`), the configuration version, and
|
|
123
|
-
optional user key/email. Reporting runs in every environment and never waits for
|
|
124
|
-
network activity in `isEnabled`. Pass `appVersion` to `init` to identify your
|
|
125
|
-
application deployment in the stored records.
|
|
126
|
-
|
|
127
|
-
Repeated checks are counted in fixed 30-second buckets by user, flag, result,
|
|
128
|
-
reason, and configuration version. Uploads run every 10 seconds in batches of up
|
|
129
|
-
to 100 aggregates. The memory buffer holds up to 1,000 aggregates. Overflow and
|
|
130
|
-
rejected checks are reported as environment-level reporting loss when delivery
|
|
131
|
-
recovers. Records keep their IDs and contents across retries; HTTP retries do not
|
|
132
|
-
increase stored counts. The server currently accepts up to 6,000 batches per
|
|
133
|
-
minute per environment and returns `429` beyond that limit.
|
|
134
|
-
|
|
135
|
-
For shutdown or serverless invocations, explicitly await delivery before closing:
|
|
136
|
-
|
|
137
|
-
```ts
|
|
138
|
-
try {
|
|
139
|
-
await flags.flush();
|
|
140
|
-
} finally {
|
|
141
|
-
flags.close();
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
`flush()` rejects on upload failure so callers can choose their shutdown policy.
|
|
146
|
-
`close()` also starts a best-effort flush and stops recurring reporting. Delivery
|
|
147
|
-
is best effort: abrupt process exits can lose in-memory checks. Old SDK versions
|
|
148
|
-
do not report exposure; no observed activity does not prove a path is unused.
|
|
37
|
+
Flagbearer's integration tests run this SDK against the real backend through
|
|
38
|
+
[`testing/servers/node-server.ts`](../../testing/servers/node-server.ts).
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ function evaluateFlag(flag, user, version = null, defaultValue = false) {
|
|
|
32
32
|
configurationVersion: version
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
-
// ../
|
|
35
|
+
// ../core/src/clock.ts
|
|
36
36
|
var realClock = {
|
|
37
37
|
now: () => Date.now(),
|
|
38
38
|
setTimeout(callback, delayMs) {
|
|
@@ -41,7 +41,7 @@ var realClock = {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
// ../
|
|
44
|
+
// ../core/src/debug.ts
|
|
45
45
|
function createDebugLogger(enabled = false) {
|
|
46
46
|
return (message, details) => {
|
|
47
47
|
if (!enabled)
|
|
@@ -55,7 +55,7 @@ function createDebugLogger(enabled = false) {
|
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
// ../
|
|
58
|
+
// ../core/src/validation.ts
|
|
59
59
|
function isRecord(value) {
|
|
60
60
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
61
61
|
}
|
|
@@ -84,7 +84,7 @@ function isVersionEvent(value) {
|
|
|
84
84
|
return isRecord(value) && isVersion(value.version);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
// ../
|
|
87
|
+
// ../core/src/refresh.ts
|
|
88
88
|
var REQUEST_TIMEOUT_MS = 5000;
|
|
89
89
|
var POLL_INTERVAL_MS = 30000;
|
|
90
90
|
var MAX_RETRY_MS = 30000;
|
|
@@ -156,6 +156,8 @@ function createRefreshCoordinator(options) {
|
|
|
156
156
|
let cancelVersionRefresh;
|
|
157
157
|
let retryAttempt = 0;
|
|
158
158
|
let notBefore = 0;
|
|
159
|
+
let serverNotBefore = 0;
|
|
160
|
+
let lastCheckedAt = Number.NEGATIVE_INFINITY;
|
|
159
161
|
function clearRetry() {
|
|
160
162
|
cancelRetry?.();
|
|
161
163
|
cancelRetry = undefined;
|
|
@@ -196,6 +198,9 @@ function createRefreshCoordinator(options) {
|
|
|
196
198
|
const jittered = Math.min(MAX_RETRY_MS, backoff * (0.8 + Math.random() * 0.4));
|
|
197
199
|
const delay = Math.max(jittered, error instanceof RefreshError ? error.retryAfterMs : 0);
|
|
198
200
|
notBefore = clock.now() + delay;
|
|
201
|
+
if (error instanceof RefreshError && error.retryAfterMs > 0) {
|
|
202
|
+
serverNotBefore = Math.max(serverNotBefore, clock.now() + error.retryAfterMs);
|
|
203
|
+
}
|
|
199
204
|
clearRetry();
|
|
200
205
|
debug("Refresh retry scheduled", {
|
|
201
206
|
delayMs: Math.ceil(delay),
|
|
@@ -229,6 +234,7 @@ function createRefreshCoordinator(options) {
|
|
|
229
234
|
cancelRequestTimeout = undefined;
|
|
230
235
|
if (stopped)
|
|
231
236
|
return;
|
|
237
|
+
lastCheckedAt = clock.now();
|
|
232
238
|
if (result === null) {
|
|
233
239
|
debug("Snapshot unchanged", { version });
|
|
234
240
|
if (isBehindWantedVersion()) {
|
|
@@ -266,6 +272,7 @@ function createRefreshCoordinator(options) {
|
|
|
266
272
|
} catch (error) {
|
|
267
273
|
if (stopped)
|
|
268
274
|
return;
|
|
275
|
+
options.onRefreshFailed?.(error);
|
|
269
276
|
if (error instanceof RefreshError && !error.retryable) {
|
|
270
277
|
debug("Automatic refresh stopped after permanent failure");
|
|
271
278
|
automaticBlocked = true;
|
|
@@ -335,6 +342,28 @@ function createRefreshCoordinator(options) {
|
|
|
335
342
|
ensurePolling();
|
|
336
343
|
}
|
|
337
344
|
}
|
|
345
|
+
function resume() {
|
|
346
|
+
if (stopped || automaticBlocked)
|
|
347
|
+
return;
|
|
348
|
+
if (clock.now() - lastCheckedAt < (options.resumeStaleMs ?? 15000)) {
|
|
349
|
+
ensurePolling();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (clock.now() >= serverNotBefore) {
|
|
353
|
+
clearRetry();
|
|
354
|
+
notBefore = 0;
|
|
355
|
+
retryAttempt = 0;
|
|
356
|
+
}
|
|
357
|
+
clearPolling();
|
|
358
|
+
refreshInBackground();
|
|
359
|
+
ensurePolling();
|
|
360
|
+
}
|
|
361
|
+
function pause() {
|
|
362
|
+
if (stopped)
|
|
363
|
+
return;
|
|
364
|
+
streaming = false;
|
|
365
|
+
clearPolling();
|
|
366
|
+
}
|
|
338
367
|
function close() {
|
|
339
368
|
if (stopped)
|
|
340
369
|
return;
|
|
@@ -349,16 +378,26 @@ function createRefreshCoordinator(options) {
|
|
|
349
378
|
activeRequest?.abort();
|
|
350
379
|
}
|
|
351
380
|
ensurePolling();
|
|
352
|
-
return {
|
|
381
|
+
return {
|
|
382
|
+
sync,
|
|
383
|
+
refreshInBackground,
|
|
384
|
+
onVersion,
|
|
385
|
+
setStreaming,
|
|
386
|
+
resume,
|
|
387
|
+
pause,
|
|
388
|
+
close
|
|
389
|
+
};
|
|
353
390
|
}
|
|
354
|
-
// ../
|
|
391
|
+
// ../core/src/exposures.ts
|
|
355
392
|
var WINDOW_MS = 30000;
|
|
393
|
+
var FLUSH_MS = 30000;
|
|
356
394
|
var MAX_RECORDS = 1000;
|
|
357
395
|
var BATCH_SIZE = 100;
|
|
358
396
|
function createExposureReporter(options) {
|
|
359
397
|
const reporterId = crypto.randomUUID();
|
|
360
398
|
const active = new Map;
|
|
361
|
-
const
|
|
399
|
+
const sealed = [];
|
|
400
|
+
let sealedEvents = 0;
|
|
362
401
|
let droppedChecks = 0;
|
|
363
402
|
let reportedDroppedChecks = 0;
|
|
364
403
|
let inFlight;
|
|
@@ -387,39 +426,62 @@ function createExposureReporter(options) {
|
|
|
387
426
|
bucket
|
|
388
427
|
]);
|
|
389
428
|
const existing = active.get(key);
|
|
390
|
-
if (existing && existing.checkCount < 1e9) {
|
|
391
|
-
existing.checkCount++;
|
|
392
|
-
existing.lastCheckedAt = new Date(now).toISOString();
|
|
429
|
+
if (existing && existing.event.checkCount < 1e9) {
|
|
430
|
+
existing.event.checkCount++;
|
|
431
|
+
existing.event.lastCheckedAt = new Date(now).toISOString();
|
|
393
432
|
return;
|
|
394
433
|
}
|
|
395
|
-
if (existing || active.size +
|
|
434
|
+
if (existing || active.size + sealedEvents >= MAX_RECORDS) {
|
|
396
435
|
droppedChecks++;
|
|
397
436
|
return;
|
|
398
437
|
}
|
|
399
438
|
active.set(key, {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
439
|
+
user: { key: userKey, email },
|
|
440
|
+
event: {
|
|
441
|
+
flagKey,
|
|
442
|
+
result: details.value,
|
|
443
|
+
reason: details.reason,
|
|
444
|
+
configurationVersion: details.configurationVersion,
|
|
445
|
+
bucket,
|
|
446
|
+
checkCount: 1,
|
|
447
|
+
firstCheckedAt: new Date(now).toISOString(),
|
|
448
|
+
lastCheckedAt: new Date(now).toISOString()
|
|
449
|
+
}
|
|
411
450
|
});
|
|
412
451
|
} catch {
|
|
413
452
|
droppedChecks++;
|
|
414
453
|
}
|
|
415
454
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
455
|
+
function seal() {
|
|
456
|
+
const open = new Map;
|
|
457
|
+
for (const [key, { user, event }] of active) {
|
|
458
|
+
const userId = JSON.stringify([user.key, user.email]);
|
|
459
|
+
let batch = open.get(userId);
|
|
460
|
+
if (!batch || batch.events.length >= BATCH_SIZE) {
|
|
461
|
+
batch = { batchId: crypto.randomUUID(), user, events: [] };
|
|
462
|
+
open.set(userId, batch);
|
|
463
|
+
sealed.push(batch);
|
|
464
|
+
}
|
|
465
|
+
batch.events.push(event);
|
|
466
|
+
sealedEvents++;
|
|
419
467
|
active.delete(key);
|
|
420
468
|
}
|
|
421
|
-
|
|
422
|
-
|
|
469
|
+
}
|
|
470
|
+
function settle(batch) {
|
|
471
|
+
if (sealed[0] !== batch)
|
|
472
|
+
return;
|
|
473
|
+
sealed.shift();
|
|
474
|
+
sealedEvents -= batch.events.length;
|
|
475
|
+
}
|
|
476
|
+
async function send() {
|
|
477
|
+
seal();
|
|
478
|
+
while (sealed.length > 0 || droppedChecks > reportedDroppedChecks) {
|
|
479
|
+
const batch = sealed[0] ?? {
|
|
480
|
+
batchId: crypto.randomUUID(),
|
|
481
|
+
user: {},
|
|
482
|
+
events: []
|
|
483
|
+
};
|
|
484
|
+
const checks = batch.events.reduce((sum, item) => sum + item.checkCount, 0);
|
|
423
485
|
const dropped = droppedChecks;
|
|
424
486
|
const response = await fetch(options.url, {
|
|
425
487
|
method: "POST",
|
|
@@ -430,15 +492,17 @@ function createExposureReporter(options) {
|
|
|
430
492
|
Authorization: `Bearer ${options.key}`
|
|
431
493
|
},
|
|
432
494
|
body: JSON.stringify({
|
|
495
|
+
batchId: batch.batchId,
|
|
433
496
|
reporterId,
|
|
434
|
-
|
|
497
|
+
user: batch.user,
|
|
498
|
+
events: batch.events,
|
|
435
499
|
droppedChecks: dropped,
|
|
436
500
|
sdk: options.sdk,
|
|
437
|
-
sdkVersion:
|
|
501
|
+
sdkVersion: options.sdkVersion,
|
|
438
502
|
appVersion: options.appVersion?.slice(0, 100)
|
|
439
503
|
}),
|
|
440
504
|
signal: AbortSignal.timeout(1e4),
|
|
441
|
-
keepalive: batch.length < 10
|
|
505
|
+
keepalive: batch.events.length < 10
|
|
442
506
|
});
|
|
443
507
|
if (!response.ok) {
|
|
444
508
|
const retryAfter = Number(response.headers.get("Retry-After"));
|
|
@@ -446,18 +510,18 @@ function createExposureReporter(options) {
|
|
|
446
510
|
retryAt = Math.max(retryAt, Date.now() + Math.min(retryAfter, 3600) * 1000);
|
|
447
511
|
}
|
|
448
512
|
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
449
|
-
droppedChecks +=
|
|
450
|
-
|
|
513
|
+
droppedChecks += checks;
|
|
514
|
+
settle(batch);
|
|
451
515
|
retryAt = Date.now() + 60000;
|
|
452
516
|
}
|
|
453
517
|
throw new Error(`Exposure upload failed (${response.status})`);
|
|
454
518
|
}
|
|
455
519
|
const receipt = await response.json();
|
|
456
|
-
if (typeof receipt !== "object" || receipt === null || !("accepted" in receipt) || typeof receipt.accepted !== "number" || !Number.isSafeInteger(receipt.accepted) || receipt.accepted < 0 || receipt.accepted > batch.length || !("rejectedChecks" in receipt) || typeof receipt.rejectedChecks !== "number" || !Number.isSafeInteger(receipt.rejectedChecks) || receipt.rejectedChecks < 0 || receipt.rejectedChecks >
|
|
520
|
+
if (typeof receipt !== "object" || receipt === null || !("accepted" in receipt) || typeof receipt.accepted !== "number" || !Number.isSafeInteger(receipt.accepted) || receipt.accepted < 0 || receipt.accepted > batch.events.length || !("rejectedChecks" in receipt) || typeof receipt.rejectedChecks !== "number" || !Number.isSafeInteger(receipt.rejectedChecks) || receipt.rejectedChecks < 0 || receipt.rejectedChecks > checks) {
|
|
457
521
|
throw new Error("Invalid exposure upload receipt");
|
|
458
522
|
}
|
|
459
523
|
droppedChecks += receipt.rejectedChecks;
|
|
460
|
-
|
|
524
|
+
settle(batch);
|
|
461
525
|
reportedDroppedChecks = Math.max(reportedDroppedChecks, dropped);
|
|
462
526
|
}
|
|
463
527
|
failures = 0;
|
|
@@ -477,7 +541,7 @@ function createExposureReporter(options) {
|
|
|
477
541
|
const timer = setInterval(() => {
|
|
478
542
|
if (!closed && Date.now() >= retryAt)
|
|
479
543
|
flush().catch(() => {});
|
|
480
|
-
},
|
|
544
|
+
}, FLUSH_MS);
|
|
481
545
|
timer.unref?.();
|
|
482
546
|
function close() {
|
|
483
547
|
closed = true;
|
|
@@ -638,6 +702,9 @@ function createVersionStream(options) {
|
|
|
638
702
|
return { close };
|
|
639
703
|
}
|
|
640
704
|
|
|
705
|
+
// src/version.ts
|
|
706
|
+
var SDK_VERSION = "0.1.5";
|
|
707
|
+
|
|
641
708
|
// src/index.ts
|
|
642
709
|
var DEFAULT_URL = "https://sdk.flagbearer.dev";
|
|
643
710
|
function init({
|
|
@@ -659,6 +726,7 @@ function init({
|
|
|
659
726
|
url: new URL("/v1/exposures", baseUrl),
|
|
660
727
|
key,
|
|
661
728
|
sdk: "node",
|
|
729
|
+
sdkVersion: SDK_VERSION,
|
|
662
730
|
appVersion
|
|
663
731
|
});
|
|
664
732
|
let configurationVersion = null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SDK_VERSION: string;
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flagbearer/node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Flagbearer Node.js SDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://flagbearer.dev",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/
|
|
10
|
-
"directory": "
|
|
9
|
+
"url": "git+https://github.com/flagbearer-dev/sdks.git",
|
|
10
|
+
"directory": "js/node"
|
|
11
11
|
},
|
|
12
12
|
"keywords": [
|
|
13
13
|
"feature-flags",
|
|
@@ -34,13 +34,13 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
|
-
"build": "bun build ./src/index.ts --target node --format esm --outdir ./dist && tsc -p tsconfig.json --emitDeclarationOnly",
|
|
37
|
+
"build": "bun build ./src/index.ts --target node --format esm --outdir ./dist --define __FLAGBEARER_SDK_VERSION__=\\\"$npm_package_version\\\" && tsc -p tsconfig.json --emitDeclarationOnly",
|
|
38
38
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
39
39
|
"prepack": "bun run build"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@flagbearer/evaluator": "
|
|
43
|
-
"@flagbearer/sdk-core": "
|
|
42
|
+
"@flagbearer/evaluator": "0.0.0",
|
|
43
|
+
"@flagbearer/sdk-core": "0.0.0",
|
|
44
44
|
"@types/node": "^26.6.3"
|
|
45
45
|
}
|
|
46
46
|
}
|