@omniretail/omniflags-core 1.0.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,143 @@
1
+ # @omniretail/omniflags-core
2
+
3
+ JavaScript/TypeScript SDK for OmniFlags.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @omniretail/omniflags-core
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```ts
14
+ import { OmniFlagsClient } from "@omniretail/omniflags-core";
15
+
16
+ const client = new OmniFlagsClient({ sdkKey: "your-sdk-key" });
17
+
18
+ await client.waitForReady();
19
+
20
+ const enabled = client.isEnabled("store.dark-mode");
21
+ const theme = client.getString("store.theme", "light");
22
+ ```
23
+
24
+ ## Configuration
25
+
26
+ ```ts
27
+ const client = new OmniFlagsClient({
28
+ sdkKey: "your-sdk-key", // required
29
+ hooks: [loggingHook], // optional evaluation hooks
30
+ });
31
+ ```
32
+
33
+ | Option | Type | Description |
34
+ |--------|------|-------------|
35
+ | `sdkKey` | `string` | **Required.** SDK key from the OmniFlags dashboard. |
36
+ | `hooks` | `Hook[]` | Lifecycle hooks called before/after each evaluation. |
37
+
38
+ ## Evaluation
39
+
40
+ Flag keys are namespaced by project: `{projectKey}.{flagKey}`.
41
+
42
+ ```ts
43
+ // Boolean
44
+ client.isEnabled("store.show-banner");
45
+ client.isEnabled("store.show-banner", { customerId: "cust-123" }); // with context
46
+
47
+ // Typed value methods
48
+ client.getBoolean("store.dark-mode", false);
49
+ client.getString("store.theme", "light");
50
+ client.getNumber("store.max-items", 10);
51
+
52
+ // Generic — useful when the type is only known at runtime
53
+ client.getValue<{ color: string }>("store.config", defaultConfig);
54
+
55
+ // Full evaluation result (variant, reason, rule matched)
56
+ const result = client.getVariant("store.show-banner");
57
+ // result.value, result.variant, result.reason, result.ruleId, result.errorCode
58
+ ```
59
+
60
+ ## Context
61
+
62
+ Pass context per evaluation call — customer, business, branch, country, etc. There is no persistent context state on the client.
63
+
64
+ ```ts
65
+ const ctx = {
66
+ customerId: request.customerId,
67
+ agentId: request.agentId,
68
+ businessId: request.businessId,
69
+ businessBranchId: request.businessBranchId,
70
+ };
71
+
72
+ client.isEnabled("store.show-banner", ctx);
73
+ client.getString("store.theme", "light", ctx);
74
+ ```
75
+
76
+ ## Loading state
77
+
78
+ ```ts
79
+ const state = client.loadingState;
80
+ // state.isFetching — a network request is in flight
81
+ // state.isLoading — no flags have been loaded yet (first fetch pending)
82
+ // state.origin — "NONE" | "CACHE" | "SERVER"
83
+ // state.error — last fetch error, or null
84
+ ```
85
+
86
+ Subscribe to changes:
87
+
88
+ ```ts
89
+ const unsub = client.on("PROVIDER_LOADING_STATE_CHANGED", () => {
90
+ console.log(client.loadingState);
91
+ });
92
+
93
+ unsub(); // remove the listener
94
+ ```
95
+
96
+ ## Status & lifecycle
97
+
98
+ ```ts
99
+ client.status; // "not_ready" | "ready" | "stale" | "error"
100
+ await client.waitForReady(); // resolves after the first fetch or cache hit
101
+ await client.shutdown(); // stop polling and flush metrics
102
+ ```
103
+
104
+ The SDK polls for flag updates automatically on a server-driven interval (default: 5 minutes).
105
+
106
+ ## Events
107
+
108
+ ```ts
109
+ const unsub = client.on("PROVIDER_CONFIGURATION_CHANGED", () => {
110
+ // flags updated
111
+ });
112
+
113
+ unsub(); // remove the listener
114
+ ```
115
+
116
+ | Event | When |
117
+ |-------|------|
118
+ | `PROVIDER_READY` | First snapshot loaded |
119
+ | `PROVIDER_STALE` | Fetch failed but cached flags are available |
120
+ | `PROVIDER_ERROR` | Fetch failed and no cache |
121
+ | `PROVIDER_CONFIGURATION_CHANGED` | Flags updated by a background poll |
122
+ | `PROVIDER_LOADING_STATE_CHANGED` | Loading state changed |
123
+
124
+ ## Hooks
125
+
126
+ ```ts
127
+ import type { Hook } from "@omniretail/omniflags-core";
128
+
129
+ const loggingHook: Hook = {
130
+ after(flagKey, result) {
131
+ console.log(`[flag] ${flagKey} → ${result.variant} (${result.reason})`);
132
+ },
133
+ };
134
+
135
+ const client = new OmniFlagsClient({ sdkKey: "your-sdk-key", hooks: [loggingHook] });
136
+
137
+ // Or add after construction
138
+ client.addHook(loggingHook);
139
+ ```
140
+
141
+ ## Metrics
142
+
143
+ Flag evaluations are tracked and flushed to OmniFlags automatically. The flush interval and batch size are driven by the server — no configuration needed.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * FNV-1a (32-bit) deterministic bucketing.
3
+ *
4
+ * Canonical input: `{flagKey}|{environmentKey}|{targetingKey}|{salt}`
5
+ * Result range: [1, 100_000]
6
+ */
7
+ /** Compute FNV-1a 32-bit hash of a UTF-8 string */
8
+ export declare function fnv1a32(input: string): number;
9
+ /**
10
+ * Compute the deterministic bucket for a flag + targeting key combination.
11
+ * @returns bucket in range [1, 100_000]
12
+ * @throws BucketError with code MISSING_TARGETING_KEY if targetingKey is empty
13
+ */
14
+ export declare function computeBucket(flagKey: string, environmentKey: string, targetingKey: string, salt?: string): number;
15
+ /** Resolve rollout variant from bucket value and weighted partitions */
16
+ export declare function resolveRolloutVariant(bucket: number, rollout: {
17
+ variant: string;
18
+ weight: number;
19
+ }[]): string;
20
+ export declare class BucketError extends Error {
21
+ readonly code: string;
22
+ constructor(code: string);
23
+ }
package/dist/bucket.js ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * FNV-1a (32-bit) deterministic bucketing.
3
+ *
4
+ * Canonical input: `{flagKey}|{environmentKey}|{targetingKey}|{salt}`
5
+ * Result range: [1, 100_000]
6
+ */
7
+ const FNV_OFFSET_BASIS = 0x811c9dc5;
8
+ const FNV_PRIME = 0x01000193;
9
+ /** Compute FNV-1a 32-bit hash of a UTF-8 string */
10
+ export function fnv1a32(input) {
11
+ let hash = FNV_OFFSET_BASIS;
12
+ const bytes = new TextEncoder().encode(input);
13
+ for (const byte of bytes) {
14
+ hash ^= byte;
15
+ hash = Math.imul(hash, FNV_PRIME) >>> 0;
16
+ }
17
+ return hash >>> 0;
18
+ }
19
+ /**
20
+ * Compute the deterministic bucket for a flag + targeting key combination.
21
+ * @returns bucket in range [1, 100_000]
22
+ * @throws BucketError with code MISSING_TARGETING_KEY if targetingKey is empty
23
+ */
24
+ export function computeBucket(flagKey, environmentKey, targetingKey, salt = "v1") {
25
+ if (!targetingKey) {
26
+ throw new BucketError("MISSING_TARGETING_KEY");
27
+ }
28
+ const input = `${flagKey}|${environmentKey}|${targetingKey}|${salt}`;
29
+ return (fnv1a32(input) % 100_000) + 1;
30
+ }
31
+ /** Resolve rollout variant from bucket value and weighted partitions */
32
+ export function resolveRolloutVariant(bucket, rollout) {
33
+ let cumulative = 0;
34
+ for (const split of rollout) {
35
+ cumulative += split.weight;
36
+ if (bucket <= cumulative) {
37
+ return split.variant;
38
+ }
39
+ }
40
+ return rollout[rollout.length - 1].variant;
41
+ }
42
+ export class BucketError extends Error {
43
+ code;
44
+ constructor(code) {
45
+ super(code);
46
+ this.code = code;
47
+ this.name = "BucketError";
48
+ }
49
+ }
50
+ //# sourceMappingURL=bucket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bucket.js","sourceRoot":"","sources":["../src/bucket.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,gBAAgB,GAAG,UAAU,CAAC;AACpC,MAAM,SAAS,GAAG,UAAU,CAAC;AAE7B,mDAAmD;AACnD,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,IAAI,IAAI,GAAG,gBAAgB,CAAC;IAC5B,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,IAAI,CAAC;QACb,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAe,EACf,cAAsB,EACtB,YAAoB,EACpB,IAAI,GAAG,IAAI;IAEX,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,WAAW,CAAC,uBAAuB,CAAC,CAAC;IACjD,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,OAAO,IAAI,cAAc,IAAI,YAAY,IAAI,IAAI,EAAE,CAAC;IACrE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;AACxC,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,qBAAqB,CACnC,MAAc,EACd,OAA8C;IAE9C,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,UAAU,IAAI,KAAK,CAAC,MAAM,CAAC;QAC3B,IAAI,MAAM,IAAI,UAAU,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC,OAAO,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;AAC7C,CAAC;AAED,MAAM,OAAO,WAAY,SAAQ,KAAK;IACR;IAA5B,YAA4B,IAAY;QACtC,KAAK,CAAC,IAAI,CAAC,CAAC;QADc,SAAI,GAAJ,IAAI,CAAQ;QAEtC,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ import type { CacheAdapter } from "./types.js";
2
+ export declare class LocalStorageCache implements CacheAdapter {
3
+ get(key: string): Promise<string | null>;
4
+ set(key: string, value: string): Promise<void>;
5
+ remove(key: string): Promise<void>;
6
+ }
7
+ export declare class MemoryCache implements CacheAdapter {
8
+ private store;
9
+ get(key: string): Promise<string | null>;
10
+ set(key: string, value: string): Promise<void>;
11
+ remove(key: string): Promise<void>;
12
+ }
package/dist/cache.js ADDED
@@ -0,0 +1,30 @@
1
+ const PREFIX = "omniflags:snapshot:";
2
+ export class LocalStorageCache {
3
+ async get(key) {
4
+ try {
5
+ return globalThis.localStorage.getItem(PREFIX + key);
6
+ }
7
+ catch {
8
+ return null;
9
+ }
10
+ }
11
+ async set(key, value) {
12
+ try {
13
+ globalThis.localStorage.setItem(PREFIX + key, value);
14
+ }
15
+ catch { /* full or unavailable */ }
16
+ }
17
+ async remove(key) {
18
+ try {
19
+ globalThis.localStorage.removeItem(PREFIX + key);
20
+ }
21
+ catch { /* ignore */ }
22
+ }
23
+ }
24
+ export class MemoryCache {
25
+ store = new Map();
26
+ async get(key) { return this.store.get(key) ?? null; }
27
+ async set(key, value) { this.store.set(key, value); }
28
+ async remove(key) { this.store.delete(key); }
29
+ }
30
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAG,qBAAqB,CAAC;AAErC,MAAM,OAAO,iBAAiB;IAC5B,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,IAAI,CAAC;YAAC,OAAO,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,IAAI,CAAC;QAAC,CAAC;IACtF,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAa;QAClC,IAAI,CAAC;YAAC,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,yBAAyB,CAAC,CAAC;IACnG,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,IAAI,CAAC;YAAC,UAAU,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAClF,CAAC;CACF;AAED,MAAM,OAAO,WAAW;IACd,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,KAAK,CAAC,GAAG,CAAC,GAAW,IAA4B,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;IACtF,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAa,IAAmB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACpF,KAAK,CAAC,MAAM,CAAC,GAAW,IAAmB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CACrE"}
@@ -0,0 +1,46 @@
1
+ import type { CacheAdapter, EvaluationContext, EvaluationResult, Hook, LoadingState, OmniFlagsOptions, ProviderEvent, ProviderStatus } from "./types.js";
2
+ export declare class OmniFlagsClient {
3
+ private flags;
4
+ private hooks;
5
+ private provider;
6
+ private cache?;
7
+ private readonly sdkKey;
8
+ private pollingTimer;
9
+ private _status;
10
+ private _readyPromise;
11
+ private _resolveReady;
12
+ private metrics?;
13
+ private eventHandlers;
14
+ private _isFetching;
15
+ private _origin;
16
+ private _fetchError;
17
+ private envKey;
18
+ private pollingIntervalMs;
19
+ constructor(opts: OmniFlagsOptions, cache?: CacheAdapter);
20
+ get status(): ProviderStatus;
21
+ get loadingState(): LoadingState;
22
+ waitForReady(): Promise<void>;
23
+ /** Trigger an immediate poll without waiting for the next scheduled interval. */
24
+ refresh(): void;
25
+ shutdown(): Promise<void>;
26
+ isEnabled(flagKey: string, context?: EvaluationContext, defaultValue?: boolean): boolean;
27
+ getValue<T = unknown>(flagKey: string, context?: EvaluationContext, defaultValue?: T): T;
28
+ getVariant(flagKey: string, context?: EvaluationContext): EvaluationResult;
29
+ getBoolean(flagKey: string, defaultValue: boolean, context?: EvaluationContext): boolean;
30
+ getString(flagKey: string, defaultValue: string, context?: EvaluationContext): string;
31
+ getNumber(flagKey: string, defaultValue: number, context?: EvaluationContext): number;
32
+ getObject<T = Record<string, unknown>>(flagKey: string, defaultValue: T, context?: EvaluationContext): T;
33
+ getBooleanDetail(flagKey: string, defaultValue: boolean, context?: EvaluationContext): EvaluationResult<boolean>;
34
+ getStringDetail(flagKey: string, defaultValue: string, context?: EvaluationContext): EvaluationResult<string>;
35
+ addHook(hook: Hook): void;
36
+ on(event: ProviderEvent, handler: () => void): () => void;
37
+ private evaluateSync;
38
+ private applySnapshot;
39
+ private init;
40
+ private startPolling;
41
+ private stopPolling;
42
+ private poll;
43
+ private loadFromCache;
44
+ private saveToCache;
45
+ private emitEvent;
46
+ }
package/dist/client.js ADDED
@@ -0,0 +1,265 @@
1
+ import { PermanentProviderError } from "./types.js";
2
+ import { evaluate } from "./evaluator.js";
3
+ import { HttpProvider } from "./http-provider.js";
4
+ import { MetricsReporter } from "./metrics.js";
5
+ import { LocalStorageCache } from "./cache.js";
6
+ export class OmniFlagsClient {
7
+ flags = new Map();
8
+ hooks = [];
9
+ provider;
10
+ cache;
11
+ sdkKey;
12
+ pollingTimer = null;
13
+ _status = "not_ready";
14
+ _readyPromise;
15
+ _resolveReady;
16
+ metrics;
17
+ eventHandlers = new Map();
18
+ // Loading state
19
+ _isFetching = false;
20
+ _origin = "NONE";
21
+ _fetchError = null;
22
+ // Derived from the first snapshot — not required from the caller
23
+ envKey = "";
24
+ pollingIntervalMs = 30_000; // overwritten by server on first fetch
25
+ constructor(opts, cache) {
26
+ if (opts.sdkKey.startsWith("sk_")) {
27
+ throw new Error("OmniFlags: server-side SDK keys (sk_) cannot be used in client-side SDKs. " +
28
+ "Create a client key (pk_) for use in browser and mobile applications.");
29
+ }
30
+ this.sdkKey = opts.sdkKey;
31
+ this.cache = cache ?? new LocalStorageCache();
32
+ this.hooks = opts.hooks ?? [];
33
+ this.provider = opts.provider ?? new HttpProvider();
34
+ this._readyPromise = new Promise((resolve) => { this._resolveReady = resolve; });
35
+ void this.init();
36
+ }
37
+ // ── Lifecycle ──────────────────────────────────────────────────────────
38
+ get status() { return this._status; }
39
+ get loadingState() {
40
+ return {
41
+ isFetching: this._isFetching,
42
+ isLoading: this._origin === "NONE",
43
+ origin: this._origin,
44
+ error: this._fetchError,
45
+ };
46
+ }
47
+ waitForReady() { return this._readyPromise; }
48
+ /** Trigger an immediate poll without waiting for the next scheduled interval. */
49
+ refresh() { void this.poll(); }
50
+ async shutdown() {
51
+ this.stopPolling();
52
+ await this.metrics?.shutdown();
53
+ await this.provider.shutdown?.();
54
+ }
55
+ // ── Typed Evaluation ───────────────────────────────────────────────────
56
+ isEnabled(flagKey, context, defaultValue = false) {
57
+ return this.evaluateSync(flagKey, "boolean", defaultValue, context).value;
58
+ }
59
+ getValue(flagKey, context, defaultValue) {
60
+ const flag = this.flags.get(flagKey);
61
+ const type = flag?.type ?? "string";
62
+ return this.evaluateSync(flagKey, type, defaultValue, context).value;
63
+ }
64
+ getVariant(flagKey, context) {
65
+ const flag = this.flags.get(flagKey);
66
+ const type = flag?.type ?? "boolean";
67
+ return this.evaluateSync(flagKey, type, null, context);
68
+ }
69
+ getBoolean(flagKey, defaultValue, context) {
70
+ return this.evaluateSync(flagKey, "boolean", defaultValue, context).value;
71
+ }
72
+ getString(flagKey, defaultValue, context) {
73
+ return this.evaluateSync(flagKey, "string", defaultValue, context).value;
74
+ }
75
+ getNumber(flagKey, defaultValue, context) {
76
+ return this.evaluateSync(flagKey, "number", defaultValue, context).value;
77
+ }
78
+ getObject(flagKey, defaultValue, context) {
79
+ return this.evaluateSync(flagKey, "json", defaultValue, context).value;
80
+ }
81
+ getBooleanDetail(flagKey, defaultValue, context) {
82
+ return this.evaluateSync(flagKey, "boolean", defaultValue, context);
83
+ }
84
+ getStringDetail(flagKey, defaultValue, context) {
85
+ return this.evaluateSync(flagKey, "string", defaultValue, context);
86
+ }
87
+ // ── Hooks & Events ─────────────────────────────────────────────────────
88
+ addHook(hook) { this.hooks.push(hook); }
89
+ on(event, handler) {
90
+ if (!this.eventHandlers.has(event))
91
+ this.eventHandlers.set(event, new Set());
92
+ this.eventHandlers.get(event).add(handler);
93
+ return () => { this.eventHandlers.get(event)?.delete(handler); };
94
+ }
95
+ // ── Internal ───────────────────────────────────────────────────────────
96
+ evaluateSync(flagKey, expectedType, callerDefault, invocationContext) {
97
+ if (this._status === "not_ready") {
98
+ return { value: callerDefault, variant: null, reason: "ERROR", ruleId: null, errorCode: "PROVIDER_NOT_READY" };
99
+ }
100
+ const ctx = invocationContext ?? {};
101
+ for (const h of this.hooks) {
102
+ try {
103
+ h.before?.(flagKey, ctx);
104
+ }
105
+ catch { /* swallow */ }
106
+ }
107
+ try {
108
+ const flag = this.flags.get(flagKey);
109
+ const result = evaluate(flag, flagKey, expectedType, callerDefault, ctx, this.envKey);
110
+ if (result.variant)
111
+ this.metrics?.track(flagKey, result.variant);
112
+ for (const h of this.hooks) {
113
+ try {
114
+ h.after?.(flagKey, result);
115
+ }
116
+ catch { /* swallow */ }
117
+ }
118
+ return result;
119
+ }
120
+ catch (err) {
121
+ for (const h of this.hooks) {
122
+ try {
123
+ h.error?.(flagKey, err);
124
+ }
125
+ catch { /* swallow */ }
126
+ }
127
+ return { value: callerDefault, variant: null, reason: "ERROR", ruleId: null, errorCode: "UNKNOWN" };
128
+ }
129
+ }
130
+ applySnapshot(snapshot) {
131
+ // Derive envKey and polling interval from the server response
132
+ if (snapshot.envKey)
133
+ this.envKey = snapshot.envKey;
134
+ if (snapshot.pollingIntervalMs > 0)
135
+ this.pollingIntervalMs = snapshot.pollingIntervalMs;
136
+ const m = new Map();
137
+ for (const f of snapshot.flags)
138
+ m.set(f.key, f);
139
+ this.flags = m;
140
+ // Wire up metrics once we know envKey (first snapshot)
141
+ if (!this.metrics && this.envKey) {
142
+ this.metrics = new MetricsReporter(this.sdkKey, this.envKey);
143
+ }
144
+ // Apply server-driven metrics config on every snapshot
145
+ if (snapshot.metricsFlushIntervalMs > 0 && snapshot.metricsMaxBatchSize > 0) {
146
+ this.metrics?.updateConfig(snapshot.metricsFlushIntervalMs, snapshot.metricsMaxBatchSize);
147
+ }
148
+ }
149
+ // ── Init & Polling ─────────────────────────────────────────────────────
150
+ async init() {
151
+ await this.loadFromCache();
152
+ this._isFetching = true;
153
+ this.emitEvent("PROVIDER_LOADING_STATE_CHANGED");
154
+ try {
155
+ const snapshot = await this.provider.initialize(this.sdkKey);
156
+ if (snapshot) {
157
+ this.applySnapshot(snapshot);
158
+ await this.saveToCache(snapshot);
159
+ }
160
+ this._origin = "SERVER";
161
+ this._fetchError = null;
162
+ this._status = "ready";
163
+ this._resolveReady();
164
+ this.emitEvent("PROVIDER_READY");
165
+ }
166
+ catch (err) {
167
+ this._fetchError = err instanceof Error ? err : new Error(String(err));
168
+ if (this.flags.size > 0) {
169
+ this._status = "stale";
170
+ this._resolveReady();
171
+ this.emitEvent("PROVIDER_STALE");
172
+ }
173
+ else {
174
+ this._status = "error";
175
+ this._resolveReady();
176
+ this.emitEvent("PROVIDER_ERROR");
177
+ }
178
+ }
179
+ finally {
180
+ this._isFetching = false;
181
+ this.emitEvent("PROVIDER_LOADING_STATE_CHANGED");
182
+ }
183
+ this.startPolling();
184
+ this.metrics?.start();
185
+ }
186
+ startPolling() {
187
+ if (this.pollingTimer)
188
+ return;
189
+ // Use server-driven interval, re-schedule if interval changes after next fetch
190
+ const scheduleNext = () => {
191
+ this.pollingTimer = setTimeout(() => void this.poll().then(scheduleNext), this.pollingIntervalMs);
192
+ };
193
+ scheduleNext();
194
+ }
195
+ stopPolling() {
196
+ if (this.pollingTimer) {
197
+ clearTimeout(this.pollingTimer);
198
+ this.pollingTimer = null;
199
+ }
200
+ }
201
+ async poll() {
202
+ this._isFetching = true;
203
+ this.emitEvent("PROVIDER_LOADING_STATE_CHANGED");
204
+ try {
205
+ const snap = await this.provider.refresh?.(this.sdkKey);
206
+ if (snap) {
207
+ this.applySnapshot(snap);
208
+ await this.saveToCache(snap);
209
+ this._origin = "SERVER";
210
+ this._fetchError = null;
211
+ this._status = "ready";
212
+ this.emitEvent("PROVIDER_CONFIGURATION_CHANGED");
213
+ }
214
+ }
215
+ catch (err) {
216
+ this._fetchError = err instanceof Error ? err : new Error(String(err));
217
+ if (err instanceof PermanentProviderError) {
218
+ this.stopPolling();
219
+ this._status = "error";
220
+ this.emitEvent("PROVIDER_ERROR");
221
+ return;
222
+ }
223
+ if (this._status === "ready") {
224
+ this._status = "stale";
225
+ this.emitEvent("PROVIDER_STALE");
226
+ }
227
+ }
228
+ finally {
229
+ this._isFetching = false;
230
+ this.emitEvent("PROVIDER_LOADING_STATE_CHANGED");
231
+ }
232
+ }
233
+ async loadFromCache() {
234
+ if (!this.cache)
235
+ return;
236
+ try {
237
+ // Cache key is sdkKey — each key maps to exactly one environment
238
+ const raw = await this.cache.get(this.sdkKey);
239
+ if (raw) {
240
+ this.applySnapshot(JSON.parse(raw));
241
+ this._origin = "CACHE";
242
+ }
243
+ }
244
+ catch { /* corrupt cache */ }
245
+ }
246
+ async saveToCache(snapshot) {
247
+ if (!this.cache)
248
+ return;
249
+ try {
250
+ await this.cache.set(this.sdkKey, JSON.stringify(snapshot));
251
+ }
252
+ catch { /* non-fatal */ }
253
+ }
254
+ emitEvent(event) {
255
+ const handlers = this.eventHandlers.get(event);
256
+ if (handlers)
257
+ for (const h of handlers) {
258
+ try {
259
+ h();
260
+ }
261
+ catch { /* swallow */ }
262
+ }
263
+ }
264
+ }
265
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { EvaluationContext } from "./types.js";
2
+ /**
3
+ * Merge contexts with precedence: global < client < invocation.
4
+ * Higher precedence overwrites lower on key collision.
5
+ */
6
+ export declare function mergeContexts(...contexts: (EvaluationContext | undefined)[]): EvaluationContext;
7
+ /**
8
+ * Resolve a dot-path attribute from the evaluation context.
9
+ * e.g. "user.plan" resolves ctx.user.plan if nested.
10
+ */
11
+ export declare function resolveAttribute(ctx: EvaluationContext, attribute: string): unknown;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Merge contexts with precedence: global < client < invocation.
3
+ * Higher precedence overwrites lower on key collision.
4
+ */
5
+ export function mergeContexts(...contexts) {
6
+ const result = {};
7
+ for (const ctx of contexts) {
8
+ if (ctx) {
9
+ Object.assign(result, ctx);
10
+ }
11
+ }
12
+ return result;
13
+ }
14
+ /**
15
+ * Resolve a dot-path attribute from the evaluation context.
16
+ * e.g. "user.plan" resolves ctx.user.plan if nested.
17
+ */
18
+ export function resolveAttribute(ctx, attribute) {
19
+ if (attribute in ctx) {
20
+ return ctx[attribute];
21
+ }
22
+ const parts = attribute.split(".");
23
+ let current = ctx;
24
+ for (const part of parts) {
25
+ if (current == null || typeof current !== "object")
26
+ return undefined;
27
+ current = current[part];
28
+ }
29
+ return current;
30
+ }
31
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,GAAG,QAA2C;IAE9C,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAsB,EAAE,SAAiB;IACxE,IAAI,SAAS,IAAI,GAAG,EAAE,CAAC;QACrB,OAAO,GAAG,CAAC,SAAS,CAAC,CAAC;IACxB,CAAC;IACD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,OAAO,GAAY,GAAG,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACrE,OAAO,GAAI,OAAmC,CAAC,IAAI,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { EvaluationContext, EvaluationResult, FlagConfig, FlagType } from "./types.js";
2
+ /**
3
+ * Core evaluation engine implementing evaluation-spec v2 §5.
4
+ * All SDKs must produce identical results for identical inputs.
5
+ */
6
+ export declare function evaluate(flag: FlagConfig | undefined, _flagKey: string, expectedType: FlagType, callerDefault: unknown, context: EvaluationContext, envKey: string): EvaluationResult;