@avsbhq/node 1.7.2 → 1.9.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 +34 -23
- package/dist/index.cjs +913 -881
- package/dist/index.d.cts +9 -107
- package/dist/index.d.ts +9 -107
- package/dist/index.js +905 -872
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -35,8 +35,8 @@ if (!result.success) {
|
|
|
35
35
|
// Bind the context once per request.
|
|
36
36
|
const user = server.forUser({ kind: 'user', key: 'u_123', plan: 'pro' });
|
|
37
37
|
|
|
38
|
-
const flag = user.getBoolFlag('
|
|
39
|
-
const checkoutTemplate = flag.isEnabled() ? '
|
|
38
|
+
const flag = user.getBoolFlag('new_checkout_flow', false);
|
|
39
|
+
const checkoutTemplate = flag.isEnabled() ? 'checkout_v2' : 'checkout_v1';
|
|
40
40
|
|
|
41
41
|
user.track('checkout_started', { revenue: 99.0 });
|
|
42
42
|
```
|
|
@@ -139,7 +139,7 @@ const user = server.forUser({
|
|
|
139
139
|
country: req.user.country,
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
-
const flag = user.getBoolFlag('
|
|
142
|
+
const flag = user.getBoolFlag('checkout_v2', false);
|
|
143
143
|
user.track('page_viewed');
|
|
144
144
|
```
|
|
145
145
|
|
|
@@ -150,7 +150,7 @@ user.track('page_viewed');
|
|
|
150
150
|
Context is the THIRD POSITIONAL argument on every evaluation call, matching `@avsbhq/edge`:
|
|
151
151
|
|
|
152
152
|
```ts
|
|
153
|
-
const flag = server.getBoolFlag('
|
|
153
|
+
const flag = server.getBoolFlag('checkout_v2', false, {
|
|
154
154
|
kind: 'user',
|
|
155
155
|
key: 'u_123',
|
|
156
156
|
plan: 'pro',
|
|
@@ -175,7 +175,7 @@ const user = server.forUser({
|
|
|
175
175
|
Audiences built with the commerce condition types (cart value, products viewed, purchase history) read fixed attribute names off the context:
|
|
176
176
|
|
|
177
177
|
```ts
|
|
178
|
-
const flag = server.getBoolFlag('
|
|
178
|
+
const flag = server.getBoolFlag('big_cart_banner', false, {
|
|
179
179
|
kind: 'user',
|
|
180
180
|
key: 'u_123',
|
|
181
181
|
cartValue: 120.5, // decimal MAJOR units of the project currency
|
|
@@ -290,7 +290,7 @@ interface Flag<T = unknown> {
|
|
|
290
290
|
```ts
|
|
291
291
|
import { DecideOption } from '@avsbhq/node';
|
|
292
292
|
|
|
293
|
-
user.getBoolFlag('
|
|
293
|
+
user.getBoolFlag('checkout_v2', false, {
|
|
294
294
|
decideOptions: [DecideOption.DISABLE_EXPOSURE],
|
|
295
295
|
});
|
|
296
296
|
```
|
|
@@ -314,12 +314,12 @@ Called without a context, or before the datafile loads, it returns `{}` AND warn
|
|
|
314
314
|
### Overrides, for QA and testing
|
|
315
315
|
|
|
316
316
|
```ts
|
|
317
|
-
server.setOverrideForUser('
|
|
318
|
-
server.setOverrideForUser('
|
|
319
|
-
server.setGlobalOverride('
|
|
317
|
+
server.setOverrideForUser('checkout_v2', 'u_qa', 'on');
|
|
318
|
+
server.setOverrideForUser('checkout_v2', 'acme', 'on', 'organization'); // explicit kind
|
|
319
|
+
server.setGlobalOverride('checkout_v2', 'on');
|
|
320
320
|
|
|
321
|
-
server.clearOverrideForUser('
|
|
322
|
-
server.clearGlobalOverride('
|
|
321
|
+
server.clearOverrideForUser('checkout_v2', 'u_qa');
|
|
322
|
+
server.clearGlobalOverride('checkout_v2');
|
|
323
323
|
server.clearAllOverrides();
|
|
324
324
|
```
|
|
325
325
|
|
|
@@ -327,8 +327,8 @@ Per-request forced decisions, which never touch the shared state above:
|
|
|
327
327
|
|
|
328
328
|
```ts
|
|
329
329
|
const user = server.forUser({ kind: 'user', key: 'u_qa' });
|
|
330
|
-
user.setForcedDecision('
|
|
331
|
-
user.clearForcedDecision('
|
|
330
|
+
user.setForcedDecision('checkout_v2', { variationKey: 'on' });
|
|
331
|
+
user.clearForcedDecision('checkout_v2');
|
|
332
332
|
```
|
|
333
333
|
|
|
334
334
|
---
|
|
@@ -380,12 +380,12 @@ Purchases are queued before ready exactly like `track`. They used to be dropped
|
|
|
380
380
|
### Manual exposure
|
|
381
381
|
|
|
382
382
|
```ts
|
|
383
|
-
const flag = user.getBoolFlag('
|
|
383
|
+
const flag = user.getBoolFlag('checkout_v2', false, {
|
|
384
384
|
decideOptions: [DecideOption.DISABLE_EXPOSURE],
|
|
385
385
|
});
|
|
386
386
|
|
|
387
387
|
// Later, at the point the visitor actually sees the variation:
|
|
388
|
-
user.manualExposure('
|
|
388
|
+
user.manualExposure('checkout_v2');
|
|
389
389
|
```
|
|
390
390
|
|
|
391
391
|
---
|
|
@@ -474,7 +474,7 @@ actually being served.
|
|
|
474
474
|
- **Where it shows up**: Environments in the project sidebar, in the dashboard. Each
|
|
475
475
|
environment shows Connected, Stale, or Not connected, plus the `sdkType` and
|
|
476
476
|
`sdkVersion` that last reported, which is how you see "production is running
|
|
477
|
-
@avsbhq/node 1.
|
|
477
|
+
@avsbhq/node 1.8.0". An environment goes Stale after five minutes without a
|
|
478
478
|
heartbeat.
|
|
479
479
|
- **Turning it off**: publish a datafile with no `heartbeatEndpoint`. The SDK
|
|
480
480
|
never guesses a host, so an absent field simply means no heartbeats.
|
|
@@ -520,6 +520,17 @@ server.utils.hydrateBootstrap(blob); // blob must carry `datafile`
|
|
|
520
520
|
|
|
521
521
|
`hydrateBootstrap` THROWS on a blob that carries only a pre-evaluated snapshot: a snapshot belongs to one context, and a server evaluates a different context per request, so adopting one process-wide would serve one user's values to another. Serialize with `{ includeDatafile: true }`, or pass `bootstrap: blob.datafile`.
|
|
522
522
|
|
|
523
|
+
### Fresh snapshots
|
|
524
|
+
|
|
525
|
+
```ts
|
|
526
|
+
const ctx = { kind: 'user', key: 'user-123' };
|
|
527
|
+
|
|
528
|
+
const snap = server.utils.snapshot(ctx); // values in memory, no network call
|
|
529
|
+
const fresh = await server.utils.snapshotFresh(ctx); // fetches the datafile first
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
`snapshotFresh` and `refreshAndSnapshot` call `refreshDatafile()` before they read, so the snapshot reflects the flags published right now. A failed fetch keeps the datafile already in memory, logs a warning, and the snapshot shows that datafile. Before 1.9.0 both helpers skipped the fetch and returned the in-memory values.
|
|
533
|
+
|
|
523
534
|
For Next.js SSR see `@avsbhq/next`.
|
|
524
535
|
|
|
525
536
|
---
|
|
@@ -587,7 +598,7 @@ import { createMockServer, flagsFromTestData, TestData } from '@avsbhq/test';
|
|
|
587
598
|
|
|
588
599
|
const server = createMockServer(
|
|
589
600
|
flagsFromTestData([
|
|
590
|
-
TestData.flag('
|
|
601
|
+
TestData.flag('checkout_v2')
|
|
591
602
|
.booleanFlag()
|
|
592
603
|
.variationForUser('u_paying', true)
|
|
593
604
|
.fallthroughVariation(false)
|
|
@@ -596,7 +607,7 @@ const server = createMockServer(
|
|
|
596
607
|
);
|
|
597
608
|
|
|
598
609
|
const user = server.forUser({ kind: 'user', key: 'u_paying' });
|
|
599
|
-
expect(user.getBoolFlag('
|
|
610
|
+
expect(user.getBoolFlag('checkout_v2', false).value).toBe(true);
|
|
600
611
|
expect(server._evaluations()).toHaveLength(1);
|
|
601
612
|
```
|
|
602
613
|
|
|
@@ -630,7 +641,7 @@ app.use(
|
|
|
630
641
|
|
|
631
642
|
// Deep inside a service, with no `req` in scope:
|
|
632
643
|
function pricingService() {
|
|
633
|
-
return getRequestClient()?.getBoolFlag('
|
|
644
|
+
return getRequestClient()?.getBoolFlag('new_pricing', false).value ?? false;
|
|
634
645
|
}
|
|
635
646
|
```
|
|
636
647
|
|
|
@@ -640,8 +651,8 @@ Every route then reads flags off `req.avsb`:
|
|
|
640
651
|
// docs-example: not typechecked here, because express is not a dependency of the A vs B repository, so a route handler's Request and Response types cannot be resolved
|
|
641
652
|
|
|
642
653
|
app.get('/checkout', (req, res) => {
|
|
643
|
-
const flag = req.avsb.getBoolFlag('
|
|
644
|
-
res.render(flag.isEnabled() ? '
|
|
654
|
+
const flag = req.avsb.getBoolFlag('new_checkout_flow', false);
|
|
655
|
+
res.render(flag.isEnabled() ? 'checkout_v2' : 'checkout_v1');
|
|
645
656
|
});
|
|
646
657
|
```
|
|
647
658
|
|
|
@@ -682,7 +693,7 @@ await app.register(fastifyPlugin, {
|
|
|
682
693
|
});
|
|
683
694
|
|
|
684
695
|
app.get('/features', (req, reply) => {
|
|
685
|
-
reply.send({ enabled: req.avsb.getBoolFlag('
|
|
696
|
+
reply.send({ enabled: req.avsb.getBoolFlag('new_feature', false).isEnabled() });
|
|
686
697
|
});
|
|
687
698
|
```
|
|
688
699
|
|
|
@@ -716,7 +727,7 @@ The Redis service keeps evaluation synchronous: a lookup is a cache hit or null,
|
|
|
716
727
|
|
|
717
728
|
```ts
|
|
718
729
|
const user = server.forUser({ kind: 'user', key: 'u_123' });
|
|
719
|
-
user.getBoolFlag('
|
|
730
|
+
user.getBoolFlag('checkout_v2', false);
|
|
720
731
|
|
|
721
732
|
const entries = user.getDecisionLog().entries;
|
|
722
733
|
// [{ flagKey, value, variationKey, source, ruleId, ruleType, reasons,
|