@core-sdk/feature-flags-sdk 0.1.1 → 0.3.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 +56 -0
- package/dist/client.d.ts +19 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +44 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @core-sdk/feature-flags-sdk
|
|
2
|
+
|
|
3
|
+
HTTP client + in-memory TTL cache for feature flag definitions; evaluation uses [`@core-sdk/feature-flags-core`](https://www.npmjs.com/package/@core-sdk/feature-flags-core) on the client.
|
|
4
|
+
|
|
5
|
+
- **Node:** 18+
|
|
6
|
+
- **Module:** ESM (`"type": "module"`)
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @core-sdk/feature-flags-sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## PostHog-style `identify` (one-shot context)
|
|
15
|
+
|
|
16
|
+
Call **`identify`** once after login (or when profile is known). Stored fields are merged into every **`evaluate` / `evaluateAsync` / `isEnabled`** call. Per-flag overrides win on key clashes.
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
import { FeatureFlagsClient } from "@core-sdk/feature-flags-sdk";
|
|
20
|
+
|
|
21
|
+
const client = new FeatureFlagsClient({
|
|
22
|
+
baseUrl: "https://flag-api.example.com",
|
|
23
|
+
projectKey: "my-project",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
await client.bootstrap();
|
|
27
|
+
|
|
28
|
+
client.identify(profile.userId, {
|
|
29
|
+
email: profile.email,
|
|
30
|
+
accountId: profile.accountId,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Uses merged context (email, accountId, userId = profile.userId)
|
|
34
|
+
const on = await client.isEnabled("new-checkout");
|
|
35
|
+
|
|
36
|
+
// Flag-specific props override the base for this call only
|
|
37
|
+
const promo = await client.isEnabled("promo-banner", { channelId: "web" });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- **`reset()`** clears the identified person; evaluation uses `{}` again until the next `identify`.
|
|
41
|
+
- **`distinctIdKey`** (optional, default `userId`): which context key receives the first `identify` argument. Match your portal rule `field` names.
|
|
42
|
+
|
|
43
|
+
Until **`identify`** is called, `isEnabled("x")` uses an empty context (`{}`). Rules that require user fields therefore evaluate to **false** for logged-out users—no extra anonymous id layer is needed.
|
|
44
|
+
|
|
45
|
+
## API routes
|
|
46
|
+
|
|
47
|
+
The client calls:
|
|
48
|
+
|
|
49
|
+
- `GET {baseUrl}/projects/{projectKey}/flags`
|
|
50
|
+
- `GET {baseUrl}/projects/{projectKey}/flags/{flagKey}`
|
|
51
|
+
- `GET .../flags/{flagKey}/history`
|
|
52
|
+
- `GET .../flags/{flagKey}/versions/{version}`
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
See your organization’s license terms.
|
package/dist/client.d.ts
CHANGED
|
@@ -4,7 +4,26 @@ export declare class FeatureFlagsClient {
|
|
|
4
4
|
private readonly config;
|
|
5
5
|
private readonly fetchImpl;
|
|
6
6
|
private readonly cache;
|
|
7
|
+
/** Merged evaluation base from `identify` (empty until then). */
|
|
8
|
+
private personBase;
|
|
9
|
+
private identifiedDistinctId;
|
|
10
|
+
private identifiedTraits;
|
|
7
11
|
constructor(config: FeatureFlagsSdkConfig);
|
|
12
|
+
private distinctIdKey;
|
|
13
|
+
private rebuildPersonBase;
|
|
14
|
+
/**
|
|
15
|
+
* Merge stored person context with per-call overrides (override wins on key clash).
|
|
16
|
+
*/
|
|
17
|
+
private mergeContext;
|
|
18
|
+
/**
|
|
19
|
+
* Set person traits once (PostHog-style). First argument is written to `distinctIdKey` (default `userId`)
|
|
20
|
+
* and wins over the same key in `traits` if present.
|
|
21
|
+
*/
|
|
22
|
+
identify(distinctId: string, traits?: EvaluationContext): void;
|
|
23
|
+
/**
|
|
24
|
+
* Clear identified person; evaluation base becomes `{}` until `identify` is called again.
|
|
25
|
+
*/
|
|
26
|
+
reset(): void;
|
|
8
27
|
private headers;
|
|
9
28
|
private parseJson;
|
|
10
29
|
/**
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACvB,MAAM,8BAA8B,CAAC;AAStC,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EAEtB,MAAM,YAAY,CAAC;AAiBpB,qBAAa,kBAAkB;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACvB,MAAM,8BAA8B,CAAC;AAStC,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EAEtB,MAAM,YAAY,CAAC;AAiBpB,qBAAa,kBAAkB;IAQjB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,iEAAiE;IACjE,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,gBAAgB,CAAyB;gBAEpB,MAAM,EAAE,qBAAqB;IAQ1D,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,iBAAiB;IAYzB;;OAEG;IACH,OAAO,CAAC,YAAY;IAIpB;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,GAAE,iBAAsB,GAAG,IAAI;IAMlE;;OAEG;IACH,KAAK,IAAI,IAAI;YAMC,OAAO;YAQP,SAAS;IAUvB;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAsB/C;;OAEG;IACG,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;IA2BtE,QAAQ,CACN,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,iBAAsB,GAC9B,kBAAkB;IAUf,aAAa,CACjB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,kBAAkB,CAAC;IAQxB,SAAS,CACb,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,OAAO,CAAC;IAUnB;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAanD;;OAEG;IACG,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAc1E,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;CAOnC"}
|
package/dist/client.js
CHANGED
|
@@ -19,10 +19,52 @@ export class FeatureFlagsClient {
|
|
|
19
19
|
config;
|
|
20
20
|
fetchImpl;
|
|
21
21
|
cache;
|
|
22
|
+
/** Merged evaluation base from `identify` (empty until then). */
|
|
23
|
+
personBase = {};
|
|
24
|
+
identifiedDistinctId = null;
|
|
25
|
+
identifiedTraits = {};
|
|
22
26
|
constructor(config) {
|
|
23
27
|
this.config = config;
|
|
24
28
|
this.fetchImpl = config.fetchImpl ?? defaultFetch();
|
|
25
29
|
this.cache = new TtlMemoryCache(config.cacheTtlMs ?? 60_000);
|
|
30
|
+
this.rebuildPersonBase();
|
|
31
|
+
}
|
|
32
|
+
distinctIdKey() {
|
|
33
|
+
return this.config.distinctIdKey ?? "userId";
|
|
34
|
+
}
|
|
35
|
+
rebuildPersonBase() {
|
|
36
|
+
if (this.identifiedDistinctId === null) {
|
|
37
|
+
this.personBase = {};
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const key = this.distinctIdKey();
|
|
41
|
+
this.personBase = {
|
|
42
|
+
...this.identifiedTraits,
|
|
43
|
+
[key]: this.identifiedDistinctId,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Merge stored person context with per-call overrides (override wins on key clash).
|
|
48
|
+
*/
|
|
49
|
+
mergeContext(extra) {
|
|
50
|
+
return { ...this.personBase, ...extra };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Set person traits once (PostHog-style). First argument is written to `distinctIdKey` (default `userId`)
|
|
54
|
+
* and wins over the same key in `traits` if present.
|
|
55
|
+
*/
|
|
56
|
+
identify(distinctId, traits = {}) {
|
|
57
|
+
this.identifiedDistinctId = distinctId;
|
|
58
|
+
this.identifiedTraits = { ...traits };
|
|
59
|
+
this.rebuildPersonBase();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Clear identified person; evaluation base becomes `{}` until `identify` is called again.
|
|
63
|
+
*/
|
|
64
|
+
reset() {
|
|
65
|
+
this.identifiedDistinctId = null;
|
|
66
|
+
this.identifiedTraits = {};
|
|
67
|
+
this.rebuildPersonBase();
|
|
26
68
|
}
|
|
27
69
|
async headers() {
|
|
28
70
|
const extra = this.config.getHeaders ? await this.config.getHeaders() : {};
|
|
@@ -93,14 +135,14 @@ export class FeatureFlagsClient {
|
|
|
93
135
|
if (!rec) {
|
|
94
136
|
throw new Error(`Flag not in cache: ${flagKey}; call bootstrap() or getFlag() first`);
|
|
95
137
|
}
|
|
96
|
-
return evaluateFlag(toDefinition(rec), context);
|
|
138
|
+
return evaluateFlag(toDefinition(rec), this.mergeContext(context));
|
|
97
139
|
}
|
|
98
140
|
async evaluateAsync(flagKey, context = {}) {
|
|
99
141
|
const rec = (await this.getFlag(flagKey)) ?? this.cache.get(flagKey);
|
|
100
142
|
if (!rec) {
|
|
101
143
|
throw new Error(`Unknown flag: ${flagKey}`);
|
|
102
144
|
}
|
|
103
|
-
return evaluateFlag(toDefinition(rec), context);
|
|
145
|
+
return evaluateFlag(toDefinition(rec), this.mergeContext(context));
|
|
104
146
|
}
|
|
105
147
|
async isEnabled(flagKey, context = {}) {
|
|
106
148
|
try {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { FeatureFlagsClient } from "./client.js";
|
|
2
2
|
export type { FeatureFlagRecord, FeatureFlagsSdkConfig, FlagListJson, HistoryEntryJson, HttpFetch, } from "./types.js";
|
|
3
|
-
export type
|
|
3
|
+
export type { EvaluationContext } from "@core-sdk/feature-flags-core";
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,YAAY,EACZ,gBAAgB,EAChB,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,YAAY,EACZ,gBAAgB,EAChB,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export interface FeatureFlagsSdkConfig {
|
|
|
17
17
|
readonly getHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
18
18
|
/** Cache TTL for in-memory flag definitions (default 60_000) */
|
|
19
19
|
readonly cacheTtlMs?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Context key for `identify(distinctId, traits)` first argument (default `userId`).
|
|
22
|
+
* Align with declarative rule `field` names from your flag API.
|
|
23
|
+
*/
|
|
24
|
+
readonly distinctIdKey?: string;
|
|
20
25
|
}
|
|
21
26
|
/** Loose API body — server may wrap in `{ data }`. */
|
|
22
27
|
export type FlagListJson = unknown;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;CAC5C;AAED,MAAM,MAAM,SAAS,GAAG,CACtB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,WAAW,KACf,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,+BAA+B;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,yCAAyC;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAClB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACtB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpC,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;CAC5C;AAED,MAAM,MAAM,SAAS,GAAG,CACtB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,WAAW,KACf,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,+BAA+B;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,yCAAyC;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAClB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACtB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpC,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,sDAAsD;AACtD,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC;AAEnC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC"}
|
package/package.json
CHANGED