@configdirector/server-sdk 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ConfigDirector
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is furnished
10
+ to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # ConfigDirector JavaScript Server SDK
2
+
3
+ ## Getting started
4
+
5
+ ### 1. Install
6
+
7
+ Install from NPM:
8
+
9
+ ```bash
10
+ npm install --save @configdirector/server-sdk
11
+ ```
12
+
13
+ ### 2. Create and initialize a client using your server SDK key
14
+
15
+ ```ts
16
+ import { createClient } from "@configdirector/server-sdk";
17
+
18
+ const client = createClient("YOUR-SERVER-SDK-KEY");
19
+ await client.initialize();
20
+ ```
21
+
22
+ ### 3. Retrieve config values
23
+
24
+ Retrieve a config value synchronously via `getValue`. The first argument is the config key, and the second the default value to be returned if the config state is not available. The third (optional) argument is the user context for targeting rules evaluation.
25
+
26
+ If `getValue` is called before initialization is complete, the default value will be returned.
27
+
28
+ ```ts
29
+ client.getValue("my-string-config-key", "Default");
30
+
31
+ client.getValue("my-boolean-config-key", false, { id: "user-id", name: "Example User" });
32
+
33
+ client.getValue<MyEnum>("my-enum-config-key", MyEnum.SomeDefaultValue);
34
+ ```
35
+
36
+ You can also subscribe to config value updates:
37
+
38
+ ```ts
39
+ const unwatchMyKey = client.watch("my-string-config-key", "Default", (newValue) => {
40
+ console.log("Value updated:", newValue);
41
+ }, { id: "user-id" } /* Optional user context */);
42
+
43
+ unwatchMyKey(); // Call the unwatch function returned to remove the observer
44
+
45
+ client.unwatch("my-string-config-key"); // Removes all observers for that key
46
+ ```
47
+
48
+ ## Getting Help
49
+
50
+ Reach out to us via https://www.configdirector.com/support
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`eventsource-client`),l=require(`node:events`);l=s(l);let u=require(`@jsonjoy.com/json-pointer`),d=require(`semver`);d=s(d);let f=require(`rapidhash-js`);var p=class e extends Error{constructor(t,n){super(t),this.name=`ConfigDirectorConnectionError`,this.status=n,Object.setPrototypeOf(this,e.prototype)}},m=class e extends Error{constructor(t){super(t),this.name=`ConfigDirectorValidationError`,Object.setPrototypeOf(this,e.prototype)}},h=class{constructor(e){this.options=e,this.eventEmitter=new l.EventEmitter,this.options=e,this.logger=e.logger,this.url=new URL(`server/sse/v1`,e.baseUrl)}async connect(e){this.eventSource&&this.close();let t,n,r=async(e,r)=>{let i=await fetch(e,r);return t=i.status,i.ok||(n=await i.text()),i},i=new Promise((e,i)=>{this.eventSource=(0,c.createEventSource)({url:this.url,fetch:r,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey}),onMessage:({data:e})=>{this.dispatchMessage(e)},onConnect:()=>{t&&this.isStatusFatal(t)?(this.close(),i(this.prepareFatalError(t,n))):(this.logger.debug(`[EventSourceTransport] Connected, status: %s`,t),e(this))},onScheduleReconnect:e=>{t&&this.isStatusFatal(t)?(this.close(),i(this.prepareFatalError(t,n))):this.logger.warn(`[EventSourceTransport] Scheduling reconnect in ${e.delay}. Response status: ${t}`)},onDisconnect:()=>{this.logger.debug(`[EventSourceTransport] Disconnected`)}})});return Promise.race([i,new Promise(t=>{setTimeout(()=>t(this),e)})])}dispatchMessage(e){try{let t=JSON.parse(e);this.eventEmitter.emit(`configBundleReceived`,t)}catch(e){this.logger.error(`[EventSourceTransport] Error parsing and dispatching config data update: `,e)}}prepareFatalError(e,t){let n=e??0;return new p(`${`Connection failed with status: ${e??`unknown`}`}${(t?.trim()?.length??0)>0?` (${t})`:``}. This is an unrecoverable error, will not attempt to reconnect.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}get isConnected(){return this.eventSource?.readyState==`open`}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}clear(){this.eventEmitter.removeAllListeners()}close(){this.eventSource?.close()}dispose(){this.close(),this.clear()}};const g=(e,t,n)=>{switch(t.toLowerCase()){case`=`:case`equals`:return e==n[0];case`!=`:case`does not equal`:return e!=n[0];case`is one of`:return n.includes(e);case`is not one of`:return!n.includes(e);case`starts with any of`:return n.findIndex(t=>e?.startsWith(t))>=0;case`does not start with any of`:return n.findIndex(t=>e?.startsWith(t))<0;case`ends with any of`:return n.findIndex(t=>e?.endsWith(t))>=0;case`does not end with any of`:return n.findIndex(t=>e?.endsWith(t))<0;case`matches regex`:return _(n[0],e);case`does not match regex`:return!_(n[0],e);default:return!1}},_=(e,t)=>{try{return new RegExp(e).test(t)}catch{return!1}},v=(e,t,n)=>{if(typeof e==`boolean`)return!1;let r=t.toLowerCase();if(e==null)return r==`!=`||r==`does not equal`;let{value:i,target:a}=y(e,n[0]);switch(r){case`=`:case`equals`:return i==a;case`!=`:case`does not equal`:return i!=a;case`<`:return i<a;case`<=`:return i<=a;case`>`:return i>a;case`>=`:return i>=a;default:return!1}},y=(e,t)=>typeof e==`number`?{value:e,target:b(t)}:typeof e==`bigint`?{value:e,target:BigInt(t)}:{value:b(e),target:b(t)},b=e=>{let t=Number.parseFloat(e.toString());if(isNaN(t)||!isFinite(t))throw TypeError(`Invalid numeric value: ${e}`);return t},x=(e,t,n)=>{if(e==null)return!1;try{let r=new Date(e),i=new Date(n[0]);switch(t){case`is after`:return r>i;case`is before`:return r<i;default:return!1}}catch{return!1}},S=(e,t,n)=>{let r=t.toLowerCase();if(e==null||e.trim().length==0)return r==`is not one of`;let i=d.coerce(e)??e,a=n.map(e=>d.coerce(e)??e);try{switch(r){case`=`:return C(i,a[0]);case`<`:return d.lt(i,a[0]);case`<=`:return d.lte(i,a[0]);case`>`:return d.gt(i,a[0]);case`>=`:return d.gte(i,a[0]);case`is one of`:return a.findIndex(e=>C(i,e))>=0;case`is not one of`:return a.findIndex(e=>C(i,e))<0;default:return!1}}catch{return!1}},C=(e,t)=>{try{return d.eq(e,t)}catch{return!1}},w=(e,t,n)=>{let r=t.toLowerCase();if(!Array.isArray(e))return r==`does not contain any of`;switch(r){case`contains any of`:return e.some(e=>n.includes(e));case`does not contain any of`:return!e.some(e=>n.includes(e));default:return!1}};var T=class{evaluate(e,t){if(!this.isConditionValidForComparison(e))return!1;switch(e.attribute){case`identifier`:return this.compare(t?.context?.id,e);case`name`:return this.compare(t?.context?.name,e);case`appName`:return this.compare(t?.metadata?.appName,e);case`appVersion`:return this.compare(t?.metadata?.appVersion,e);case`traits`:if(e.trait){let n=this.findTraitValue(t?.context?.traits,e.trait);return this.compare(n,e)}}return!1}compare(e,t){switch(t.targetType){case`text`:return g(e?.toString(),t.operator,t.targetValues);case`number`:return v(e,t.operator,t.targetValues);case`datetime`:return x(e?.toString(),t.operator,t.targetValues);case`semver`:return S(e?.toString(),t.operator,t.targetValues);case`array`:return w(e,t.operator,t.targetValues)}return!1}findTraitValue(e,t){try{return(0,u.findByPointer)(t,e).val}catch{return}}isConditionValidForComparison(e){return Array.isArray(e.targetValues)&&e.targetValues.length>0}};const E=e=>{let t=`${e.contextIdentifier}-${e.configId}`;return Number((0,f.rapidhash)(t,{seed:15426857351n})%1000n)/10};var D=class{constructor(e){if(this.logger=e,this.conditionEvaluator=new T,!e||!e.warn||!e.error||!e.info)throw TypeError(`The provided logger is not a valid 'ConfigDirectorLogger'`);this.logger=e}evaluate(e,t){return{id:e.id,key:e.key,type:e.type,value:this.getConfigValue(e,t)}}getConfigValue(e,t){let n=[...e.target?.rules??[]].sort((e,t)=>(e.order??2**53-1)-(t.order??2**53-1));for(let r of n){let n=this.evaluateRule(r,e,t);if(n.success)return n.value}return e.target?.defaultValue}evaluateRule(e,t,n){try{if(e.type==`percentage`)return this.evaluatePercentageRule(e,t,n);if(e.type==`conditional`)return this.evaluateConditionalRule(e,t,n)}catch(n){this.logger.warn(`There was an error while evaluating a targeting rule '${e?.id}' for '${t?.key}'. The rule will be disregarded.`,{error:n,configKey:t?.key,ruleId:e?.id})}return{success:!1}}evaluatePercentageRule(e,t,n){return this.evaluatePercentage(e.percentages??[],t,n)}evaluatePercentage(e,t,n){let r=E({configId:t.id,contextIdentifier:n?.context?.id??crypto.randomUUID()}),i=0,a=0,o;for(;!o&&i<e.length;)r<=e[i].percentage+a&&(o=e[i]),a+=e[i].percentage,i+=1;return o?.value==null?{success:!1}:{success:!0,value:o.value.toString()}}evaluateConditionalRule(e,t,n){let r=e.conditions??[],i=0,a;for(;!a&&i<r.length;)this.conditionEvaluator.evaluate(r[i],n)&&(a=r[i]),i+=1;return a&&e.target==`value`&&e.value!=null?{success:!0,value:e.value.toString()}:a&&e.target==`percentage`?this.evaluatePercentage(e.percentages??[],t,n):{success:!1}}};const O=e=>{let t=typeof e;if(t===`object`)try{return e.constructor?.name??t}catch{return t}else if(t===`function`)try{let n=e.name;return n?`function: ${n}`:t}catch{return t}return t},k=e=>e===`number`||e===`bigint`,A=(e,t)=>{let n=e.value,r=O(t);if(!n)return{parsedValue:t,requestedType:r,usedDefault:!0,reason:`value-missing`};if(typeof t==`string`)return{parsedValue:n,requestedType:r,usedDefault:!1,reason:`found-match`};if(typeof t==`boolean`&&e.type===`boolean`){let e=j(n),i=typeof e==`boolean`;return{parsedValue:i?e:t,requestedType:r,usedDefault:!i,reason:i?`found-match`:`invalid-boolean`}}if(k(typeof t)&&e.type===`integer`){let e=M(n),i=typeof e==`number`;return{parsedValue:i?e:t,requestedType:r,usedDefault:!i,reason:i?`found-match`:`invalid-number`}}if(k(typeof t)&&(e.type===`float`||e.type===`enum`)){let e=N(n),i=typeof e==`number`;return{parsedValue:i?e:t,requestedType:r,usedDefault:!i,reason:i?`found-match`:`invalid-number`}}return{parsedValue:n,requestedType:r,usedDefault:!1,reason:`found-match`}},j=e=>{if(!e)return;let t=e.toLowerCase();if(!(t!=`true`&&t!=`false`))return t===`true`},M=e=>{if(!e)return;let t=Number.parseInt(e);if(!(isNaN(t)||!isFinite(t)))return t},N=e=>{if(!e)return;let t=Number.parseFloat(e);if(!(isNaN(t)||!isFinite(t)))return t},P={off:-1,error:0,warn:1,info:2,debug:3},F=()=>{let e={year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,timeZoneName:`short`};try{return new Intl.DateTimeFormat(void 0,{...e,fractionalSecondDigits:3})}catch{return new Intl.DateTimeFormat(void 0,e)}};var I=class{constructor(e,t){this.level=e,this.decorator=t,this.level=e,this.decorator=t,this.dateFormatter=F()}debug(e,...t){this.log(console.debug,`debug`,e,...t)}info(e,...t){this.log(console.info,`info`,e,...t)}warn(e,...t){this.log(console.warn,`warn`,e,...t)}error(e,...t){this.log(console.error,`error`,e,...t)}log(e,t,n,...r){P[this.level]>=P[t]&&e(`[${this.dateFormatter.format(new Date)}] ${this.decorator?.decorateMessage(n)}`,...r)}},L=class{decorateMessage(e){return`[ConfigDirector:js-server-sdk] ${e}`}};const R=(e,t)=>new I(e??`warn`,t??new L),z=e=>B(new TextEncoder().encode(e)).toString(16).padStart(8,`0`),B=e=>{let t=5381;for(let n=0;n<e.length;n++)t=(t<<5)+t+e[n],t>>>=0;return t},V=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},H=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0},U=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(t,{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}};var W=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.url=new URL(`server/telemetry/v1`,e.baseUrl)}async report({discreteEvents:e,aggregatedEvents:t,droppedEvents:n}){if(!this.executeRequests)return{success:!1,fatalError:!0};let r={serverSdkKey:this.sdkKey,discreteEvents:e,aggregatedEvents:t,droppedEvents:n};if(this.isReportEmpty(r))return{success:!0,fatalError:!1};let i=await this.sendReport(r);return i.fatalError&&(this.executeRequests=!1),i}isReportEmpty(e){return V(e.discreteEvents)&&V(e.aggregatedEvents)&&H(e.droppedEvents)}async sendReport(e){try{let t=await U(5e3,this.url,{method:`POST`,body:JSON.stringify(e)},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:!1}}}isStatusFatal(e){return!!e&&e>=400&&e<500}},G=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},K=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},q=class{constructor(e){this.evaluationEventQueue=new K,this.aggregator=new G,this.collectEvents=!0,this.logger=e.logger,this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}sanitizeValue(e,t){if(t===`json`)try{return z(JSON.stringify(e))}catch{return e.toString().slice(0,500)}return e.toString().slice(0,500)}async flushAndScheduleNext(){(await this.flush()).fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay)}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=this.evaluationEventQueue.takeSnapshot();return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount}})}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},J=class extends q{constructor(e){super(e),this.reporter=new W(e)}evaluatedConfig(e){this.collectEvents&&this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(e))}sanitizeEvaluatedConfigEvent(e){return{key:e.key,type:e.type,defaultValue:this.sanitizeValue(e.defaultValue,e.type),requestedType:e.requestedType,evaluatedValue:this.sanitizeValue(e.evaluatedValue,e.type),usedDefault:e.usedDefault,evaluationReason:e.evaluationReason}}};const Y=new URL(`https://server-sdk-api.configdirector.com`);var X=class{constructor(e,t,n){this.handlersMap=new Map,this.eventEmitter=new l.default,this.ready=!1,this.logger=n?.logger??R(),this.timeout=n?.connection?.timeout??3e3,this.metaContext=n?.metadata??{},this.configEvaluator=new D(this.logger);let r=this.parseUrl(n?.connection?.url)??Y;this.streaming=n?.connection?.streaming!==!1;let i=h;this.usageEventCollector=new J({sdkKey:e,logger:this.logger,baseUrl:r}),this.transport=new i({serverSdkKey:e,baseUrl:r,metaContext:{...n?.metadata,sdkName:t.sdkName,sdkVersion:t.sdkVersion},logger:this.logger}),this.transport.on(`configBundleReceived`,e=>{this.readyResolve?.();let t=Object.keys(e.configs);!this.configSet||e.kind==`full`?(this.configSet=e,this.eventEmitter.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)):(this.configSet.configs={...this.configSet.configs,...e.configs},this.eventEmitter.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)),this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server:`,{keys:t})})}async initialize(){try{this.ready=!1,this.readyPromise=new Promise(e=>{this.readyResolve=e}).then(()=>{this.ready=!0,this.eventEmitter.emit(`clientReady`),this.logger.debug(`[ConfigDirectorClient] Received initial payload from the server, client is ready`)});let e=new Date().getTime();await this.transport.connect(this.timeout);let t=new Date().getTime()-e,n=this.timeout-t;if(n>0&&await Promise.race([this.readyPromise,new Promise(e=>{setTimeout(()=>e(),n)})]),!this.ready){let e=this.streaming?`The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.`:`Since the client was configured without streaming, configs may not update and always return the default value.`;this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${e}`)}}catch(e){this.logger.error(`[ConfigDirectorClient] An error occurred during initialization: `,e)}}updateWatchers(e){Object.values(e).forEach(e=>this.updateWatchersForConfig(e))}updateWatchersForConfig(e){this.handlersMap.get(e.key)?.forEach(t=>{let n=this.getValueFromConfigDefinition(e.key,e,t.defaultValue,t.context);t.handler(n)})}watch(e,t,n,r){this.validateDefaultValue(t);let i=this.handlersMap.get(e),a={handler:n,defaultValue:t,requestedType:typeof t,context:r};return i?i.push(a):this.handlersMap.set(e,[a]),()=>this.unwatch(e,n)}unwatch(e,t){let n=this.handlersMap.get(e);if(n)if(t){let e=n.findIndex(e=>e.handler==t);e>=0&&n?.splice(e,1)}else n.splice(0)}getValue(e,t,n){this.validateDefaultValue(t);let r=this.configSet?.configs[e];return this.getValueFromConfigDefinition(e,r,t,n)}getValueFromConfigDefinition(e,t,n,r){if(t){let i=A(this.configEvaluator.evaluate(t,{context:r,metadata:this.metaContext}),n);return this.usageEventCollector.evaluatedConfig({contextId:r?.id,key:e,defaultValue:n,requestedType:i.requestedType,evaluatedValue:i.parsedValue,usedDefault:i.usedDefault,evaluationReason:i.reason}),this.logger.debug(`[ConfigDirectorClient] Evaluated '${e}' to '${i.parsedValue}'`),i.parsedValue}else return this.logger.debug(`[ConfigDirectorClient] No config state found for '${e}', returning default value '${n}'`),this.usageEventCollector.evaluatedConfig({contextId:r?.id,key:e,defaultValue:n,requestedType:O(n),evaluatedValue:n,usedDefault:!0,evaluationReason:`config-state-missing`}),n}parseUrl(e){if(e)try{return new URL(e)}catch(t){throw new m(`Invalid base URL '${e}'. Parsing failed: ${t}`)}}validateDefaultValue(e){if(e==null)throw new m(`Invalid default value. The default value for a config must be defined and non-null.`);if(typeof e==`function`)throw new m(`Invalid default value. The default value for a config cannot be a function.`)}get isReady(){return this.ready}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}removeAllObservers(){this.logger.debug(`[ConfigDirectorClient] removeAllObservers() has been called, removing all observers`),this.eventEmitter.removeAllListeners(),this.handlersMap.clear()}unwatchAll(){this.handlersMap.clear()}closeConnection(){this.logger.debug(`[ConfigDirectorClient] closeConnection() has been called, closing connection to server`),this.transport.close(),this.ready=!1}dispose(){this.removeAllObservers(),this.closeConnection()}};const Z=(e,t)=>new X(e,{sdkName:`js-server-sdk`,sdkVersion:`0.1.0`},t),Q=(e,t)=>R(e,t);exports.createClient=Z,exports.createConsoleLogger=Q;
@@ -0,0 +1,200 @@
1
+ //#region ../shared/src/types.d.ts
2
+ type ConfigEnumLikeType = {
3
+ [key: string]: string | number;
4
+ };
5
+ type ConfigValueType = string | number | boolean | object | ConfigEnumLikeType;
6
+ type ConfigDirectorLoggingLevel = "debug" | "info" | "warn" | "error" | "off";
7
+ interface ConfigDirectorLogger {
8
+ debug(message: string, ...args: any): void;
9
+ info(message: string, ...args: any): void;
10
+ warn(message: string, ...args: any): void;
11
+ error(message: string, ...args: any): void;
12
+ }
13
+ interface ConfigDirectorLogMessageDecorator {
14
+ decorateMessage(message: string): string;
15
+ }
16
+ /**
17
+ * The user's context to be sent to ConfigDirector. This context will be used for targeting
18
+ * rules evaluation.
19
+ */
20
+ type ConfigDirectorContext = {
21
+ /**
22
+ * The user's identifier. This should be a value that uniquely identifies an application
23
+ * user.
24
+ * In the case of anonymous users, you could generate a UUID or alternatively not provide
25
+ * the {@link id} and the SDK will generate a random UUID. However, keep in mind that this
26
+ * value is used for segmenting users in percentage rollouts, and changes to the {@link id}
27
+ * could result in the user being assigned to a different percentile.
28
+ */
29
+ id?: string;
30
+ /**
31
+ * The user's display name. This will be shown in the ConfigDirector dashboard and may be
32
+ * used for targeting rules.
33
+ */
34
+ name?: string;
35
+ /**
36
+ * Any arbitrary traits for the current user. They will be shown in the ConfigDirector
37
+ * dashboard and may be used for targeting rules.
38
+ */
39
+ traits?: {
40
+ [key: string]: unknown;
41
+ };
42
+ };
43
+ type ConfigDirectorMetaContext = {
44
+ appVersion?: string;
45
+ appName?: string;
46
+ };
47
+ //#endregion
48
+ //#region src/types.d.ts
49
+ /**
50
+ * Configuration options for the {@link ConfigDirectorClient}
51
+ */
52
+ type ConfigDirectorClientOptions = {
53
+ /**
54
+ * Application metadata that remains constant through the lifetime of the connection
55
+ */
56
+ metadata?: ConfigDirectorMetaContext;
57
+ /**
58
+ * Connection options
59
+ */
60
+ connection?: {
61
+ /**
62
+ * Whether to open a streaming connection or use a one-time pull of configuration state.
63
+ * If set to true, the streaming connection will remain open and receive updates whenever
64
+ * config state is updated on the ConfigDirector dashboard.
65
+ * When set to false, there will be an initial request to retrieve config state during
66
+ * initialization, and an additional request whenever {@link ConfigDirectorClient.updateContext}
67
+ * is called. But not updates will be received after those requests.
68
+ *
69
+ * Defaults to true (streaming connection)
70
+ */
71
+ streaming?: boolean;
72
+ /**
73
+ * The timeout, in milliseconds, to be used in initialization and when updating the context.
74
+ * If streaming is enabled, the operation (initialization or context update) may still succeed
75
+ * after it times out if no unrecoverable errors are encountered (like an invalid SDK key).
76
+ * If streaming is disabled, if the operation times out, it will not be retried.
77
+ */
78
+ timeout?: number;
79
+ /**
80
+ * The base URL to the ConfigDirector SDK server. To be used only when needing to route through a
81
+ * proxy to connect to the ConfigDirector SDK server. Please refer to the docs on how to configure
82
+ * a proxy for the client SDK.
83
+ */
84
+ url?: string;
85
+ };
86
+ /**
87
+ * A logger that implements {@link ConfigDirectorLogger}. It defaults to the ConfigDirector console
88
+ * logger set to 'warn' level.
89
+ *
90
+ * The log level of the default logger can be adjusted by creating a default logger with the desired
91
+ * level and providing it in this property:
92
+ * @example
93
+ * import { createClient, createConsoleLogger } from "@configdirector/client-sdk";
94
+ * const client = createClient(
95
+ * "YOUR-SDK-KEY",
96
+ * { logger: createConsoleLogger("debug") },
97
+ * );
98
+ */
99
+ logger?: ConfigDirectorLogger;
100
+ };
101
+ type ClientEvents = {
102
+ configsUpdated: {
103
+ keys: string[];
104
+ };
105
+ clientReady: undefined;
106
+ };
107
+ type WatchHandler<T extends ConfigValueType> = (message: T) => void;
108
+ /**
109
+ * The ConfigDirector SDK client object.
110
+ *
111
+ * Applications should create a single instance of `ConfigDirectorClient`, and call
112
+ * {@link initialize} during application initialization.
113
+ */
114
+ interface ConfigDirectorClient {
115
+ /**
116
+ * Initializes the connection to ConfigDirector to retrieve config definitions. Until
117
+ * initialization is successful, all flags will return their default value provided to
118
+ * {@link watch} or {@link getValue}.
119
+ *
120
+ * If the connection fails or is interrupted with a transient error (network error,
121
+ * internal server error, etc) the client will continue to attempt to connect. However,
122
+ * if the connection fails with a persistent error, like an invalid SDK key, the client will
123
+ * not attempt to re-connect and an error will be logged to the console or the provided
124
+ * logger.
125
+ */
126
+ initialize(): Promise<void>;
127
+ /**
128
+ * Returns whether or not the client is ready after calling {@link initialize}
129
+ *
130
+ * The definition of ready is that the connection to the server was successful, and config definitions
131
+ * were received.
132
+ */
133
+ get isReady(): boolean;
134
+ /**
135
+ * Evaluates a config and returns its value based on the current context and targeting rules
136
+ *
137
+ * @returns The evaluated config value, or the `defaultValue` if the config state was unavailable
138
+ * @param configKey The config key to evaluate
139
+ * @param defaultValue The default value to be returned if the config state is unavailable. For
140
+ * example, if the client cannot connect to the server due to network conditions, or if getValue
141
+ * is called before initialization is done.
142
+ * @param context The user's context to be used for targeting rules evaluation (optional)
143
+ */
144
+ getValue<T extends ConfigValueType>(configKey: string, defaultValue: T, context?: ConfigDirectorContext): T;
145
+ /**
146
+ * Watches for changes to a a config evaluation value. Whenever the config value changes, the
147
+ * provided callback function will be called with the new value. Changes can happen due to updates
148
+ * to the config in the ConfigDirector dashboard, or if the context is updated via {@link updateContext}.
149
+ *
150
+ * @returns An 'unwatch' function that can be called to remove the subscriber
151
+ *
152
+ * @param configKey The config key to watch
153
+ * @param defaultValue The default value to be referenced if the config state is unavailable
154
+ * @param callback The callback function to be called whenever the config value is updated
155
+ * @param context The user's context to be used for targeting rules evaluation (optional)
156
+ */
157
+ watch<T extends ConfigValueType>(configKey: string, defaultValue: T, callback: WatchHandler<T>, context?: ConfigDirectorContext): () => void;
158
+ /**
159
+ * Removes a particular subscriber to the given `configKey`, or all subscribers if no callback
160
+ * is provided.
161
+ *
162
+ * @param configKey The config key to remove subscribers from
163
+ * @param callback The subscriber to be removed. If not provided, all subscribers are removed for
164
+ * the given `configKey`.
165
+ */
166
+ unwatch<T extends ConfigValueType>(configKey: string, callback?: WatchHandler<T>): void;
167
+ /**
168
+ * Removes all subscribers from all config keys
169
+ */
170
+ unwatchAll(): void;
171
+ on<T extends keyof ClientEvents>(eventName: T, handler: (args: ClientEvents[T]) => void): void;
172
+ off<T extends keyof ClientEvents>(eventName: T, handler?: (args: ClientEvents[T]) => void): void;
173
+ /**
174
+ * Disposes of the client. All connections are closed, and all event and config key subscribers
175
+ * are removed.
176
+ *
177
+ * Intended to be called when your application shuts down
178
+ */
179
+ dispose(): void;
180
+ }
181
+ //#endregion
182
+ //#region ../shared/src/logger.d.ts
183
+ declare class DefaultConsoleLogger implements ConfigDirectorLogger {
184
+ private readonly level;
185
+ private readonly decorator;
186
+ private readonly dateFormatter;
187
+ constructor(level: ConfigDirectorLoggingLevel, decorator: ConfigDirectorLogMessageDecorator);
188
+ debug(message: string, ...args: any): void;
189
+ info(message: string, ...args: any): void;
190
+ warn(message: string, ...args: any): void;
191
+ error(message: string, ...args: any): void;
192
+ private log;
193
+ }
194
+ //#endregion
195
+ //#region src/api.d.ts
196
+ declare const createClient: (serverSdkKey: string, clientOptions?: ConfigDirectorClientOptions) => ConfigDirectorClient;
197
+ declare const createConsoleLogger: (level: ConfigDirectorLoggingLevel, messageDecorator?: ConfigDirectorLogMessageDecorator) => DefaultConsoleLogger;
198
+ //#endregion
199
+ export { type ConfigDirectorClient, type ConfigDirectorClientOptions, type ConfigDirectorContext, type ConfigDirectorLogMessageDecorator, type ConfigDirectorLogger, type ConfigDirectorLoggingLevel, type ConfigValueType, createClient, createConsoleLogger };
200
+ //# sourceMappingURL=configdirector-server.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configdirector-server.d.cts","names":[],"sources":["../../shared/src/types.ts","../src/types.ts","../../shared/src/logger.ts","../src/api.ts"],"mappings":";KAYY,kBAAA;EAAA,CAAwB,GAAA;AAAA;AAAA,KAExB,eAAA,wCAAuD,kBAAA;AAAA,KAcvD,0BAAA;AAAA,UAEK,oBAAA;EACf,KAAA,CAAM,OAAA,aAAoB,IAAA;EAE1B,IAAA,CAAK,OAAA,aAAoB,IAAA;EAEzB,IAAA,CAAK,OAAA,aAAoB,IAAA;EAEzB,KAAA,CAAM,OAAA,aAAoB,IAAA;AAAA;AAAA,UAGX,iCAAA;EACf,eAAA,CAAgB,OAAA;AAAA;;;;;KAgBN,qBAAA;EApBoB;;AAGhC;;;;;AAiBA;EASE,EAAA;;;;;EAMA,IAAA;EAMY;;;AAGd;EAHE,MAAA;IAAA,CAAY,GAAA;EAAA;AAAA;AAAA,KAGF,yBAAA;EACV,UAAA;EACA,OAAA;AAAA;;;;;;KC9DU,2BAAA;EDPe;;;ECWzB,QAAA,GAAW,yBAAA;EDGD;;;ECCV,UAAA;IDDoC;AAEtC;;;;;;;;;ICUI,SAAA;IDLF;;;;;;ICYE,OAAA;IDV4B;AAGhC;;;;ICaI,GAAA;EAAA;EDI6B;;;;;;;;;AAwBjC;;;;ECbE,MAAA,GAAS,oBAAA;AAAA;AAAA,KAGC,YAAA;EACV,cAAA;IAAkB,IAAA;EAAA;EAClB,WAAA;AAAA;AAAA,KAGU,YAAA,WAAuB,eAAA,KAAoB,OAAA,EAAS,CAAA;;;;;;;UAQ/C,oBAAA;EAhBc;;AAG/B;;;;;;;;;EAyBE,UAAA,IAAc,OAAA;EApBQ;;;;;;EAAA,IA4BlB,OAAA;EA5B2D;;AAQjE;;;;;;;;EAgCE,QAAA,WAAmB,eAAA,EAAiB,SAAA,UAAmB,YAAA,EAAc,CAAA,EAAG,OAAA,GAAU,qBAAA,GAAwB,CAAA;EAgB1F;;;;;;;;;;;;EAFhB,KAAA,WAAgB,eAAA,EACd,SAAA,UACA,YAAA,EAAc,CAAA,EACd,QAAA,EAAU,YAAA,CAAa,CAAA,GACvB,OAAA,GAAU,qBAAA;EAoBqD;;;;;;;;EATjE,OAAA,WAAkB,eAAA,EAAiB,SAAA,UAAmB,QAAA,GAAW,YAAA,CAAa,CAAA;EA7B3D;;;EAkCnB,UAAA;EAEA,EAAA,iBAAmB,YAAA,EAAc,SAAA,EAAW,CAAA,EAAG,OAAA,GAAU,IAAA,EAAM,YAAA,CAAa,CAAA;EAE5E,GAAA,iBAAoB,YAAA,EAAc,SAAA,EAAW,CAAA,EAAG,OAAA,IAAW,IAAA,EAAM,YAAA,CAAa,CAAA;EAtC4B;;;;;;EA8C1G,OAAA;AAAA;;;cCnIW,oBAAA,YAAgC,oBAAA;EAAA,iBAGd,KAAA;EAAA,iBAAoD,SAAA;EAAA,iBAFhE,aAAA;cAEY,KAAA,EAAO,0BAAA,EAA6C,SAAA,EAAW,iCAAA;EAM5F,KAAA,CAAM,OAAA,aAAoB,IAAA;EAI1B,IAAA,CAAK,OAAA,aAAoB,IAAA;EAIzB,IAAA,CAAK,OAAA,aAAoB,IAAA;EAIzB,KAAA,CAAM,OAAA,aAAoB,IAAA;EAAA,QAIlB,GAAA;AAAA;;;cCnDG,YAAA,GACX,YAAA,UACA,aAAA,GAAgB,2BAAA,KACf,oBAAA;AAAA,cAQU,mBAAA,GACX,KAAA,EAAO,0BAAA,EACP,gBAAA,GAAmB,iCAAA,KAAiC,oBAAA"}
@@ -0,0 +1,200 @@
1
+ //#region ../shared/src/types.d.ts
2
+ type ConfigEnumLikeType = {
3
+ [key: string]: string | number;
4
+ };
5
+ type ConfigValueType = string | number | boolean | object | ConfigEnumLikeType;
6
+ type ConfigDirectorLoggingLevel = "debug" | "info" | "warn" | "error" | "off";
7
+ interface ConfigDirectorLogger {
8
+ debug(message: string, ...args: any): void;
9
+ info(message: string, ...args: any): void;
10
+ warn(message: string, ...args: any): void;
11
+ error(message: string, ...args: any): void;
12
+ }
13
+ interface ConfigDirectorLogMessageDecorator {
14
+ decorateMessage(message: string): string;
15
+ }
16
+ /**
17
+ * The user's context to be sent to ConfigDirector. This context will be used for targeting
18
+ * rules evaluation.
19
+ */
20
+ type ConfigDirectorContext = {
21
+ /**
22
+ * The user's identifier. This should be a value that uniquely identifies an application
23
+ * user.
24
+ * In the case of anonymous users, you could generate a UUID or alternatively not provide
25
+ * the {@link id} and the SDK will generate a random UUID. However, keep in mind that this
26
+ * value is used for segmenting users in percentage rollouts, and changes to the {@link id}
27
+ * could result in the user being assigned to a different percentile.
28
+ */
29
+ id?: string;
30
+ /**
31
+ * The user's display name. This will be shown in the ConfigDirector dashboard and may be
32
+ * used for targeting rules.
33
+ */
34
+ name?: string;
35
+ /**
36
+ * Any arbitrary traits for the current user. They will be shown in the ConfigDirector
37
+ * dashboard and may be used for targeting rules.
38
+ */
39
+ traits?: {
40
+ [key: string]: unknown;
41
+ };
42
+ };
43
+ type ConfigDirectorMetaContext = {
44
+ appVersion?: string;
45
+ appName?: string;
46
+ };
47
+ //#endregion
48
+ //#region src/types.d.ts
49
+ /**
50
+ * Configuration options for the {@link ConfigDirectorClient}
51
+ */
52
+ type ConfigDirectorClientOptions = {
53
+ /**
54
+ * Application metadata that remains constant through the lifetime of the connection
55
+ */
56
+ metadata?: ConfigDirectorMetaContext;
57
+ /**
58
+ * Connection options
59
+ */
60
+ connection?: {
61
+ /**
62
+ * Whether to open a streaming connection or use a one-time pull of configuration state.
63
+ * If set to true, the streaming connection will remain open and receive updates whenever
64
+ * config state is updated on the ConfigDirector dashboard.
65
+ * When set to false, there will be an initial request to retrieve config state during
66
+ * initialization, and an additional request whenever {@link ConfigDirectorClient.updateContext}
67
+ * is called. But not updates will be received after those requests.
68
+ *
69
+ * Defaults to true (streaming connection)
70
+ */
71
+ streaming?: boolean;
72
+ /**
73
+ * The timeout, in milliseconds, to be used in initialization and when updating the context.
74
+ * If streaming is enabled, the operation (initialization or context update) may still succeed
75
+ * after it times out if no unrecoverable errors are encountered (like an invalid SDK key).
76
+ * If streaming is disabled, if the operation times out, it will not be retried.
77
+ */
78
+ timeout?: number;
79
+ /**
80
+ * The base URL to the ConfigDirector SDK server. To be used only when needing to route through a
81
+ * proxy to connect to the ConfigDirector SDK server. Please refer to the docs on how to configure
82
+ * a proxy for the client SDK.
83
+ */
84
+ url?: string;
85
+ };
86
+ /**
87
+ * A logger that implements {@link ConfigDirectorLogger}. It defaults to the ConfigDirector console
88
+ * logger set to 'warn' level.
89
+ *
90
+ * The log level of the default logger can be adjusted by creating a default logger with the desired
91
+ * level and providing it in this property:
92
+ * @example
93
+ * import { createClient, createConsoleLogger } from "@configdirector/client-sdk";
94
+ * const client = createClient(
95
+ * "YOUR-SDK-KEY",
96
+ * { logger: createConsoleLogger("debug") },
97
+ * );
98
+ */
99
+ logger?: ConfigDirectorLogger;
100
+ };
101
+ type ClientEvents = {
102
+ configsUpdated: {
103
+ keys: string[];
104
+ };
105
+ clientReady: undefined;
106
+ };
107
+ type WatchHandler<T extends ConfigValueType> = (message: T) => void;
108
+ /**
109
+ * The ConfigDirector SDK client object.
110
+ *
111
+ * Applications should create a single instance of `ConfigDirectorClient`, and call
112
+ * {@link initialize} during application initialization.
113
+ */
114
+ interface ConfigDirectorClient {
115
+ /**
116
+ * Initializes the connection to ConfigDirector to retrieve config definitions. Until
117
+ * initialization is successful, all flags will return their default value provided to
118
+ * {@link watch} or {@link getValue}.
119
+ *
120
+ * If the connection fails or is interrupted with a transient error (network error,
121
+ * internal server error, etc) the client will continue to attempt to connect. However,
122
+ * if the connection fails with a persistent error, like an invalid SDK key, the client will
123
+ * not attempt to re-connect and an error will be logged to the console or the provided
124
+ * logger.
125
+ */
126
+ initialize(): Promise<void>;
127
+ /**
128
+ * Returns whether or not the client is ready after calling {@link initialize}
129
+ *
130
+ * The definition of ready is that the connection to the server was successful, and config definitions
131
+ * were received.
132
+ */
133
+ get isReady(): boolean;
134
+ /**
135
+ * Evaluates a config and returns its value based on the current context and targeting rules
136
+ *
137
+ * @returns The evaluated config value, or the `defaultValue` if the config state was unavailable
138
+ * @param configKey The config key to evaluate
139
+ * @param defaultValue The default value to be returned if the config state is unavailable. For
140
+ * example, if the client cannot connect to the server due to network conditions, or if getValue
141
+ * is called before initialization is done.
142
+ * @param context The user's context to be used for targeting rules evaluation (optional)
143
+ */
144
+ getValue<T extends ConfigValueType>(configKey: string, defaultValue: T, context?: ConfigDirectorContext): T;
145
+ /**
146
+ * Watches for changes to a a config evaluation value. Whenever the config value changes, the
147
+ * provided callback function will be called with the new value. Changes can happen due to updates
148
+ * to the config in the ConfigDirector dashboard, or if the context is updated via {@link updateContext}.
149
+ *
150
+ * @returns An 'unwatch' function that can be called to remove the subscriber
151
+ *
152
+ * @param configKey The config key to watch
153
+ * @param defaultValue The default value to be referenced if the config state is unavailable
154
+ * @param callback The callback function to be called whenever the config value is updated
155
+ * @param context The user's context to be used for targeting rules evaluation (optional)
156
+ */
157
+ watch<T extends ConfigValueType>(configKey: string, defaultValue: T, callback: WatchHandler<T>, context?: ConfigDirectorContext): () => void;
158
+ /**
159
+ * Removes a particular subscriber to the given `configKey`, or all subscribers if no callback
160
+ * is provided.
161
+ *
162
+ * @param configKey The config key to remove subscribers from
163
+ * @param callback The subscriber to be removed. If not provided, all subscribers are removed for
164
+ * the given `configKey`.
165
+ */
166
+ unwatch<T extends ConfigValueType>(configKey: string, callback?: WatchHandler<T>): void;
167
+ /**
168
+ * Removes all subscribers from all config keys
169
+ */
170
+ unwatchAll(): void;
171
+ on<T extends keyof ClientEvents>(eventName: T, handler: (args: ClientEvents[T]) => void): void;
172
+ off<T extends keyof ClientEvents>(eventName: T, handler?: (args: ClientEvents[T]) => void): void;
173
+ /**
174
+ * Disposes of the client. All connections are closed, and all event and config key subscribers
175
+ * are removed.
176
+ *
177
+ * Intended to be called when your application shuts down
178
+ */
179
+ dispose(): void;
180
+ }
181
+ //#endregion
182
+ //#region ../shared/src/logger.d.ts
183
+ declare class DefaultConsoleLogger implements ConfigDirectorLogger {
184
+ private readonly level;
185
+ private readonly decorator;
186
+ private readonly dateFormatter;
187
+ constructor(level: ConfigDirectorLoggingLevel, decorator: ConfigDirectorLogMessageDecorator);
188
+ debug(message: string, ...args: any): void;
189
+ info(message: string, ...args: any): void;
190
+ warn(message: string, ...args: any): void;
191
+ error(message: string, ...args: any): void;
192
+ private log;
193
+ }
194
+ //#endregion
195
+ //#region src/api.d.ts
196
+ declare const createClient: (serverSdkKey: string, clientOptions?: ConfigDirectorClientOptions) => ConfigDirectorClient;
197
+ declare const createConsoleLogger: (level: ConfigDirectorLoggingLevel, messageDecorator?: ConfigDirectorLogMessageDecorator) => DefaultConsoleLogger;
198
+ //#endregion
199
+ export { type ConfigDirectorClient, type ConfigDirectorClientOptions, type ConfigDirectorContext, type ConfigDirectorLogMessageDecorator, type ConfigDirectorLogger, type ConfigDirectorLoggingLevel, type ConfigValueType, createClient, createConsoleLogger };
200
+ //# sourceMappingURL=configdirector-server.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configdirector-server.d.mts","names":[],"sources":["../../shared/src/types.ts","../src/types.ts","../../shared/src/logger.ts","../src/api.ts"],"mappings":";KAYY,kBAAA;EAAA,CAAwB,GAAA;AAAA;AAAA,KAExB,eAAA,wCAAuD,kBAAA;AAAA,KAcvD,0BAAA;AAAA,UAEK,oBAAA;EACf,KAAA,CAAM,OAAA,aAAoB,IAAA;EAE1B,IAAA,CAAK,OAAA,aAAoB,IAAA;EAEzB,IAAA,CAAK,OAAA,aAAoB,IAAA;EAEzB,KAAA,CAAM,OAAA,aAAoB,IAAA;AAAA;AAAA,UAGX,iCAAA;EACf,eAAA,CAAgB,OAAA;AAAA;;;;;KAgBN,qBAAA;EApBoB;;AAGhC;;;;;AAiBA;EASE,EAAA;;;;;EAMA,IAAA;EAMY;;;AAGd;EAHE,MAAA;IAAA,CAAY,GAAA;EAAA;AAAA;AAAA,KAGF,yBAAA;EACV,UAAA;EACA,OAAA;AAAA;;;;;;KC9DU,2BAAA;EDPe;;;ECWzB,QAAA,GAAW,yBAAA;EDGD;;;ECCV,UAAA;IDDoC;AAEtC;;;;;;;;;ICUI,SAAA;IDLF;;;;;;ICYE,OAAA;IDV4B;AAGhC;;;;ICaI,GAAA;EAAA;EDI6B;;;;;;;;;AAwBjC;;;;ECbE,MAAA,GAAS,oBAAA;AAAA;AAAA,KAGC,YAAA;EACV,cAAA;IAAkB,IAAA;EAAA;EAClB,WAAA;AAAA;AAAA,KAGU,YAAA,WAAuB,eAAA,KAAoB,OAAA,EAAS,CAAA;;;;;;;UAQ/C,oBAAA;EAhBc;;AAG/B;;;;;;;;;EAyBE,UAAA,IAAc,OAAA;EApBQ;;;;;;EAAA,IA4BlB,OAAA;EA5B2D;;AAQjE;;;;;;;;EAgCE,QAAA,WAAmB,eAAA,EAAiB,SAAA,UAAmB,YAAA,EAAc,CAAA,EAAG,OAAA,GAAU,qBAAA,GAAwB,CAAA;EAgB1F;;;;;;;;;;;;EAFhB,KAAA,WAAgB,eAAA,EACd,SAAA,UACA,YAAA,EAAc,CAAA,EACd,QAAA,EAAU,YAAA,CAAa,CAAA,GACvB,OAAA,GAAU,qBAAA;EAoBqD;;;;;;;;EATjE,OAAA,WAAkB,eAAA,EAAiB,SAAA,UAAmB,QAAA,GAAW,YAAA,CAAa,CAAA;EA7B3D;;;EAkCnB,UAAA;EAEA,EAAA,iBAAmB,YAAA,EAAc,SAAA,EAAW,CAAA,EAAG,OAAA,GAAU,IAAA,EAAM,YAAA,CAAa,CAAA;EAE5E,GAAA,iBAAoB,YAAA,EAAc,SAAA,EAAW,CAAA,EAAG,OAAA,IAAW,IAAA,EAAM,YAAA,CAAa,CAAA;EAtC4B;;;;;;EA8C1G,OAAA;AAAA;;;cCnIW,oBAAA,YAAgC,oBAAA;EAAA,iBAGd,KAAA;EAAA,iBAAoD,SAAA;EAAA,iBAFhE,aAAA;cAEY,KAAA,EAAO,0BAAA,EAA6C,SAAA,EAAW,iCAAA;EAM5F,KAAA,CAAM,OAAA,aAAoB,IAAA;EAI1B,IAAA,CAAK,OAAA,aAAoB,IAAA;EAIzB,IAAA,CAAK,OAAA,aAAoB,IAAA;EAIzB,KAAA,CAAM,OAAA,aAAoB,IAAA;EAAA,QAIlB,GAAA;AAAA;;;cCnDG,YAAA,GACX,YAAA,UACA,aAAA,GAAgB,2BAAA,KACf,oBAAA;AAAA,cAQU,mBAAA,GACX,KAAA,EAAO,0BAAA,EACP,gBAAA,GAAmB,iCAAA,KAAiC,oBAAA"}
@@ -0,0 +1,2 @@
1
+ import{createEventSource as e}from"eventsource-client";import t,{EventEmitter as n}from"node:events";import{findByPointer as r}from"@jsonjoy.com/json-pointer";import*as i from"semver";import{rapidhash as a}from"rapidhash-js";var o=class e extends Error{constructor(t,n){super(t),this.name=`ConfigDirectorConnectionError`,this.status=n,Object.setPrototypeOf(this,e.prototype)}},s=class e extends Error{constructor(t){super(t),this.name=`ConfigDirectorValidationError`,Object.setPrototypeOf(this,e.prototype)}},c=class{constructor(e){this.options=e,this.eventEmitter=new n,this.options=e,this.logger=e.logger,this.url=new URL(`server/sse/v1`,e.baseUrl)}async connect(t){this.eventSource&&this.close();let n,r,i=async(e,t)=>{let i=await fetch(e,t);return n=i.status,i.ok||(r=await i.text()),i},a=new Promise((t,a)=>{this.eventSource=e({url:this.url,fetch:i,method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey}),onMessage:({data:e})=>{this.dispatchMessage(e)},onConnect:()=>{n&&this.isStatusFatal(n)?(this.close(),a(this.prepareFatalError(n,r))):(this.logger.debug(`[EventSourceTransport] Connected, status: %s`,n),t(this))},onScheduleReconnect:e=>{n&&this.isStatusFatal(n)?(this.close(),a(this.prepareFatalError(n,r))):this.logger.warn(`[EventSourceTransport] Scheduling reconnect in ${e.delay}. Response status: ${n}`)},onDisconnect:()=>{this.logger.debug(`[EventSourceTransport] Disconnected`)}})});return Promise.race([a,new Promise(e=>{setTimeout(()=>e(this),t)})])}dispatchMessage(e){try{let t=JSON.parse(e);this.eventEmitter.emit(`configBundleReceived`,t)}catch(e){this.logger.error(`[EventSourceTransport] Error parsing and dispatching config data update: `,e)}}prepareFatalError(e,t){let n=e??0;return new o(`${`Connection failed with status: ${e??`unknown`}`}${(t?.trim()?.length??0)>0?` (${t})`:``}. This is an unrecoverable error, will not attempt to reconnect.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}get isConnected(){return this.eventSource?.readyState==`open`}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}clear(){this.eventEmitter.removeAllListeners()}close(){this.eventSource?.close()}dispose(){this.close(),this.clear()}};const l=(e,t,n)=>{switch(t.toLowerCase()){case`=`:case`equals`:return e==n[0];case`!=`:case`does not equal`:return e!=n[0];case`is one of`:return n.includes(e);case`is not one of`:return!n.includes(e);case`starts with any of`:return n.findIndex(t=>e?.startsWith(t))>=0;case`does not start with any of`:return n.findIndex(t=>e?.startsWith(t))<0;case`ends with any of`:return n.findIndex(t=>e?.endsWith(t))>=0;case`does not end with any of`:return n.findIndex(t=>e?.endsWith(t))<0;case`matches regex`:return u(n[0],e);case`does not match regex`:return!u(n[0],e);default:return!1}},u=(e,t)=>{try{return new RegExp(e).test(t)}catch{return!1}},d=(e,t,n)=>{if(typeof e==`boolean`)return!1;let r=t.toLowerCase();if(e==null)return r==`!=`||r==`does not equal`;let{value:i,target:a}=f(e,n[0]);switch(r){case`=`:case`equals`:return i==a;case`!=`:case`does not equal`:return i!=a;case`<`:return i<a;case`<=`:return i<=a;case`>`:return i>a;case`>=`:return i>=a;default:return!1}},f=(e,t)=>typeof e==`number`?{value:e,target:p(t)}:typeof e==`bigint`?{value:e,target:BigInt(t)}:{value:p(e),target:p(t)},p=e=>{let t=Number.parseFloat(e.toString());if(isNaN(t)||!isFinite(t))throw TypeError(`Invalid numeric value: ${e}`);return t},m=(e,t,n)=>{if(e==null)return!1;try{let r=new Date(e),i=new Date(n[0]);switch(t){case`is after`:return r>i;case`is before`:return r<i;default:return!1}}catch{return!1}},h=(e,t,n)=>{let r=t.toLowerCase();if(e==null||e.trim().length==0)return r==`is not one of`;let a=i.coerce(e)??e,o=n.map(e=>i.coerce(e)??e);try{switch(r){case`=`:return g(a,o[0]);case`<`:return i.lt(a,o[0]);case`<=`:return i.lte(a,o[0]);case`>`:return i.gt(a,o[0]);case`>=`:return i.gte(a,o[0]);case`is one of`:return o.findIndex(e=>g(a,e))>=0;case`is not one of`:return o.findIndex(e=>g(a,e))<0;default:return!1}}catch{return!1}},g=(e,t)=>{try{return i.eq(e,t)}catch{return!1}},_=(e,t,n)=>{let r=t.toLowerCase();if(!Array.isArray(e))return r==`does not contain any of`;switch(r){case`contains any of`:return e.some(e=>n.includes(e));case`does not contain any of`:return!e.some(e=>n.includes(e));default:return!1}};var v=class{evaluate(e,t){if(!this.isConditionValidForComparison(e))return!1;switch(e.attribute){case`identifier`:return this.compare(t?.context?.id,e);case`name`:return this.compare(t?.context?.name,e);case`appName`:return this.compare(t?.metadata?.appName,e);case`appVersion`:return this.compare(t?.metadata?.appVersion,e);case`traits`:if(e.trait){let n=this.findTraitValue(t?.context?.traits,e.trait);return this.compare(n,e)}}return!1}compare(e,t){switch(t.targetType){case`text`:return l(e?.toString(),t.operator,t.targetValues);case`number`:return d(e,t.operator,t.targetValues);case`datetime`:return m(e?.toString(),t.operator,t.targetValues);case`semver`:return h(e?.toString(),t.operator,t.targetValues);case`array`:return _(e,t.operator,t.targetValues)}return!1}findTraitValue(e,t){try{return r(t,e).val}catch{return}}isConditionValidForComparison(e){return Array.isArray(e.targetValues)&&e.targetValues.length>0}};const y=e=>{let t=`${e.contextIdentifier}-${e.configId}`;return Number(a(t,{seed:15426857351n})%1000n)/10};var b=class{constructor(e){if(this.logger=e,this.conditionEvaluator=new v,!e||!e.warn||!e.error||!e.info)throw TypeError(`The provided logger is not a valid 'ConfigDirectorLogger'`);this.logger=e}evaluate(e,t){return{id:e.id,key:e.key,type:e.type,value:this.getConfigValue(e,t)}}getConfigValue(e,t){let n=[...e.target?.rules??[]].sort((e,t)=>(e.order??2**53-1)-(t.order??2**53-1));for(let r of n){let n=this.evaluateRule(r,e,t);if(n.success)return n.value}return e.target?.defaultValue}evaluateRule(e,t,n){try{if(e.type==`percentage`)return this.evaluatePercentageRule(e,t,n);if(e.type==`conditional`)return this.evaluateConditionalRule(e,t,n)}catch(n){this.logger.warn(`There was an error while evaluating a targeting rule '${e?.id}' for '${t?.key}'. The rule will be disregarded.`,{error:n,configKey:t?.key,ruleId:e?.id})}return{success:!1}}evaluatePercentageRule(e,t,n){return this.evaluatePercentage(e.percentages??[],t,n)}evaluatePercentage(e,t,n){let r=y({configId:t.id,contextIdentifier:n?.context?.id??crypto.randomUUID()}),i=0,a=0,o;for(;!o&&i<e.length;)r<=e[i].percentage+a&&(o=e[i]),a+=e[i].percentage,i+=1;return o?.value==null?{success:!1}:{success:!0,value:o.value.toString()}}evaluateConditionalRule(e,t,n){let r=e.conditions??[],i=0,a;for(;!a&&i<r.length;)this.conditionEvaluator.evaluate(r[i],n)&&(a=r[i]),i+=1;return a&&e.target==`value`&&e.value!=null?{success:!0,value:e.value.toString()}:a&&e.target==`percentage`?this.evaluatePercentage(e.percentages??[],t,n):{success:!1}}};const x=e=>{let t=typeof e;if(t===`object`)try{return e.constructor?.name??t}catch{return t}else if(t===`function`)try{let n=e.name;return n?`function: ${n}`:t}catch{return t}return t},S=e=>e===`number`||e===`bigint`,C=(e,t)=>{let n=e.value,r=x(t);if(!n)return{parsedValue:t,requestedType:r,usedDefault:!0,reason:`value-missing`};if(typeof t==`string`)return{parsedValue:n,requestedType:r,usedDefault:!1,reason:`found-match`};if(typeof t==`boolean`&&e.type===`boolean`){let e=w(n),i=typeof e==`boolean`;return{parsedValue:i?e:t,requestedType:r,usedDefault:!i,reason:i?`found-match`:`invalid-boolean`}}if(S(typeof t)&&e.type===`integer`){let e=T(n),i=typeof e==`number`;return{parsedValue:i?e:t,requestedType:r,usedDefault:!i,reason:i?`found-match`:`invalid-number`}}if(S(typeof t)&&(e.type===`float`||e.type===`enum`)){let e=E(n),i=typeof e==`number`;return{parsedValue:i?e:t,requestedType:r,usedDefault:!i,reason:i?`found-match`:`invalid-number`}}return{parsedValue:n,requestedType:r,usedDefault:!1,reason:`found-match`}},w=e=>{if(!e)return;let t=e.toLowerCase();if(!(t!=`true`&&t!=`false`))return t===`true`},T=e=>{if(!e)return;let t=Number.parseInt(e);if(!(isNaN(t)||!isFinite(t)))return t},E=e=>{if(!e)return;let t=Number.parseFloat(e);if(!(isNaN(t)||!isFinite(t)))return t},D={off:-1,error:0,warn:1,info:2,debug:3},O=()=>{let e={year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,timeZoneName:`short`};try{return new Intl.DateTimeFormat(void 0,{...e,fractionalSecondDigits:3})}catch{return new Intl.DateTimeFormat(void 0,e)}};var k=class{constructor(e,t){this.level=e,this.decorator=t,this.level=e,this.decorator=t,this.dateFormatter=O()}debug(e,...t){this.log(console.debug,`debug`,e,...t)}info(e,...t){this.log(console.info,`info`,e,...t)}warn(e,...t){this.log(console.warn,`warn`,e,...t)}error(e,...t){this.log(console.error,`error`,e,...t)}log(e,t,n,...r){D[this.level]>=D[t]&&e(`[${this.dateFormatter.format(new Date)}] ${this.decorator?.decorateMessage(n)}`,...r)}},A=class{decorateMessage(e){return`[ConfigDirector:js-server-sdk] ${e}`}};const j=(e,t)=>new k(e??`warn`,t??new A),M=e=>N(new TextEncoder().encode(e)).toString(16).padStart(8,`0`),N=e=>{let t=5381;for(let n=0;n<e.length;n++)t=(t<<5)+t+e[n],t>>>=0;return t},P=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},F=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0},I=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(t,{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}};var L=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.url=new URL(`server/telemetry/v1`,e.baseUrl)}async report({discreteEvents:e,aggregatedEvents:t,droppedEvents:n}){if(!this.executeRequests)return{success:!1,fatalError:!0};let r={serverSdkKey:this.sdkKey,discreteEvents:e,aggregatedEvents:t,droppedEvents:n};if(this.isReportEmpty(r))return{success:!0,fatalError:!1};let i=await this.sendReport(r);return i.fatalError&&(this.executeRequests=!1),i}isReportEmpty(e){return P(e.discreteEvents)&&P(e.aggregatedEvents)&&F(e.droppedEvents)}async sendReport(e){try{let t=await I(5e3,this.url,{method:`POST`,body:JSON.stringify(e)},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:!1}}}isStatusFatal(e){return!!e&&e>=400&&e<500}},R=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},z=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||(this.startTime=new Date);let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},B=class{constructor(e){this.evaluationEventQueue=new z,this.aggregator=new R,this.collectEvents=!0,this.logger=e.logger,this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}sanitizeValue(e,t){if(t===`json`)try{return M(JSON.stringify(e))}catch{return e.toString().slice(0,500)}return e.toString().slice(0,500)}async flushAndScheduleNext(){(await this.flush()).fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay)}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=this.evaluationEventQueue.takeSnapshot();return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount}})}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},V=class extends B{constructor(e){super(e),this.reporter=new L(e)}evaluatedConfig(e){this.collectEvents&&this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(e))}sanitizeEvaluatedConfigEvent(e){return{key:e.key,type:e.type,defaultValue:this.sanitizeValue(e.defaultValue,e.type),requestedType:e.requestedType,evaluatedValue:this.sanitizeValue(e.evaluatedValue,e.type),usedDefault:e.usedDefault,evaluationReason:e.evaluationReason}}};const H=new URL(`https://server-sdk-api.configdirector.com`);var U=class{constructor(e,n,r){this.handlersMap=new Map,this.eventEmitter=new t,this.ready=!1,this.logger=r?.logger??j(),this.timeout=r?.connection?.timeout??3e3,this.metaContext=r?.metadata??{},this.configEvaluator=new b(this.logger);let i=this.parseUrl(r?.connection?.url)??H;this.streaming=r?.connection?.streaming!==!1;let a=c;this.usageEventCollector=new V({sdkKey:e,logger:this.logger,baseUrl:i}),this.transport=new a({serverSdkKey:e,baseUrl:i,metaContext:{...r?.metadata,sdkName:n.sdkName,sdkVersion:n.sdkVersion},logger:this.logger}),this.transport.on(`configBundleReceived`,e=>{this.readyResolve?.();let t=Object.keys(e.configs);!this.configSet||e.kind==`full`?(this.configSet=e,this.eventEmitter.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)):(this.configSet.configs={...this.configSet.configs,...e.configs},this.eventEmitter.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)),this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server:`,{keys:t})})}async initialize(){try{this.ready=!1,this.readyPromise=new Promise(e=>{this.readyResolve=e}).then(()=>{this.ready=!0,this.eventEmitter.emit(`clientReady`),this.logger.debug(`[ConfigDirectorClient] Received initial payload from the server, client is ready`)});let e=new Date().getTime();await this.transport.connect(this.timeout);let t=new Date().getTime()-e,n=this.timeout-t;if(n>0&&await Promise.race([this.readyPromise,new Promise(e=>{setTimeout(()=>e(),n)})]),!this.ready){let e=this.streaming?`The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.`:`Since the client was configured without streaming, configs may not update and always return the default value.`;this.logger.warn(`[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${e}`)}}catch(e){this.logger.error(`[ConfigDirectorClient] An error occurred during initialization: `,e)}}updateWatchers(e){Object.values(e).forEach(e=>this.updateWatchersForConfig(e))}updateWatchersForConfig(e){this.handlersMap.get(e.key)?.forEach(t=>{let n=this.getValueFromConfigDefinition(e.key,e,t.defaultValue,t.context);t.handler(n)})}watch(e,t,n,r){this.validateDefaultValue(t);let i=this.handlersMap.get(e),a={handler:n,defaultValue:t,requestedType:typeof t,context:r};return i?i.push(a):this.handlersMap.set(e,[a]),()=>this.unwatch(e,n)}unwatch(e,t){let n=this.handlersMap.get(e);if(n)if(t){let e=n.findIndex(e=>e.handler==t);e>=0&&n?.splice(e,1)}else n.splice(0)}getValue(e,t,n){this.validateDefaultValue(t);let r=this.configSet?.configs[e];return this.getValueFromConfigDefinition(e,r,t,n)}getValueFromConfigDefinition(e,t,n,r){if(t){let i=C(this.configEvaluator.evaluate(t,{context:r,metadata:this.metaContext}),n);return this.usageEventCollector.evaluatedConfig({contextId:r?.id,key:e,defaultValue:n,requestedType:i.requestedType,evaluatedValue:i.parsedValue,usedDefault:i.usedDefault,evaluationReason:i.reason}),this.logger.debug(`[ConfigDirectorClient] Evaluated '${e}' to '${i.parsedValue}'`),i.parsedValue}else return this.logger.debug(`[ConfigDirectorClient] No config state found for '${e}', returning default value '${n}'`),this.usageEventCollector.evaluatedConfig({contextId:r?.id,key:e,defaultValue:n,requestedType:x(n),evaluatedValue:n,usedDefault:!0,evaluationReason:`config-state-missing`}),n}parseUrl(e){if(e)try{return new URL(e)}catch(t){throw new s(`Invalid base URL '${e}'. Parsing failed: ${t}`)}}validateDefaultValue(e){if(e==null)throw new s(`Invalid default value. The default value for a config must be defined and non-null.`);if(typeof e==`function`)throw new s(`Invalid default value. The default value for a config cannot be a function.`)}get isReady(){return this.ready}on(e,t){this.eventEmitter.on(e,t)}off(e,t){t?this.eventEmitter.off(e,t):this.eventEmitter.removeAllListeners(e)}removeAllObservers(){this.logger.debug(`[ConfigDirectorClient] removeAllObservers() has been called, removing all observers`),this.eventEmitter.removeAllListeners(),this.handlersMap.clear()}unwatchAll(){this.handlersMap.clear()}closeConnection(){this.logger.debug(`[ConfigDirectorClient] closeConnection() has been called, closing connection to server`),this.transport.close(),this.ready=!1}dispose(){this.removeAllObservers(),this.closeConnection()}};const W=(e,t)=>new U(e,{sdkName:`js-server-sdk`,sdkVersion:`0.1.0`},t),G=(e,t)=>j(e,t);export{W as createClient,G as createConsoleLogger};
2
+ //# sourceMappingURL=configdirector-server.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configdirector-server.mjs","names":["EventEmitter"],"sources":["../../shared/src/errors.ts","../src/StreamingTransport.ts","../../config-evaluator/src/text-comparison.ts","../../config-evaluator/src/numeric-comparison.ts","../../config-evaluator/src/date-comparison.ts","../../config-evaluator/src/semver-comparison.ts","../../config-evaluator/src/array-comparison.ts","../../config-evaluator/src/ConditionEvaluator.ts","../../config-evaluator/src/percent-hashing.ts","../../config-evaluator/src/ConfigEvaluator.ts","../../shared/src/value-parser.ts","../../shared/src/logger.ts","../src/logger.ts","../../shared/src/telemetry/utils.ts","../../shared/src/fetchWithTimeout.ts","../src/telemetry/ServerEventReporter.ts","../../shared/src/telemetry/EventAggregator.ts","../../shared/src/telemetry/EventQueue.ts","../../shared/src/telemetry/TelemetryEventCollector.ts","../src/telemetry/ServerTelemetryEventCollector.ts","../src/DefaultConfigDirectorClient.ts","../src/api.ts"],"sourcesContent":["export class ConfigDirectorConnectionError extends Error {\n public override readonly name: string = \"ConfigDirectorConnectionError\";\n public readonly status?: number;\n\n constructor(message: string, status?: number) {\n super(message);\n this.status = status;\n\n Object.setPrototypeOf(this, ConfigDirectorConnectionError.prototype);\n }\n}\n\nexport class ConfigDirectorValidationError extends Error {\n public override readonly name: string = \"ConfigDirectorValidationError\";\n\n constructor(message: string) {\n super(message);\n\n Object.setPrototypeOf(this, ConfigDirectorValidationError.prototype);\n }\n}\n","import { createEventSource, type EventSourceClient } from \"eventsource-client\";\nimport type {\n ConfigBundle,\n ConfigDirectorLogger,\n Transport,\n TransportEvents,\n TransportOptions,\n} from \"./types\";\nimport { ConfigDirectorConnectionError } from \"@shared/errors\";\nimport { EventEmitter } from \"node:events\";\n\nexport class StreamingTransport implements Transport {\n private logger: ConfigDirectorLogger;\n private eventSource: EventSourceClient | undefined;\n private eventEmitter = new EventEmitter();\n private url: URL;\n\n constructor(private readonly options: TransportOptions) {\n this.options = options;\n this.logger = options.logger;\n this.url = new URL(\"server/sse/v1\", options.baseUrl);\n }\n\n public async connect(timeout: number): Promise<this> {\n if (this.eventSource) {\n this.close();\n }\n\n let responseStatus: number | undefined = undefined;\n let errorBody: string | undefined = undefined;\n\n const customFetch = async (url: string | URL | Request, init?: RequestInit | undefined) => {\n const response = await fetch(url, init);\n responseStatus = response.status;\n if (!response.ok) {\n errorBody = await response.text();\n }\n return response;\n };\n\n const eventSourcePromise = new Promise<this>((resolve, reject) => {\n this.eventSource = createEventSource({\n url: this.url,\n fetch: customFetch,\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n metaContext: this.options.metaContext,\n serverSdkKey: this.options.serverSdkKey,\n }),\n\n onMessage: ({ data }) => {\n this.dispatchMessage(data);\n },\n onConnect: () => {\n if (responseStatus && this.isStatusFatal(responseStatus)) {\n this.close();\n reject(this.prepareFatalError(responseStatus, errorBody));\n } else {\n this.logger.debug(\"[EventSourceTransport] Connected, status: %s\", responseStatus);\n resolve(this);\n }\n },\n onScheduleReconnect: (info: { delay: number }) => {\n if (responseStatus && this.isStatusFatal(responseStatus)) {\n this.close();\n reject(this.prepareFatalError(responseStatus, errorBody));\n } else {\n this.logger.warn(\n `[EventSourceTransport] Scheduling reconnect in ${info.delay}. Response status: ${responseStatus}`,\n );\n }\n },\n onDisconnect: () => {\n this.logger.debug(\"[EventSourceTransport] Disconnected\");\n },\n });\n });\n return Promise.race([\n eventSourcePromise,\n new Promise<this>((resolve) => {\n setTimeout(() => resolve(this), timeout);\n }),\n ]);\n }\n\n private dispatchMessage(data: string) {\n try {\n const json = JSON.parse(data);\n this.eventEmitter.emit(\"configBundleReceived\", json as ConfigBundle);\n } catch (error) {\n this.logger.error(\"[EventSourceTransport] Error parsing and dispatching config data update: \", error);\n }\n }\n\n private prepareFatalError(\n responseStatus: number,\n errorBody: string | undefined,\n ): ConfigDirectorConnectionError {\n const status = responseStatus ?? 0;\n const headline = `Connection failed with status: ${responseStatus ?? \"unknown\"}`;\n const serverBody = (errorBody?.trim()?.length ?? 0) > 0 ? ` (${errorBody})` : \"\";\n const message = `${headline}${serverBody}. This is an unrecoverable error, will not attempt to reconnect.`;\n return new ConfigDirectorConnectionError(message, status);\n }\n\n private isStatusFatal(status: number | undefined): boolean {\n return !!status && status >= 400 && status < 500;\n }\n\n public get isConnected(): boolean {\n return this.eventSource?.readyState == \"open\";\n }\n\n public on<TName extends keyof TransportEvents>(\n name: TName,\n handler: (payload: TransportEvents[TName]) => void,\n ): void {\n this.eventEmitter.on(name, handler);\n }\n\n public off<TName extends keyof TransportEvents>(\n name: TName,\n handler?: ((payload: TransportEvents[TName]) => void) | undefined,\n ): void {\n if (handler) {\n this.eventEmitter.off(name, handler);\n } else {\n this.eventEmitter.removeAllListeners(name);\n }\n }\n\n public clear(): void {\n this.eventEmitter.removeAllListeners();\n }\n\n public close() {\n this.eventSource?.close();\n }\n\n public dispose(): void {\n this.close();\n this.clear();\n }\n}\n","import { Operator } from \"./types\";\n\nexport const compareText = (value: string | null | undefined, operator: Operator, targetValues: string[]) => {\n switch (operator.toLowerCase()) {\n case \"=\":\n case \"equals\":\n return value == targetValues[0];\n case \"!=\":\n case \"does not equal\":\n return value != targetValues[0];\n case \"is one of\":\n return targetValues.includes(value as any);\n case \"is not one of\":\n return !targetValues.includes(value as any);\n case \"starts with any of\":\n return targetValues.findIndex((v) => value?.startsWith(v)) >= 0;\n case \"does not start with any of\":\n return targetValues.findIndex((v) => value?.startsWith(v)) < 0;\n case \"ends with any of\":\n return targetValues.findIndex((v) => value?.endsWith(v)) >= 0;\n case \"does not end with any of\":\n return targetValues.findIndex((v) => value?.endsWith(v)) < 0;\n case \"matches regex\":\n return matchesRegex(targetValues[0], value);\n case \"does not match regex\":\n return !matchesRegex(targetValues[0], value);\n default:\n return false;\n }\n};\n\nconst matchesRegex = (regexString: string, value: string | null | undefined) => {\n try {\n return new RegExp(regexString).test(value as any);\n } catch {\n return false;\n }\n};\n","import { Operator } from \"./types\";\n\nexport const compareNumeric = (\n givenValue: number | bigint | string | boolean | null | undefined,\n operator: Operator,\n targetValues: string[],\n) => {\n if (typeof givenValue == \"boolean\") {\n return false;\n }\n const lowercaseOperator = operator.toLowerCase();\n if (givenValue == null) {\n return lowercaseOperator == \"!=\" || lowercaseOperator == \"does not equal\";\n }\n\n const { value, target } = parseValueAndTarget(givenValue, targetValues[0]);\n\n switch (lowercaseOperator) {\n case \"=\":\n case \"equals\":\n return value == target;\n case \"!=\":\n case \"does not equal\":\n return value != target;\n case \"<\":\n return value < target;\n case \"<=\":\n return value <= target;\n case \">\":\n return value > target;\n case \">=\":\n return value >= target;\n default:\n return false;\n }\n};\n\nconst parseValueAndTarget = (value: number | bigint | string, targetValue: string) => {\n if (typeof value == \"number\") {\n return { value, target: parseAsNumber(targetValue) };\n } else if (typeof value == \"bigint\") {\n return { value, target: BigInt(targetValue) };\n } else {\n return { value: parseAsNumber(value), target: parseAsNumber(targetValue) };\n }\n};\n\nconst parseAsNumber = (value: number | bigint | string): number => {\n const n = Number.parseFloat(value.toString());\n if (isNaN(n) || !isFinite(n)) {\n throw new TypeError(`Invalid numeric value: ${value}`);\n }\n return n;\n};\n","import { Operator } from \"./types\";\n\nexport const compareDate = (value: string | null | undefined, operator: Operator, targetValues: string[]) => {\n if (value == null) {\n return false;\n }\n\n try {\n const dateValue = new Date(value);\n const dateTarget = new Date(targetValues[0]);\n switch (operator) {\n case \"is after\":\n return dateValue > dateTarget;\n case \"is before\":\n return dateValue < dateTarget;\n default:\n return false;\n }\n } catch {\n return false;\n }\n};\n","import { Operator } from \"./types\";\nimport * as semver from \"semver\";\n\nexport const compareSemver = (value: string | null | undefined, operator: Operator, targetValues: string[]) => {\n const lowercaseOperator = operator.toLowerCase();\n if (value == null || value.trim().length == 0) {\n return lowercaseOperator == \"is not one of\";\n }\n\n const semverValue = semver.coerce(value) ?? value;\n const semverTargets = targetValues.map((v) => semver.coerce(v) ?? v);\n try {\n switch (lowercaseOperator) {\n case \"=\":\n return safeEquals(semverValue, semverTargets[0]);\n case \"<\":\n return semver.lt(semverValue, semverTargets[0]);\n case \"<=\":\n return semver.lte(semverValue, semverTargets[0]);\n case \">\":\n return semver.gt(semverValue, semverTargets[0]);\n case \">=\":\n return semver.gte(semverValue, semverTargets[0]);\n case \"is one of\":\n return semverTargets.findIndex((v) => safeEquals(semverValue, v)) >= 0;\n case \"is not one of\":\n return semverTargets.findIndex((v) => safeEquals(semverValue, v)) < 0;\n default:\n return false;\n }\n } catch {\n return false;\n }\n};\n\nconst safeEquals = (v1: string | semver.SemVer, v2: string | semver.SemVer): boolean => {\n try {\n return semver.eq(v1, v2);\n } catch {\n return false;\n }\n};\n","import { Operator } from \"./types\";\n\nexport const compareArray = (givenValue: any, operator: Operator, targetValues: string[]) => {\n const lowercaseOperator = operator.toLowerCase();\n if (!Array.isArray(givenValue)) {\n return lowercaseOperator == \"does not contain any of\";\n }\n\n switch (lowercaseOperator) {\n case \"contains any of\":\n return givenValue.some((v) => targetValues.includes(v));\n case \"does not contain any of\":\n return !givenValue.some((v) => targetValues.includes(v));\n default:\n return false;\n }\n};\n","import { Condition, EvaluationContext } from \"./types\";\nimport { findByPointer } from \"@jsonjoy.com/json-pointer\";\nimport { compareText } from \"./text-comparison\";\nimport { compareNumeric } from \"./numeric-comparison\";\nimport { compareDate } from \"./date-comparison\";\nimport { compareSemver } from \"./semver-comparison\";\nimport { compareArray } from \"./array-comparison\";\n\nexport class ConditionEvaluator {\n public evaluate(condition: Condition, context?: EvaluationContext): boolean {\n if (!this.isConditionValidForComparison(condition)) {\n return false;\n }\n\n switch (condition.attribute) {\n case \"identifier\":\n return this.compare(context?.context?.id, condition);\n case \"name\":\n return this.compare(context?.context?.name, condition);\n case \"appName\":\n return this.compare(context?.metadata?.appName, condition);\n case \"appVersion\":\n return this.compare(context?.metadata?.appVersion, condition);\n case \"traits\":\n if (condition.trait) {\n const value = this.findTraitValue(context?.context?.traits, condition.trait);\n return this.compare(value, condition);\n }\n }\n\n return false;\n }\n\n private compare(\n value: any,\n condition: Condition,\n ): boolean {\n switch (condition.targetType) {\n case \"text\":\n return compareText(value?.toString(), condition.operator, condition.targetValues);\n case \"number\":\n return compareNumeric(value, condition.operator, condition.targetValues);\n case \"datetime\":\n return compareDate(value?.toString(), condition.operator, condition.targetValues);\n case \"semver\":\n return compareSemver(value?.toString(), condition.operator, condition.targetValues);\n case \"array\":\n return compareArray(value, condition.operator, condition.targetValues);\n }\n return false;\n }\n\n private findTraitValue(traits: Record<string, unknown> | undefined, path: string) {\n try {\n const reference = findByPointer(path, traits);\n return reference.val;\n } catch {\n return undefined;\n }\n }\n\n private isConditionValidForComparison(condition: Condition): boolean {\n return Array.isArray(condition.targetValues) && condition.targetValues.length > 0;\n }\n}\n","import { rapidhash } from \"rapidhash-js\";\n\nconst SEED = 0x397832987n;\n\nexport const assignPercentage = (data: {configId: string, contextIdentifier: string}): number => {\n const value = `${data.contextIdentifier}-${data.configId}`;\n\n return Number(rapidhash(value, { seed: SEED }) % 1_000n) / 10.0;\n};\n","import { ConditionEvaluator } from \"./ConditionEvaluator\";\nimport { assignPercentage } from \"./percent-hashing\";\nimport {\n ConditionalRule,\n Config,\n EvaluationContext,\n ConfigState,\n Percentage,\n PercentageRule,\n Rule,\n Condition,\n ConfigDirectorLogger,\n} from \"./types\";\n\ntype RuleSuccess = {\n success: true;\n value: string;\n};\n\ntype RuleFailure = {\n success: false;\n};\n\ntype RuleEvaluationResult = RuleSuccess | RuleFailure;\n\nexport class ConfigEvaluator {\n private readonly conditionEvaluator = new ConditionEvaluator();\n\n constructor(private readonly logger: ConfigDirectorLogger) {\n if (!logger || !logger.warn || !logger.error || !logger.info) {\n throw new TypeError(\"The provided logger is not a valid 'ConfigDirectorLogger'\");\n }\n\n this.logger = logger;\n }\n\n public evaluate(config: Config, context?: EvaluationContext): ConfigState {\n return {\n id: config.id,\n key: config.key,\n type: config.type,\n value: this.getConfigValue(config, context),\n };\n }\n\n private getConfigValue(config: Config, context?: EvaluationContext) {\n const rules = [...(config.target?.rules ?? [])].sort(\n (a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER),\n );\n for (const rule of rules) {\n const result = this.evaluateRule(rule, config, context);\n if (result.success) {\n return result.value;\n }\n }\n return config.target?.defaultValue;\n }\n\n private evaluateRule(rule: Rule, config: Config, context?: EvaluationContext): RuleEvaluationResult {\n try {\n if (rule.type == \"percentage\") {\n return this.evaluatePercentageRule(rule, config, context);\n } else if (rule.type == \"conditional\") {\n return this.evaluateConditionalRule(rule, config, context);\n }\n } catch (error) {\n this.logger.warn(`There was an error while evaluating a targeting rule '${rule?.id}' for '${config?.key}'. The rule will be disregarded.`, {\n error,\n configKey: config?.key,\n ruleId: rule?.id,\n });\n }\n\n return { success: false };\n }\n\n private evaluatePercentageRule(\n rule: PercentageRule,\n config: Config,\n context?: EvaluationContext,\n ): RuleEvaluationResult {\n return this.evaluatePercentage(rule.percentages ?? [], config, context);\n }\n\n private evaluatePercentage(\n percentages: Percentage[],\n config: Config,\n context?: EvaluationContext,\n ): RuleEvaluationResult {\n const assignedPercentage = assignPercentage({\n configId: config.id,\n contextIdentifier: context?.context?.id ?? crypto.randomUUID(),\n });\n let index = 0;\n let sum = 0.0;\n let bucket: Percentage | undefined = undefined;\n while (!bucket && index < percentages.length) {\n if (assignedPercentage <= percentages[index].percentage + sum) {\n bucket = percentages[index];\n }\n\n sum += percentages[index].percentage;\n index += 1;\n }\n\n if (bucket?.value != null) {\n return { success: true, value: bucket.value.toString() };\n }\n return { success: false };\n }\n\n private evaluateConditionalRule(\n rule: ConditionalRule,\n config: Config,\n context?: EvaluationContext,\n ): RuleEvaluationResult {\n const conditions = rule.conditions ?? [];\n let index = 0;\n let condition: Condition | undefined = undefined;\n while (!condition && index < conditions.length) {\n if (this.conditionEvaluator.evaluate(conditions[index], context)) {\n condition = conditions[index];\n }\n index += 1;\n }\n\n if (condition && rule.target == \"value\" && rule.value != null) {\n return { success: true, value: rule.value.toString() };\n } else if (condition && rule.target == \"percentage\") {\n return this.evaluatePercentage(rule.percentages ?? [], config, context);\n }\n return { success: false };\n }\n}\n","import type { ConfigState, ConfigValueType, EvaluationReason } from \"./types\";\n\ntype NativeType =\n | \"string\"\n | \"number\"\n | \"bigint\"\n | \"boolean\"\n | \"symbol\"\n | \"undefined\"\n | \"object\"\n | \"function\";\n\ntype ParseResult<T extends ConfigValueType> = {\n parsedValue: T;\n requestedType: NativeType | string;\n usedDefault: boolean;\n reason: EvaluationReason;\n};\n\nexport const getRequestedType = <T extends ConfigValueType>(defaultValue: T): string => {\n const baseType = typeof defaultValue;\n if (baseType === \"object\") {\n try {\n return (defaultValue as object).constructor?.name ?? baseType;\n } catch {\n return baseType;\n }\n } else if (baseType === \"function\") {\n try {\n const functionName = (defaultValue as any).name;\n return functionName ? `function: ${functionName}` : baseType;\n } catch {\n return baseType;\n }\n }\n return baseType;\n};\n\nconst isNumericNativeType = (requestedType: NativeType): boolean => {\n return requestedType === \"number\" || requestedType === \"bigint\";\n};\n\nexport const parseConfigValue = <T extends ConfigValueType>(\n configState: ConfigState,\n defaultValue: T,\n): ParseResult<T> => {\n const value = configState.value;\n const requestedType = getRequestedType(defaultValue);\n\n if (!value) {\n return {\n parsedValue: defaultValue as T,\n requestedType,\n usedDefault: true,\n reason: \"value-missing\",\n };\n }\n\n if (typeof defaultValue === \"string\") {\n return {\n parsedValue: value as T,\n requestedType,\n usedDefault: false,\n reason: \"found-match\",\n };\n }\n\n if (typeof defaultValue === \"boolean\" && configState.type === \"boolean\") {\n const boolValue = parseConfigBoolean(value);\n const hasBoolean = typeof boolValue === \"boolean\";\n return {\n parsedValue: (hasBoolean ? boolValue : defaultValue) as T,\n requestedType,\n usedDefault: !hasBoolean,\n reason: hasBoolean ? \"found-match\" : \"invalid-boolean\",\n };\n }\n\n if (isNumericNativeType(typeof defaultValue) && configState.type === \"integer\") {\n const numValue = parseConfigInteger(value);\n const hasNumber = typeof numValue === \"number\";\n return {\n parsedValue: (hasNumber ? numValue : defaultValue) as T,\n requestedType,\n usedDefault: !hasNumber,\n reason: hasNumber ? \"found-match\" : \"invalid-number\",\n };\n }\n if (isNumericNativeType(typeof defaultValue) && (configState.type === \"float\" || configState.type === \"enum\" )) {\n const numValue = parseConfigFloat(value);\n const hasNumber = typeof numValue === \"number\";\n return {\n parsedValue: (hasNumber ? numValue : defaultValue) as T,\n requestedType,\n usedDefault: !hasNumber,\n reason: hasNumber ? \"found-match\" : \"invalid-number\",\n };\n }\n\n return {\n parsedValue: value as T,\n requestedType,\n usedDefault: false,\n reason: \"found-match\",\n };\n};\n\nconst parseConfigBoolean = (value: string): boolean | undefined => {\n if (!value) {\n return;\n }\n const lowerValue = value.toLowerCase();\n if (lowerValue != \"true\" && lowerValue != \"false\") {\n return;\n }\n return lowerValue === \"true\";\n};\n\nconst parseConfigInteger = (value: string): number | undefined => {\n if (!value) {\n return;\n }\n const num = Number.parseInt(value);\n if (isNaN(num) || !isFinite(num)) {\n return;\n }\n return num;\n};\n\nconst parseConfigFloat = (value: string): number | undefined => {\n if (!value) {\n return;\n }\n const num = Number.parseFloat(value);\n if (isNaN(num) || !isFinite(num)) {\n return;\n }\n return num;\n};\n","import { ConfigDirectorLogger, ConfigDirectorLogMessageDecorator, ConfigDirectorLoggingLevel } from \"./types\";\n\nconst LEVELS: Record<ConfigDirectorLoggingLevel, number> = {\n off: -1,\n error: 0,\n warn: 1,\n info: 2,\n debug: 3,\n};\n\nconst buildDateFormatter = () => {\n const baseOptions: Intl.DateTimeFormatOptions = {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n timeZoneName: \"short\",\n };\n\n try {\n return new Intl.DateTimeFormat(\n undefined,\n { ...baseOptions, fractionalSecondDigits: 3 } as Intl.DateTimeFormatOptions,\n );\n } catch {\n return new Intl.DateTimeFormat(undefined, baseOptions);\n }\n};\n\nexport class DefaultConsoleLogger implements ConfigDirectorLogger {\n private readonly dateFormatter;\n\n constructor(private readonly level: ConfigDirectorLoggingLevel, private readonly decorator: ConfigDirectorLogMessageDecorator) {\n this.level = level;\n this.decorator = decorator;\n this.dateFormatter = buildDateFormatter();\n }\n\n debug(message: string, ...args: any): void {\n this.log(console.debug, \"debug\", message, ...args);\n }\n\n info(message: string, ...args: any): void {\n this.log(console.info, \"info\", message, ...args);\n }\n\n warn(message: string, ...args: any): void {\n this.log(console.warn, \"warn\", message, ...args);\n }\n\n error(message: string, ...args: any): void {\n this.log(console.error, \"error\", message, ...args);\n }\n\n private log(loggerFunction: (...args: any) => void, level: ConfigDirectorLoggingLevel, message: string, ...args: any) {\n if (LEVELS[this.level] >= LEVELS[level]) {\n loggerFunction(`[${this.dateFormatter.format(new Date())}] ${this.decorator?.decorateMessage(message)}`, ...args);\n }\n }\n}\n","import { DefaultConsoleLogger } from \"@shared/logger\";\nimport { ConfigDirectorLoggingLevel, ConfigDirectorLogMessageDecorator } from \"@shared/types\";\n\nclass LogMessageDecorator implements ConfigDirectorLogMessageDecorator {\n decorateMessage(message: string): string {\n return `[ConfigDirector:js-server-sdk] ${message}`;\n }\n}\n\nexport const createDefaultLogger = (\n level?: ConfigDirectorLoggingLevel,\n messageDecorator?: ConfigDirectorLogMessageDecorator,\n) => {\n return new DefaultConsoleLogger(level ?? \"warn\", messageDecorator ?? new LogMessageDecorator());\n};\n","import { DroppedEvents } from \"./types\";\n\nexport const djb2Hash = (data: string): string => {\n const hash = djb2(new TextEncoder().encode(data));\n return hash.toString(16).padStart(8, \"0\");\n};\n\nconst djb2 = (bytes: Uint8Array): number => {\n let hash = 5381;\n for (let i = 0; i < bytes.length; i++) {\n hash = (hash << 5) + hash + bytes[i];\n hash = hash >>> 0;\n }\n return hash;\n};\n\nexport const isEventListEmpty = <T extends Record<string | symbol, any[]>>(eventList: T): boolean => {\n const keys = Object.keys(eventList);\n if (keys.length == 0) {\n return true;\n }\n\n for (const key of keys) {\n if ((eventList[key]?.length ?? 0) > 0) {\n return false;\n }\n }\n\n return true;\n};\n\nexport const isDroppedEventsEmpty = (droppedEvents?: DroppedEvents): boolean => {\n if (!droppedEvents) {\n return true;\n }\n const keys = Object.keys(droppedEvents);\n if (keys.length == 0) {\n return true;\n }\n\n for (const key of keys) {\n if ((droppedEvents[key] ?? 0) > 0) {\n return false;\n }\n }\n\n return true;\n};\n","import { ConfigDirectorLogger } from \"./types\";\n\nexport const fetchWithTimeout = async (\n timeout: number,\n resource: string | URL | Request,\n options: RequestInit | undefined,\n logger: ConfigDirectorLogger,\n) => {\n const abortController = new AbortController();\n const abortTimeoutId = setTimeout(() => {\n logger.debug(\"[fetchWithTimeout] Reached timeout, aborting request\");\n abortController.abort();\n }, timeout);\n\n try {\n const response = await fetch(resource, {\n ...options,\n signal: abortController.signal,\n });\n clearTimeout(abortTimeoutId);\n return response;\n } catch (error) {\n logger.warn(\"[fetchWithTimeout] Fetch error: \", error);\n clearTimeout(abortTimeoutId);\n throw error;\n }\n};\n","import { isDroppedEventsEmpty, isEventListEmpty } from \"@shared/telemetry/utils\";\nimport { ConfigDirectorLogger } from \"../types\";\nimport { EventReport, EventReporter, EventReporterPayload, ReporterResponse } from \"./types\";\nimport { fetchWithTimeout } from \"@shared/fetchWithTimeout\";\n\nexport type EventReporterOptions = {\n sdkKey: string;\n logger: ConfigDirectorLogger;\n baseUrl: URL;\n};\n\nexport class ServerEventReporter implements EventReporter {\n private readonly sdkKey: string;\n private readonly logger: ConfigDirectorLogger;\n private readonly url: URL;\n private executeRequests = true;\n\n constructor(options: EventReporterOptions) {\n this.sdkKey = options.sdkKey;\n this.logger = options.logger;\n this.url = new URL(\"server/telemetry/v1\", options.baseUrl);\n }\n\n public async report({\n discreteEvents,\n aggregatedEvents,\n droppedEvents,\n }: EventReporterPayload): Promise<ReporterResponse> {\n if (!this.executeRequests) {\n return { success: false, fatalError: true };\n }\n\n const eventReport: EventReport = {\n serverSdkKey: this.sdkKey,\n discreteEvents,\n aggregatedEvents,\n droppedEvents,\n };\n if (this.isReportEmpty(eventReport)) {\n return { success: true, fatalError: false };\n }\n\n const response = await this.sendReport(eventReport);\n if (response.fatalError) {\n this.executeRequests = false;\n }\n return response;\n }\n\n private isReportEmpty(eventReport: EventReport) {\n return (\n isEventListEmpty(eventReport.discreteEvents) &&\n isEventListEmpty(eventReport.aggregatedEvents) &&\n isDroppedEventsEmpty(eventReport.droppedEvents)\n );\n }\n\n private async sendReport(eventReport: EventReport): Promise<ReporterResponse> {\n try {\n const response = await fetchWithTimeout(\n 5_000,\n this.url,\n {\n method: \"POST\",\n body: JSON.stringify(eventReport),\n },\n this.logger,\n );\n\n const isFatalError = this.isStatusFatal(response.status);\n if (isFatalError) {\n this.logger.warn(\n `[EventReporter] Received a fatal status response (${response.status}) from the telemetry endpoint. No more telemetry data will be sent.`,\n );\n }\n return { success: response.ok, fatalError: isFatalError };\n } catch (error) {\n this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${error}`, { error });\n return { success: false, fatalError: false };\n }\n }\n\n private isStatusFatal(status: number | undefined): boolean {\n return !!status && status >= 400 && status < 500;\n }\n}\n","import { AggregatedEvent, EventQueueSnapshot, ReportableEvent } from \"./types\";\n\nexport class EventAggregator {\n public aggregate<T extends ReportableEvent>(snapshot: EventQueueSnapshot<T>): AggregatedEvent<T>[] {\n if (snapshot.events.length == 0) {\n return [];\n }\n\n const map = new Map<string, { event: T; count: number }>();\n for (const event of snapshot.events) {\n const serializedEvent = JSON.stringify(event);\n const count = map.get(serializedEvent)?.count || 0;\n map.set(serializedEvent, { event, count: count + 1 });\n }\n return Array.from(map).map(([, v]) => {\n return {\n startTime: snapshot.startTime,\n endTime: snapshot.endTime,\n count: v.count,\n event: v.event,\n };\n });\n }\n}\n","import { EventQueueSnapshot, ReportableEvent } from \"./types\";\n\nexport class EventQueue<T extends ReportableEvent> {\n private readonly limit: number;\n private startTime?: Date;\n private _events: T[] = [];\n private _droppedEventCount = 0;\n\n constructor(limit?: number) {\n this.limit = limit ?? 1_000;\n }\n\n public get events(): T[] {\n return this._events;\n }\n\n public get reachedLimit(): boolean {\n return this._events.length >= this.limit;\n }\n\n public get droppedEventCount(): number {\n return this._droppedEventCount;\n }\n\n public push(...newEvents: T[]) {\n if (!this.startTime) {\n this.startTime = new Date();\n }\n\n const newEventsCountToDrop = Math.max(0, newEvents.length - this.limit);\n const eventsToPush = newEvents.slice(newEventsCountToDrop, newEvents.length);\n this._droppedEventCount += newEventsCountToDrop;\n if (this._events.length + eventsToPush.length > this.limit) {\n const eventCountToDrop = this._events.length + eventsToPush.length - this.limit;\n this._events.splice(0, eventCountToDrop);\n this._droppedEventCount += eventCountToDrop;\n }\n return this._events.push(...eventsToPush);\n }\n\n public takeSnapshot(): EventQueueSnapshot<T> {\n const endTime = new Date();\n const startTime = this.startTime ?? endTime;\n const eventsSnapshot = this._events.splice(0);\n const droppedCount = this._droppedEventCount;\n this.startTime = undefined;\n this._droppedEventCount = 0;\n return {\n startTime,\n endTime,\n events: eventsSnapshot,\n droppedCount,\n };\n }\n\n public clear() {\n this._events = [];\n this.startTime = undefined;\n this._droppedEventCount = 0;\n }\n}\n","import { ConfigDirectorContext, ConfigDirectorLogger, ConfigType, ConfigValueType } from \"../types\";\nimport { EventAggregator } from \"./EventAggregator\";\nimport { EventQueue } from \"./EventQueue\";\nimport { djb2Hash } from \"./utils\";\nimport { EventReporter, ReportableEvent, ReporterResponse } from \"./types\";\n\nconst CONFIG_VALUE_MAX_LENGTH = 500;\n\nexport type TelemetryEventCollectorOptions = {\n sdkKey: string;\n logger: ConfigDirectorLogger;\n baseUrl: URL;\n flushIntervalDelay?: number;\n initialFlushIntervalDelay?: number;\n};\n\nexport abstract class TelemetryEventCollector<T extends ReportableEvent> {\n protected readonly logger: ConfigDirectorLogger;\n protected abstract readonly reporter: EventReporter | undefined;\n protected evaluationEventQueue: EventQueue<T> = new EventQueue();\n protected readonly aggregator: EventAggregator = new EventAggregator();\n protected flushIntervalDelay: number;\n protected flushTimeout: ReturnType<typeof setTimeout>;\n protected collectEvents = true;\n protected abstract _context?: ConfigDirectorContext;\n\n constructor(options: TelemetryEventCollectorOptions) {\n this.logger = options.logger;\n this.flushIntervalDelay = options.flushIntervalDelay ?? 30_000;\n const initialDelay = options.initialFlushIntervalDelay ?? 5_000;\n this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), initialDelay);\n }\n\n public abstract evaluatedConfig(event: T): void;\n\n protected sanitizeValue<T extends ConfigValueType>(value: T, type?: ConfigType): string {\n if (type === \"json\") {\n try {\n const json = JSON.stringify(value);\n return djb2Hash(json);\n } catch {\n return value.toString().slice(0, CONFIG_VALUE_MAX_LENGTH);\n }\n }\n\n return value.toString().slice(0, CONFIG_VALUE_MAX_LENGTH);\n }\n\n protected async flushAndScheduleNext() {\n const response = await this.flush();\n if (response.fatalError) {\n this.collectEvents = false;\n this.close();\n this.logger.warn(\n \"[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.\",\n );\n } else {\n this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), this.flushIntervalDelay);\n }\n }\n\n protected async flush(): Promise<ReporterResponse> {\n if (!this.reporter) {\n return { success: false, fatalError: false };\n }\n const evaluationSnapshot = this.evaluationEventQueue.takeSnapshot();\n const response = await this.reporter?.report({\n context: this._context,\n discreteEvents: {},\n aggregatedEvents: {\n evaluatedConfig: this.aggregator.aggregate(evaluationSnapshot),\n },\n droppedEvents: {\n evaluatedConfig: evaluationSnapshot.droppedCount,\n },\n });\n return response;\n }\n\n protected cancelScheduledFlush() {\n clearTimeout(this.flushTimeout);\n }\n\n public close() {\n this.collectEvents = false;\n clearTimeout(this.flushTimeout);\n this.flush();\n this.evaluationEventQueue.clear();\n }\n\n public dispose() {\n this.close();\n }\n}\n","import { ConfigDirectorContext, ConfigValueType } from \"../types\";\nimport { ServerEventReporter } from \"./ServerEventReporter\";\nimport { EvaluatedConfigEvent } from \"./telemetry-events\";\nimport { EventReporter } from \"./types\";\nimport { TelemetryEventCollector, TelemetryEventCollectorOptions } from \"@shared/telemetry/TelemetryEventCollector\";\n\nexport class ServerTelemetryEventCollector extends TelemetryEventCollector<EvaluatedConfigEvent<string>> {\n protected readonly reporter: EventReporter;\n protected _context?: ConfigDirectorContext;\n\n constructor(options: TelemetryEventCollectorOptions) {\n super(options);\n this.reporter = new ServerEventReporter(options);\n }\n\n public evaluatedConfig<T extends ConfigValueType>(event: EvaluatedConfigEvent<T>): void {\n if (!this.collectEvents) {\n return;\n }\n\n this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(event));\n }\n\n private sanitizeEvaluatedConfigEvent<T extends ConfigValueType>(\n event: EvaluatedConfigEvent<T>,\n ): EvaluatedConfigEvent<string> {\n return {\n key: event.key,\n type: event.type,\n defaultValue: this.sanitizeValue(event.defaultValue, event.type),\n requestedType: event.requestedType,\n evaluatedValue: this.sanitizeValue(event.evaluatedValue, event.type),\n usedDefault: event.usedDefault,\n evaluationReason: event.evaluationReason,\n };\n }\n}\n","import { StreamingTransport } from \"./StreamingTransport\";\nimport { ConfigEvaluator } from \"@config-evaluator/ConfigEvaluator\";\nimport { getRequestedType, parseConfigValue } from \"@shared/value-parser\";\nimport {\n ConfigDirectorContext,\n ConfigDirectorClientOptions,\n ConfigDirectorClient,\n ClientEvents,\n WatchHandler,\n ConfigValueType,\n ConfigDirectorLogger,\n Transport,\n IdentifyingSdkOptions,\n ConfigBundle,\n ConfigDefinition,\n} from \"./types\";\nimport { createDefaultLogger } from \"./logger\";\nimport { ConfigDirectorValidationError } from \"@shared/errors\";\nimport EventEmitter from \"node:events\";\nimport { ConfigDirectorMetaContext } from \"@shared/types\";\nimport { ServerTelemetryEventCollector } from \"./telemetry\";\n\nconst defaultBaseUrl = new URL(\"https://server-sdk-api.configdirector.com\");\n\ntype WatchHandlerWithOptions<T extends ConfigValueType> = {\n handler: WatchHandler<T>;\n defaultValue: T;\n requestedType: string;\n context?: ConfigDirectorContext;\n};\n\nexport class DefaultConfigDirectorClient implements ConfigDirectorClient {\n private logger: ConfigDirectorLogger;\n private usageEventCollector: ServerTelemetryEventCollector;\n private configSet: ConfigBundle | undefined;\n private handlersMap: Map<string, WatchHandlerWithOptions<any>[]> = new Map();\n private transport: Transport;\n private eventEmitter = new EventEmitter();\n private timeout: number;\n private ready = false;\n private readyPromise: Promise<void> | undefined;\n private readyResolve: (() => void) | undefined;\n private streaming: boolean;\n private configEvaluator: ConfigEvaluator;\n private metaContext: ConfigDirectorMetaContext;\n\n constructor(\n serverSdkKey: string,\n sdkOptions: IdentifyingSdkOptions,\n clientOptions?: ConfigDirectorClientOptions,\n ) {\n this.logger = clientOptions?.logger ?? createDefaultLogger();\n this.timeout = clientOptions?.connection?.timeout ?? 3_000;\n this.metaContext = clientOptions?.metadata ?? {};\n this.configEvaluator = new ConfigEvaluator(this.logger);\n const baseUrl = this.parseUrl(clientOptions?.connection?.url) ?? defaultBaseUrl;\n this.streaming = clientOptions?.connection?.streaming === false ? false : true;\n const transportConstructor = StreamingTransport;\n this.usageEventCollector = new ServerTelemetryEventCollector({\n sdkKey: serverSdkKey,\n logger: this.logger,\n baseUrl,\n });\n this.transport = new transportConstructor({\n serverSdkKey,\n baseUrl,\n metaContext: {\n ...clientOptions?.metadata,\n sdkName: sdkOptions.sdkName,\n sdkVersion: sdkOptions.sdkVersion,\n },\n logger: this.logger,\n });\n\n this.transport.on(\"configBundleReceived\", (configBundle: ConfigBundle) => {\n this.readyResolve?.();\n const configKeys = Object.keys(configBundle.configs);\n if (!this.configSet || configBundle.kind == \"full\") {\n this.configSet = configBundle;\n this.eventEmitter.emit(\"configsUpdated\", { keys: configKeys });\n this.updateWatchers(configBundle.configs);\n } else {\n this.configSet.configs = {\n ...this.configSet.configs,\n ...configBundle.configs,\n };\n this.eventEmitter.emit(\"configsUpdated\", { keys: configKeys });\n this.updateWatchers(configBundle.configs);\n }\n this.logger.debug(\"[ConfigDirectorClient] ConfigSet updated from server:\", { keys: configKeys });\n });\n }\n\n public async initialize() {\n try {\n this.ready = false;\n this.readyPromise = new Promise<void>((resolve) => {\n this.readyResolve = resolve;\n }).then(() => {\n this.ready = true;\n this.eventEmitter.emit(\"clientReady\");\n this.logger.debug(\"[ConfigDirectorClient] Received initial payload from the server, client is ready\");\n });\n const startTime = new Date().getTime();\n await this.transport.connect(this.timeout);\n const elapsedTime = new Date().getTime() - startTime;\n const remainingTimeout = this.timeout - elapsedTime;\n if (remainingTimeout > 0) {\n await Promise.race([\n this.readyPromise,\n new Promise<void>((resolve) => {\n setTimeout(() => resolve(), remainingTimeout);\n }),\n ]);\n }\n if (!this.ready) {\n const warningDetails = this.streaming\n ? \"The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.\"\n : \"Since the client was configured without streaming, configs may not update and always return the default value.\";\n this.logger.warn(\n `[ConfigDirectorClient] Timed out waiting for initialization after ${this.timeout}ms. ${warningDetails}`,\n );\n }\n } catch (error) {\n this.logger.error(\"[ConfigDirectorClient] An error occurred during initialization: \", error);\n }\n }\n\n private updateWatchers(configsMap: Record<string, ConfigDefinition>) {\n Object.values(configsMap).forEach((v) => this.updateWatchersForConfig(v as ConfigDefinition));\n }\n\n private updateWatchersForConfig(configDefinition: ConfigDefinition) {\n this.handlersMap.get(configDefinition.key)?.forEach((h) => {\n const value = this.getValueFromConfigDefinition(\n configDefinition.key,\n configDefinition,\n h.defaultValue,\n h.context,\n );\n h.handler(value);\n });\n }\n\n public watch<T extends ConfigValueType>(\n configKey: string,\n defaultValue: T,\n callback: WatchHandler<T>,\n context?: ConfigDirectorContext,\n ) {\n this.validateDefaultValue(defaultValue);\n\n const handlers = this.handlersMap.get(configKey);\n const handlerWithOptions = {\n handler: callback,\n defaultValue,\n requestedType: typeof defaultValue,\n context,\n };\n if (handlers) {\n handlers.push(handlerWithOptions);\n } else {\n this.handlersMap.set(configKey, [handlerWithOptions]);\n }\n\n return () => this.unwatch(configKey, callback);\n }\n\n public unwatch<T extends ConfigValueType>(configKey: string, callback?: WatchHandler<T>) {\n const handlers = this.handlersMap.get(configKey);\n if (!handlers) {\n return;\n }\n\n if (callback) {\n const index = handlers.findIndex((h) => h.handler == callback);\n if (index >= 0) {\n handlers?.splice(index, 1);\n }\n } else {\n handlers.splice(0);\n }\n }\n\n public getValue<T extends ConfigValueType>(\n configKey: string,\n defaultValue: T,\n context?: ConfigDirectorContext,\n ): T {\n this.validateDefaultValue(defaultValue);\n\n const configDefinition = this.configSet?.configs[configKey];\n return this.getValueFromConfigDefinition(configKey, configDefinition, defaultValue, context);\n }\n\n private getValueFromConfigDefinition<T extends ConfigValueType>(\n configKey: string,\n configDefinition: ConfigDefinition | undefined,\n defaultValue: T,\n context?: ConfigDirectorContext,\n ): T {\n if (configDefinition) {\n const configState = this.configEvaluator.evaluate(configDefinition, {\n context,\n metadata: this.metaContext,\n });\n const parseResult = parseConfigValue<T>(configState, defaultValue);\n this.usageEventCollector.evaluatedConfig({\n contextId: context?.id,\n key: configKey,\n defaultValue: defaultValue,\n requestedType: parseResult.requestedType,\n evaluatedValue: parseResult.parsedValue,\n usedDefault: parseResult.usedDefault,\n evaluationReason: parseResult.reason,\n });\n this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);\n return parseResult.parsedValue;\n } else {\n this.logger.debug(\n `[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`,\n );\n this.usageEventCollector.evaluatedConfig({\n contextId: context?.id,\n key: configKey,\n defaultValue: defaultValue,\n requestedType: getRequestedType(defaultValue),\n evaluatedValue: defaultValue,\n usedDefault: true,\n evaluationReason: \"config-state-missing\",\n });\n return defaultValue;\n }\n }\n\n private parseUrl(url: string | undefined): URL | undefined {\n if (!url) {\n return;\n }\n\n try {\n return new URL(url);\n } catch (error) {\n throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);\n }\n }\n\n private validateDefaultValue<T extends ConfigValueType>(defaultValue: T) {\n if (defaultValue === undefined || defaultValue === null) {\n throw new ConfigDirectorValidationError(\n \"Invalid default value. The default value for a config must be defined and non-null.\",\n );\n }\n\n if (typeof defaultValue === \"function\") {\n throw new ConfigDirectorValidationError(\n \"Invalid default value. The default value for a config cannot be a function.\",\n );\n }\n }\n\n public get isReady(): boolean {\n return this.ready;\n }\n\n public on<T extends keyof ClientEvents>(eventName: T, handler: (args: ClientEvents[T]) => void): void {\n this.eventEmitter.on(eventName, handler);\n }\n\n public off<T extends keyof ClientEvents>(eventName: T, handler?: (args: ClientEvents[T]) => void) {\n if (handler) {\n this.eventEmitter.off(eventName, handler);\n } else {\n this.eventEmitter.removeAllListeners(eventName);\n }\n }\n\n public removeAllObservers() {\n this.logger.debug(\"[ConfigDirectorClient] removeAllObservers() has been called, removing all observers\");\n this.eventEmitter.removeAllListeners();\n this.handlersMap.clear();\n }\n\n public unwatchAll() {\n this.handlersMap.clear();\n }\n\n public closeConnection() {\n this.logger.debug(\n \"[ConfigDirectorClient] closeConnection() has been called, closing connection to server\",\n );\n this.transport.close();\n this.ready = false;\n }\n\n public dispose() {\n this.removeAllObservers();\n this.closeConnection();\n }\n}\n","import { ConfigDirectorLoggingLevel, ConfigDirectorLogMessageDecorator } from \"@shared/types\";\nimport { DefaultConfigDirectorClient } from \"./DefaultConfigDirectorClient\";\nimport { ConfigDirectorClient, ConfigDirectorClientOptions } from \"./types\";\nimport { createDefaultLogger } from \"./logger\";\n\nexport const createClient = (\n serverSdkKey: string,\n clientOptions?: ConfigDirectorClientOptions,\n): ConfigDirectorClient => {\n return new DefaultConfigDirectorClient(\n serverSdkKey,\n { sdkName: \"js-server-sdk\", sdkVersion: \"__VERSION__\" },\n clientOptions,\n );\n};\n\nexport const createConsoleLogger = (\n level: ConfigDirectorLoggingLevel,\n messageDecorator?: ConfigDirectorLogMessageDecorator,\n) => {\n return createDefaultLogger(level, messageDecorator);\n};\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@configdirector/server-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Javascript server SDK for ConfigDirector. ConfigDirector is a remote configuration and feature flag service.",
5
+ "license": "MIT",
6
+ "author": "ConfigDirector",
7
+ "keywords": [
8
+ "configdirector",
9
+ "config",
10
+ "configuration",
11
+ "remote config",
12
+ "remote configuration",
13
+ "feature flag",
14
+ "feature toggle",
15
+ "feature switch"
16
+ ],
17
+ "homepage": "https://www.configdirector.com",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/ConfigDirector/js-sdks"
21
+ },
22
+ "type": "module",
23
+ "exports": {
24
+ ".": {
25
+ "require": {
26
+ "types": "./dist/configdirector-server.d.cts",
27
+ "default": "./dist/configdirector-server.cjs"
28
+ },
29
+ "default": {
30
+ "types": "./dist/configdirector-server.d.mts",
31
+ "default": "./dist/configdirector-server.mjs"
32
+ }
33
+ }
34
+ },
35
+ "files": [
36
+ "dist/*server.*"
37
+ ],
38
+ "types": "dist/configdirector-server.d.mts",
39
+ "module": "dist/configdirector-server.mjs",
40
+ "scripts": {
41
+ "build": "tsdown",
42
+ "clean": "rimraf dist/**",
43
+ "lint": "eslint .",
44
+ "test": "vitest",
45
+ "coverage": "vitest run --coverage",
46
+ "ci:test": "vitest run"
47
+ },
48
+ "devDependencies": {
49
+ "@eslint/js": "^10.0.1",
50
+ "@stylistic/eslint-plugin": "^5.10.0",
51
+ "@types/node": "^25.5.0",
52
+ "@types/semver": "^7",
53
+ "@vitest/coverage-v8": "^4.1.2",
54
+ "eslint": "^10.1.0",
55
+ "globals": "^17.3.0",
56
+ "msw": "^2.12.9",
57
+ "prettier": "^3.8.1",
58
+ "rimraf": "^6.1.3",
59
+ "tsdown": "^0.21.0-beta.2",
60
+ "tslib": "^2.8.1",
61
+ "typescript": "^5.9.3",
62
+ "typescript-eslint": "^8.58.0",
63
+ "vite": "^8.0.3",
64
+ "vitest": "^4.1.2"
65
+ },
66
+ "dependencies": {
67
+ "@jsonjoy.com/json-pointer": "^18.1.0",
68
+ "eventsource-client": "^1.2.0",
69
+ "rapidhash-js": "^3.0.0",
70
+ "semver": "^7.7.4"
71
+ }
72
+ }