@featureflip/js 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,118 @@
1
+ # @featureflip/sdk
2
+
3
+ Server-side JavaScript/TypeScript SDK for [Featureflip](https://github.com/featureflip/featureflip) - evaluate feature flags locally with near-zero latency.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @featureflip/sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import { FeatureflipClient } from '@featureflip/sdk';
15
+
16
+ const client = new FeatureflipClient({
17
+ sdkKey: 'your-sdk-key',
18
+ });
19
+
20
+ await client.waitForInitialization();
21
+
22
+ const enabled = client.boolVariation('my-feature', { user_id: '123' }, false);
23
+
24
+ if (enabled) {
25
+ console.log('Feature is enabled!');
26
+ }
27
+
28
+ await client.close();
29
+ ```
30
+
31
+ ## Configuration
32
+
33
+ ```ts
34
+ const client = new FeatureflipClient({
35
+ sdkKey: 'your-sdk-key',
36
+ baseUrl: 'https://eval.featureflip.io', // Evaluation API URL (default)
37
+ streaming: true, // Use SSE for real-time updates (default)
38
+ pollInterval: 30000, // Polling interval in ms if streaming=false
39
+ flushInterval: 30000, // Event flush interval in ms
40
+ flushBatchSize: 100, // Events per batch
41
+ initTimeout: 10000, // Max ms to wait for initialization
42
+ maxStreamRetries: 5, // SSE retries before falling back to polling
43
+ });
44
+ ```
45
+
46
+ The SDK key can also be set via the `FEATUREFLIP_SDK_KEY` environment variable.
47
+
48
+ ## Evaluation
49
+
50
+ ```ts
51
+ const context = { user_id: '123', email: 'user@example.com' };
52
+
53
+ // Boolean flag
54
+ const enabled = client.boolVariation('feature-key', context, false);
55
+
56
+ // String flag
57
+ const tier = client.stringVariation('pricing-tier', context, 'free');
58
+
59
+ // Number flag
60
+ const limit = client.numberVariation('rate-limit', context, 100);
61
+
62
+ // JSON flag
63
+ const config = client.jsonVariation('ui-config', context, { theme: 'light' });
64
+ ```
65
+
66
+ ### Detailed Evaluation
67
+
68
+ ```ts
69
+ const detail = client.variationDetail('feature-key', { user_id: '123' }, false);
70
+
71
+ console.log(detail.value); // The evaluated value
72
+ console.log(detail.reason); // "RuleMatch", "Fallthrough", "FlagDisabled", etc.
73
+ console.log(detail.ruleId); // Rule ID if reason is "RuleMatch"
74
+ ```
75
+
76
+ ## Event Tracking
77
+
78
+ ```ts
79
+ // Track custom events
80
+ client.track('checkout-completed', { user_id: '123' }, { total: 99.99 });
81
+
82
+ // Identify users for segment building
83
+ client.identify({ user_id: '123', email: 'user@example.com', plan: 'pro' });
84
+
85
+ // Force flush pending events
86
+ await client.flush();
87
+ ```
88
+
89
+ ## Testing
90
+
91
+ Use `forTesting()` to create a client with predetermined flag values -- no network calls.
92
+
93
+ ```ts
94
+ const client = FeatureflipClient.forTesting({
95
+ 'my-feature': true,
96
+ 'pricing-tier': 'pro',
97
+ });
98
+
99
+ client.boolVariation('my-feature', {}, false); // true
100
+ client.stringVariation('pricing-tier', {}, 'free'); // 'pro'
101
+ client.boolVariation('unknown', {}, false); // false (default)
102
+ ```
103
+
104
+ ## Features
105
+
106
+ - **Local evaluation** - Near-zero latency after initialization
107
+ - **Real-time updates** - SSE streaming with automatic polling fallback
108
+ - **Event tracking** - Automatic batching and background flushing
109
+ - **Test support** - `forTesting()` factory for deterministic unit tests
110
+ - **TypeScript** - Full type definitions included
111
+
112
+ ## Requirements
113
+
114
+ - Node.js 18+
115
+
116
+ ## License
117
+
118
+ MIT
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./client-Bf3tS17L.cjs`);function t(){return{md5(e){return i(e)},createEventSource(e,t){let n=new EventSource(e);return{addEventListener:(e,t)=>{n.addEventListener(e,e=>t(e))},close:()=>n.close(),get readyState(){return n.readyState}}},async fetch(e,t){return globalThis.fetch(e,t)}}}var n=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],r=new Uint32Array(64);for(let e=0;e<64;e++)r[e]=Math.floor(2**32*Math.abs(Math.sin(e+1)))>>>0;function i(e){let t=new TextEncoder().encode(e),i=t.length*8,a=(56-(t.length+1)%64+64)%64,o=new Uint8Array(t.length+1+a+8);o.set(t),o[t.length]=128;let s=new DataView(o.buffer);s.setUint32(o.length-8,i>>>0,!0),s.setUint32(o.length-4,0,!0);let c=1732584193,l=4023233417,u=2562383102,d=271733878;for(let e=0;e<o.length;e+=64){let t=new Uint32Array(16);for(let n=0;n<16;n++)t[n]=s.getUint32(e+n*4,!0);let i=c,a=l,o=u,f=d;for(let e=0;e<64;e++){let s,c;e<16?(s=a&o|~a&f,c=e):e<32?(s=f&a|~f&o,c=(5*e+1)%16):e<48?(s=a^o^f,c=(3*e+5)%16):(s=o^(a|~f),c=7*e%16),s=s+i+r[e]+t[c]>>>0,i=f,f=o,o=a,a=a+(s<<n[e]|s>>>32-n[e])>>>0}c=c+i>>>0,l=l+a>>>0,u=u+o>>>0,d=d+f>>>0}let f=new Uint8Array(16),p=new DataView(f.buffer);return p.setUint32(0,c,!0),p.setUint32(4,l,!0),p.setUint32(8,u,!0),p.setUint32(12,d,!0),f}exports.FeatureflipClient=e.t,exports.createBrowserPlatform=t;
@@ -0,0 +1,117 @@
1
+ export declare function createBrowserPlatform(): Platform;
2
+
3
+ export declare type EvaluationContext = Record<string, unknown>;
4
+
5
+ export declare interface EvaluationDetail<T = unknown> {
6
+ value: T;
7
+ variationKey?: string;
8
+ reason: EvaluationReason;
9
+ ruleId?: string;
10
+ }
11
+
12
+ export declare type EvaluationReason = 'RuleMatch' | 'Fallthrough' | 'FlagDisabled' | 'FlagNotFound' | 'Error';
13
+
14
+ declare interface EventSourceLike {
15
+ addEventListener(type: string, listener: (event: {
16
+ data: string;
17
+ }) => void): void;
18
+ close(): void;
19
+ readonly readyState: number;
20
+ }
21
+
22
+ export declare class FeatureflipClient {
23
+ private readonly config;
24
+ private readonly store;
25
+ private readonly events;
26
+ private readonly platform;
27
+ private initialized;
28
+ private initPromise;
29
+ private eventSource;
30
+ private pollTimer;
31
+ private closed;
32
+ private streamRetryCount;
33
+ private streamRetryTimer;
34
+ constructor(config: FeatureflipConfig, platform: Platform);
35
+ /** Whether the client has successfully loaded initial flag data. */
36
+ get isInitialized(): boolean;
37
+ /**
38
+ * Wait for the client to finish initialization.
39
+ * Rejects after initTimeout if initial flag fetch fails.
40
+ */
41
+ waitForInitialization(): Promise<void>;
42
+ /**
43
+ * Evaluate a boolean flag.
44
+ * Returns defaultValue if flag not found or evaluation fails.
45
+ */
46
+ boolVariation(key: string, context: EvaluationContext, defaultValue: boolean): boolean;
47
+ /**
48
+ * Evaluate a string flag.
49
+ */
50
+ stringVariation(key: string, context: EvaluationContext, defaultValue: string): string;
51
+ /**
52
+ * Evaluate a number flag.
53
+ */
54
+ numberVariation(key: string, context: EvaluationContext, defaultValue: number): number;
55
+ /**
56
+ * Evaluate a JSON flag.
57
+ */
58
+ jsonVariation<T>(key: string, context: EvaluationContext, defaultValue: T): T;
59
+ /**
60
+ * Evaluate a flag and return the full detail including reason.
61
+ */
62
+ variationDetail<T>(key: string, context: EvaluationContext, defaultValue: T): EvaluationDetail<T>;
63
+ /**
64
+ * Track a custom event.
65
+ */
66
+ track(eventKey: string, context: EvaluationContext, metadata?: Record<string, unknown>): void;
67
+ /**
68
+ * Send an identify event for the given context.
69
+ */
70
+ identify(context: EvaluationContext): void;
71
+ /**
72
+ * Flush any pending events immediately.
73
+ */
74
+ flush(): Promise<void>;
75
+ /**
76
+ * Close the client, flushing pending events and stopping all connections.
77
+ */
78
+ close(): Promise<void>;
79
+ /**
80
+ * Create a test client with hardcoded flag values. No network calls.
81
+ */
82
+ static forTesting(flags: Record<string, unknown>): FeatureflipClient;
83
+ private evaluateFlag;
84
+ private initialize;
85
+ private fetchFlags;
86
+ private startDataSource;
87
+ private startStreaming;
88
+ private startPolling;
89
+ private fetchSingleFlag;
90
+ private recordEvaluation;
91
+ private headers;
92
+ }
93
+
94
+ export declare interface FeatureflipConfig {
95
+ sdkKey: string;
96
+ baseUrl: string;
97
+ streaming?: boolean;
98
+ pollInterval?: number;
99
+ flushInterval?: number;
100
+ flushBatchSize?: number;
101
+ initTimeout?: number;
102
+ maxStreamRetries?: number;
103
+ }
104
+
105
+ export declare type FlagType = 'Boolean' | 'String' | 'Number' | 'Json';
106
+
107
+ export declare interface Platform {
108
+ md5(input: string): Uint8Array;
109
+ createEventSource(url: string, headers: Record<string, string>): EventSourceLike;
110
+ fetch(url: string, init?: RequestInit): Promise<Response>;
111
+ /** Extra headers the platform can inject (e.g. User-Agent on Node). */
112
+ readonly extraHeaders?: Record<string, string>;
113
+ /** Whether the platform's EventSource implementation supports custom headers. */
114
+ readonly sseSupportsHeaders?: boolean;
115
+ }
116
+
117
+ export { }
@@ -0,0 +1,112 @@
1
+ import { t as e } from "./client-ChXPtrh5.js";
2
+ //#region src/platform/browser.ts
3
+ function t() {
4
+ return {
5
+ md5(e) {
6
+ return i(e);
7
+ },
8
+ createEventSource(e, t) {
9
+ let n = new EventSource(e);
10
+ return {
11
+ addEventListener: (e, t) => {
12
+ n.addEventListener(e, (e) => t(e));
13
+ },
14
+ close: () => n.close(),
15
+ get readyState() {
16
+ return n.readyState;
17
+ }
18
+ };
19
+ },
20
+ async fetch(e, t) {
21
+ return globalThis.fetch(e, t);
22
+ }
23
+ };
24
+ }
25
+ var n = [
26
+ 7,
27
+ 12,
28
+ 17,
29
+ 22,
30
+ 7,
31
+ 12,
32
+ 17,
33
+ 22,
34
+ 7,
35
+ 12,
36
+ 17,
37
+ 22,
38
+ 7,
39
+ 12,
40
+ 17,
41
+ 22,
42
+ 5,
43
+ 9,
44
+ 14,
45
+ 20,
46
+ 5,
47
+ 9,
48
+ 14,
49
+ 20,
50
+ 5,
51
+ 9,
52
+ 14,
53
+ 20,
54
+ 5,
55
+ 9,
56
+ 14,
57
+ 20,
58
+ 4,
59
+ 11,
60
+ 16,
61
+ 23,
62
+ 4,
63
+ 11,
64
+ 16,
65
+ 23,
66
+ 4,
67
+ 11,
68
+ 16,
69
+ 23,
70
+ 4,
71
+ 11,
72
+ 16,
73
+ 23,
74
+ 6,
75
+ 10,
76
+ 15,
77
+ 21,
78
+ 6,
79
+ 10,
80
+ 15,
81
+ 21,
82
+ 6,
83
+ 10,
84
+ 15,
85
+ 21,
86
+ 6,
87
+ 10,
88
+ 15,
89
+ 21
90
+ ], r = new Uint32Array(64);
91
+ for (let e = 0; e < 64; e++) r[e] = Math.floor(2 ** 32 * Math.abs(Math.sin(e + 1))) >>> 0;
92
+ function i(e) {
93
+ let t = new TextEncoder().encode(e), i = t.length * 8, a = (56 - (t.length + 1) % 64 + 64) % 64, o = new Uint8Array(t.length + 1 + a + 8);
94
+ o.set(t), o[t.length] = 128;
95
+ let s = new DataView(o.buffer);
96
+ s.setUint32(o.length - 8, i >>> 0, !0), s.setUint32(o.length - 4, 0, !0);
97
+ let c = 1732584193, l = 4023233417, u = 2562383102, d = 271733878;
98
+ for (let e = 0; e < o.length; e += 64) {
99
+ let t = new Uint32Array(16);
100
+ for (let n = 0; n < 16; n++) t[n] = s.getUint32(e + n * 4, !0);
101
+ let i = c, a = l, o = u, f = d;
102
+ for (let e = 0; e < 64; e++) {
103
+ let s, c;
104
+ e < 16 ? (s = a & o | ~a & f, c = e) : e < 32 ? (s = f & a | ~f & o, c = (5 * e + 1) % 16) : e < 48 ? (s = a ^ o ^ f, c = (3 * e + 5) % 16) : (s = o ^ (a | ~f), c = 7 * e % 16), s = s + i + r[e] + t[c] >>> 0, i = f, f = o, o = a, a = a + (s << n[e] | s >>> 32 - n[e]) >>> 0;
105
+ }
106
+ c = c + i >>> 0, l = l + a >>> 0, u = u + o >>> 0, d = d + f >>> 0;
107
+ }
108
+ let f = new Uint8Array(16), p = new DataView(f.buffer);
109
+ return p.setUint32(0, c, !0), p.setUint32(4, l, !0), p.setUint32(8, u, !0), p.setUint32(12, d, !0), f;
110
+ }
111
+ //#endregion
112
+ export { e as FeatureflipClient, t as createBrowserPlatform };
@@ -0,0 +1 @@
1
+ var e={streaming:!0,pollInterval:3e4,flushInterval:3e4,flushBatchSize:100,initTimeout:1e4,maxStreamRetries:5};function t(t){if(!t.sdkKey)throw Error(`sdkKey is required`);if(!t.baseUrl)throw Error(`baseUrl is required`);let n=t.baseUrl.replace(/\/+$/,``);return{sdkKey:t.sdkKey,baseUrl:n,streaming:t.streaming??e.streaming,pollInterval:t.pollInterval??e.pollInterval,flushInterval:t.flushInterval??e.flushInterval,flushBatchSize:t.flushBatchSize??e.flushBatchSize,initTimeout:t.initTimeout??e.initTimeout,maxStreamRetries:t.maxStreamRetries??e.maxStreamRetries}}var n=class{flags=new Map;segments=new Map;listeners=[];version=0;getFlag(e){return this.flags.get(e)}getSegment(e){return this.segments.get(e)}getAllFlags(){return Array.from(this.flags.values())}getVersion(){return this.version}init(e,t,n){this.flags.clear(),this.segments.clear();for(let t of e)this.flags.set(t.key,t);for(let e of t)this.segments.set(e.key,e);this.version=n;for(let t of e)this.notifyListeners(t.key)}upsert(e){let t=this.flags.get(e.key);t&&t.version>=e.version||(this.flags.set(e.key,e),this.notifyListeners(e.key))}delete(e){this.flags.delete(e)&&this.notifyListeners(e)}onChange(e){return this.listeners.push(e),()=>{let t=this.listeners.indexOf(e);t>=0&&this.listeners.splice(t,1)}}notifyListeners(e){for(let t of this.listeners)try{t(e)}catch{}}};function r(e,t,n){let r=n(`${e}:${t}`);return((r[0]|r[1]<<8|r[2]<<16|r[3]<<24>>>0)>>>0)%100}function i(e,t){let n=e[t];if(n!==void 0)return n;if(t===`userId`)return e.user_id;if(t===`user_id`)return e.userId}function a(e,t,n){switch(e){case`Equals`:return n.some(e=>t===e);case`NotEquals`:return n.every(e=>t!==e);case`Contains`:return n.some(e=>t.includes(e));case`NotContains`:return n.every(e=>!t.includes(e));case`StartsWith`:return n.some(e=>t.startsWith(e));case`EndsWith`:return n.some(e=>t.endsWith(e));case`In`:return n.includes(t);case`NotIn`:return!n.includes(t);case`MatchesRegex`:return n.some(e=>{try{return new RegExp(e,`i`).test(t)}catch{return!1}});case`GreaterThan`:return o(t,n[0],`>`);case`GreaterThanOrEqual`:return o(t,n[0],`>=`);case`LessThan`:return o(t,n[0],`<`);case`LessThanOrEqual`:return o(t,n[0],`<=`);case`Before`:return t<n[0];case`After`:return t>n[0];default:return!1}}function o(e,t,n){let r=parseFloat(e),i=parseFloat(t);if(isNaN(r)||isNaN(i))return!1;switch(n){case`>`:return r>i;case`<`:return r<i;case`>=`:return r>=i;case`<=`:return r<=i}}function s(e,t){let n=i(t,e.attribute);if(n==null)return e.negate;let r=String(n).toLowerCase(),o=e.values.map(e=>e.toLowerCase()),s=a(e.operator,r,o);return e.negate?!s:s}function c(e,t,n){return e.length===0?!0:t===`And`?e.every(e=>s(e,n)):e.some(e=>s(e,n))}function l(e,t){return e.length===0?!0:e.every(e=>c(e.conditions,e.operator,t))}function u(e,t,n){if(e.type===`Fixed`)return e.variation??``;let a=i(t,e.bucketBy??`userId`),o=a==null?``:String(a),s=r(e.salt??``,o,n),c=0;for(let t of e.variations??[])if(c+=t.weight,s<c)return t.key;let l=e.variations??[];return l.length>0?l[l.length-1].key:``}function d(e,t,n){if(!e.enabled)return{value:e.variations.find(t=>t.key===e.offVariation)?.value??null,variationKey:e.offVariation,reason:`FlagDisabled`};let r=[...e.rules].sort((e,t)=>e.priority-t.priority);for(let i of r){let r;if(i.segmentKey&&n.getSegment){let e=n.getSegment(i.segmentKey);r=e?c(e.conditions,e.conditionLogic,t):!1}else r=l(i.conditionGroups,t);if(r){let r=u(i.serve,t,n.md5);return{value:e.variations.find(e=>e.key===r)?.value??null,variationKey:r,reason:`RuleMatch`,ruleId:i.id}}}let i=u(e.fallthrough,t,n.md5);return{value:e.variations.find(e=>e.key===i)?.value??null,variationKey:i,reason:`Fallthrough`}}var f=class{queue=[];flushTimer=null;closed=!1;flushPromise=null;constructor(e,t,n){this.sender=e,this.flushInterval=t,this.flushBatchSize=n}start(){this.flushTimer||=setInterval(()=>{this.flush()},this.flushInterval)}enqueue(e){this.closed||(this.queue.push(e),this.queue.length>=this.flushBatchSize&&this.flush())}async flush(){if(this.queue.length!==0)return this.flushPromise||=(async()=>{for(;this.queue.length>0;){let e=this.queue.splice(0,this.flushBatchSize);try{await this.sender.sendEvents({events:e})}catch{}}})().finally(()=>{this.flushPromise=null}),this.flushPromise}async close(){for(this.closed=!0,this.flushTimer&&=(clearInterval(this.flushTimer),null);this.queue.length>0;)await this.flush()}},p=class e{config;store;events;platform;initialized=!1;initPromise=null;eventSource=null;pollTimer=null;closed=!1;streamRetryCount=0;streamRetryTimer=null;constructor(e,r){this.config=t(e),this.store=new n,this.platform=r,this.events=new f({sendEvents:async e=>{await this.platform.fetch(`${this.config.baseUrl}/v1/sdk/events`,{method:`POST`,headers:this.headers(),body:JSON.stringify(e)})}},this.config.flushInterval,this.config.flushBatchSize)}get isInitialized(){return this.initialized}async waitForInitialization(){if(!this.initialized)return this.initPromise||=this.initialize(),this.initPromise}boolVariation(e,t,n){return this.evaluateFlag(e,t,n)}stringVariation(e,t,n){return this.evaluateFlag(e,t,n)}numberVariation(e,t,n){return this.evaluateFlag(e,t,n)}jsonVariation(e,t,n){return this.evaluateFlag(e,t,n)}variationDetail(e,t,n){let r=this.store.getFlag(e);if(!r)return this.recordEvaluation(e,t,void 0),{value:n,reason:`FlagNotFound`};try{let i=d(r,t,{md5:e=>this.platform.md5(e),getSegment:e=>this.store.getSegment(e)}),a=i.value!==void 0&&i.value!==null?i.value:n;return this.recordEvaluation(e,t,i.variationKey),{value:a,reason:i.reason,ruleId:i.ruleId}}catch{return this.recordEvaluation(e,t,void 0),{value:n,reason:`Error`}}}track(e,t,n){let r=t.user_id==null?void 0:String(t.user_id);this.events.enqueue({type:`Custom`,flagKey:e,userId:r,timestamp:new Date().toISOString(),metadata:n})}identify(e){let t=e.user_id==null?void 0:String(e.user_id),{user_id:n,...r}=e;this.events.enqueue({type:`Identify`,flagKey:`$identify`,userId:t,timestamp:new Date().toISOString(),metadata:Object.keys(r).length>0?r:void 0})}async flush(){await this.events.flush()}async close(){this.closed=!0,this.eventSource?.close(),this.eventSource=null,this.streamRetryTimer&&=(clearTimeout(this.streamRetryTimer),null),this.pollTimer&&=(clearInterval(this.pollTimer),null),await this.events.close()}static forTesting(t){let n=Object.entries(t).map(([e,t])=>({key:e,version:1,type:typeof t==`boolean`?`Boolean`:typeof t==`number`?`Number`:typeof t==`string`?`String`:`Json`,enabled:!0,variations:[{key:`default`,value:t}],rules:[],fallthrough:{type:`Fixed`,variation:`default`},offVariation:`default`})),r=new e({sdkKey:`test-key`,baseUrl:`http://localhost`},{md5:()=>new Uint8Array(16),createEventSource:()=>({addEventListener:()=>{},close:()=>{},readyState:2}),fetch:async()=>new Response});return r.store.init(n,[],1),r.initialized=!0,r}evaluateFlag(e,t,n){return this.variationDetail(e,t,n).value}async initialize(){let e,t=new Promise((t,n)=>{e=setTimeout(()=>n(Error(`Initialization timed out`)),this.config.initTimeout)}),n=(async()=>{await this.fetchFlags(),this.initialized=!0,this.events.start(),this.startDataSource()})();try{await Promise.race([n,t])}finally{clearTimeout(e)}}async fetchFlags(){let e=await this.platform.fetch(`${this.config.baseUrl}/v1/sdk/flags`,{headers:this.headers()});if(!e.ok)throw Error(`Failed to fetch flags: ${e.status}`);let t=await e.json();this.store.init(t.flags,t.segments,t.version)}startDataSource(){this.closed||(this.config.streaming?this.startStreaming():this.startPolling())}startStreaming(){if(this.closed)return;let e=this.platform.sseSupportsHeaders?`${this.config.baseUrl}/v1/sdk/stream`:`${this.config.baseUrl}/v1/sdk/stream?authorization=${encodeURIComponent(this.config.sdkKey)}`;this.eventSource=this.platform.createEventSource(e,this.headers());for(let e of[`flag.created`,`flag.updated`])this.eventSource.addEventListener(e,e=>{try{let t=JSON.parse(e.data);t.key&&this.fetchSingleFlag(t.key)}catch{}});this.eventSource.addEventListener(`flag.deleted`,e=>{try{let t=JSON.parse(e.data);t.key&&this.store.delete(t.key)}catch{}}),this.eventSource.addEventListener(`segment.updated`,()=>{this.fetchFlags().catch(()=>{})}),this.eventSource.addEventListener(`open`,()=>{this.streamRetryCount=0}),this.eventSource.addEventListener(`error`,()=>{if(this.eventSource?.close(),this.eventSource=null,this.closed)return;if(this.streamRetryCount>=this.config.maxStreamRetries){console.warn(`[featureflip] SSE connection failed after ${this.config.maxStreamRetries} retries, falling back to polling`),this.startPolling();return}let e=Math.min(1e3*2**this.streamRetryCount,3e4);this.streamRetryCount++,this.streamRetryTimer=setTimeout(()=>{this.streamRetryTimer=null,this.startStreaming()},e)})}startPolling(){this.pollTimer=setInterval(()=>{this.fetchFlags().catch(()=>{})},this.config.pollInterval)}async fetchSingleFlag(e){try{let t=await this.platform.fetch(`${this.config.baseUrl}/v1/sdk/flags/${encodeURIComponent(e)}`,{headers:this.headers()});if(t.ok){let e=await t.json();this.store.upsert(e)}}catch{}}recordEvaluation(e,t,n){let r=t.user_id==null?void 0:String(t.user_id);this.events.enqueue({type:`Evaluation`,flagKey:e,userId:r,variation:n,timestamp:new Date().toISOString()})}headers(){return{Authorization:this.config.sdkKey,"Content-Type":`application/json`,...this.platform.extraHeaders}}};Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return p}});
@@ -0,0 +1,406 @@
1
+ //#region src/config.ts
2
+ var e = {
3
+ streaming: !0,
4
+ pollInterval: 3e4,
5
+ flushInterval: 3e4,
6
+ flushBatchSize: 100,
7
+ initTimeout: 1e4,
8
+ maxStreamRetries: 5
9
+ };
10
+ function t(t) {
11
+ if (!t.sdkKey) throw Error("sdkKey is required");
12
+ if (!t.baseUrl) throw Error("baseUrl is required");
13
+ let n = t.baseUrl.replace(/\/+$/, "");
14
+ return {
15
+ sdkKey: t.sdkKey,
16
+ baseUrl: n,
17
+ streaming: t.streaming ?? e.streaming,
18
+ pollInterval: t.pollInterval ?? e.pollInterval,
19
+ flushInterval: t.flushInterval ?? e.flushInterval,
20
+ flushBatchSize: t.flushBatchSize ?? e.flushBatchSize,
21
+ initTimeout: t.initTimeout ?? e.initTimeout,
22
+ maxStreamRetries: t.maxStreamRetries ?? e.maxStreamRetries
23
+ };
24
+ }
25
+ //#endregion
26
+ //#region src/core/store.ts
27
+ var n = class {
28
+ flags = /* @__PURE__ */ new Map();
29
+ segments = /* @__PURE__ */ new Map();
30
+ listeners = [];
31
+ version = 0;
32
+ getFlag(e) {
33
+ return this.flags.get(e);
34
+ }
35
+ getSegment(e) {
36
+ return this.segments.get(e);
37
+ }
38
+ getAllFlags() {
39
+ return Array.from(this.flags.values());
40
+ }
41
+ getVersion() {
42
+ return this.version;
43
+ }
44
+ init(e, t, n) {
45
+ this.flags.clear(), this.segments.clear();
46
+ for (let t of e) this.flags.set(t.key, t);
47
+ for (let e of t) this.segments.set(e.key, e);
48
+ this.version = n;
49
+ for (let t of e) this.notifyListeners(t.key);
50
+ }
51
+ upsert(e) {
52
+ let t = this.flags.get(e.key);
53
+ t && t.version >= e.version || (this.flags.set(e.key, e), this.notifyListeners(e.key));
54
+ }
55
+ delete(e) {
56
+ this.flags.delete(e) && this.notifyListeners(e);
57
+ }
58
+ onChange(e) {
59
+ return this.listeners.push(e), () => {
60
+ let t = this.listeners.indexOf(e);
61
+ t >= 0 && this.listeners.splice(t, 1);
62
+ };
63
+ }
64
+ notifyListeners(e) {
65
+ for (let t of this.listeners) try {
66
+ t(e);
67
+ } catch {}
68
+ }
69
+ };
70
+ //#endregion
71
+ //#region src/core/evaluator.ts
72
+ function r(e, t, n) {
73
+ let r = n(`${e}:${t}`);
74
+ return ((r[0] | r[1] << 8 | r[2] << 16 | r[3] << 24 >>> 0) >>> 0) % 100;
75
+ }
76
+ function i(e, t) {
77
+ let n = e[t];
78
+ if (n !== void 0) return n;
79
+ if (t === "userId") return e.user_id;
80
+ if (t === "user_id") return e.userId;
81
+ }
82
+ function a(e, t, n) {
83
+ switch (e) {
84
+ case "Equals": return n.some((e) => t === e);
85
+ case "NotEquals": return n.every((e) => t !== e);
86
+ case "Contains": return n.some((e) => t.includes(e));
87
+ case "NotContains": return n.every((e) => !t.includes(e));
88
+ case "StartsWith": return n.some((e) => t.startsWith(e));
89
+ case "EndsWith": return n.some((e) => t.endsWith(e));
90
+ case "In": return n.includes(t);
91
+ case "NotIn": return !n.includes(t);
92
+ case "MatchesRegex": return n.some((e) => {
93
+ try {
94
+ return new RegExp(e, "i").test(t);
95
+ } catch {
96
+ return !1;
97
+ }
98
+ });
99
+ case "GreaterThan": return o(t, n[0], ">");
100
+ case "GreaterThanOrEqual": return o(t, n[0], ">=");
101
+ case "LessThan": return o(t, n[0], "<");
102
+ case "LessThanOrEqual": return o(t, n[0], "<=");
103
+ case "Before": return t < n[0];
104
+ case "After": return t > n[0];
105
+ default: return !1;
106
+ }
107
+ }
108
+ function o(e, t, n) {
109
+ let r = parseFloat(e), i = parseFloat(t);
110
+ if (isNaN(r) || isNaN(i)) return !1;
111
+ switch (n) {
112
+ case ">": return r > i;
113
+ case "<": return r < i;
114
+ case ">=": return r >= i;
115
+ case "<=": return r <= i;
116
+ }
117
+ }
118
+ function s(e, t) {
119
+ let n = i(t, e.attribute);
120
+ if (n == null) return e.negate;
121
+ let r = String(n).toLowerCase(), o = e.values.map((e) => e.toLowerCase()), s = a(e.operator, r, o);
122
+ return e.negate ? !s : s;
123
+ }
124
+ function c(e, t, n) {
125
+ return e.length === 0 ? !0 : t === "And" ? e.every((e) => s(e, n)) : e.some((e) => s(e, n));
126
+ }
127
+ function l(e, t) {
128
+ return e.length === 0 ? !0 : e.every((e) => c(e.conditions, e.operator, t));
129
+ }
130
+ function u(e, t, n) {
131
+ if (e.type === "Fixed") return e.variation ?? "";
132
+ let a = i(t, e.bucketBy ?? "userId"), o = a == null ? "" : String(a), s = r(e.salt ?? "", o, n), c = 0;
133
+ for (let t of e.variations ?? []) if (c += t.weight, s < c) return t.key;
134
+ let l = e.variations ?? [];
135
+ return l.length > 0 ? l[l.length - 1].key : "";
136
+ }
137
+ function d(e, t, n) {
138
+ if (!e.enabled) return {
139
+ value: e.variations.find((t) => t.key === e.offVariation)?.value ?? null,
140
+ variationKey: e.offVariation,
141
+ reason: "FlagDisabled"
142
+ };
143
+ let r = [...e.rules].sort((e, t) => e.priority - t.priority);
144
+ for (let i of r) {
145
+ let r;
146
+ if (i.segmentKey && n.getSegment) {
147
+ let e = n.getSegment(i.segmentKey);
148
+ r = e ? c(e.conditions, e.conditionLogic, t) : !1;
149
+ } else r = l(i.conditionGroups, t);
150
+ if (r) {
151
+ let r = u(i.serve, t, n.md5);
152
+ return {
153
+ value: e.variations.find((e) => e.key === r)?.value ?? null,
154
+ variationKey: r,
155
+ reason: "RuleMatch",
156
+ ruleId: i.id
157
+ };
158
+ }
159
+ }
160
+ let i = u(e.fallthrough, t, n.md5);
161
+ return {
162
+ value: e.variations.find((e) => e.key === i)?.value ?? null,
163
+ variationKey: i,
164
+ reason: "Fallthrough"
165
+ };
166
+ }
167
+ //#endregion
168
+ //#region src/core/events.ts
169
+ var f = class {
170
+ queue = [];
171
+ flushTimer = null;
172
+ closed = !1;
173
+ flushPromise = null;
174
+ constructor(e, t, n) {
175
+ this.sender = e, this.flushInterval = t, this.flushBatchSize = n;
176
+ }
177
+ start() {
178
+ this.flushTimer ||= setInterval(() => {
179
+ this.flush();
180
+ }, this.flushInterval);
181
+ }
182
+ enqueue(e) {
183
+ this.closed || (this.queue.push(e), this.queue.length >= this.flushBatchSize && this.flush());
184
+ }
185
+ async flush() {
186
+ if (this.queue.length !== 0) return this.flushPromise ||= (async () => {
187
+ for (; this.queue.length > 0;) {
188
+ let e = this.queue.splice(0, this.flushBatchSize);
189
+ try {
190
+ await this.sender.sendEvents({ events: e });
191
+ } catch {}
192
+ }
193
+ })().finally(() => {
194
+ this.flushPromise = null;
195
+ }), this.flushPromise;
196
+ }
197
+ async close() {
198
+ for (this.closed = !0, this.flushTimer &&= (clearInterval(this.flushTimer), null); this.queue.length > 0;) await this.flush();
199
+ }
200
+ }, p = class e {
201
+ config;
202
+ store;
203
+ events;
204
+ platform;
205
+ initialized = !1;
206
+ initPromise = null;
207
+ eventSource = null;
208
+ pollTimer = null;
209
+ closed = !1;
210
+ streamRetryCount = 0;
211
+ streamRetryTimer = null;
212
+ constructor(e, r) {
213
+ this.config = t(e), this.store = new n(), this.platform = r, this.events = new f({ sendEvents: async (e) => {
214
+ await this.platform.fetch(`${this.config.baseUrl}/v1/sdk/events`, {
215
+ method: "POST",
216
+ headers: this.headers(),
217
+ body: JSON.stringify(e)
218
+ });
219
+ } }, this.config.flushInterval, this.config.flushBatchSize);
220
+ }
221
+ get isInitialized() {
222
+ return this.initialized;
223
+ }
224
+ async waitForInitialization() {
225
+ if (!this.initialized) return this.initPromise ||= this.initialize(), this.initPromise;
226
+ }
227
+ boolVariation(e, t, n) {
228
+ return this.evaluateFlag(e, t, n);
229
+ }
230
+ stringVariation(e, t, n) {
231
+ return this.evaluateFlag(e, t, n);
232
+ }
233
+ numberVariation(e, t, n) {
234
+ return this.evaluateFlag(e, t, n);
235
+ }
236
+ jsonVariation(e, t, n) {
237
+ return this.evaluateFlag(e, t, n);
238
+ }
239
+ variationDetail(e, t, n) {
240
+ let r = this.store.getFlag(e);
241
+ if (!r) return this.recordEvaluation(e, t, void 0), {
242
+ value: n,
243
+ reason: "FlagNotFound"
244
+ };
245
+ try {
246
+ let i = d(r, t, {
247
+ md5: (e) => this.platform.md5(e),
248
+ getSegment: (e) => this.store.getSegment(e)
249
+ }), a = i.value !== void 0 && i.value !== null ? i.value : n;
250
+ return this.recordEvaluation(e, t, i.variationKey), {
251
+ value: a,
252
+ reason: i.reason,
253
+ ruleId: i.ruleId
254
+ };
255
+ } catch {
256
+ return this.recordEvaluation(e, t, void 0), {
257
+ value: n,
258
+ reason: "Error"
259
+ };
260
+ }
261
+ }
262
+ track(e, t, n) {
263
+ let r = t.user_id == null ? void 0 : String(t.user_id);
264
+ this.events.enqueue({
265
+ type: "Custom",
266
+ flagKey: e,
267
+ userId: r,
268
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
269
+ metadata: n
270
+ });
271
+ }
272
+ identify(e) {
273
+ let t = e.user_id == null ? void 0 : String(e.user_id), { user_id: n, ...r } = e;
274
+ this.events.enqueue({
275
+ type: "Identify",
276
+ flagKey: "$identify",
277
+ userId: t,
278
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
279
+ metadata: Object.keys(r).length > 0 ? r : void 0
280
+ });
281
+ }
282
+ async flush() {
283
+ await this.events.flush();
284
+ }
285
+ async close() {
286
+ this.closed = !0, this.eventSource?.close(), this.eventSource = null, this.streamRetryTimer &&= (clearTimeout(this.streamRetryTimer), null), this.pollTimer &&= (clearInterval(this.pollTimer), null), await this.events.close();
287
+ }
288
+ static forTesting(t) {
289
+ let n = Object.entries(t).map(([e, t]) => ({
290
+ key: e,
291
+ version: 1,
292
+ type: typeof t == "boolean" ? "Boolean" : typeof t == "number" ? "Number" : typeof t == "string" ? "String" : "Json",
293
+ enabled: !0,
294
+ variations: [{
295
+ key: "default",
296
+ value: t
297
+ }],
298
+ rules: [],
299
+ fallthrough: {
300
+ type: "Fixed",
301
+ variation: "default"
302
+ },
303
+ offVariation: "default"
304
+ })), r = new e({
305
+ sdkKey: "test-key",
306
+ baseUrl: "http://localhost"
307
+ }, {
308
+ md5: () => new Uint8Array(16),
309
+ createEventSource: () => ({
310
+ addEventListener: () => {},
311
+ close: () => {},
312
+ readyState: 2
313
+ }),
314
+ fetch: async () => new Response()
315
+ });
316
+ return r.store.init(n, [], 1), r.initialized = !0, r;
317
+ }
318
+ evaluateFlag(e, t, n) {
319
+ return this.variationDetail(e, t, n).value;
320
+ }
321
+ async initialize() {
322
+ let e, t = new Promise((t, n) => {
323
+ e = setTimeout(() => n(/* @__PURE__ */ Error("Initialization timed out")), this.config.initTimeout);
324
+ }), n = (async () => {
325
+ await this.fetchFlags(), this.initialized = !0, this.events.start(), this.startDataSource();
326
+ })();
327
+ try {
328
+ await Promise.race([n, t]);
329
+ } finally {
330
+ clearTimeout(e);
331
+ }
332
+ }
333
+ async fetchFlags() {
334
+ let e = await this.platform.fetch(`${this.config.baseUrl}/v1/sdk/flags`, { headers: this.headers() });
335
+ if (!e.ok) throw Error(`Failed to fetch flags: ${e.status}`);
336
+ let t = await e.json();
337
+ this.store.init(t.flags, t.segments, t.version);
338
+ }
339
+ startDataSource() {
340
+ this.closed || (this.config.streaming ? this.startStreaming() : this.startPolling());
341
+ }
342
+ startStreaming() {
343
+ if (this.closed) return;
344
+ let e = this.platform.sseSupportsHeaders ? `${this.config.baseUrl}/v1/sdk/stream` : `${this.config.baseUrl}/v1/sdk/stream?authorization=${encodeURIComponent(this.config.sdkKey)}`;
345
+ this.eventSource = this.platform.createEventSource(e, this.headers());
346
+ for (let e of ["flag.created", "flag.updated"]) this.eventSource.addEventListener(e, (e) => {
347
+ try {
348
+ let t = JSON.parse(e.data);
349
+ t.key && this.fetchSingleFlag(t.key);
350
+ } catch {}
351
+ });
352
+ this.eventSource.addEventListener("flag.deleted", (e) => {
353
+ try {
354
+ let t = JSON.parse(e.data);
355
+ t.key && this.store.delete(t.key);
356
+ } catch {}
357
+ }), this.eventSource.addEventListener("segment.updated", () => {
358
+ this.fetchFlags().catch(() => {});
359
+ }), this.eventSource.addEventListener("open", () => {
360
+ this.streamRetryCount = 0;
361
+ }), this.eventSource.addEventListener("error", () => {
362
+ if (this.eventSource?.close(), this.eventSource = null, this.closed) return;
363
+ if (this.streamRetryCount >= this.config.maxStreamRetries) {
364
+ console.warn(`[featureflip] SSE connection failed after ${this.config.maxStreamRetries} retries, falling back to polling`), this.startPolling();
365
+ return;
366
+ }
367
+ let e = Math.min(1e3 * 2 ** this.streamRetryCount, 3e4);
368
+ this.streamRetryCount++, this.streamRetryTimer = setTimeout(() => {
369
+ this.streamRetryTimer = null, this.startStreaming();
370
+ }, e);
371
+ });
372
+ }
373
+ startPolling() {
374
+ this.pollTimer = setInterval(() => {
375
+ this.fetchFlags().catch(() => {});
376
+ }, this.config.pollInterval);
377
+ }
378
+ async fetchSingleFlag(e) {
379
+ try {
380
+ let t = await this.platform.fetch(`${this.config.baseUrl}/v1/sdk/flags/${encodeURIComponent(e)}`, { headers: this.headers() });
381
+ if (t.ok) {
382
+ let e = await t.json();
383
+ this.store.upsert(e);
384
+ }
385
+ } catch {}
386
+ }
387
+ recordEvaluation(e, t, n) {
388
+ let r = t.user_id == null ? void 0 : String(t.user_id);
389
+ this.events.enqueue({
390
+ type: "Evaluation",
391
+ flagKey: e,
392
+ userId: r,
393
+ variation: n,
394
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
395
+ });
396
+ }
397
+ headers() {
398
+ return {
399
+ Authorization: this.config.sdkKey,
400
+ "Content-Type": "application/json",
401
+ ...this.platform.extraHeaders
402
+ };
403
+ }
404
+ };
405
+ //#endregion
406
+ export { p as t };
package/dist/node.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./client-Bf3tS17L.cjs`);let t=require(`crypto`);var n=(0,require(`module`).createRequire)({}.url);function r(){return{md5(e){return(0,t.createHash)(`md5`).update(e,`utf8`).digest()},createEventSource(e,t){let{EventSource:r}=n(`eventsource`),i=new r(e,{fetch:(e,n)=>globalThis.fetch(e,{...n,headers:{...n?.headers,...t}})});return{addEventListener:(e,t)=>{i.addEventListener(e,t)},close:()=>i.close(),get readyState(){return i.readyState}}},async fetch(e,t){return globalThis.fetch(e,t)},extraHeaders:{"User-Agent":`featureflip-js/0.1.0`},sseSupportsHeaders:!0}}exports.FeatureflipClient=e.t,exports.createNodePlatform=r;
package/dist/node.d.ts ADDED
@@ -0,0 +1,117 @@
1
+ export declare function createNodePlatform(): Platform;
2
+
3
+ export declare type EvaluationContext = Record<string, unknown>;
4
+
5
+ export declare interface EvaluationDetail<T = unknown> {
6
+ value: T;
7
+ variationKey?: string;
8
+ reason: EvaluationReason;
9
+ ruleId?: string;
10
+ }
11
+
12
+ export declare type EvaluationReason = 'RuleMatch' | 'Fallthrough' | 'FlagDisabled' | 'FlagNotFound' | 'Error';
13
+
14
+ declare interface EventSourceLike {
15
+ addEventListener(type: string, listener: (event: {
16
+ data: string;
17
+ }) => void): void;
18
+ close(): void;
19
+ readonly readyState: number;
20
+ }
21
+
22
+ export declare class FeatureflipClient {
23
+ private readonly config;
24
+ private readonly store;
25
+ private readonly events;
26
+ private readonly platform;
27
+ private initialized;
28
+ private initPromise;
29
+ private eventSource;
30
+ private pollTimer;
31
+ private closed;
32
+ private streamRetryCount;
33
+ private streamRetryTimer;
34
+ constructor(config: FeatureflipConfig, platform: Platform);
35
+ /** Whether the client has successfully loaded initial flag data. */
36
+ get isInitialized(): boolean;
37
+ /**
38
+ * Wait for the client to finish initialization.
39
+ * Rejects after initTimeout if initial flag fetch fails.
40
+ */
41
+ waitForInitialization(): Promise<void>;
42
+ /**
43
+ * Evaluate a boolean flag.
44
+ * Returns defaultValue if flag not found or evaluation fails.
45
+ */
46
+ boolVariation(key: string, context: EvaluationContext, defaultValue: boolean): boolean;
47
+ /**
48
+ * Evaluate a string flag.
49
+ */
50
+ stringVariation(key: string, context: EvaluationContext, defaultValue: string): string;
51
+ /**
52
+ * Evaluate a number flag.
53
+ */
54
+ numberVariation(key: string, context: EvaluationContext, defaultValue: number): number;
55
+ /**
56
+ * Evaluate a JSON flag.
57
+ */
58
+ jsonVariation<T>(key: string, context: EvaluationContext, defaultValue: T): T;
59
+ /**
60
+ * Evaluate a flag and return the full detail including reason.
61
+ */
62
+ variationDetail<T>(key: string, context: EvaluationContext, defaultValue: T): EvaluationDetail<T>;
63
+ /**
64
+ * Track a custom event.
65
+ */
66
+ track(eventKey: string, context: EvaluationContext, metadata?: Record<string, unknown>): void;
67
+ /**
68
+ * Send an identify event for the given context.
69
+ */
70
+ identify(context: EvaluationContext): void;
71
+ /**
72
+ * Flush any pending events immediately.
73
+ */
74
+ flush(): Promise<void>;
75
+ /**
76
+ * Close the client, flushing pending events and stopping all connections.
77
+ */
78
+ close(): Promise<void>;
79
+ /**
80
+ * Create a test client with hardcoded flag values. No network calls.
81
+ */
82
+ static forTesting(flags: Record<string, unknown>): FeatureflipClient;
83
+ private evaluateFlag;
84
+ private initialize;
85
+ private fetchFlags;
86
+ private startDataSource;
87
+ private startStreaming;
88
+ private startPolling;
89
+ private fetchSingleFlag;
90
+ private recordEvaluation;
91
+ private headers;
92
+ }
93
+
94
+ export declare interface FeatureflipConfig {
95
+ sdkKey: string;
96
+ baseUrl: string;
97
+ streaming?: boolean;
98
+ pollInterval?: number;
99
+ flushInterval?: number;
100
+ flushBatchSize?: number;
101
+ initTimeout?: number;
102
+ maxStreamRetries?: number;
103
+ }
104
+
105
+ export declare type FlagType = 'Boolean' | 'String' | 'Number' | 'Json';
106
+
107
+ export declare interface Platform {
108
+ md5(input: string): Uint8Array;
109
+ createEventSource(url: string, headers: Record<string, string>): EventSourceLike;
110
+ fetch(url: string, init?: RequestInit): Promise<Response>;
111
+ /** Extra headers the platform can inject (e.g. User-Agent on Node). */
112
+ readonly extraHeaders?: Record<string, string>;
113
+ /** Whether the platform's EventSource implementation supports custom headers. */
114
+ readonly sseSupportsHeaders?: boolean;
115
+ }
116
+
117
+ export { }
package/dist/node.mjs ADDED
@@ -0,0 +1,37 @@
1
+ import { t as e } from "./client-ChXPtrh5.js";
2
+ import { createHash as t } from "crypto";
3
+ import { createRequire as n } from "module";
4
+ //#region src/platform/node.ts
5
+ var r = n(import.meta.url);
6
+ function i() {
7
+ return {
8
+ md5(e) {
9
+ return t("md5").update(e, "utf8").digest();
10
+ },
11
+ createEventSource(e, t) {
12
+ let { EventSource: n } = r("eventsource"), i = new n(e, { fetch: (e, n) => globalThis.fetch(e, {
13
+ ...n,
14
+ headers: {
15
+ ...n?.headers,
16
+ ...t
17
+ }
18
+ }) });
19
+ return {
20
+ addEventListener: (e, t) => {
21
+ i.addEventListener(e, t);
22
+ },
23
+ close: () => i.close(),
24
+ get readyState() {
25
+ return i.readyState;
26
+ }
27
+ };
28
+ },
29
+ async fetch(e, t) {
30
+ return globalThis.fetch(e, t);
31
+ },
32
+ extraHeaders: { "User-Agent": "featureflip-js/0.1.0" },
33
+ sseSupportsHeaders: !0
34
+ };
35
+ }
36
+ //#endregion
37
+ export { e as FeatureflipClient, i as createNodePlatform };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@featureflip/js",
3
+ "version": "1.0.0",
4
+ "description": "JavaScript/TypeScript SDK for FeatureFlip",
5
+ "license": "Apache-2.0",
6
+ "homepage": "https://featureflip.io/docs",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/node.d.ts",
11
+ "node": {
12
+ "import": "./dist/node.mjs",
13
+ "require": "./dist/node.cjs"
14
+ },
15
+ "browser": {
16
+ "import": "./dist/browser.mjs",
17
+ "require": "./dist/browser.cjs"
18
+ },
19
+ "default": "./dist/browser.mjs"
20
+ }
21
+ },
22
+ "types": "./dist/node.d.ts",
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "scripts": {
27
+ "build": "vite build",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest",
30
+ "test:coverage": "vitest run --coverage"
31
+ },
32
+ "dependencies": {
33
+ "eventsource": "^4.0.0"
34
+ },
35
+ "engines": {
36
+ "node": ">=20.19.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^25.5.0",
40
+ "typescript": "^6.0.2",
41
+ "vite": "^8.0.3",
42
+ "vite-plugin-dts": "^4.0.0",
43
+ "vitest": "^4.1.2"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ }
48
+ }