@core-sdk/feature-flags-sdk 0.1.2 → 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 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
  /**
@@ -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;IAIjB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;gBAE7B,MAAM,EAAE,qBAAqB;YAO5C,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"}
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/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;
@@ -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;CAC9B;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"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@core-sdk/feature-flags-sdk",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "HTTP client + cache for feature flags; uses @core-sdk/feature-flags-core for evaluation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",