@configdirector/server-sdk 0.7.0 → 0.9.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/dist/configdirector-server.cjs +2 -2
- package/dist/configdirector-server.d.cts +35 -0
- package/dist/configdirector-server.d.cts.map +1 -1
- package/dist/configdirector-server.d.mts +35 -0
- package/dist/configdirector-server.d.mts.map +1 -1
- package/dist/configdirector-server.mjs +2 -2
- package/dist/configdirector-server.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
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(`node:events`);c=s(c);let l=require(`@jsonjoy.com/json-pointer`),u=require(`semver`);u=s(u);let d=require(`rapidhash-js`);var
|
|
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(`node:events`);c=s(c);let l=require(`@jsonjoy.com/json-pointer`),u=require(`semver`);u=s(u);let d=require(`rapidhash-js`),f=require(`node:crypto`);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 e extends Error{constructor(t){super(t),this.name=`StreamClosedError`,Object.setPrototypeOf(this,e.prototype)}},ee=class e extends Error{constructor(t){super(t),this.name=`MissingResponseBodyError`,Object.setPrototypeOf(this,e.prototype)}},g=class e extends Error{constructor(t){super(t),this.name=`InvalidOptionError`,Object.setPrototypeOf(this,e.prototype)}},_=class e extends Error{constructor(t){super(t),this.name=`ValueOutOfRangeError`,Object.setPrototypeOf(this,e.prototype)}},v=class{constructor(e){this.isFirstChunk=!0,this.bufferedInput=``,this.currentEvent={data:``},this.currentEvent={data:``},this.onEvent=e.onEvent??(()=>{}),this.onRetry=e.onRetry,this.onComment=e.onComment}parse(e){let t=e;this.isFirstChunk&&(this.isFirstChunk=!1,t.startsWith(``)?t=t.slice(1):t.startsWith(``)&&(t=t.slice(3)));let n=this.scanLines(this.bufferedInput+t);for(let e of n)this.dispatchLine(e)}finish(){this.bufferedInput=``}scanLines(e){let t=[],n=0,r=0;for(;r<e.length;){let i=e[r];i===`\r`||i===`
|
|
2
2
|
`?(t.push(e.slice(n,r)),i===`\r`&&e[r+1]===`
|
|
3
3
|
`&&r++,r++,n=r):r++}return this.bufferedInput=e.slice(n),t}dispatchLine(e){if(e.startsWith(`:`)){this.onComment?.(this.extractValue(e,1));return}if(e===``){this.emitEvent();return}this.applyField(this.parseField(e))}emitEvent(){let e=this.currentEvent.data.endsWith(`
|
|
4
4
|
`)?this.currentEvent.data.slice(0,-1):this.currentEvent.data;e!==``&&this.onEvent({...this.currentEvent,id:this.lastEventId,data:e}),this.currentEvent={data:``}}applyField(e){switch(e.field){case`event`:this.currentEvent.type=e.value;break;case`data`:this.currentEvent.data+=e.value+`
|
|
5
|
-
`;break;case`id`:e.value.includes(`\0`)||(this.lastEventId=e.value);break;case`retry`:/^\d+$/.test(e.value)&&this.onRetry?.(Number.parseInt(e.value,10));break;default:break}}parseField(e){let t=e.indexOf(`:`);return t===-1?{field:e,value:``}:{field:e.slice(0,t),value:this.extractValue(e,t+1)}}extractValue(e,t){return e[t]===` `?e.slice(t+1):e.slice(t)}};let v=function(e){return e.OPEN=`open`,e.CLOSED=`closed`,e.CONNECTING=`connecting`,e}({});var y=class{constructor(e){this.DEFAULT_SHOULD_RECONNECT=()=>!0,this.DEFAULT_CALCULATE_RECONNECT_DELAY=()=>this.serverReconnectionTime,this.reconnectAttempt=0,this.abortController=new AbortController,this._readyState=v.CLOSED,this.serverReconnectionTime=2e3,this.url=e.url.toString(),this.lastEventId=e.lastEventId,this.headers={Accept:`text/event-stream`,...e.headers},this.requestOptions={body:e.body,method:e.method??`GET`,mode:e.mode,credentials:e.credentials,redirect:e.redirect,referrer:e.referrer,referrerPolicy:e.referrerPolicy},this._fetch=e.fetch??((e,t)=>fetch(e,t)),this._onError=this.resolveCallback(e.onError,()=>{},`onError`),this._onConnect=this.resolveCallback(e.onConnect,()=>{},`onConnect`),this._onDisconnect=this.resolveCallback(e.onDisconnect,()=>{},`onDisconnect`),this._onMessage=this.resolveCallback(e.onMessage,()=>{},`onMessage`),this._onComment=this.resolveCallback(e.onComment,()=>{},`onComment`),this._shouldReconnect=this.resolveCallback(e.shouldReconnect,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`),this._calculateReconnectDelay=this.resolveCallback(e.calculateReconnectDelay,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`calculateReconnectDelay`)}set onError(e){this._onError=this.resolveCallback(e,()=>{},`onError`)}set onMessage(e){this._onMessage=this.resolveCallback(e,()=>{},`onMessage`)}set onComment(e){this._onComment=this.resolveCallback(e,()=>{},`onComment`)}set onConnect(e){this._onConnect=this.resolveCallback(e,()=>{},`onConnect`)}set onDisconnect(e){this._onDisconnect=this.resolveCallback(e,()=>{},`onDisconnect`)}set shouldReconnect(e){this._shouldReconnect=this.resolveCallback(e,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`)}set calculateReconnectDelay(e){this._calculateReconnectDelay=this.resolveCallback(e,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`shouldReconnect`)}get readyState(){return this._readyState}connect(){this._readyState===v.CLOSED&&(this.reconnectAttempt=0,this.initiateConnection())}close(){clearTimeout(this.reconnectTimeout),this._readyState=v.CLOSED,this.abortController.abort()}initiateConnection(){this.abortController=new AbortController,this._readyState=v.CONNECTING,this._fetch(this.url,{...this.requestOptions,headers:this.buildRequestHeaders(),signal:this.abortController.signal,cache:`no-store`}).then(e=>this.handleFetchResponse(e)).catch(e=>{if(e.name===`AbortError`||this.abortController.signal.aborted){this.close();return}this._onError(e),this.scheduleReconnect({error:e})})}async handleFetchResponse(e){let{body:t,status:n}=e;if(n===204){this.disconnected();return}if(n>=400){this.scheduleReconnect({status:n});return}if(!t)throw new h(`The server response did not have a body`);this._readyState=v.OPEN,this._onConnect(),this.reconnectAttempt=0;let r=new _({onEvent:e=>{typeof e.id==`string`&&(this.lastEventId=e.id),this._onMessage(e)},onComment:e=>this._onComment(e),onRetry:e=>this.serverReconnectionTime=e});await this.readResponseStream(t,r),this.scheduleReconnect({status:n,error:new m(`The server response stream was closed`)})}async readResponseStream(e,t){let n=e.getReader(),r=new TextDecoder,i=!1;for(;!i;){let e=await n.read();e.value&&t.parse(r.decode(e.value,{stream:!0})),i=e.done}t.finish()}disconnected(){clearTimeout(this.reconnectTimeout),this._readyState=v.CLOSED,this._onDisconnect()}buildRequestHeaders(){let e=this.lastEventId?{"Last-Event-ID":this.lastEventId}:void 0;return{...this.headers,...e}}scheduleReconnect(e){if(this.abortController.signal.aborted)return;clearTimeout(this.reconnectTimeout),this.reconnectAttempt+=1;let t={attempt:this.reconnectAttempt,status:e.status,error:e.error,serverReconnectionTime:this.serverReconnectionTime};if(!this._shouldReconnect(t)){this.disconnected();return}this._readyState=v.CONNECTING;let n=this._calculateReconnectDelay(t);(!Number.isFinite(n)||n<1||n>1e3*60*60)&&(this._onError(new g(`The calculated reconnect delay is out of range: ${n}. Defaulting to ${this.serverReconnectionTime}`)),n=this.serverReconnectionTime),this.reconnectTimeout=setTimeout(()=>this.initiateConnection(),n)}resolveCallback(e,t,n){if(e==null)return t;if(typeof e!=`function`)throw new ee(`${n} must be a function`);return e}};const b=e=>new y(e);var x=class{constructor(e){this.options=e,this.eventEmitter=new c.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=new Promise((e,t)=>{this.eventSource=b({url:this.url,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:()=>{this.logger.debug(`[StreamingTransport] Connected`),e(this)},shouldReconnect:e=>{let n=!this.isStatusFatal(e.status);return n||t(this.prepareFatalError(e.status,e.error)),n},calculateReconnectDelay:e=>{let t=2**Math.min(e.attempt,9)*1e3,n=`[StreamingTransport] Scheduling reconnect attempt #${e.attempt} in ${t}ms.`;return e.attempt<=5?this.logger.info(n):this.logger.warn(n),t},onDisconnect:()=>{this.logger.debug(`[StreamingTransport] Disconnected`)}}),this.eventSource.connect()});return Promise.race([t,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(`[StreamingTransport] Error parsing and dispatching config data update: `,e)}}prepareFatalError(e,t){let n=e??0;return new f(`${`Connection failed with status: ${e??`unknown`}.`}${t?` Error: ${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()}},S=class{constructor(){this.fatalError=!1,this.pollingIntervalSeconds=60}clearPollingInterval(){clearInterval(this.pollingInterval)}schedulePollingInterval(e){clearInterval(this.pollingInterval),!(this.pollingIntervalSeconds<=0)&&(this.pollingInterval=setInterval(e,this.pollingIntervalSeconds*1e3))}async handleNonOkResponse(e){if(!e.ok)throw this.isStatusFatal(e.status)?(this.fatalError=!0,this.prepareFatalResponseStatusError(e.status,await e.text())):new f(`Connection failed with status: ${e.status}`,e.status)}handleFetchError(e){throw this.isFetchErrorFatal(e)?(this.fatalError=!0,this.close(),new f(`Connection failed with fatal error: ${e}. This is an unrecoverable error, retry attempts will be ignored.`)):e instanceof SyntaxError?new f(`Failed to parse the response from the server: ${e}`):new f(`Connection failed with error: ${e}.`)}prepareFatalResponseStatusError(e,t){let n=e??0;return new f(`${`Connection failed with status: ${e??`unknown`}`}${(t?.trim()?.length??0)>0?` (${t})`:``}. This is an unrecoverable error, retry attempts will be ignored.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}isFetchErrorFatal(e){return e?.name===`NotAllowedError`?!0:e instanceof TypeError}get isConnected(){return this.pollingInterval!==void 0}close(){clearInterval(this.pollingInterval),this.pollingInterval=void 0}dispose(){this.close(),this.clear()}};const C=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(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}};var w=class extends S{constructor(e){super(),this.options=e,this.eventEmitter=new c.EventEmitter,this.options=e,this.logger=e.logger,this.url=new URL(`server/polling/v1`,e.baseUrl),this.pollingIntervalSeconds=e.pollingInterval??60}async connect(e){return this.clearPollingInterval(),await this.fetchConfigs(e),this.schedulePollingInterval(()=>{this.fetchConfigs(e).catch(e=>{this.logger.error(`[PollingTransport] Error during polling:`,e)})}),this}async fetchConfigs(e){if(this.fatalError){this.logger.warn(`[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.`);return}try{let t=await C(e,this.url,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey,lastUpdateTimestamp:this.lastUpdateTimestamp})},this.logger);if(await this.handleNonOkResponse(t),t.status===200){let e=JSON.parse(await t.text());this.lastUpdateTimestamp=e.timestamp,this.eventEmitter.emit(`configBundleReceived`,e)}}catch(e){this.handleFetchError(e)}}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()}},te=class extends w{constructor(e){super(e),this.pollingIntervalSeconds=0}};const T=(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 E(n[0],e);case`does not match regex`:return!E(n[0],e);default:return!1}},E=(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}=O(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}},O=(e,t)=>typeof e==`number`?{value:e,target:k(t)}:typeof e==`bigint`?{value:e,target:BigInt(t)}:{value:k(e),target:k(t)},k=e=>{let t=Number.parseFloat(e.toString());if(isNaN(t)||!isFinite(t))throw TypeError(`Invalid numeric value: ${e}`);return t},A=(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}},j=(e,t,n)=>{let r=t.toLowerCase();if(e==null||e.trim().length==0)return r==`is not one of`;let i=u.coerce(e)??e,a=n.map(e=>u.coerce(e)??e);try{switch(r){case`=`:return M(i,a[0]);case`<`:return u.lt(i,a[0]);case`<=`:return u.lte(i,a[0]);case`>`:return u.gt(i,a[0]);case`>=`:return u.gte(i,a[0]);case`is one of`:return a.findIndex(e=>M(i,e))>=0;case`is not one of`:return a.findIndex(e=>M(i,e))<0;default:return!1}}catch{return!1}},M=(e,t)=>{try{return u.eq(e,t)}catch{return!1}},N=(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 P=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 T(e?.toString(),t.operator,t.targetValues);case`number`:return D(e,t.operator,t.targetValues);case`datetime`:return A(e?.toString(),t.operator,t.targetValues);case`semver`:return j(e?.toString(),t.operator,t.targetValues);case`array`:return N(e,t.operator,t.targetValues)}return!1}findTraitValue(e,t){try{return(0,l.findByPointer)(t,e).val}catch{return}}isConditionValidForComparison(e){return Array.isArray(e.targetValues)&&e.targetValues.length>0}};const F=e=>{let t=`${e.contextIdentifier}-${e.configId}`;return Number((0,d.rapidhash)(t,{seed:15426857351n})%1000n)/10};var I=class{constructor(e){if(this.logger=e,this.conditionEvaluator=new P,!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=F({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 L=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},R=e=>e===`number`||e===`bigint`,z=(e,t)=>{let n=e.value,r=L(t);if(!n)return{parsedValue:t,requestedType:r,usedDefault:!0,reason:`value-missing`};if(e.type===`json`)return U(n,t,r);if(typeof t==`string`)return{parsedValue:n,requestedType:r,usedDefault:!1,reason:`found-match`};if(typeof t==`boolean`&&e.type===`boolean`){let e=B(n),i=typeof e==`boolean`;return{parsedValue:i?e:t,requestedType:r,usedDefault:!i,reason:i?`found-match`:`invalid-boolean`}}if(R(typeof t)&&e.type===`integer`){let e=V(n),i=typeof e==`number`;return{parsedValue:i?e:t,requestedType:r,usedDefault:!i,reason:i?`found-match`:`invalid-number`}}if(R(typeof t)&&(e.type===`float`||e.type===`enum`)){let e=H(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`}},B=e=>{if(!e)return;let t=e.toLowerCase();if(!(t!=`true`&&t!=`false`))return t===`true`},V=e=>{if(!e)return;let t=Number.parseInt(e);if(!(isNaN(t)||!isFinite(t)))return t},H=e=>{if(!e)return;let t=Number.parseFloat(e);if(!(isNaN(t)||!isFinite(t)))return t},U=(e,t,n)=>{if(typeof t==`string`)return{parsedValue:e,requestedType:n,usedDefault:!1,reason:`found-match`};try{return{parsedValue:JSON.parse(e),requestedType:n,usedDefault:!1,reason:`found-match`}}catch{return{parsedValue:t,requestedType:n,usedDefault:!0,reason:`invalid-json`}}},W={off:-1,error:0,warn:1,info:2,debug:3},ne=()=>{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 G=class{constructor(e,t){this.level=e,this.decorator=t,this.level=e,this.decorator=t,this.dateFormatter=ne()}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){W[this.level]>=W[t]&&e(`[${this.dateFormatter.format(new Date)}] ${this.decorator?.decorateMessage(n)}`,...r)}},K=class{decorateMessage(e){return`[ConfigDirector:js-server-sdk] ${e}`}};const q=(e,t)=>new G(e??`warn`,t??new K),J=(e,t)=>new URL(e,t?.toString()),Y=(e,t)=>{if(t===`json`||t==null&&typeof e==`object`)try{return{digest:X(JSON.stringify(e))}}catch{return{digest:e.toString()}}return e.toString().length>500?{digest:X(e.toString())}:{value:e.toString()}},X=e=>Z(new TextEncoder().encode(e)).toString(16).padStart(8,`0`),Z=e=>{let t=5381;for(let n=0;n<e.length;n++)t=(t<<5)+t+(e[n]??0),t>>>=0;return t},Q=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},re=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};var ie=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.url=new URL(`server/telemetry/v1`,e.baseUrl.toString())}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 Q(e.discreteEvents)&&Q(e.aggregatedEvents)&&re(e.droppedEvents)}async sendReport(e){try{let t=await C(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}},ae=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}))}},oe=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}},$=class{constructor(e){this.aggregator=new ae,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new oe(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}sanitizeValue(e,t){return Y(e,t)}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)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},se=class extends Map{constructor(e,t){super(t),this._droppedCount=0,this.limit=e,this.enforceLimit()}set(e,t){return super.set(e,t),this.enforceLimit(),this}get droppedCount(){return this._droppedCount}clearAndReset(){this.clear(),this._droppedCount=0}enforceLimit(){for(;this.size>this.limit;){let e=this.keys().next().value;e!==void 0&&(this.delete(e),this._droppedCount+=1)}}},ce=class extends ${constructor(e){super(e),this.reporter=new ie(e),this.contexts=new se(e.contextLimit??1e3)}evaluatedConfig(e){if(!this.collectEvents)return;e.context?.id&&!e.context.anonymous&&this.contexts.set(e.context.id,e.context);let t={...e.evaluation,contextId:e.context?.anonymous?void 0:e.context?.id};this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(t))}sanitizeEvaluatedConfigEvent(e){return{...e,defaultValue:this.sanitizeValue(e.defaultValue,e.type),evaluatedValue:this.sanitizeValue(e.evaluatedValue,e.type)}}snapshotContexts(){let e=Array.from(this.contexts.values()),t=this.contexts.droppedCount;return this.contexts.clearAndReset(),{contexts:e,droppedCount:t}}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=this.evaluationEventQueue.takeSnapshot(),t=this.snapshotContexts();return await this.reporter?.report({context:this._context,discreteEvents:{capturedContexts:t.contexts},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount,capturedContexts:t.droppedCount}})}};const le=new URL(`https://server-sdk-api.configdirector.com`);var ue=class{constructor(e,t,n){this.handlersMap=new Map,this.eventEmitter=new c.default,this.ready=!1,this.logger=n?.logger??q(),this.timeout=n?.connection?.timeout??3e3,this.metaContext=n?.metadata??{},this.configEvaluator=new I(this.logger);let r=this.parseUrl(n?.connection?.url)??le;this.connectionMode=n?.connection?.mode??`streaming`;let i=this.getTransportConstructor(this.connectionMode),a=n?.telemetry?.eventQueueLimit??5e3;this.usageEventCollector=new ce({sdkKey:e,logger:this.logger,baseUrl:r,flushIntervalDelay:n?.telemetry?.flushInterval??3e4,evaluationQueueLimit:Math.ceil(a*.7),contextLimit:Math.floor(a*.3),urlFactory:J}),this.transport=new i({serverSdkKey:e,baseUrl:r,metaContext:{...n?.metadata,sdkName:t.sdkName,sdkVersion:t.sdkVersion},logger:this.logger,pollingInterval:n?.connection?.pollingInterval}),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 with ${t.length} key(s)`,{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.connectionMode===`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)}}getTransportConstructor(e){switch(e){case`one-time`:return te;case`polling`:return w;default:return x}}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=this.configEvaluator.evaluate(t,{context:r,metadata:this.metaContext}),a=z(i,n);return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,type:i.type,defaultValue:n,requestedType:a.requestedType,evaluatedValue:a.parsedValue,usedDefault:a.usedDefault,evaluationReason:a.reason}}),this.logger.debug(`[ConfigDirectorClient] Evaluated '${e}' to '${a.parsedValue}'`),a.parsedValue}else return this.logger.debug(`[ConfigDirectorClient] No config state found for '${e}', returning default value '${n}'`),this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,defaultValue:n,requestedType:L(n),evaluatedValue:n,usedDefault:!0,evaluationReason:`config-state-missing`}}),n}parseUrl(e){if(e)try{return new URL(e)}catch(t){throw new p(`Invalid base URL '${e}'. Parsing failed: ${t}`)}}validateDefaultValue(e){if(e==null)throw new p(`Invalid default value. The default value for a config must be defined and non-null.`);if(typeof e==`function`)throw new p(`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()}getAllConfigs(e){if(!this.configSet)return{};let t=Object.entries(this.configSet.configs),n=e?.configKeys?t.filter(([t])=>e.configKeys?.includes(t)):t;return Object.fromEntries(n.map(([t,n])=>[t,this.configEvaluator.evaluate(n,{context:e?.context,metadata:this.metaContext})]))}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 de=(e,t)=>new ue(e,{sdkName:`js-server-sdk`,sdkVersion:`0.7.0`},t),fe=(e,t)=>q(e,t);exports.createClient=de,exports.createConsoleLogger=fe;
|
|
5
|
+
`;break;case`id`:e.value.includes(`\0`)||(this.lastEventId=e.value);break;case`retry`:/^\d+$/.test(e.value)&&this.onRetry?.(Number.parseInt(e.value,10));break;default:break}}parseField(e){let t=e.indexOf(`:`);return t===-1?{field:e,value:``}:{field:e.slice(0,t),value:this.extractValue(e,t+1)}}extractValue(e,t){return e[t]===` `?e.slice(t+1):e.slice(t)}};let y=function(e){return e.OPEN=`open`,e.CLOSED=`closed`,e.CONNECTING=`connecting`,e}({});var b=class{constructor(e){this.DEFAULT_SHOULD_RECONNECT=()=>!0,this.DEFAULT_CALCULATE_RECONNECT_DELAY=()=>this.serverReconnectionTime,this.reconnectAttempt=0,this.abortController=new AbortController,this._readyState=y.CLOSED,this.serverReconnectionTime=2e3,this.url=e.url.toString(),this.lastEventId=e.lastEventId,this.headers={Accept:`text/event-stream`,...e.headers},this.requestOptions={body:e.body,method:e.method??`GET`,mode:e.mode,credentials:e.credentials,redirect:e.redirect,referrer:e.referrer,referrerPolicy:e.referrerPolicy},this._fetch=e.fetch??((e,t)=>fetch(e,t)),this._onError=this.resolveCallback(e.onError,()=>{},`onError`),this._onConnect=this.resolveCallback(e.onConnect,()=>{},`onConnect`),this._onDisconnect=this.resolveCallback(e.onDisconnect,()=>{},`onDisconnect`),this._onMessage=this.resolveCallback(e.onMessage,()=>{},`onMessage`),this._onComment=this.resolveCallback(e.onComment,()=>{},`onComment`),this._shouldReconnect=this.resolveCallback(e.shouldReconnect,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`),this._calculateReconnectDelay=this.resolveCallback(e.calculateReconnectDelay,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`calculateReconnectDelay`)}set onError(e){this._onError=this.resolveCallback(e,()=>{},`onError`)}set onMessage(e){this._onMessage=this.resolveCallback(e,()=>{},`onMessage`)}set onComment(e){this._onComment=this.resolveCallback(e,()=>{},`onComment`)}set onConnect(e){this._onConnect=this.resolveCallback(e,()=>{},`onConnect`)}set onDisconnect(e){this._onDisconnect=this.resolveCallback(e,()=>{},`onDisconnect`)}set shouldReconnect(e){this._shouldReconnect=this.resolveCallback(e,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`)}set calculateReconnectDelay(e){this._calculateReconnectDelay=this.resolveCallback(e,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`shouldReconnect`)}get readyState(){return this._readyState}connect(){this._readyState===y.CLOSED&&(this.reconnectAttempt=0,this.initiateConnection())}close(){clearTimeout(this.reconnectTimeout),this._readyState=y.CLOSED,this.abortController.abort()}initiateConnection(){this.abortController=new AbortController,this._readyState=y.CONNECTING,this._fetch(this.url,{...this.requestOptions,headers:this.buildRequestHeaders(),signal:this.abortController.signal,cache:`no-store`}).then(e=>this.handleFetchResponse(e)).catch(e=>{if(e.name===`AbortError`||this.abortController.signal.aborted){this.close();return}this._onError(e),this.scheduleReconnect({error:e})})}async handleFetchResponse(e){let{body:t,status:n}=e;if(n===204){this.disconnected();return}if(n>=400){this.scheduleReconnect({status:n});return}if(!t)throw new ee(`The server response did not have a body`);this._readyState=y.OPEN,this._onConnect(),this.reconnectAttempt=0;let r=new v({onEvent:e=>{typeof e.id==`string`&&(this.lastEventId=e.id),this._onMessage(e)},onComment:e=>this._onComment(e),onRetry:e=>this.serverReconnectionTime=e});await this.readResponseStream(t,r),this.scheduleReconnect({status:n,error:new h(`The server response stream was closed`)})}async readResponseStream(e,t){let n=e.getReader(),r=new TextDecoder,i=!1;for(;!i;){let e=await n.read();e.value&&t.parse(r.decode(e.value,{stream:!0})),i=e.done}t.finish()}disconnected(){clearTimeout(this.reconnectTimeout),this._readyState=y.CLOSED,this._onDisconnect()}buildRequestHeaders(){let e=this.lastEventId?{"Last-Event-ID":this.lastEventId}:void 0;return{...this.headers,...e}}scheduleReconnect(e){if(this.abortController.signal.aborted)return;clearTimeout(this.reconnectTimeout),this.reconnectAttempt+=1;let t={attempt:this.reconnectAttempt,status:e.status,error:e.error,serverReconnectionTime:this.serverReconnectionTime};if(!this._shouldReconnect(t)){this.disconnected();return}this._readyState=y.CONNECTING;let n=this._calculateReconnectDelay(t);(!Number.isFinite(n)||n<1||n>1e3*60*60)&&(this._onError(new _(`The calculated reconnect delay is out of range: ${n}. Defaulting to ${this.serverReconnectionTime}`)),n=this.serverReconnectionTime),this.reconnectTimeout=setTimeout(()=>this.initiateConnection(),n)}resolveCallback(e,t,n){if(e==null)return t;if(typeof e!=`function`)throw new g(`${n} must be a function`);return e}};const x=e=>new b(e);var S=class{constructor(e){this.options=e,this.eventEmitter=new c.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=new Promise((e,t)=>{this.eventSource=x({url:this.url,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:()=>{this.logger.debug(`[StreamingTransport] Connected`),e(this)},shouldReconnect:e=>{let n=!this.isStatusFatal(e.status);return n||t(this.prepareFatalError(e.status,e.error)),n},calculateReconnectDelay:e=>{let t=2**Math.min(e.attempt,9)*1e3,n=`[StreamingTransport] Scheduling reconnect attempt #${e.attempt} in ${t}ms.`;return e.attempt<=5?this.logger.info(n):this.logger.warn(n),t},onDisconnect:()=>{this.logger.debug(`[StreamingTransport] Disconnected`)}}),this.eventSource.connect()});return Promise.race([t,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(`[StreamingTransport] 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?` Error: ${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()}},C=class{constructor(){this.fatalError=!1,this.pollingIntervalSeconds=60}clearPollingInterval(){clearInterval(this.pollingInterval)}schedulePollingInterval(e){clearInterval(this.pollingInterval),!(this.pollingIntervalSeconds<=0)&&(this.pollingInterval=setInterval(e,this.pollingIntervalSeconds*1e3))}async handleNonOkResponse(e){if(!e.ok)throw this.isStatusFatal(e.status)?(this.fatalError=!0,this.prepareFatalResponseStatusError(e.status,await e.text())):new p(`Connection failed with status: ${e.status}`,e.status)}handleFetchError(e){throw this.isFetchErrorFatal(e)?(this.fatalError=!0,this.close(),new p(`Connection failed with fatal error: ${e}. This is an unrecoverable error, retry attempts will be ignored.`)):e instanceof SyntaxError?new p(`Failed to parse the response from the server: ${e}`):new p(`Connection failed with error: ${e}.`)}prepareFatalResponseStatusError(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, retry attempts will be ignored.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}isFetchErrorFatal(e){return e?.name===`NotAllowedError`?!0:e instanceof TypeError}get isConnected(){return this.pollingInterval!==void 0}close(){clearInterval(this.pollingInterval),this.pollingInterval=void 0}dispose(){this.close(),this.clear()}};const w=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(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}};var T=class extends C{constructor(e){super(),this.options=e,this.eventEmitter=new c.EventEmitter,this.options=e,this.logger=e.logger,this.url=new URL(`server/polling/v1`,e.baseUrl),this.pollingIntervalSeconds=e.pollingInterval??60}async connect(e){return this.clearPollingInterval(),await this.fetchConfigs(e),this.schedulePollingInterval(()=>{this.fetchConfigs(e).catch(e=>{this.logger.error(`[PollingTransport] Error during polling:`,e)})}),this}async fetchConfigs(e){if(this.fatalError){this.logger.warn(`[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.`);return}try{let t=await w(e,this.url,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey,lastUpdateTimestamp:this.lastUpdateTimestamp})},this.logger);if(await this.handleNonOkResponse(t),t.status===200){let e=JSON.parse(await t.text());this.lastUpdateTimestamp=e.timestamp,this.eventEmitter.emit(`configBundleReceived`,e)}}catch(e){this.handleFetchError(e)}}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()}},te=class extends T{constructor(e){super(e),this.pollingIntervalSeconds=0}};const E=(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 D(n[0],e);case`does not match regex`:return!D(n[0],e);default:return!1}},D=(e,t)=>{try{return new RegExp(e).test(t)}catch{return!1}},O=(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}=k(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}},k=(e,t)=>typeof e==`number`?{value:e,target:A(t)}:typeof e==`bigint`?{value:e,target:BigInt(t)}:{value:A(e),target:A(t)},A=e=>{let t=Number.parseFloat(e.toString());if(isNaN(t)||!isFinite(t))throw TypeError(`Invalid numeric value: ${e}`);return t},j=(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}},M=(e,t,n)=>{let r=t.toLowerCase();if(e==null||e.trim().length==0)return r==`is not one of`;let i=u.coerce(e)??e,a=n.map(e=>u.coerce(e)??e);try{switch(r){case`=`:return N(i,a[0]);case`<`:return u.lt(i,a[0]);case`<=`:return u.lte(i,a[0]);case`>`:return u.gt(i,a[0]);case`>=`:return u.gte(i,a[0]);case`is one of`:return a.findIndex(e=>N(i,e))>=0;case`is not one of`:return a.findIndex(e=>N(i,e))<0;default:return!1}}catch{return!1}},N=(e,t)=>{try{return u.eq(e,t)}catch{return!1}},P=(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 F=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 E(e?.toString(),t.operator,t.targetValues);case`number`:return O(e,t.operator,t.targetValues);case`datetime`:return j(e?.toString(),t.operator,t.targetValues);case`semver`:return M(e?.toString(),t.operator,t.targetValues);case`array`:return P(e,t.operator,t.targetValues)}return!1}findTraitValue(e,t){try{return(0,l.findByPointer)(t,e).val}catch{return}}isConditionValidForComparison(e){return Array.isArray(e.targetValues)&&e.targetValues.length>0}};const I=e=>{let t=`${e.contextIdentifier}-${e.configId}`;return Number((0,d.rapidhash)(t,{seed:15426857351n})%1000n)/10};var L=class{constructor(e){if(this.logger=e,this.conditionEvaluator=new F,!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=I({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 R=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},z=e=>e===`number`||e===`bigint`,B=(e,t)=>{let n=e.value,r=R(t);if(!n)return{parsedValue:t,parsedValueId:void 0,requestedType:r,usedDefault:!0,reason:`value-missing`};if(e.type===`json`)return W(n,t,r,e.valueId);if(typeof t==`string`)return{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`};if(typeof t==`boolean`&&(e.type===`boolean`||e.type===`string`)){let i=V(n),a=typeof i==`boolean`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-boolean`}}if(z(typeof t)&&e.type===`integer`){let i=H(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}if(z(typeof t)&&(e.type===`float`||e.type===`enum`||e.type===`string`)){let i=U(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}return{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`}},V=e=>{if(!e)return;let t=e.toLowerCase();if(!(t!=`true`&&t!=`false`))return t===`true`},H=e=>{if(!e)return;let t=Number.parseInt(e);if(!(isNaN(t)||!isFinite(t)))return t},U=e=>{if(!e)return;let t=Number.parseFloat(e);if(!(isNaN(t)||!isFinite(t)))return t},W=(e,t,n,r)=>{if(typeof t==`string`)return{parsedValue:e,parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`};try{return{parsedValue:JSON.parse(e),parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`}}catch{return{parsedValue:t,parsedValueId:void 0,requestedType:n,usedDefault:!0,reason:`invalid-json`}}},G={off:-1,error:0,warn:1,info:2,debug:3},ne=()=>{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=ne()}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){G[this.level]>=G[t]&&e(`[${this.dateFormatter.format(new Date)}] ${this.decorator?.decorateMessage(n)}`,...r)}},q=class{decorateMessage(e){return`[ConfigDirector:js-server-sdk] ${e}`}};const J=(e,t)=>new K(e??`warn`,t??new q),Y=(e,t)=>new URL(e,t?.toString()),X=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,Z=e=>{if(e.type===`json`||e.type==null&&typeof e.value==`object`){if(e.valueId)return{valueId:e.valueId,type:`json`};try{return{value:JSON.stringify(e.value),type:`json`}}catch{return{value:e.value.toString(),type:`json`}}}let t=e.value?.toString();return t&&t.length<=500?{value:t}:e.valueId?{valueId:e.valueId}:{value:t}},Q=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},re=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};var ie=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.url=new URL(`server/telemetry/v1`,e.baseUrl.toString())}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 Q(e.discreteEvents)&&Q(e.aggregatedEvents)&&re(e.droppedEvents)}async sendReport(e){try{let t=await w(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}},ae=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}))}},oe=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}},$=class{constructor(e){this.aggregator=new ae,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new oe(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}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)}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},se=class extends Map{constructor(e,t){super(t),this._droppedCount=0,this.limit=e,this.enforceLimit()}set(e,t){return super.set(e,t),this.enforceLimit(),this}get droppedCount(){return this._droppedCount}clearAndReset(){this.clear(),this._droppedCount=0}enforceLimit(){for(;this.size>this.limit;){let e=this.keys().next().value;e!==void 0&&(this.delete(e),this._droppedCount+=1)}}},ce=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(e){let t=await Promise.all(e.events.map(async e=>({...e,defaultValue:await X(e.defaultValue,this.valueIdGenerator),evaluatedValue:await X(e.evaluatedValue,this.valueIdGenerator)})));return{...e,events:t}}},le=class extends ${constructor(e){super(e),this.reporter=new ie(e),this.contexts=new se(e.contextLimit??1e3),this.evaluationEventSnapshotPreprocessor=new ce(e.valueIdGenerator)}evaluatedConfig(e){if(!this.collectEvents)return;e.context?.id&&!e.context.anonymous&&this.contexts.set(e.context.id,e.context);let t={...e.evaluation,contextId:e.context?.anonymous?void 0:e.context?.id};this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(t))}sanitizeEvaluatedConfigEvent(e){return{...e,defaultValue:Z({value:e.defaultValue,type:e.type}),evaluatedValue:Z({value:e.evaluatedValue,valueId:e.evaluatedValueId,type:e.type})}}snapshotContexts(){let e=Array.from(this.contexts.values()),t=this.contexts.droppedCount;return this.contexts.clearAndReset(),{contexts:e,droppedCount:t}}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot()),t=this.snapshotContexts();return await this.reporter?.report({context:this._context,discreteEvents:{capturedContexts:t.contexts},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount,capturedContexts:t.droppedCount}})}};const ue=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},de=Math.ceil(128/Math.log2(62)),fe=async e=>ue((0,f.createHash)(`sha256`).update(e?.toString()??``).digest().subarray(0,16),de),pe=new URL(`https://server-sdk-api.configdirector.com`);var me=class{constructor(e,t,n){this.handlersMap=new Map,this.eventEmitter=new c.default,this.ready=!1,this.logger=n?.logger??J(),this.timeout=n?.connection?.timeout??3e3,this.metaContext=n?.metadata??{},this.configEvaluator=new L(this.logger);let r=this.parseUrl(n?.connection?.url)??pe;this.connectionMode=n?.connection?.mode??`streaming`;let i=this.getTransportConstructor(this.connectionMode),a=n?.telemetry?.eventQueueLimit??5e3;this.usageEventCollector=new le({sdkKey:e,logger:this.logger,baseUrl:r,flushIntervalDelay:n?.telemetry?.flushInterval??3e4,evaluationQueueLimit:Math.ceil(a*.7),contextLimit:Math.floor(a*.3),urlFactory:Y,valueIdGenerator:fe}),this.transport=new i({serverSdkKey:e,baseUrl:r,metaContext:{...n?.metadata,sdkName:t.sdkName,sdkVersion:t.sdkVersion},logger:this.logger,pollingInterval:n?.connection?.pollingInterval}),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 with ${t.length} key(s)`,{keys:t})}),this.registerHandler(`clientReady`,n?.hooks?.clientReady),this.registerHandler(`configEvaluated`,n?.hooks?.configEvaluated),this.registerHandler(`configsUpdated`,n?.hooks?.configsUpdated)}registerHandler(e,t){if(!t)return;let n=Array.isArray(t)?t:[t];for(let t of n)typeof t==`function`&&this.on(e,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.connectionMode===`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)}}getTransportConstructor(e){switch(e){case`one-time`:return te;case`polling`:return T;default:return S}}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=this.configEvaluator.evaluate(t,{context:r,metadata:this.metaContext}),a=B(i,n);return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,type:i.type,defaultValue:n,requestedType:a.requestedType,evaluatedValue:a.parsedValue,evaluatedValueId:a.parsedValueId,usedDefault:a.usedDefault,evaluationReason:a.reason}}),this.dispatchEvaluationEvent({key:e,value:a.parsedValue,valueId:a.parsedValueId??void 0,isDefaultValue:a.usedDefault,reason:a.reason,context:r}),this.logger.debug(`[ConfigDirectorClient] Evaluated '${e}' to '${a.parsedValue}'`),a.parsedValue}else{this.logger.debug(`[ConfigDirectorClient] No config state found for '${e}', returning default value '${n}'`);let t=this.isReady?`config-state-missing`:`client-not-ready`;return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,defaultValue:n,requestedType:R(n),evaluatedValue:n,usedDefault:!0,evaluationReason:t}}),this.dispatchEvaluationEvent({key:e,value:n,isDefaultValue:!0,reason:t,context:r}),n}}dispatchEvaluationEvent(e){setTimeout(()=>this.eventEmitter.emit(`configEvaluated`,{evaluation:e}),0)}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()}getAllConfigs(e){if(!this.configSet)return{};let t=Object.entries(this.configSet.configs),n=e?.configKeys?t.filter(([t])=>e.configKeys?.includes(t)):t;return Object.fromEntries(n.map(([t,n])=>[t,this.configEvaluator.evaluate(n,{context:e?.context,metadata:this.metaContext})]))}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 he=(e,t)=>new me(e,{sdkName:`js-server-sdk`,sdkVersion:`0.9.0`},t),ge=(e,t)=>J(e,t);exports.createClient=he,exports.createConsoleLogger=ge;
|
|
@@ -10,6 +10,7 @@ type ConfigState = {
|
|
|
10
10
|
key: string;
|
|
11
11
|
type: ConfigType;
|
|
12
12
|
value: string | undefined | null;
|
|
13
|
+
valueId?: string | undefined | null;
|
|
13
14
|
};
|
|
14
15
|
type ConfigDirectorLoggingLevel = "debug" | "info" | "warn" | "error" | "off";
|
|
15
16
|
interface ConfigDirectorLogger {
|
|
@@ -21,6 +22,15 @@ interface ConfigDirectorLogger {
|
|
|
21
22
|
interface ConfigDirectorLogMessageDecorator {
|
|
22
23
|
decorateMessage(message: string): string;
|
|
23
24
|
}
|
|
25
|
+
type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean";
|
|
26
|
+
type ConfigEvaluation = {
|
|
27
|
+
key: string;
|
|
28
|
+
value: ConfigValueType;
|
|
29
|
+
valueId?: string;
|
|
30
|
+
isDefaultValue: boolean;
|
|
31
|
+
reason: EvaluationReason;
|
|
32
|
+
context?: ConfigDirectorContext;
|
|
33
|
+
};
|
|
24
34
|
/**
|
|
25
35
|
* The user's context to be sent to ConfigDirector. This context will be used for targeting
|
|
26
36
|
* rules evaluation.
|
|
@@ -156,13 +166,38 @@ type ConfigDirectorClientOptions = {
|
|
|
156
166
|
*/
|
|
157
167
|
flushInterval?: number;
|
|
158
168
|
};
|
|
169
|
+
/**
|
|
170
|
+
* Handlers for hooks/events emitted by the client. Each hook accepts either a single event handler or
|
|
171
|
+
* an array of event handlers.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* import { createClient, createConsoleLogger } from "@configdirector/server-sdk";
|
|
175
|
+
* const client = createClient(
|
|
176
|
+
* "YOUR-SERVER-SDK-KEY",
|
|
177
|
+
* {
|
|
178
|
+
* hooks: {
|
|
179
|
+
* configEvaluated: (event) => { console.log(event.evaluation); }
|
|
180
|
+
* },
|
|
181
|
+
* },
|
|
182
|
+
* );
|
|
183
|
+
*/
|
|
184
|
+
hooks?: ClientHooks;
|
|
159
185
|
};
|
|
160
186
|
type ClientEvents = {
|
|
161
187
|
configsUpdated: {
|
|
162
188
|
keys: string[];
|
|
163
189
|
};
|
|
164
190
|
clientReady: undefined;
|
|
191
|
+
configEvaluated: {
|
|
192
|
+
evaluation: ConfigEvaluation;
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
type ClientHooks = {
|
|
196
|
+
clientReady?: HookHandler<"clientReady"> | HookHandler<"clientReady">[];
|
|
197
|
+
configsUpdated?: HookHandler<"configsUpdated"> | HookHandler<"configsUpdated">[];
|
|
198
|
+
configEvaluated?: HookHandler<"configEvaluated"> | HookHandler<"configEvaluated">[];
|
|
165
199
|
};
|
|
200
|
+
type HookHandler<TEvent extends keyof ClientEvents> = (payload: ClientEvents[TEvent]) => void;
|
|
166
201
|
type WatchHandler<T extends ConfigValueType> = (message: T) => void;
|
|
167
202
|
/**
|
|
168
203
|
* The ConfigDirector SDK client object.
|
|
@@ -1 +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":";cAAa,cAAA;AAAA,KAUD,UAAA,WAAqB,cAAA;AAAA,KAErB,kBAAA;EAAA,CAAwB,GAAA;AAAA;AAAA,KAExB,eAAA,wCAAuD,kBAAA;AAAA,KAEvD,WAAA;EACV,EAAA;EACA,GAAA;EACA,IAAA,EAAM,UAAA;EACN,KAAA;AAAA;AAAA,KAQU,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
|
|
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":"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"}
|
|
@@ -10,6 +10,7 @@ type ConfigState = {
|
|
|
10
10
|
key: string;
|
|
11
11
|
type: ConfigType;
|
|
12
12
|
value: string | undefined | null;
|
|
13
|
+
valueId?: string | undefined | null;
|
|
13
14
|
};
|
|
14
15
|
type ConfigDirectorLoggingLevel = "debug" | "info" | "warn" | "error" | "off";
|
|
15
16
|
interface ConfigDirectorLogger {
|
|
@@ -21,6 +22,15 @@ interface ConfigDirectorLogger {
|
|
|
21
22
|
interface ConfigDirectorLogMessageDecorator {
|
|
22
23
|
decorateMessage(message: string): string;
|
|
23
24
|
}
|
|
25
|
+
type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean";
|
|
26
|
+
type ConfigEvaluation = {
|
|
27
|
+
key: string;
|
|
28
|
+
value: ConfigValueType;
|
|
29
|
+
valueId?: string;
|
|
30
|
+
isDefaultValue: boolean;
|
|
31
|
+
reason: EvaluationReason;
|
|
32
|
+
context?: ConfigDirectorContext;
|
|
33
|
+
};
|
|
24
34
|
/**
|
|
25
35
|
* The user's context to be sent to ConfigDirector. This context will be used for targeting
|
|
26
36
|
* rules evaluation.
|
|
@@ -156,13 +166,38 @@ type ConfigDirectorClientOptions = {
|
|
|
156
166
|
*/
|
|
157
167
|
flushInterval?: number;
|
|
158
168
|
};
|
|
169
|
+
/**
|
|
170
|
+
* Handlers for hooks/events emitted by the client. Each hook accepts either a single event handler or
|
|
171
|
+
* an array of event handlers.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* import { createClient, createConsoleLogger } from "@configdirector/server-sdk";
|
|
175
|
+
* const client = createClient(
|
|
176
|
+
* "YOUR-SERVER-SDK-KEY",
|
|
177
|
+
* {
|
|
178
|
+
* hooks: {
|
|
179
|
+
* configEvaluated: (event) => { console.log(event.evaluation); }
|
|
180
|
+
* },
|
|
181
|
+
* },
|
|
182
|
+
* );
|
|
183
|
+
*/
|
|
184
|
+
hooks?: ClientHooks;
|
|
159
185
|
};
|
|
160
186
|
type ClientEvents = {
|
|
161
187
|
configsUpdated: {
|
|
162
188
|
keys: string[];
|
|
163
189
|
};
|
|
164
190
|
clientReady: undefined;
|
|
191
|
+
configEvaluated: {
|
|
192
|
+
evaluation: ConfigEvaluation;
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
type ClientHooks = {
|
|
196
|
+
clientReady?: HookHandler<"clientReady"> | HookHandler<"clientReady">[];
|
|
197
|
+
configsUpdated?: HookHandler<"configsUpdated"> | HookHandler<"configsUpdated">[];
|
|
198
|
+
configEvaluated?: HookHandler<"configEvaluated"> | HookHandler<"configEvaluated">[];
|
|
165
199
|
};
|
|
200
|
+
type HookHandler<TEvent extends keyof ClientEvents> = (payload: ClientEvents[TEvent]) => void;
|
|
166
201
|
type WatchHandler<T extends ConfigValueType> = (message: T) => void;
|
|
167
202
|
/**
|
|
168
203
|
* The ConfigDirector SDK client object.
|
|
@@ -1 +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":";cAAa,cAAA;AAAA,KAUD,UAAA,WAAqB,cAAA;AAAA,KAErB,kBAAA;EAAA,CAAwB,GAAA;AAAA;AAAA,KAExB,eAAA,wCAAuD,kBAAA;AAAA,KAEvD,WAAA;EACV,EAAA;EACA,GAAA;EACA,IAAA,EAAM,UAAA;EACN,KAAA;AAAA;AAAA,KAQU,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
|
|
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":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import e,{EventEmitter as t}from"node:events";import{findByPointer as n}from"@jsonjoy.com/json-pointer";import*as r from"semver";import{rapidhash as i}from"rapidhash-js";var
|
|
1
|
+
import e,{EventEmitter as t}from"node:events";import{findByPointer as n}from"@jsonjoy.com/json-pointer";import*as r from"semver";import{rapidhash as i}from"rapidhash-js";import{createHash as a}from"node:crypto";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 e extends Error{constructor(t){super(t),this.name=`StreamClosedError`,Object.setPrototypeOf(this,e.prototype)}},l=class e extends Error{constructor(t){super(t),this.name=`MissingResponseBodyError`,Object.setPrototypeOf(this,e.prototype)}},u=class e extends Error{constructor(t){super(t),this.name=`InvalidOptionError`,Object.setPrototypeOf(this,e.prototype)}},d=class e extends Error{constructor(t){super(t),this.name=`ValueOutOfRangeError`,Object.setPrototypeOf(this,e.prototype)}},f=class{constructor(e){this.isFirstChunk=!0,this.bufferedInput=``,this.currentEvent={data:``},this.currentEvent={data:``},this.onEvent=e.onEvent??(()=>{}),this.onRetry=e.onRetry,this.onComment=e.onComment}parse(e){let t=e;this.isFirstChunk&&(this.isFirstChunk=!1,t.startsWith(``)?t=t.slice(1):t.startsWith(``)&&(t=t.slice(3)));let n=this.scanLines(this.bufferedInput+t);for(let e of n)this.dispatchLine(e)}finish(){this.bufferedInput=``}scanLines(e){let t=[],n=0,r=0;for(;r<e.length;){let i=e[r];i===`\r`||i===`
|
|
2
2
|
`?(t.push(e.slice(n,r)),i===`\r`&&e[r+1]===`
|
|
3
3
|
`&&r++,r++,n=r):r++}return this.bufferedInput=e.slice(n),t}dispatchLine(e){if(e.startsWith(`:`)){this.onComment?.(this.extractValue(e,1));return}if(e===``){this.emitEvent();return}this.applyField(this.parseField(e))}emitEvent(){let e=this.currentEvent.data.endsWith(`
|
|
4
4
|
`)?this.currentEvent.data.slice(0,-1):this.currentEvent.data;e!==``&&this.onEvent({...this.currentEvent,id:this.lastEventId,data:e}),this.currentEvent={data:``}}applyField(e){switch(e.field){case`event`:this.currentEvent.type=e.value;break;case`data`:this.currentEvent.data+=e.value+`
|
|
5
|
-
`;break;case`id`:e.value.includes(`\0`)||(this.lastEventId=e.value);break;case`retry`:/^\d+$/.test(e.value)&&this.onRetry?.(Number.parseInt(e.value,10));break;default:break}}parseField(e){let t=e.indexOf(`:`);return t===-1?{field:e,value:``}:{field:e.slice(0,t),value:this.extractValue(e,t+1)}}extractValue(e,t){return e[t]===` `?e.slice(t+1):e.slice(t)}};let f=function(e){return e.OPEN=`open`,e.CLOSED=`closed`,e.CONNECTING=`connecting`,e}({});var p=class{constructor(e){this.DEFAULT_SHOULD_RECONNECT=()=>!0,this.DEFAULT_CALCULATE_RECONNECT_DELAY=()=>this.serverReconnectionTime,this.reconnectAttempt=0,this.abortController=new AbortController,this._readyState=f.CLOSED,this.serverReconnectionTime=2e3,this.url=e.url.toString(),this.lastEventId=e.lastEventId,this.headers={Accept:`text/event-stream`,...e.headers},this.requestOptions={body:e.body,method:e.method??`GET`,mode:e.mode,credentials:e.credentials,redirect:e.redirect,referrer:e.referrer,referrerPolicy:e.referrerPolicy},this._fetch=e.fetch??((e,t)=>fetch(e,t)),this._onError=this.resolveCallback(e.onError,()=>{},`onError`),this._onConnect=this.resolveCallback(e.onConnect,()=>{},`onConnect`),this._onDisconnect=this.resolveCallback(e.onDisconnect,()=>{},`onDisconnect`),this._onMessage=this.resolveCallback(e.onMessage,()=>{},`onMessage`),this._onComment=this.resolveCallback(e.onComment,()=>{},`onComment`),this._shouldReconnect=this.resolveCallback(e.shouldReconnect,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`),this._calculateReconnectDelay=this.resolveCallback(e.calculateReconnectDelay,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`calculateReconnectDelay`)}set onError(e){this._onError=this.resolveCallback(e,()=>{},`onError`)}set onMessage(e){this._onMessage=this.resolveCallback(e,()=>{},`onMessage`)}set onComment(e){this._onComment=this.resolveCallback(e,()=>{},`onComment`)}set onConnect(e){this._onConnect=this.resolveCallback(e,()=>{},`onConnect`)}set onDisconnect(e){this._onDisconnect=this.resolveCallback(e,()=>{},`onDisconnect`)}set shouldReconnect(e){this._shouldReconnect=this.resolveCallback(e,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`)}set calculateReconnectDelay(e){this._calculateReconnectDelay=this.resolveCallback(e,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`shouldReconnect`)}get readyState(){return this._readyState}connect(){this._readyState===f.CLOSED&&(this.reconnectAttempt=0,this.initiateConnection())}close(){clearTimeout(this.reconnectTimeout),this._readyState=f.CLOSED,this.abortController.abort()}initiateConnection(){this.abortController=new AbortController,this._readyState=f.CONNECTING,this._fetch(this.url,{...this.requestOptions,headers:this.buildRequestHeaders(),signal:this.abortController.signal,cache:`no-store`}).then(e=>this.handleFetchResponse(e)).catch(e=>{if(e.name===`AbortError`||this.abortController.signal.aborted){this.close();return}this._onError(e),this.scheduleReconnect({error:e})})}async handleFetchResponse(e){let{body:t,status:n}=e;if(n===204){this.disconnected();return}if(n>=400){this.scheduleReconnect({status:n});return}if(!t)throw new c(`The server response did not have a body`);this._readyState=f.OPEN,this._onConnect(),this.reconnectAttempt=0;let r=new d({onEvent:e=>{typeof e.id==`string`&&(this.lastEventId=e.id),this._onMessage(e)},onComment:e=>this._onComment(e),onRetry:e=>this.serverReconnectionTime=e});await this.readResponseStream(t,r),this.scheduleReconnect({status:n,error:new s(`The server response stream was closed`)})}async readResponseStream(e,t){let n=e.getReader(),r=new TextDecoder,i=!1;for(;!i;){let e=await n.read();e.value&&t.parse(r.decode(e.value,{stream:!0})),i=e.done}t.finish()}disconnected(){clearTimeout(this.reconnectTimeout),this._readyState=f.CLOSED,this._onDisconnect()}buildRequestHeaders(){let e=this.lastEventId?{"Last-Event-ID":this.lastEventId}:void 0;return{...this.headers,...e}}scheduleReconnect(e){if(this.abortController.signal.aborted)return;clearTimeout(this.reconnectTimeout),this.reconnectAttempt+=1;let t={attempt:this.reconnectAttempt,status:e.status,error:e.error,serverReconnectionTime:this.serverReconnectionTime};if(!this._shouldReconnect(t)){this.disconnected();return}this._readyState=f.CONNECTING;let n=this._calculateReconnectDelay(t);(!Number.isFinite(n)||n<1||n>1e3*60*60)&&(this._onError(new u(`The calculated reconnect delay is out of range: ${n}. Defaulting to ${this.serverReconnectionTime}`)),n=this.serverReconnectionTime),this.reconnectTimeout=setTimeout(()=>this.initiateConnection(),n)}resolveCallback(e,t,n){if(e==null)return t;if(typeof e!=`function`)throw new l(`${n} must be a function`);return e}};const m=e=>new p(e);var h=class{constructor(e){this.options=e,this.eventEmitter=new t,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=new Promise((e,t)=>{this.eventSource=m({url:this.url,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:()=>{this.logger.debug(`[StreamingTransport] Connected`),e(this)},shouldReconnect:e=>{let n=!this.isStatusFatal(e.status);return n||t(this.prepareFatalError(e.status,e.error)),n},calculateReconnectDelay:e=>{let t=2**Math.min(e.attempt,9)*1e3,n=`[StreamingTransport] Scheduling reconnect attempt #${e.attempt} in ${t}ms.`;return e.attempt<=5?this.logger.info(n):this.logger.warn(n),t},onDisconnect:()=>{this.logger.debug(`[StreamingTransport] Disconnected`)}}),this.eventSource.connect()});return Promise.race([t,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(`[StreamingTransport] Error parsing and dispatching config data update: `,e)}}prepareFatalError(e,t){let n=e??0;return new a(`${`Connection failed with status: ${e??`unknown`}.`}${t?` Error: ${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()}},g=class{constructor(){this.fatalError=!1,this.pollingIntervalSeconds=60}clearPollingInterval(){clearInterval(this.pollingInterval)}schedulePollingInterval(e){clearInterval(this.pollingInterval),!(this.pollingIntervalSeconds<=0)&&(this.pollingInterval=setInterval(e,this.pollingIntervalSeconds*1e3))}async handleNonOkResponse(e){if(!e.ok)throw this.isStatusFatal(e.status)?(this.fatalError=!0,this.prepareFatalResponseStatusError(e.status,await e.text())):new a(`Connection failed with status: ${e.status}`,e.status)}handleFetchError(e){throw this.isFetchErrorFatal(e)?(this.fatalError=!0,this.close(),new a(`Connection failed with fatal error: ${e}. This is an unrecoverable error, retry attempts will be ignored.`)):e instanceof SyntaxError?new a(`Failed to parse the response from the server: ${e}`):new a(`Connection failed with error: ${e}.`)}prepareFatalResponseStatusError(e,t){let n=e??0;return new a(`${`Connection failed with status: ${e??`unknown`}`}${(t?.trim()?.length??0)>0?` (${t})`:``}. This is an unrecoverable error, retry attempts will be ignored.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}isFetchErrorFatal(e){return e?.name===`NotAllowedError`?!0:e instanceof TypeError}get isConnected(){return this.pollingInterval!==void 0}close(){clearInterval(this.pollingInterval),this.pollingInterval=void 0}dispose(){this.close(),this.clear()}};const _=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(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}};var v=class extends g{constructor(e){super(),this.options=e,this.eventEmitter=new t,this.options=e,this.logger=e.logger,this.url=new URL(`server/polling/v1`,e.baseUrl),this.pollingIntervalSeconds=e.pollingInterval??60}async connect(e){return this.clearPollingInterval(),await this.fetchConfigs(e),this.schedulePollingInterval(()=>{this.fetchConfigs(e).catch(e=>{this.logger.error(`[PollingTransport] Error during polling:`,e)})}),this}async fetchConfigs(e){if(this.fatalError){this.logger.warn(`[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.`);return}try{let t=await _(e,this.url,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey,lastUpdateTimestamp:this.lastUpdateTimestamp})},this.logger);if(await this.handleNonOkResponse(t),t.status===200){let e=JSON.parse(await t.text());this.lastUpdateTimestamp=e.timestamp,this.eventEmitter.emit(`configBundleReceived`,e)}}catch(e){this.handleFetchError(e)}}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()}},y=class extends v{constructor(e){super(e),this.pollingIntervalSeconds=0}};const b=(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 x(n[0],e);case`does not match regex`:return!x(n[0],e);default:return!1}},x=(e,t)=>{try{return new RegExp(e).test(t)}catch{return!1}},S=(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}=C(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}},C=(e,t)=>typeof e==`number`?{value:e,target:w(t)}:typeof e==`bigint`?{value:e,target:BigInt(t)}:{value:w(e),target:w(t)},w=e=>{let t=Number.parseFloat(e.toString());if(isNaN(t)||!isFinite(t))throw TypeError(`Invalid numeric value: ${e}`);return t},T=(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}},E=(e,t,n)=>{let i=t.toLowerCase();if(e==null||e.trim().length==0)return i==`is not one of`;let a=r.coerce(e)??e,o=n.map(e=>r.coerce(e)??e);try{switch(i){case`=`:return D(a,o[0]);case`<`:return r.lt(a,o[0]);case`<=`:return r.lte(a,o[0]);case`>`:return r.gt(a,o[0]);case`>=`:return r.gte(a,o[0]);case`is one of`:return o.findIndex(e=>D(a,e))>=0;case`is not one of`:return o.findIndex(e=>D(a,e))<0;default:return!1}}catch{return!1}},D=(e,t)=>{try{return r.eq(e,t)}catch{return!1}},O=(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 k=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 b(e?.toString(),t.operator,t.targetValues);case`number`:return S(e,t.operator,t.targetValues);case`datetime`:return T(e?.toString(),t.operator,t.targetValues);case`semver`:return E(e?.toString(),t.operator,t.targetValues);case`array`:return O(e,t.operator,t.targetValues)}return!1}findTraitValue(e,t){try{return n(t,e).val}catch{return}}isConditionValidForComparison(e){return Array.isArray(e.targetValues)&&e.targetValues.length>0}};const A=e=>{let t=`${e.contextIdentifier}-${e.configId}`;return Number(i(t,{seed:15426857351n})%1000n)/10};var j=class{constructor(e){if(this.logger=e,this.conditionEvaluator=new k,!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=A({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 M=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},N=e=>e===`number`||e===`bigint`,P=(e,t)=>{let n=e.value,r=M(t);if(!n)return{parsedValue:t,requestedType:r,usedDefault:!0,reason:`value-missing`};if(e.type===`json`)return R(n,t,r);if(typeof t==`string`)return{parsedValue:n,requestedType:r,usedDefault:!1,reason:`found-match`};if(typeof t==`boolean`&&e.type===`boolean`){let e=F(n),i=typeof e==`boolean`;return{parsedValue:i?e:t,requestedType:r,usedDefault:!i,reason:i?`found-match`:`invalid-boolean`}}if(N(typeof t)&&e.type===`integer`){let e=I(n),i=typeof e==`number`;return{parsedValue:i?e:t,requestedType:r,usedDefault:!i,reason:i?`found-match`:`invalid-number`}}if(N(typeof t)&&(e.type===`float`||e.type===`enum`)){let e=L(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`}},F=e=>{if(!e)return;let t=e.toLowerCase();if(!(t!=`true`&&t!=`false`))return t===`true`},I=e=>{if(!e)return;let t=Number.parseInt(e);if(!(isNaN(t)||!isFinite(t)))return t},L=e=>{if(!e)return;let t=Number.parseFloat(e);if(!(isNaN(t)||!isFinite(t)))return t},R=(e,t,n)=>{if(typeof t==`string`)return{parsedValue:e,requestedType:n,usedDefault:!1,reason:`found-match`};try{return{parsedValue:JSON.parse(e),requestedType:n,usedDefault:!1,reason:`found-match`}}catch{return{parsedValue:t,requestedType:n,usedDefault:!0,reason:`invalid-json`}}},z={off:-1,error:0,warn:1,info:2,debug:3},ee=()=>{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 B=class{constructor(e,t){this.level=e,this.decorator=t,this.level=e,this.decorator=t,this.dateFormatter=ee()}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){z[this.level]>=z[t]&&e(`[${this.dateFormatter.format(new Date)}] ${this.decorator?.decorateMessage(n)}`,...r)}},V=class{decorateMessage(e){return`[ConfigDirector:js-server-sdk] ${e}`}};const H=(e,t)=>new B(e??`warn`,t??new V),U=(e,t)=>new URL(e,t?.toString()),W=(e,t)=>{if(t===`json`||t==null&&typeof e==`object`)try{return{digest:G(JSON.stringify(e))}}catch{return{digest:e.toString()}}return e.toString().length>500?{digest:G(e.toString())}:{value:e.toString()}},G=e=>K(new TextEncoder().encode(e)).toString(16).padStart(8,`0`),K=e=>{let t=5381;for(let n=0;n<e.length;n++)t=(t<<5)+t+(e[n]??0),t>>>=0;return t},q=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},J=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};var Y=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.url=new URL(`server/telemetry/v1`,e.baseUrl.toString())}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 q(e.discreteEvents)&&q(e.aggregatedEvents)&&J(e.droppedEvents)}async sendReport(e){try{let t=await _(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}},X=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}},Q=class{constructor(e){this.aggregator=new X,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new Z(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}sanitizeValue(e,t){return W(e,t)}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)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},$=class extends Map{constructor(e,t){super(t),this._droppedCount=0,this.limit=e,this.enforceLimit()}set(e,t){return super.set(e,t),this.enforceLimit(),this}get droppedCount(){return this._droppedCount}clearAndReset(){this.clear(),this._droppedCount=0}enforceLimit(){for(;this.size>this.limit;){let e=this.keys().next().value;e!==void 0&&(this.delete(e),this._droppedCount+=1)}}},te=class extends Q{constructor(e){super(e),this.reporter=new Y(e),this.contexts=new $(e.contextLimit??1e3)}evaluatedConfig(e){if(!this.collectEvents)return;e.context?.id&&!e.context.anonymous&&this.contexts.set(e.context.id,e.context);let t={...e.evaluation,contextId:e.context?.anonymous?void 0:e.context?.id};this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(t))}sanitizeEvaluatedConfigEvent(e){return{...e,defaultValue:this.sanitizeValue(e.defaultValue,e.type),evaluatedValue:this.sanitizeValue(e.evaluatedValue,e.type)}}snapshotContexts(){let e=Array.from(this.contexts.values()),t=this.contexts.droppedCount;return this.contexts.clearAndReset(),{contexts:e,droppedCount:t}}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=this.evaluationEventQueue.takeSnapshot(),t=this.snapshotContexts();return await this.reporter?.report({context:this._context,discreteEvents:{capturedContexts:t.contexts},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount,capturedContexts:t.droppedCount}})}};const ne=new URL(`https://server-sdk-api.configdirector.com`);var re=class{constructor(t,n,r){this.handlersMap=new Map,this.eventEmitter=new e,this.ready=!1,this.logger=r?.logger??H(),this.timeout=r?.connection?.timeout??3e3,this.metaContext=r?.metadata??{},this.configEvaluator=new j(this.logger);let i=this.parseUrl(r?.connection?.url)??ne;this.connectionMode=r?.connection?.mode??`streaming`;let a=this.getTransportConstructor(this.connectionMode),o=r?.telemetry?.eventQueueLimit??5e3;this.usageEventCollector=new te({sdkKey:t,logger:this.logger,baseUrl:i,flushIntervalDelay:r?.telemetry?.flushInterval??3e4,evaluationQueueLimit:Math.ceil(o*.7),contextLimit:Math.floor(o*.3),urlFactory:U}),this.transport=new a({serverSdkKey:t,baseUrl:i,metaContext:{...r?.metadata,sdkName:n.sdkName,sdkVersion:n.sdkVersion},logger:this.logger,pollingInterval:r?.connection?.pollingInterval}),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 with ${t.length} key(s)`,{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.connectionMode===`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)}}getTransportConstructor(e){switch(e){case`one-time`:return y;case`polling`:return v;default:return h}}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=this.configEvaluator.evaluate(t,{context:r,metadata:this.metaContext}),a=P(i,n);return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,type:i.type,defaultValue:n,requestedType:a.requestedType,evaluatedValue:a.parsedValue,usedDefault:a.usedDefault,evaluationReason:a.reason}}),this.logger.debug(`[ConfigDirectorClient] Evaluated '${e}' to '${a.parsedValue}'`),a.parsedValue}else return this.logger.debug(`[ConfigDirectorClient] No config state found for '${e}', returning default value '${n}'`),this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,defaultValue:n,requestedType:M(n),evaluatedValue:n,usedDefault:!0,evaluationReason:`config-state-missing`}}),n}parseUrl(e){if(e)try{return new URL(e)}catch(t){throw new o(`Invalid base URL '${e}'. Parsing failed: ${t}`)}}validateDefaultValue(e){if(e==null)throw new o(`Invalid default value. The default value for a config must be defined and non-null.`);if(typeof e==`function`)throw new o(`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()}getAllConfigs(e){if(!this.configSet)return{};let t=Object.entries(this.configSet.configs),n=e?.configKeys?t.filter(([t])=>e.configKeys?.includes(t)):t;return Object.fromEntries(n.map(([t,n])=>[t,this.configEvaluator.evaluate(n,{context:e?.context,metadata:this.metaContext})]))}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 ie=(e,t)=>new re(e,{sdkName:`js-server-sdk`,sdkVersion:`0.7.0`},t),ae=(e,t)=>H(e,t);export{ie as createClient,ae as createConsoleLogger};
|
|
5
|
+
`;break;case`id`:e.value.includes(`\0`)||(this.lastEventId=e.value);break;case`retry`:/^\d+$/.test(e.value)&&this.onRetry?.(Number.parseInt(e.value,10));break;default:break}}parseField(e){let t=e.indexOf(`:`);return t===-1?{field:e,value:``}:{field:e.slice(0,t),value:this.extractValue(e,t+1)}}extractValue(e,t){return e[t]===` `?e.slice(t+1):e.slice(t)}};let p=function(e){return e.OPEN=`open`,e.CLOSED=`closed`,e.CONNECTING=`connecting`,e}({});var m=class{constructor(e){this.DEFAULT_SHOULD_RECONNECT=()=>!0,this.DEFAULT_CALCULATE_RECONNECT_DELAY=()=>this.serverReconnectionTime,this.reconnectAttempt=0,this.abortController=new AbortController,this._readyState=p.CLOSED,this.serverReconnectionTime=2e3,this.url=e.url.toString(),this.lastEventId=e.lastEventId,this.headers={Accept:`text/event-stream`,...e.headers},this.requestOptions={body:e.body,method:e.method??`GET`,mode:e.mode,credentials:e.credentials,redirect:e.redirect,referrer:e.referrer,referrerPolicy:e.referrerPolicy},this._fetch=e.fetch??((e,t)=>fetch(e,t)),this._onError=this.resolveCallback(e.onError,()=>{},`onError`),this._onConnect=this.resolveCallback(e.onConnect,()=>{},`onConnect`),this._onDisconnect=this.resolveCallback(e.onDisconnect,()=>{},`onDisconnect`),this._onMessage=this.resolveCallback(e.onMessage,()=>{},`onMessage`),this._onComment=this.resolveCallback(e.onComment,()=>{},`onComment`),this._shouldReconnect=this.resolveCallback(e.shouldReconnect,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`),this._calculateReconnectDelay=this.resolveCallback(e.calculateReconnectDelay,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`calculateReconnectDelay`)}set onError(e){this._onError=this.resolveCallback(e,()=>{},`onError`)}set onMessage(e){this._onMessage=this.resolveCallback(e,()=>{},`onMessage`)}set onComment(e){this._onComment=this.resolveCallback(e,()=>{},`onComment`)}set onConnect(e){this._onConnect=this.resolveCallback(e,()=>{},`onConnect`)}set onDisconnect(e){this._onDisconnect=this.resolveCallback(e,()=>{},`onDisconnect`)}set shouldReconnect(e){this._shouldReconnect=this.resolveCallback(e,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`)}set calculateReconnectDelay(e){this._calculateReconnectDelay=this.resolveCallback(e,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`shouldReconnect`)}get readyState(){return this._readyState}connect(){this._readyState===p.CLOSED&&(this.reconnectAttempt=0,this.initiateConnection())}close(){clearTimeout(this.reconnectTimeout),this._readyState=p.CLOSED,this.abortController.abort()}initiateConnection(){this.abortController=new AbortController,this._readyState=p.CONNECTING,this._fetch(this.url,{...this.requestOptions,headers:this.buildRequestHeaders(),signal:this.abortController.signal,cache:`no-store`}).then(e=>this.handleFetchResponse(e)).catch(e=>{if(e.name===`AbortError`||this.abortController.signal.aborted){this.close();return}this._onError(e),this.scheduleReconnect({error:e})})}async handleFetchResponse(e){let{body:t,status:n}=e;if(n===204){this.disconnected();return}if(n>=400){this.scheduleReconnect({status:n});return}if(!t)throw new l(`The server response did not have a body`);this._readyState=p.OPEN,this._onConnect(),this.reconnectAttempt=0;let r=new f({onEvent:e=>{typeof e.id==`string`&&(this.lastEventId=e.id),this._onMessage(e)},onComment:e=>this._onComment(e),onRetry:e=>this.serverReconnectionTime=e});await this.readResponseStream(t,r),this.scheduleReconnect({status:n,error:new c(`The server response stream was closed`)})}async readResponseStream(e,t){let n=e.getReader(),r=new TextDecoder,i=!1;for(;!i;){let e=await n.read();e.value&&t.parse(r.decode(e.value,{stream:!0})),i=e.done}t.finish()}disconnected(){clearTimeout(this.reconnectTimeout),this._readyState=p.CLOSED,this._onDisconnect()}buildRequestHeaders(){let e=this.lastEventId?{"Last-Event-ID":this.lastEventId}:void 0;return{...this.headers,...e}}scheduleReconnect(e){if(this.abortController.signal.aborted)return;clearTimeout(this.reconnectTimeout),this.reconnectAttempt+=1;let t={attempt:this.reconnectAttempt,status:e.status,error:e.error,serverReconnectionTime:this.serverReconnectionTime};if(!this._shouldReconnect(t)){this.disconnected();return}this._readyState=p.CONNECTING;let n=this._calculateReconnectDelay(t);(!Number.isFinite(n)||n<1||n>1e3*60*60)&&(this._onError(new d(`The calculated reconnect delay is out of range: ${n}. Defaulting to ${this.serverReconnectionTime}`)),n=this.serverReconnectionTime),this.reconnectTimeout=setTimeout(()=>this.initiateConnection(),n)}resolveCallback(e,t,n){if(e==null)return t;if(typeof e!=`function`)throw new u(`${n} must be a function`);return e}};const h=e=>new m(e);var g=class{constructor(e){this.options=e,this.eventEmitter=new t,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=new Promise((e,t)=>{this.eventSource=h({url:this.url,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:()=>{this.logger.debug(`[StreamingTransport] Connected`),e(this)},shouldReconnect:e=>{let n=!this.isStatusFatal(e.status);return n||t(this.prepareFatalError(e.status,e.error)),n},calculateReconnectDelay:e=>{let t=2**Math.min(e.attempt,9)*1e3,n=`[StreamingTransport] Scheduling reconnect attempt #${e.attempt} in ${t}ms.`;return e.attempt<=5?this.logger.info(n):this.logger.warn(n),t},onDisconnect:()=>{this.logger.debug(`[StreamingTransport] Disconnected`)}}),this.eventSource.connect()});return Promise.race([t,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(`[StreamingTransport] 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?` Error: ${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()}},_=class{constructor(){this.fatalError=!1,this.pollingIntervalSeconds=60}clearPollingInterval(){clearInterval(this.pollingInterval)}schedulePollingInterval(e){clearInterval(this.pollingInterval),!(this.pollingIntervalSeconds<=0)&&(this.pollingInterval=setInterval(e,this.pollingIntervalSeconds*1e3))}async handleNonOkResponse(e){if(!e.ok)throw this.isStatusFatal(e.status)?(this.fatalError=!0,this.prepareFatalResponseStatusError(e.status,await e.text())):new o(`Connection failed with status: ${e.status}`,e.status)}handleFetchError(e){throw this.isFetchErrorFatal(e)?(this.fatalError=!0,this.close(),new o(`Connection failed with fatal error: ${e}. This is an unrecoverable error, retry attempts will be ignored.`)):e instanceof SyntaxError?new o(`Failed to parse the response from the server: ${e}`):new o(`Connection failed with error: ${e}.`)}prepareFatalResponseStatusError(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, retry attempts will be ignored.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500}isFetchErrorFatal(e){return e?.name===`NotAllowedError`?!0:e instanceof TypeError}get isConnected(){return this.pollingInterval!==void 0}close(){clearInterval(this.pollingInterval),this.pollingInterval=void 0}dispose(){this.close(),this.clear()}};const v=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(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}};var y=class extends _{constructor(e){super(),this.options=e,this.eventEmitter=new t,this.options=e,this.logger=e.logger,this.url=new URL(`server/polling/v1`,e.baseUrl),this.pollingIntervalSeconds=e.pollingInterval??60}async connect(e){return this.clearPollingInterval(),await this.fetchConfigs(e),this.schedulePollingInterval(()=>{this.fetchConfigs(e).catch(e=>{this.logger.error(`[PollingTransport] Error during polling:`,e)})}),this}async fetchConfigs(e){if(this.fatalError){this.logger.warn(`[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.`);return}try{let t=await v(e,this.url,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({metaContext:this.options.metaContext,serverSdkKey:this.options.serverSdkKey,lastUpdateTimestamp:this.lastUpdateTimestamp})},this.logger);if(await this.handleNonOkResponse(t),t.status===200){let e=JSON.parse(await t.text());this.lastUpdateTimestamp=e.timestamp,this.eventEmitter.emit(`configBundleReceived`,e)}}catch(e){this.handleFetchError(e)}}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()}},b=class extends y{constructor(e){super(e),this.pollingIntervalSeconds=0}};const ee=(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 x(n[0],e);case`does not match regex`:return!x(n[0],e);default:return!1}},x=(e,t)=>{try{return new RegExp(e).test(t)}catch{return!1}},S=(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}=C(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}},C=(e,t)=>typeof e==`number`?{value:e,target:w(t)}:typeof e==`bigint`?{value:e,target:BigInt(t)}:{value:w(e),target:w(t)},w=e=>{let t=Number.parseFloat(e.toString());if(isNaN(t)||!isFinite(t))throw TypeError(`Invalid numeric value: ${e}`);return t},T=(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}},E=(e,t,n)=>{let i=t.toLowerCase();if(e==null||e.trim().length==0)return i==`is not one of`;let a=r.coerce(e)??e,o=n.map(e=>r.coerce(e)??e);try{switch(i){case`=`:return D(a,o[0]);case`<`:return r.lt(a,o[0]);case`<=`:return r.lte(a,o[0]);case`>`:return r.gt(a,o[0]);case`>=`:return r.gte(a,o[0]);case`is one of`:return o.findIndex(e=>D(a,e))>=0;case`is not one of`:return o.findIndex(e=>D(a,e))<0;default:return!1}}catch{return!1}},D=(e,t)=>{try{return r.eq(e,t)}catch{return!1}},O=(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 k=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 ee(e?.toString(),t.operator,t.targetValues);case`number`:return S(e,t.operator,t.targetValues);case`datetime`:return T(e?.toString(),t.operator,t.targetValues);case`semver`:return E(e?.toString(),t.operator,t.targetValues);case`array`:return O(e,t.operator,t.targetValues)}return!1}findTraitValue(e,t){try{return n(t,e).val}catch{return}}isConditionValidForComparison(e){return Array.isArray(e.targetValues)&&e.targetValues.length>0}};const A=e=>{let t=`${e.contextIdentifier}-${e.configId}`;return Number(i(t,{seed:15426857351n})%1000n)/10};var j=class{constructor(e){if(this.logger=e,this.conditionEvaluator=new k,!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=A({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 M=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},N=e=>e===`number`||e===`bigint`,P=(e,t)=>{let n=e.value,r=M(t);if(!n)return{parsedValue:t,parsedValueId:void 0,requestedType:r,usedDefault:!0,reason:`value-missing`};if(e.type===`json`)return R(n,t,r,e.valueId);if(typeof t==`string`)return{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`};if(typeof t==`boolean`&&(e.type===`boolean`||e.type===`string`)){let i=F(n),a=typeof i==`boolean`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-boolean`}}if(N(typeof t)&&e.type===`integer`){let i=I(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}if(N(typeof t)&&(e.type===`float`||e.type===`enum`||e.type===`string`)){let i=L(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}return{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`}},F=e=>{if(!e)return;let t=e.toLowerCase();if(!(t!=`true`&&t!=`false`))return t===`true`},I=e=>{if(!e)return;let t=Number.parseInt(e);if(!(isNaN(t)||!isFinite(t)))return t},L=e=>{if(!e)return;let t=Number.parseFloat(e);if(!(isNaN(t)||!isFinite(t)))return t},R=(e,t,n,r)=>{if(typeof t==`string`)return{parsedValue:e,parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`};try{return{parsedValue:JSON.parse(e),parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`}}catch{return{parsedValue:t,parsedValueId:void 0,requestedType:n,usedDefault:!0,reason:`invalid-json`}}},z={off:-1,error:0,warn:1,info:2,debug:3},B=()=>{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 te=class{constructor(e,t){this.level=e,this.decorator=t,this.level=e,this.decorator=t,this.dateFormatter=B()}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){z[this.level]>=z[t]&&e(`[${this.dateFormatter.format(new Date)}] ${this.decorator?.decorateMessage(n)}`,...r)}},V=class{decorateMessage(e){return`[ConfigDirector:js-server-sdk] ${e}`}};const H=(e,t)=>new te(e??`warn`,t??new V),U=(e,t)=>new URL(e,t?.toString()),W=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,G=e=>{if(e.type===`json`||e.type==null&&typeof e.value==`object`){if(e.valueId)return{valueId:e.valueId,type:`json`};try{return{value:JSON.stringify(e.value),type:`json`}}catch{return{value:e.value.toString(),type:`json`}}}let t=e.value?.toString();return t&&t.length<=500?{value:t}:e.valueId?{valueId:e.valueId}:{value:t}},K=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},q=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};var J=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.url=new URL(`server/telemetry/v1`,e.baseUrl.toString())}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 K(e.discreteEvents)&&K(e.aggregatedEvents)&&q(e.droppedEvents)}async sendReport(e){try{let t=await v(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}},Y=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}))}},X=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}},Z=class{constructor(e){this.aggregator=new Y,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new X(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}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)}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},Q=class extends Map{constructor(e,t){super(t),this._droppedCount=0,this.limit=e,this.enforceLimit()}set(e,t){return super.set(e,t),this.enforceLimit(),this}get droppedCount(){return this._droppedCount}clearAndReset(){this.clear(),this._droppedCount=0}enforceLimit(){for(;this.size>this.limit;){let e=this.keys().next().value;e!==void 0&&(this.delete(e),this._droppedCount+=1)}}},ne=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(e){let t=await Promise.all(e.events.map(async e=>({...e,defaultValue:await W(e.defaultValue,this.valueIdGenerator),evaluatedValue:await W(e.evaluatedValue,this.valueIdGenerator)})));return{...e,events:t}}},re=class extends Z{constructor(e){super(e),this.reporter=new J(e),this.contexts=new Q(e.contextLimit??1e3),this.evaluationEventSnapshotPreprocessor=new ne(e.valueIdGenerator)}evaluatedConfig(e){if(!this.collectEvents)return;e.context?.id&&!e.context.anonymous&&this.contexts.set(e.context.id,e.context);let t={...e.evaluation,contextId:e.context?.anonymous?void 0:e.context?.id};this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(t))}sanitizeEvaluatedConfigEvent(e){return{...e,defaultValue:G({value:e.defaultValue,type:e.type}),evaluatedValue:G({value:e.evaluatedValue,valueId:e.evaluatedValueId,type:e.type})}}snapshotContexts(){let e=Array.from(this.contexts.values()),t=this.contexts.droppedCount;return this.contexts.clearAndReset(),{contexts:e,droppedCount:t}}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot()),t=this.snapshotContexts();return await this.reporter?.report({context:this._context,discreteEvents:{capturedContexts:t.contexts},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount,capturedContexts:t.droppedCount}})}};const ie=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},ae=Math.ceil(128/Math.log2(62)),oe=async e=>ie(a(`sha256`).update(e?.toString()??``).digest().subarray(0,16),ae),$=new URL(`https://server-sdk-api.configdirector.com`);var se=class{constructor(t,n,r){this.handlersMap=new Map,this.eventEmitter=new e,this.ready=!1,this.logger=r?.logger??H(),this.timeout=r?.connection?.timeout??3e3,this.metaContext=r?.metadata??{},this.configEvaluator=new j(this.logger);let i=this.parseUrl(r?.connection?.url)??$;this.connectionMode=r?.connection?.mode??`streaming`;let a=this.getTransportConstructor(this.connectionMode),o=r?.telemetry?.eventQueueLimit??5e3;this.usageEventCollector=new re({sdkKey:t,logger:this.logger,baseUrl:i,flushIntervalDelay:r?.telemetry?.flushInterval??3e4,evaluationQueueLimit:Math.ceil(o*.7),contextLimit:Math.floor(o*.3),urlFactory:U,valueIdGenerator:oe}),this.transport=new a({serverSdkKey:t,baseUrl:i,metaContext:{...r?.metadata,sdkName:n.sdkName,sdkVersion:n.sdkVersion},logger:this.logger,pollingInterval:r?.connection?.pollingInterval}),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 with ${t.length} key(s)`,{keys:t})}),this.registerHandler(`clientReady`,r?.hooks?.clientReady),this.registerHandler(`configEvaluated`,r?.hooks?.configEvaluated),this.registerHandler(`configsUpdated`,r?.hooks?.configsUpdated)}registerHandler(e,t){if(!t)return;let n=Array.isArray(t)?t:[t];for(let t of n)typeof t==`function`&&this.on(e,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.connectionMode===`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)}}getTransportConstructor(e){switch(e){case`one-time`:return b;case`polling`:return y;default:return g}}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=this.configEvaluator.evaluate(t,{context:r,metadata:this.metaContext}),a=P(i,n);return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,type:i.type,defaultValue:n,requestedType:a.requestedType,evaluatedValue:a.parsedValue,evaluatedValueId:a.parsedValueId,usedDefault:a.usedDefault,evaluationReason:a.reason}}),this.dispatchEvaluationEvent({key:e,value:a.parsedValue,valueId:a.parsedValueId??void 0,isDefaultValue:a.usedDefault,reason:a.reason,context:r}),this.logger.debug(`[ConfigDirectorClient] Evaluated '${e}' to '${a.parsedValue}'`),a.parsedValue}else{this.logger.debug(`[ConfigDirectorClient] No config state found for '${e}', returning default value '${n}'`);let t=this.isReady?`config-state-missing`:`client-not-ready`;return this.usageEventCollector.evaluatedConfig({context:r,evaluation:{key:e,defaultValue:n,requestedType:M(n),evaluatedValue:n,usedDefault:!0,evaluationReason:t}}),this.dispatchEvaluationEvent({key:e,value:n,isDefaultValue:!0,reason:t,context:r}),n}}dispatchEvaluationEvent(e){setTimeout(()=>this.eventEmitter.emit(`configEvaluated`,{evaluation:e}),0)}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()}getAllConfigs(e){if(!this.configSet)return{};let t=Object.entries(this.configSet.configs),n=e?.configKeys?t.filter(([t])=>e.configKeys?.includes(t)):t;return Object.fromEntries(n.map(([t,n])=>[t,this.configEvaluator.evaluate(n,{context:e?.context,metadata:this.metaContext})]))}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 ce=(e,t)=>new se(e,{sdkName:`js-server-sdk`,sdkVersion:`0.9.0`},t),le=(e,t)=>H(e,t);export{ce as createClient,le as createConsoleLogger};
|
|
6
6
|
//# sourceMappingURL=configdirector-server.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configdirector-server.mjs","names":["EventEmitter","EventEmitter"],"sources":["../../shared/src/errors.ts","../../eventsource/src/errors.ts","../../eventsource/src/EventSourceParser.ts","../../eventsource/src/types.ts","../../eventsource/src/EventSourceClient.ts","../../eventsource/src/index.ts","../src/StreamingTransport.ts","../../shared/src/transport/AbstractPollingTransport.ts","../../shared/src/fetchWithTimeout.ts","../src/PollingTransport.ts","../src/OneTimeTransport.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/url.ts","../../shared/src/telemetry/utils.ts","../src/telemetry/ServerEventReporter.ts","../../shared/src/telemetry/EventAggregator.ts","../../shared/src/telemetry/EventQueue.ts","../../shared/src/telemetry/TelemetryEventCollector.ts","../../shared/src/LimitedMap.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\nexport class ConfigDirectorInitializationError extends Error {\n public override readonly name: string = \"ConfigDirectorInitializationError\";\n\n constructor(message: string) {\n super(message);\n\n Object.setPrototypeOf(this, ConfigDirectorInitializationError.prototype);\n }\n}\n","export class StreamClosedError extends Error {\n public override readonly name: string = \"StreamClosedError\";\n\n constructor(message: string) {\n super(message);\n\n Object.setPrototypeOf(this, StreamClosedError.prototype);\n }\n}\n\nexport class MissingResponseBodyError extends Error {\n public override readonly name: string = \"MissingResponseBodyError\";\n\n constructor(message: string) {\n super(message);\n\n Object.setPrototypeOf(this, MissingResponseBodyError.prototype);\n }\n}\n\nexport class InvalidOptionError extends Error {\n public override readonly name: string = \"InvalidOptionError\";\n\n constructor(message: string) {\n super(message);\n\n Object.setPrototypeOf(this, InvalidOptionError.prototype);\n }\n}\n\nexport class ValueOutOfRangeError extends Error {\n public override readonly name: string = \"ValueOutOfRangeError\";\n\n constructor(message: string) {\n super(message);\n\n Object.setPrototypeOf(this, ValueOutOfRangeError.prototype);\n }\n}\n","import {\n type EventSourceParserOptions,\n type EventSourceMessageHandler,\n type EventSourceCommentHandler,\n type EventParserRetryCallback,\n} from \"./types\";\n\ntype ParsedField = {\n field: string;\n value: string;\n};\n\nexport class EventSourceParser {\n private isFirstChunk = true;\n private bufferedInput = \"\";\n private currentEvent: { type?: string; data: string } = { data: \"\" };\n private lastEventId: string | undefined;\n private onEvent: EventSourceMessageHandler;\n private onRetry: EventParserRetryCallback | undefined;\n private onComment: EventSourceCommentHandler | undefined;\n\n constructor(options: EventSourceParserOptions) {\n this.currentEvent = { data: \"\" };\n this.onEvent = options.onEvent ?? (() => {});\n this.onRetry = options.onRetry;\n this.onComment = options.onComment;\n }\n\n public parse(chunk: string): void {\n let input = chunk;\n\n if (this.isFirstChunk) {\n this.isFirstChunk = false;\n if (input.startsWith(\"\\uFEFF\")) {\n input = input.slice(1);\n } else if (input.startsWith(\"\\xEF\\xBB\\xBF\")) {\n input = input.slice(3);\n }\n }\n\n const lines = this.scanLines(this.bufferedInput + input);\n for (const line of lines) {\n this.dispatchLine(line);\n }\n }\n\n // Per the SSE spec, any data still buffered when the stream ends is discarded —\n // an event requires a terminating empty line to be dispatched.\n public finish(): void {\n this.bufferedInput = \"\";\n }\n\n /**\n * Scans `text` character by character to extract complete lines, recognizing\n * CR, LF, and CRLF as line terminators per the SSE spec. Any unterminated\n * trailing text is buffered and prepended to the next chunk.\n */\n private scanLines(text: string): string[] {\n const lines: string[] = [];\n let lineStart = 0;\n let i = 0;\n\n while (i < text.length) {\n const ch = text[i];\n if (ch === \"\\r\" || ch === \"\\n\") {\n lines.push(text.slice(lineStart, i));\n // Consume CRLF as a single terminator rather than two separate lines.\n if (ch === \"\\r\" && text[i + 1] === \"\\n\") {\n i++;\n }\n i++;\n lineStart = i;\n } else {\n i++;\n }\n }\n\n this.bufferedInput = text.slice(lineStart);\n return lines;\n }\n\n private dispatchLine(line: string): void {\n if (line.startsWith(\":\")) {\n this.onComment?.(this.extractValue(line, 1));\n return;\n }\n\n if (line === \"\") {\n this.emitEvent();\n return;\n }\n\n this.applyField(this.parseField(line));\n }\n\n private emitEvent(): void {\n const data = this.currentEvent.data.endsWith(\"\\n\")\n ? this.currentEvent.data.slice(0, -1)\n : this.currentEvent.data;\n\n if (data !== \"\") {\n this.onEvent({ ...this.currentEvent, id: this.lastEventId, data });\n }\n\n this.currentEvent = { data: \"\" };\n }\n\n private applyField(parsed: ParsedField): void {\n switch (parsed.field) {\n case \"event\":\n this.currentEvent.type = parsed.value;\n break;\n case \"data\":\n this.currentEvent.data += parsed.value + \"\\n\";\n break;\n case \"id\":\n // Spec: ignore id values that contain a null character.\n if (!parsed.value.includes(\"\\0\")) {\n this.lastEventId = parsed.value;\n }\n break;\n case \"retry\":\n if (/^\\d+$/.test(parsed.value)) {\n this.onRetry?.(Number.parseInt(parsed.value, 10));\n }\n break;\n default:\n break;\n }\n }\n\n private parseField(line: string): ParsedField {\n const colon = line.indexOf(\":\");\n if (colon === -1) {\n return { field: line, value: \"\" };\n }\n return { field: line.slice(0, colon), value: this.extractValue(line, colon + 1) };\n }\n\n /** Returns the value portion of a line, stripping a single leading space per spec. */\n private extractValue(line: string, from: number): string {\n return line[from] === \" \" ? line.slice(from + 1) : line.slice(from);\n }\n}\n","export type ReconnectionState = {\n attempt: number;\n serverReconnectionTime: number;\n status?: number | undefined;\n error?: Error | undefined;\n};\n\nexport enum ReadyState {\n OPEN = \"open\",\n CLOSED = \"closed\",\n CONNECTING = \"connecting\",\n}\n\nexport type EventSourceClientOptions = {\n url: { toString(): string } | string;\n method?: string;\n headers?: Record<string, string>;\n body?: any;\n mode?: \"cors\" | \"no-cors\" | \"same-origin\";\n credentials?: \"include\" | \"omit\" | \"same-origin\";\n redirect?: \"error\" | \"follow\";\n referrer?: string;\n referrerPolicy?: ReferrerPolicy;\n lastEventId?: string;\n fetch?: (url: string, init: RequestInit) => Promise<Response>;\n onError?: (error: Error) => void;\n onConnect?: () => void;\n onDisconnect?: () => void;\n onMessage?: EventSourceMessageHandler;\n onComment?: EventSourceCommentHandler;\n shouldReconnect?: (state: ReconnectionState) => boolean;\n calculateReconnectDelay?: (state: ReconnectionState) => number;\n};\n\nexport type EventSourceMessage = {\n id?: string | undefined;\n type?: string;\n data: string;\n};\n\nexport type EventSourceMessageHandler = (message: EventSourceMessage) => void;\nexport type EventSourceCommentHandler = (comment: string) => void;\n\nexport type EventParserRetryCallback = (retry: number) => void;\n\nexport type EventSourceParserOptions = {\n onEvent?: EventSourceMessageHandler;\n onRetry?: EventParserRetryCallback;\n onComment?: EventSourceCommentHandler;\n};\n","import {\n InvalidOptionError,\n MissingResponseBodyError,\n StreamClosedError,\n ValueOutOfRangeError,\n} from \"./errors\";\nimport { EventSourceParser } from \"./EventSourceParser\";\nimport {\n type EventSourceClientOptions,\n type EventSourceCommentHandler,\n type EventSourceMessageHandler,\n type ReconnectionState,\n ReadyState,\n} from \"./types\";\n\nexport class EventSourceClient {\n private readonly DEFAULT_SHOULD_RECONNECT = () => true;\n private readonly DEFAULT_CALCULATE_RECONNECT_DELAY = () => this.serverReconnectionTime;\n private readonly url: string;\n private readonly headers?: Record<string, string>;\n private readonly requestOptions;\n private lastEventId?: string;\n private reconnectAttempt: number = 0;\n private abortController: AbortController = new AbortController();\n private reconnectTimeout: ReturnType<typeof setTimeout> | undefined;\n private _readyState: ReadyState = ReadyState.CLOSED;\n private serverReconnectionTime: number = 2_000;\n private _fetch: (url: string, init: RequestInit) => Promise<Response>;\n private _onError: (error: Error) => void;\n private _onMessage: EventSourceMessageHandler;\n private _onComment: EventSourceCommentHandler;\n private _onConnect: () => void;\n private _onDisconnect: () => void;\n private _shouldReconnect: (state: ReconnectionState) => boolean;\n private _calculateReconnectDelay: (state: ReconnectionState) => number;\n\n constructor(options: EventSourceClientOptions) {\n this.url = options.url.toString();\n this.lastEventId = options.lastEventId;\n this.headers = {\n Accept: \"text/event-stream\",\n ...options.headers,\n };\n this.requestOptions = {\n body: options.body,\n method: options.method ?? \"GET\",\n mode: options.mode,\n credentials: options.credentials,\n redirect: options.redirect,\n referrer: options.referrer,\n referrerPolicy: options.referrerPolicy,\n };\n this._fetch = options.fetch ?? ((url, init) => fetch(url, init));\n this._onError = this.resolveCallback(options.onError, () => {}, \"onError\");\n this._onConnect = this.resolveCallback(options.onConnect, () => {}, \"onConnect\");\n this._onDisconnect = this.resolveCallback(options.onDisconnect, () => {}, \"onDisconnect\");\n this._onMessage = this.resolveCallback(options.onMessage, () => {}, \"onMessage\");\n this._onComment = this.resolveCallback(options.onComment, () => {}, \"onComment\");\n this._shouldReconnect = this.resolveCallback(\n options.shouldReconnect,\n this.DEFAULT_SHOULD_RECONNECT,\n \"shouldReconnect\",\n );\n this._calculateReconnectDelay = this.resolveCallback(\n options.calculateReconnectDelay,\n this.DEFAULT_CALCULATE_RECONNECT_DELAY,\n \"calculateReconnectDelay\",\n );\n }\n\n public set onError(value: (error: Error) => void) {\n this._onError = this.resolveCallback(value, () => {}, \"onError\");\n }\n\n public set onMessage(value: EventSourceMessageHandler) {\n this._onMessage = this.resolveCallback(value, () => {}, \"onMessage\");\n }\n\n public set onComment(value: EventSourceCommentHandler) {\n this._onComment = this.resolveCallback(value, () => {}, \"onComment\");\n }\n\n public set onConnect(value: () => void) {\n this._onConnect = this.resolveCallback(value, () => {}, \"onConnect\");\n }\n\n public set onDisconnect(value: () => void) {\n this._onDisconnect = this.resolveCallback(value, () => {}, \"onDisconnect\");\n }\n\n public set shouldReconnect(value: (state: ReconnectionState) => boolean) {\n this._shouldReconnect = this.resolveCallback(value, this.DEFAULT_SHOULD_RECONNECT, \"shouldReconnect\");\n }\n\n public set calculateReconnectDelay(value: (state: ReconnectionState) => number) {\n this._calculateReconnectDelay = this.resolveCallback(\n value,\n this.DEFAULT_CALCULATE_RECONNECT_DELAY,\n \"shouldReconnect\",\n );\n }\n\n public get readyState(): ReadyState {\n return this._readyState;\n }\n\n public connect() {\n if (this._readyState !== ReadyState.CLOSED) {\n return;\n }\n\n this.reconnectAttempt = 0;\n this.initiateConnection();\n }\n\n public close() {\n clearTimeout(this.reconnectTimeout);\n this._readyState = ReadyState.CLOSED;\n this.abortController.abort();\n }\n\n private initiateConnection() {\n this.abortController = new AbortController();\n this._readyState = ReadyState.CONNECTING;\n\n this._fetch(this.url, {\n ...this.requestOptions,\n headers: this.buildRequestHeaders(),\n signal: this.abortController.signal,\n cache: \"no-store\",\n })\n .then((response) => this.handleFetchResponse(response))\n .catch((error: Error) => {\n if (error.name === \"AbortError\" || this.abortController.signal.aborted) {\n this.close();\n return;\n }\n\n this._onError(error);\n this.scheduleReconnect({ error });\n });\n }\n\n private async handleFetchResponse(response: Response) {\n const { body, status } = response;\n\n if (status === 204) {\n this.disconnected();\n return;\n }\n\n if (status >= 400) {\n this.scheduleReconnect({ status });\n return;\n }\n\n if (!body) {\n throw new MissingResponseBodyError(\"The server response did not have a body\");\n }\n\n this._readyState = ReadyState.OPEN;\n this._onConnect();\n this.reconnectAttempt = 0;\n\n const parser = new EventSourceParser({\n onEvent: (event) => {\n if (typeof event.id === \"string\") {\n this.lastEventId = event.id;\n }\n this._onMessage(event);\n },\n onComment: (comment) => this._onComment(comment),\n onRetry: (retryDelay) => (this.serverReconnectionTime = retryDelay),\n });\n\n await this.readResponseStream(body, parser);\n\n this.scheduleReconnect({\n status,\n error: new StreamClosedError(\"The server response stream was closed\"),\n });\n }\n\n private async readResponseStream(body: ReadableStream<Uint8Array>, parser: EventSourceParser) {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n\n let done = false;\n while (!done) {\n const result = await reader.read();\n if (result.value) {\n parser.parse(decoder.decode(result.value, { stream: true }));\n }\n done = result.done;\n }\n\n parser.finish();\n }\n\n private disconnected() {\n clearTimeout(this.reconnectTimeout);\n this._readyState = ReadyState.CLOSED;\n this._onDisconnect();\n }\n\n private buildRequestHeaders(): Record<string, string> {\n const lastEventIdHeader = this.lastEventId ? { \"Last-Event-ID\": this.lastEventId } : undefined;\n return {\n ...this.headers,\n ...lastEventIdHeader,\n };\n }\n\n private scheduleReconnect(state: { status?: number; error?: Error }) {\n if (this.abortController.signal.aborted) {\n return;\n }\n\n clearTimeout(this.reconnectTimeout);\n this.reconnectAttempt += 1;\n\n const reconnectionState: ReconnectionState = {\n attempt: this.reconnectAttempt,\n status: state.status,\n error: state.error,\n serverReconnectionTime: this.serverReconnectionTime,\n };\n\n if (!this._shouldReconnect(reconnectionState)) {\n this.disconnected();\n return;\n }\n\n this._readyState = ReadyState.CONNECTING;\n let delay = this._calculateReconnectDelay(reconnectionState);\n\n if (!Number.isFinite(delay) || delay < 1 || delay > 1_000 * 60 * 60) {\n this._onError(\n new ValueOutOfRangeError(\n `The calculated reconnect delay is out of range: ${delay}. Defaulting to ${this.serverReconnectionTime}`,\n ),\n );\n delay = this.serverReconnectionTime;\n }\n\n this.reconnectTimeout = setTimeout(() => this.initiateConnection(), delay);\n }\n\n private resolveCallback<T extends (...args: any[]) => any>(\n provided: unknown,\n fallback: T,\n optionName: string,\n ): T {\n if (provided == null) return fallback;\n if (typeof provided !== \"function\") {\n throw new InvalidOptionError(`${optionName} must be a function`);\n }\n return provided as T;\n }\n}\n","import { EventSourceClient } from \"./EventSourceClient\";\nimport { type EventSourceClientOptions } from \"./types\";\n\nexport const createEventSourceClient = (options: EventSourceClientOptions) => {\n return new EventSourceClient(options);\n};\n","import type {\n ConfigBundle,\n ConfigDirectorLogger,\n Transport,\n TransportEvents,\n TransportOptions,\n} from \"./types\";\nimport { ConfigDirectorConnectionError } from \"@shared/errors\";\nimport { EventEmitter } from \"node:events\";\nimport { createEventSourceClient } from \"@eventsource/index\";\nimport type { EventSourceClient } from \"@eventsource/EventSourceClient\";\n\nconst MAX_EXPONENTIAL_DELAY = 9; // 2^9 = 512 seconds, to cap it to under 10min\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 const eventSourcePromise = new Promise<this>((resolve, reject) => {\n this.eventSource = createEventSourceClient({\n url: this.url,\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 this.logger.debug(\"[StreamingTransport] Connected\");\n resolve(this);\n },\n shouldReconnect: (state) => {\n const reconnect = !this.isStatusFatal(state.status);\n if (!reconnect) {\n reject(this.prepareFatalError(state.status, state.error));\n }\n return reconnect;\n },\n calculateReconnectDelay: (state) => {\n const seconds = Math.pow(2, Math.min(state.attempt, MAX_EXPONENTIAL_DELAY));\n const delay = seconds * 1_000;\n const logMessage = `[StreamingTransport] Scheduling reconnect attempt #${state.attempt} in ${delay}ms.`;\n if (state.attempt <= 5) {\n this.logger.info(logMessage);\n } else {\n this.logger.warn(logMessage);\n }\n return delay;\n },\n onDisconnect: () => {\n this.logger.debug(\"[StreamingTransport] Disconnected\");\n },\n });\n this.eventSource.connect();\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(\"[StreamingTransport] Error parsing and dispatching config data update: \", error);\n }\n }\n\n private prepareFatalError(\n responseStatus: number | undefined,\n error: Error | undefined,\n ): ConfigDirectorConnectionError {\n const status = responseStatus ?? 0;\n const headline = `Connection failed with status: ${responseStatus ?? \"unknown\"}.`;\n const errorLine = error ? ` Error: ${error}.` : \"\";\n const message = `${headline}${errorLine} 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 { ConfigDirectorConnectionError } from \"../errors\";\n\nexport abstract class AbstractPollingTransport {\n protected fatalError = false;\n protected pollingInterval: ReturnType<typeof setInterval> | undefined;\n protected pollingIntervalSeconds: number = 60;\n\n protected clearPollingInterval() {\n clearInterval(this.pollingInterval);\n }\n\n protected schedulePollingInterval(callback: () => void) {\n clearInterval(this.pollingInterval);\n if (this.pollingIntervalSeconds <= 0) {\n return;\n }\n this.pollingInterval = setInterval(callback, this.pollingIntervalSeconds * 1_000);\n }\n\n protected async handleNonOkResponse(response: Response) {\n if (!response.ok) {\n if (this.isStatusFatal(response.status)) {\n this.fatalError = true;\n throw this.prepareFatalResponseStatusError(response.status, await response.text());\n } else {\n throw new ConfigDirectorConnectionError(\n `Connection failed with status: ${response.status}`,\n response.status,\n );\n }\n }\n }\n\n protected handleFetchError(fetchError: unknown) {\n if (this.isFetchErrorFatal(fetchError)) {\n this.fatalError = true;\n this.close();\n throw new ConfigDirectorConnectionError(\n `Connection failed with fatal error: ${fetchError}. This is an unrecoverable error, retry attempts will be ignored.`,\n );\n } else if (fetchError instanceof SyntaxError) {\n throw new ConfigDirectorConnectionError(`Failed to parse the response from the server: ${fetchError}`);\n } else {\n throw new ConfigDirectorConnectionError(`Connection failed with error: ${fetchError}.`);\n }\n }\n\n protected prepareFatalResponseStatusError(\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, retry attempts will be ignored.`;\n return new ConfigDirectorConnectionError(message, status);\n }\n\n protected isStatusFatal(status: number | undefined): boolean {\n return !!status && status >= 400 && status < 500;\n }\n\n protected isFetchErrorFatal(fetchError: unknown): boolean {\n if ((fetchError as any)?.name === \"NotAllowedError\") {\n return true;\n } else if (fetchError instanceof TypeError) {\n return true;\n }\n return false;\n }\n\n public get isConnected(): boolean {\n return this.pollingInterval !== undefined;\n }\n\n public close() {\n clearInterval(this.pollingInterval);\n this.pollingInterval = undefined;\n }\n\n public abstract clear(): void;\n\n public dispose(): void {\n this.close();\n this.clear();\n }\n}\n","import { type ConfigDirectorLogger } from \"./types\";\nimport type { UrlLike } from \"./url\";\n\nexport const fetchWithTimeout = async (\n timeout: number,\n resource: string | UrlLike | 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(typeof resource === \"string\" || resource instanceof Request ? resource : resource.toString(), {\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 type {\n ConfigDirectorLogger,\n Transport,\n TransportEvents,\n TransportOptions,\n} from \"./types\";\nimport { AbstractPollingTransport } from \"@shared/transport/AbstractPollingTransport\";\nimport { EventEmitter } from \"node:events\";\nimport { fetchWithTimeout } from \"@shared/fetchWithTimeout\";\n\nexport class PollingTransport extends AbstractPollingTransport implements Transport {\n private logger: ConfigDirectorLogger;\n private eventEmitter = new EventEmitter();\n private url: URL;\n private lastUpdateTimestamp: string | undefined;\n\n constructor(private readonly options: TransportOptions) {\n super();\n this.options = options;\n this.logger = options.logger;\n this.url = new URL(\"server/polling/v1\", options.baseUrl);\n this.pollingIntervalSeconds = options.pollingInterval ?? 60;\n }\n\n public async connect(timeout: number): Promise<this> {\n this.clearPollingInterval();\n\n await this.fetchConfigs(timeout);\n\n this.schedulePollingInterval(() => {\n void this.fetchConfigs(timeout).catch((error) => {\n this.logger.error(\"[PollingTransport] Error during polling:\", error);\n });\n });\n\n return this;\n }\n\n private async fetchConfigs(timeout: number) {\n if (this.fatalError) {\n this.logger.warn(\n \"[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.\",\n );\n return;\n }\n\n try {\n const response = await fetchWithTimeout(\n timeout,\n this.url,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n metaContext: this.options.metaContext,\n serverSdkKey: this.options.serverSdkKey,\n lastUpdateTimestamp: this.lastUpdateTimestamp,\n }),\n },\n this.logger,\n );\n\n await this.handleNonOkResponse(response);\n\n if (response.status === 200) {\n const json = JSON.parse(await response.text());\n this.lastUpdateTimestamp = json.timestamp;\n this.eventEmitter.emit(\"configBundleReceived\", json);\n }\n } catch (fetchError) {\n this.handleFetchError(fetchError);\n }\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","import type { TransportOptions } from \"./types\";\nimport { PollingTransport } from \"./PollingTransport\";\n\nexport class OneTimeTransport extends PollingTransport {\n constructor(options: TransportOptions) {\n super(options);\n this.pollingIntervalSeconds = 0;\n }\n}\n","import type { 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 type { 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 type { 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 type { 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 type { 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 type { 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 type {\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 = \"string\" | \"number\" | \"bigint\" | \"boolean\" | \"symbol\" | \"undefined\" | \"object\" | \"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 (configState.type === \"json\") {\n return parseJson(value, defaultValue, requestedType);\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 (\n isNumericNativeType(typeof defaultValue) &&\n (configState.type === \"float\" || configState.type === \"enum\")\n ) {\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\nconst parseJson = <T extends ConfigValueType>(\n jsonString: string,\n defaultValue: T,\n requestedType: string,\n): ParseResult<T> => {\n if (typeof defaultValue === \"string\") {\n return {\n parsedValue: jsonString as T,\n requestedType,\n usedDefault: false,\n reason: \"found-match\",\n };\n }\n\n try {\n return {\n parsedValue: JSON.parse(jsonString),\n requestedType,\n usedDefault: false,\n reason: \"found-match\",\n };\n } catch {\n return {\n parsedValue: defaultValue,\n requestedType,\n usedDefault: true,\n reason: \"invalid-json\",\n };\n }\n};\n","import type { 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 protected 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 type { 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 { ConfigDirectorValidationError } from \"./errors\";\n\nexport interface UrlLike {\n toString(): string;\n}\n\nexport type UrlFactory = (input: string, base?: UrlLike) => UrlLike;\n\nexport const defaultUrlFactory: UrlFactory = (input, base) => new URL(input, base?.toString());\n\nexport const 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 URL '${url}'. Parsing failed: ${error}`);\n }\n};\n","import type { ConfigType, ConfigValueType } from \"../types\";\nimport { type DroppedEvents } from \"./types\";\n\nconst CONFIG_VALUE_MAX_LENGTH = 500;\n\nexport type TelemetryValue = {\n value?: string;\n digest?: string;\n};\n\nexport const sanitizeValue = <TV extends ConfigValueType>(value: TV, type?: ConfigType): TelemetryValue => {\n if (type === \"json\" || (type == null && typeof value === \"object\")) {\n try {\n const json = JSON.stringify(value);\n return { digest: djb2Hash(json) };\n } catch {\n return { digest: value.toString() };\n }\n }\n\n if (value.toString().length > CONFIG_VALUE_MAX_LENGTH) {\n return { digest: djb2Hash(value.toString()) };\n } else {\n return { value: value.toString() };\n }\n};\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] ?? 0);\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 { isDroppedEventsEmpty, isEventListEmpty } from \"@shared/telemetry/utils\";\nimport type { ConfigDirectorLogger } from \"../types\";\nimport type { EventReport, EventReporter, EventReporterPayload, ReporterResponse } from \"./types\";\nimport { fetchWithTimeout } from \"@shared/fetchWithTimeout\";\nimport {type UrlLike } from \"@shared/url\";\n\nexport type EventReporterOptions = {\n sdkKey: string;\n logger: ConfigDirectorLogger;\n baseUrl: UrlLike;\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.toString());\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 type { 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 type { 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 type { ConfigDirectorContext, ConfigDirectorLogger, ConfigType, ConfigValueType } from \"../types\";\nimport { EventAggregator } from \"./EventAggregator\";\nimport { EventQueue } from \"./EventQueue\";\nimport type { TelemetryValue } from \"./utils\";\nimport { sanitizeValue } from \"./utils\";\nimport type { EventReporter, ReportableEvent, ReporterResponse } from \"./types\";\nimport type { UrlFactory, UrlLike } from \"../url\";\n\nexport type TelemetryEventCollectorOptions = {\n sdkKey: string;\n logger: ConfigDirectorLogger;\n baseUrl: UrlLike;\n urlFactory: UrlFactory;\n flushIntervalDelay?: number;\n initialFlushIntervalDelay?: number;\n evaluationQueueLimit?: number;\n};\n\nexport abstract class TelemetryEventCollector<T extends ReportableEvent> {\n protected readonly logger: ConfigDirectorLogger;\n protected abstract readonly reporter: EventReporter | undefined;\n protected readonly evaluationEventQueue: EventQueue<T>;\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.evaluationEventQueue = new EventQueue(options.evaluationQueueLimit ?? 1_000);\n this.flushIntervalDelay = options.flushIntervalDelay ?? 30_000;\n const initialDelay = options.initialFlushIntervalDelay ?? 5_000;\n this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), initialDelay);\n }\n\n protected sanitizeValue<TV extends ConfigValueType>(value: TV, type?: ConfigType): TelemetryValue {\n return sanitizeValue(value, type);\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 async close() {\n this.collectEvents = false;\n clearTimeout(this.flushTimeout);\n await this.flush();\n this.evaluationEventQueue.clear();\n }\n\n public dispose() {\n this.close();\n }\n}\n","export class LimitedMap<K, V> extends Map<K, V> {\n private readonly limit: number;\n private _droppedCount: number = 0;\n\n constructor(limit: number, entries?: [K, V][]) {\n super(entries);\n this.limit = limit;\n this.enforceLimit();\n }\n\n public override set(key: K, value: V): this {\n super.set(key, value);\n this.enforceLimit();\n return this;\n }\n\n public get droppedCount(): number {\n return this._droppedCount;\n }\n\n public clearAndReset() {\n this.clear();\n this._droppedCount = 0;\n }\n\n private enforceLimit() {\n while (this.size > this.limit) {\n const oldestKey = this.keys().next().value;\n if (oldestKey !== undefined) {\n this.delete(oldestKey);\n this._droppedCount += 1;\n }\n }\n }\n}\n","import type { ConfigDirectorContext, ConfigValueType } from \"../types\";\nimport { ServerEventReporter } from \"./ServerEventReporter\";\nimport type { EvaluatedConfigEvent } from \"./telemetry-events\";\nimport type {\n EvaluatedConfig,\n EventReporter,\n ReporterResponse,\n ServerTelemetryEventCollectorOptions,\n} from \"./types\";\nimport { TelemetryEventCollector } from \"@shared/telemetry/TelemetryEventCollector\";\nimport { LimitedMap } from \"@shared/LimitedMap\";\nimport type { TelemetryValue } from \"@shared/telemetry/utils\";\n\nconst DEFAULT_CONTEXTS_LIMIT = 1_000;\n\nexport class ServerTelemetryEventCollector extends TelemetryEventCollector<\n EvaluatedConfigEvent<TelemetryValue>\n> {\n protected readonly reporter: EventReporter;\n protected _context?: ConfigDirectorContext;\n private readonly contexts: LimitedMap<string, ConfigDirectorContext>;\n\n constructor(options: ServerTelemetryEventCollectorOptions) {\n super(options);\n this.reporter = new ServerEventReporter(options);\n this.contexts = new LimitedMap(options.contextLimit ?? DEFAULT_CONTEXTS_LIMIT);\n }\n\n public evaluatedConfig<T extends ConfigValueType>(payload: EvaluatedConfig<T>): void {\n if (!this.collectEvents) {\n return;\n }\n if (payload.context?.id && !payload.context.anonymous) {\n this.contexts.set(payload.context.id, payload.context);\n }\n const event = {\n ...payload.evaluation,\n contextId: payload.context?.anonymous ? undefined : payload.context?.id,\n };\n\n this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(event));\n }\n\n private sanitizeEvaluatedConfigEvent<T extends ConfigValueType>(\n event: EvaluatedConfigEvent<T>,\n ): EvaluatedConfigEvent<TelemetryValue> {\n return {\n ...event,\n defaultValue: this.sanitizeValue(event.defaultValue, event.type),\n evaluatedValue: this.sanitizeValue(event.evaluatedValue, event.type),\n };\n }\n\n private snapshotContexts() {\n const contexts = Array.from(this.contexts.values());\n const droppedCount = this.contexts.droppedCount;\n this.contexts.clearAndReset();\n return { contexts, droppedCount };\n }\n\n protected override async flush(): Promise<ReporterResponse> {\n if (!this.reporter) {\n return { success: false, fatalError: false };\n }\n const evaluationSnapshot = this.evaluationEventQueue.takeSnapshot();\n const contextsSnapshot = this.snapshotContexts();\n const response = await this.reporter?.report({\n context: this._context,\n discreteEvents: {\n capturedContexts: contextsSnapshot.contexts,\n },\n aggregatedEvents: {\n evaluatedConfig: this.aggregator.aggregate(evaluationSnapshot),\n },\n droppedEvents: {\n evaluatedConfig: evaluationSnapshot.droppedCount,\n capturedContexts: contextsSnapshot.droppedCount,\n },\n });\n return response;\n }\n}\n","import { StreamingTransport } from \"./StreamingTransport\";\nimport { OneTimeTransport } from \"./OneTimeTransport\";\nimport { PollingTransport } from \"./PollingTransport\";\nimport { ConfigEvaluator } from \"@config-evaluator/ConfigEvaluator\";\nimport { getRequestedType, parseConfigValue } from \"@shared/value-parser\";\nimport type {\n ConfigDirectorContext,\n ConfigDirectorClientOptions,\n ConfigDirectorClient,\n ClientEvents,\n WatchHandler,\n ConfigValueType,\n ConfigDirectorLogger,\n Transport,\n IdentifyingSdkOptions,\n ConfigBundle,\n ConfigDefinition,\n ConfigState,\n ConnectionMode,\n} from \"./types\";\nimport { createDefaultLogger } from \"./logger\";\nimport { ConfigDirectorValidationError } from \"@shared/errors\";\nimport EventEmitter from \"node:events\";\nimport type { ConfigDirectorMetaContext } from \"@shared/types\";\nimport { defaultUrlFactory } from \"@shared/url\";\nimport { ServerTelemetryEventCollector } from \"./telemetry\";\n\nconst defaultBaseUrl = new URL(\"https://server-sdk-api.configdirector.com\");\nconst DEFAULT_FLUSH_INTERVAL = 30_000;\nconst DEFAULT_EVENT_QUEUE_LIMIT = 5_000;\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 connectionMode: ConnectionMode;\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.connectionMode = clientOptions?.connection?.mode ?? \"streaming\";\n const transportConstructor = this.getTransportConstructor(this.connectionMode);\n const queueLimit = clientOptions?.telemetry?.eventQueueLimit ?? DEFAULT_EVENT_QUEUE_LIMIT;\n this.usageEventCollector = new ServerTelemetryEventCollector({\n sdkKey: serverSdkKey,\n logger: this.logger,\n baseUrl,\n flushIntervalDelay: clientOptions?.telemetry?.flushInterval ?? DEFAULT_FLUSH_INTERVAL,\n evaluationQueueLimit: Math.ceil(queueLimit * 0.7),\n contextLimit: Math.floor(queueLimit * 0.3),\n urlFactory: defaultUrlFactory,\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 pollingInterval: clientOptions?.connection?.pollingInterval,\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(\n `[ConfigDirectorClient] ConfigSet updated from server with ${configKeys.length} key(s)`,\n { keys: configKeys },\n );\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 =\n this.connectionMode === \"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 getTransportConstructor(mode: ConnectionMode) {\n switch (mode) {\n case \"one-time\":\n return OneTimeTransport;\n case \"polling\":\n return PollingTransport;\n default:\n return StreamingTransport;\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 context,\n evaluation: {\n key: configKey,\n type: configState.type,\n defaultValue: defaultValue,\n requestedType: parseResult.requestedType,\n evaluatedValue: parseResult.parsedValue,\n usedDefault: parseResult.usedDefault,\n evaluationReason: parseResult.reason,\n },\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 context,\n evaluation: {\n key: configKey,\n defaultValue: defaultValue,\n requestedType: getRequestedType(defaultValue),\n evaluatedValue: defaultValue,\n usedDefault: true,\n evaluationReason: \"config-state-missing\",\n },\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 getAllConfigs(options?: {\n context?: ConfigDirectorContext;\n configKeys?: string[];\n }): Record<string, ConfigState> {\n if (!this.configSet) return {};\n\n const entries = Object.entries(this.configSet.configs);\n const filtered = options?.configKeys\n ? entries.filter(([key]) => options.configKeys?.includes(key))\n : entries;\n\n return Object.fromEntries(\n filtered.map(([key, config]) => [\n key,\n this.configEvaluator.evaluate(config, { context: options?.context, metadata: this.metaContext }),\n ]),\n );\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 type { ConfigDirectorLoggingLevel, ConfigDirectorLogMessageDecorator } from \"@shared/types\";\nimport { DefaultConfigDirectorClient } from \"./DefaultConfigDirectorClient\";\nimport type { ConfigDirectorClient, ConfigDirectorClientOptions } from \"./types\";\nimport { createDefaultLogger } from \"./logger\";\n\n/**\n * Instantiates a ConfigDirector client to be used in a server environment. This function returns an\n * uninitialized client and does not make any network calls. You must call {@link ConfigDirectorClient.initialize}\n * before the client is ready to use:\n *\n * @param serverSdkKey string - The ConfigDirector server SDK key. This is a secret value, do not commit it\n * in your source code.\n * @param clientOptions {@link ConfigDirectorClientOptions} - Configuration options for the client (optional).\n * It is recommended that you include the {@link ConfigDirectorClientOptions.metadata} option including your `appName` and `appVersion` in order\n * to use those values in targeting rules.\n * @returns An uninitialized {@link ConfigDirectorClient} instance\n *\n * @example\n *\n * ```\n * import { createClient } from \"@configdirector/server-sdk\";\n *\n * const client = createClient(\"YOUR-SERVER-SDK-KEY\", {\n * metadata: { appName: \"my-awesome-app\", appVersion: \"1.0.0\" }\n * });\n *\n * await client.initialize();\n * ```\n *\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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"}
|
|
1
|
+
{"version":3,"file":"configdirector-server.mjs","names":["EventEmitter","EventEmitter"],"sources":["../../shared/src/errors.ts","../../eventsource/src/errors.ts","../../eventsource/src/EventSourceParser.ts","../../eventsource/src/types.ts","../../eventsource/src/EventSourceClient.ts","../../eventsource/src/index.ts","../src/StreamingTransport.ts","../../shared/src/transport/AbstractPollingTransport.ts","../../shared/src/fetchWithTimeout.ts","../src/PollingTransport.ts","../src/OneTimeTransport.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/url.ts","../../shared/src/telemetry/utils.ts","../src/telemetry/ServerEventReporter.ts","../../shared/src/telemetry/EventAggregator.ts","../../shared/src/telemetry/EventQueue.ts","../../shared/src/telemetry/TelemetryEventCollector.ts","../../shared/src/LimitedMap.ts","../../shared/src/telemetry/EvaluatedConfigEventPreprocessor.ts","../src/telemetry/ServerTelemetryEventCollector.ts","../../shared/src/base62-encoder.ts","../../shared/src/value-id.ts","../src/telemetry/value-id-generator.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\nexport class ConfigDirectorInitializationError extends Error {\n public override readonly name: string = \"ConfigDirectorInitializationError\";\n\n constructor(message: string) {\n super(message);\n\n Object.setPrototypeOf(this, ConfigDirectorInitializationError.prototype);\n }\n}\n","export class StreamClosedError extends Error {\n public override readonly name: string = \"StreamClosedError\";\n\n constructor(message: string) {\n super(message);\n\n Object.setPrototypeOf(this, StreamClosedError.prototype);\n }\n}\n\nexport class MissingResponseBodyError extends Error {\n public override readonly name: string = \"MissingResponseBodyError\";\n\n constructor(message: string) {\n super(message);\n\n Object.setPrototypeOf(this, MissingResponseBodyError.prototype);\n }\n}\n\nexport class InvalidOptionError extends Error {\n public override readonly name: string = \"InvalidOptionError\";\n\n constructor(message: string) {\n super(message);\n\n Object.setPrototypeOf(this, InvalidOptionError.prototype);\n }\n}\n\nexport class ValueOutOfRangeError extends Error {\n public override readonly name: string = \"ValueOutOfRangeError\";\n\n constructor(message: string) {\n super(message);\n\n Object.setPrototypeOf(this, ValueOutOfRangeError.prototype);\n }\n}\n","import {\n type EventSourceParserOptions,\n type EventSourceMessageHandler,\n type EventSourceCommentHandler,\n type EventParserRetryCallback,\n} from \"./types\";\n\ntype ParsedField = {\n field: string;\n value: string;\n};\n\nexport class EventSourceParser {\n private isFirstChunk = true;\n private bufferedInput = \"\";\n private currentEvent: { type?: string; data: string } = { data: \"\" };\n private lastEventId: string | undefined;\n private onEvent: EventSourceMessageHandler;\n private onRetry: EventParserRetryCallback | undefined;\n private onComment: EventSourceCommentHandler | undefined;\n\n constructor(options: EventSourceParserOptions) {\n this.currentEvent = { data: \"\" };\n this.onEvent = options.onEvent ?? (() => {});\n this.onRetry = options.onRetry;\n this.onComment = options.onComment;\n }\n\n public parse(chunk: string): void {\n let input = chunk;\n\n if (this.isFirstChunk) {\n this.isFirstChunk = false;\n if (input.startsWith(\"\\uFEFF\")) {\n input = input.slice(1);\n } else if (input.startsWith(\"\\xEF\\xBB\\xBF\")) {\n input = input.slice(3);\n }\n }\n\n const lines = this.scanLines(this.bufferedInput + input);\n for (const line of lines) {\n this.dispatchLine(line);\n }\n }\n\n // Per the SSE spec, any data still buffered when the stream ends is discarded —\n // an event requires a terminating empty line to be dispatched.\n public finish(): void {\n this.bufferedInput = \"\";\n }\n\n /**\n * Scans `text` character by character to extract complete lines, recognizing\n * CR, LF, and CRLF as line terminators per the SSE spec. Any unterminated\n * trailing text is buffered and prepended to the next chunk.\n */\n private scanLines(text: string): string[] {\n const lines: string[] = [];\n let lineStart = 0;\n let i = 0;\n\n while (i < text.length) {\n const ch = text[i];\n if (ch === \"\\r\" || ch === \"\\n\") {\n lines.push(text.slice(lineStart, i));\n // Consume CRLF as a single terminator rather than two separate lines.\n if (ch === \"\\r\" && text[i + 1] === \"\\n\") {\n i++;\n }\n i++;\n lineStart = i;\n } else {\n i++;\n }\n }\n\n this.bufferedInput = text.slice(lineStart);\n return lines;\n }\n\n private dispatchLine(line: string): void {\n if (line.startsWith(\":\")) {\n this.onComment?.(this.extractValue(line, 1));\n return;\n }\n\n if (line === \"\") {\n this.emitEvent();\n return;\n }\n\n this.applyField(this.parseField(line));\n }\n\n private emitEvent(): void {\n const data = this.currentEvent.data.endsWith(\"\\n\")\n ? this.currentEvent.data.slice(0, -1)\n : this.currentEvent.data;\n\n if (data !== \"\") {\n this.onEvent({ ...this.currentEvent, id: this.lastEventId, data });\n }\n\n this.currentEvent = { data: \"\" };\n }\n\n private applyField(parsed: ParsedField): void {\n switch (parsed.field) {\n case \"event\":\n this.currentEvent.type = parsed.value;\n break;\n case \"data\":\n this.currentEvent.data += parsed.value + \"\\n\";\n break;\n case \"id\":\n // Spec: ignore id values that contain a null character.\n if (!parsed.value.includes(\"\\0\")) {\n this.lastEventId = parsed.value;\n }\n break;\n case \"retry\":\n if (/^\\d+$/.test(parsed.value)) {\n this.onRetry?.(Number.parseInt(parsed.value, 10));\n }\n break;\n default:\n break;\n }\n }\n\n private parseField(line: string): ParsedField {\n const colon = line.indexOf(\":\");\n if (colon === -1) {\n return { field: line, value: \"\" };\n }\n return { field: line.slice(0, colon), value: this.extractValue(line, colon + 1) };\n }\n\n /** Returns the value portion of a line, stripping a single leading space per spec. */\n private extractValue(line: string, from: number): string {\n return line[from] === \" \" ? line.slice(from + 1) : line.slice(from);\n }\n}\n","export type ReconnectionState = {\n attempt: number;\n serverReconnectionTime: number;\n status?: number | undefined;\n error?: Error | undefined;\n};\n\nexport enum ReadyState {\n OPEN = \"open\",\n CLOSED = \"closed\",\n CONNECTING = \"connecting\",\n}\n\nexport type EventSourceClientOptions = {\n url: { toString(): string } | string;\n method?: string;\n headers?: Record<string, string>;\n body?: any;\n mode?: \"cors\" | \"no-cors\" | \"same-origin\";\n credentials?: \"include\" | \"omit\" | \"same-origin\";\n redirect?: \"error\" | \"follow\";\n referrer?: string;\n referrerPolicy?: ReferrerPolicy;\n lastEventId?: string;\n fetch?: (url: string, init: RequestInit) => Promise<Response>;\n onError?: (error: Error) => void;\n onConnect?: () => void;\n onDisconnect?: () => void;\n onMessage?: EventSourceMessageHandler;\n onComment?: EventSourceCommentHandler;\n shouldReconnect?: (state: ReconnectionState) => boolean;\n calculateReconnectDelay?: (state: ReconnectionState) => number;\n};\n\nexport type EventSourceMessage = {\n id?: string | undefined;\n type?: string;\n data: string;\n};\n\nexport type EventSourceMessageHandler = (message: EventSourceMessage) => void;\nexport type EventSourceCommentHandler = (comment: string) => void;\n\nexport type EventParserRetryCallback = (retry: number) => void;\n\nexport type EventSourceParserOptions = {\n onEvent?: EventSourceMessageHandler;\n onRetry?: EventParserRetryCallback;\n onComment?: EventSourceCommentHandler;\n};\n","import {\n InvalidOptionError,\n MissingResponseBodyError,\n StreamClosedError,\n ValueOutOfRangeError,\n} from \"./errors\";\nimport { EventSourceParser } from \"./EventSourceParser\";\nimport {\n type EventSourceClientOptions,\n type EventSourceCommentHandler,\n type EventSourceMessageHandler,\n type ReconnectionState,\n ReadyState,\n} from \"./types\";\n\nexport class EventSourceClient {\n private readonly DEFAULT_SHOULD_RECONNECT = () => true;\n private readonly DEFAULT_CALCULATE_RECONNECT_DELAY = () => this.serverReconnectionTime;\n private readonly url: string;\n private readonly headers?: Record<string, string>;\n private readonly requestOptions;\n private lastEventId?: string;\n private reconnectAttempt: number = 0;\n private abortController: AbortController = new AbortController();\n private reconnectTimeout: ReturnType<typeof setTimeout> | undefined;\n private _readyState: ReadyState = ReadyState.CLOSED;\n private serverReconnectionTime: number = 2_000;\n private _fetch: (url: string, init: RequestInit) => Promise<Response>;\n private _onError: (error: Error) => void;\n private _onMessage: EventSourceMessageHandler;\n private _onComment: EventSourceCommentHandler;\n private _onConnect: () => void;\n private _onDisconnect: () => void;\n private _shouldReconnect: (state: ReconnectionState) => boolean;\n private _calculateReconnectDelay: (state: ReconnectionState) => number;\n\n constructor(options: EventSourceClientOptions) {\n this.url = options.url.toString();\n this.lastEventId = options.lastEventId;\n this.headers = {\n Accept: \"text/event-stream\",\n ...options.headers,\n };\n this.requestOptions = {\n body: options.body,\n method: options.method ?? \"GET\",\n mode: options.mode,\n credentials: options.credentials,\n redirect: options.redirect,\n referrer: options.referrer,\n referrerPolicy: options.referrerPolicy,\n };\n this._fetch = options.fetch ?? ((url, init) => fetch(url, init));\n this._onError = this.resolveCallback(options.onError, () => {}, \"onError\");\n this._onConnect = this.resolveCallback(options.onConnect, () => {}, \"onConnect\");\n this._onDisconnect = this.resolveCallback(options.onDisconnect, () => {}, \"onDisconnect\");\n this._onMessage = this.resolveCallback(options.onMessage, () => {}, \"onMessage\");\n this._onComment = this.resolveCallback(options.onComment, () => {}, \"onComment\");\n this._shouldReconnect = this.resolveCallback(\n options.shouldReconnect,\n this.DEFAULT_SHOULD_RECONNECT,\n \"shouldReconnect\",\n );\n this._calculateReconnectDelay = this.resolveCallback(\n options.calculateReconnectDelay,\n this.DEFAULT_CALCULATE_RECONNECT_DELAY,\n \"calculateReconnectDelay\",\n );\n }\n\n public set onError(value: (error: Error) => void) {\n this._onError = this.resolveCallback(value, () => {}, \"onError\");\n }\n\n public set onMessage(value: EventSourceMessageHandler) {\n this._onMessage = this.resolveCallback(value, () => {}, \"onMessage\");\n }\n\n public set onComment(value: EventSourceCommentHandler) {\n this._onComment = this.resolveCallback(value, () => {}, \"onComment\");\n }\n\n public set onConnect(value: () => void) {\n this._onConnect = this.resolveCallback(value, () => {}, \"onConnect\");\n }\n\n public set onDisconnect(value: () => void) {\n this._onDisconnect = this.resolveCallback(value, () => {}, \"onDisconnect\");\n }\n\n public set shouldReconnect(value: (state: ReconnectionState) => boolean) {\n this._shouldReconnect = this.resolveCallback(value, this.DEFAULT_SHOULD_RECONNECT, \"shouldReconnect\");\n }\n\n public set calculateReconnectDelay(value: (state: ReconnectionState) => number) {\n this._calculateReconnectDelay = this.resolveCallback(\n value,\n this.DEFAULT_CALCULATE_RECONNECT_DELAY,\n \"shouldReconnect\",\n );\n }\n\n public get readyState(): ReadyState {\n return this._readyState;\n }\n\n public connect() {\n if (this._readyState !== ReadyState.CLOSED) {\n return;\n }\n\n this.reconnectAttempt = 0;\n this.initiateConnection();\n }\n\n public close() {\n clearTimeout(this.reconnectTimeout);\n this._readyState = ReadyState.CLOSED;\n this.abortController.abort();\n }\n\n private initiateConnection() {\n this.abortController = new AbortController();\n this._readyState = ReadyState.CONNECTING;\n\n this._fetch(this.url, {\n ...this.requestOptions,\n headers: this.buildRequestHeaders(),\n signal: this.abortController.signal,\n cache: \"no-store\",\n })\n .then((response) => this.handleFetchResponse(response))\n .catch((error: Error) => {\n if (error.name === \"AbortError\" || this.abortController.signal.aborted) {\n this.close();\n return;\n }\n\n this._onError(error);\n this.scheduleReconnect({ error });\n });\n }\n\n private async handleFetchResponse(response: Response) {\n const { body, status } = response;\n\n if (status === 204) {\n this.disconnected();\n return;\n }\n\n if (status >= 400) {\n this.scheduleReconnect({ status });\n return;\n }\n\n if (!body) {\n throw new MissingResponseBodyError(\"The server response did not have a body\");\n }\n\n this._readyState = ReadyState.OPEN;\n this._onConnect();\n this.reconnectAttempt = 0;\n\n const parser = new EventSourceParser({\n onEvent: (event) => {\n if (typeof event.id === \"string\") {\n this.lastEventId = event.id;\n }\n this._onMessage(event);\n },\n onComment: (comment) => this._onComment(comment),\n onRetry: (retryDelay) => (this.serverReconnectionTime = retryDelay),\n });\n\n await this.readResponseStream(body, parser);\n\n this.scheduleReconnect({\n status,\n error: new StreamClosedError(\"The server response stream was closed\"),\n });\n }\n\n private async readResponseStream(body: ReadableStream<Uint8Array>, parser: EventSourceParser) {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n\n let done = false;\n while (!done) {\n const result = await reader.read();\n if (result.value) {\n parser.parse(decoder.decode(result.value, { stream: true }));\n }\n done = result.done;\n }\n\n parser.finish();\n }\n\n private disconnected() {\n clearTimeout(this.reconnectTimeout);\n this._readyState = ReadyState.CLOSED;\n this._onDisconnect();\n }\n\n private buildRequestHeaders(): Record<string, string> {\n const lastEventIdHeader = this.lastEventId ? { \"Last-Event-ID\": this.lastEventId } : undefined;\n return {\n ...this.headers,\n ...lastEventIdHeader,\n };\n }\n\n private scheduleReconnect(state: { status?: number; error?: Error }) {\n if (this.abortController.signal.aborted) {\n return;\n }\n\n clearTimeout(this.reconnectTimeout);\n this.reconnectAttempt += 1;\n\n const reconnectionState: ReconnectionState = {\n attempt: this.reconnectAttempt,\n status: state.status,\n error: state.error,\n serverReconnectionTime: this.serverReconnectionTime,\n };\n\n if (!this._shouldReconnect(reconnectionState)) {\n this.disconnected();\n return;\n }\n\n this._readyState = ReadyState.CONNECTING;\n let delay = this._calculateReconnectDelay(reconnectionState);\n\n if (!Number.isFinite(delay) || delay < 1 || delay > 1_000 * 60 * 60) {\n this._onError(\n new ValueOutOfRangeError(\n `The calculated reconnect delay is out of range: ${delay}. Defaulting to ${this.serverReconnectionTime}`,\n ),\n );\n delay = this.serverReconnectionTime;\n }\n\n this.reconnectTimeout = setTimeout(() => this.initiateConnection(), delay);\n }\n\n private resolveCallback<T extends (...args: any[]) => any>(\n provided: unknown,\n fallback: T,\n optionName: string,\n ): T {\n if (provided == null) return fallback;\n if (typeof provided !== \"function\") {\n throw new InvalidOptionError(`${optionName} must be a function`);\n }\n return provided as T;\n }\n}\n","import { EventSourceClient } from \"./EventSourceClient\";\nimport { type EventSourceClientOptions } from \"./types\";\n\nexport const createEventSourceClient = (options: EventSourceClientOptions) => {\n return new EventSourceClient(options);\n};\n","import type {\n ConfigBundle,\n ConfigDirectorLogger,\n Transport,\n TransportEvents,\n TransportOptions,\n} from \"./types\";\nimport { ConfigDirectorConnectionError } from \"@shared/errors\";\nimport { EventEmitter } from \"node:events\";\nimport { createEventSourceClient } from \"@eventsource/index\";\nimport type { EventSourceClient } from \"@eventsource/EventSourceClient\";\n\nconst MAX_EXPONENTIAL_DELAY = 9; // 2^9 = 512 seconds, to cap it to under 10min\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 const eventSourcePromise = new Promise<this>((resolve, reject) => {\n this.eventSource = createEventSourceClient({\n url: this.url,\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 this.logger.debug(\"[StreamingTransport] Connected\");\n resolve(this);\n },\n shouldReconnect: (state) => {\n const reconnect = !this.isStatusFatal(state.status);\n if (!reconnect) {\n reject(this.prepareFatalError(state.status, state.error));\n }\n return reconnect;\n },\n calculateReconnectDelay: (state) => {\n const seconds = Math.pow(2, Math.min(state.attempt, MAX_EXPONENTIAL_DELAY));\n const delay = seconds * 1_000;\n const logMessage = `[StreamingTransport] Scheduling reconnect attempt #${state.attempt} in ${delay}ms.`;\n if (state.attempt <= 5) {\n this.logger.info(logMessage);\n } else {\n this.logger.warn(logMessage);\n }\n return delay;\n },\n onDisconnect: () => {\n this.logger.debug(\"[StreamingTransport] Disconnected\");\n },\n });\n this.eventSource.connect();\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(\"[StreamingTransport] Error parsing and dispatching config data update: \", error);\n }\n }\n\n private prepareFatalError(\n responseStatus: number | undefined,\n error: Error | undefined,\n ): ConfigDirectorConnectionError {\n const status = responseStatus ?? 0;\n const headline = `Connection failed with status: ${responseStatus ?? \"unknown\"}.`;\n const errorLine = error ? ` Error: ${error}.` : \"\";\n const message = `${headline}${errorLine} 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 { ConfigDirectorConnectionError } from \"../errors\";\n\nexport abstract class AbstractPollingTransport {\n protected fatalError = false;\n protected pollingInterval: ReturnType<typeof setInterval> | undefined;\n protected pollingIntervalSeconds: number = 60;\n\n protected clearPollingInterval() {\n clearInterval(this.pollingInterval);\n }\n\n protected schedulePollingInterval(callback: () => void) {\n clearInterval(this.pollingInterval);\n if (this.pollingIntervalSeconds <= 0) {\n return;\n }\n this.pollingInterval = setInterval(callback, this.pollingIntervalSeconds * 1_000);\n }\n\n protected async handleNonOkResponse(response: Response) {\n if (!response.ok) {\n if (this.isStatusFatal(response.status)) {\n this.fatalError = true;\n throw this.prepareFatalResponseStatusError(response.status, await response.text());\n } else {\n throw new ConfigDirectorConnectionError(\n `Connection failed with status: ${response.status}`,\n response.status,\n );\n }\n }\n }\n\n protected handleFetchError(fetchError: unknown) {\n if (this.isFetchErrorFatal(fetchError)) {\n this.fatalError = true;\n this.close();\n throw new ConfigDirectorConnectionError(\n `Connection failed with fatal error: ${fetchError}. This is an unrecoverable error, retry attempts will be ignored.`,\n );\n } else if (fetchError instanceof SyntaxError) {\n throw new ConfigDirectorConnectionError(`Failed to parse the response from the server: ${fetchError}`);\n } else {\n throw new ConfigDirectorConnectionError(`Connection failed with error: ${fetchError}.`);\n }\n }\n\n protected prepareFatalResponseStatusError(\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, retry attempts will be ignored.`;\n return new ConfigDirectorConnectionError(message, status);\n }\n\n protected isStatusFatal(status: number | undefined): boolean {\n return !!status && status >= 400 && status < 500;\n }\n\n protected isFetchErrorFatal(fetchError: unknown): boolean {\n if ((fetchError as any)?.name === \"NotAllowedError\") {\n return true;\n } else if (fetchError instanceof TypeError) {\n return true;\n }\n return false;\n }\n\n public get isConnected(): boolean {\n return this.pollingInterval !== undefined;\n }\n\n public close() {\n clearInterval(this.pollingInterval);\n this.pollingInterval = undefined;\n }\n\n public abstract clear(): void;\n\n public dispose(): void {\n this.close();\n this.clear();\n }\n}\n","import { type ConfigDirectorLogger } from \"./types\";\nimport type { UrlLike } from \"./url\";\n\nexport const fetchWithTimeout = async (\n timeout: number,\n resource: string | UrlLike | 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(typeof resource === \"string\" || resource instanceof Request ? resource : resource.toString(), {\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 type {\n ConfigDirectorLogger,\n Transport,\n TransportEvents,\n TransportOptions,\n} from \"./types\";\nimport { AbstractPollingTransport } from \"@shared/transport/AbstractPollingTransport\";\nimport { EventEmitter } from \"node:events\";\nimport { fetchWithTimeout } from \"@shared/fetchWithTimeout\";\n\nexport class PollingTransport extends AbstractPollingTransport implements Transport {\n private logger: ConfigDirectorLogger;\n private eventEmitter = new EventEmitter();\n private url: URL;\n private lastUpdateTimestamp: string | undefined;\n\n constructor(private readonly options: TransportOptions) {\n super();\n this.options = options;\n this.logger = options.logger;\n this.url = new URL(\"server/polling/v1\", options.baseUrl);\n this.pollingIntervalSeconds = options.pollingInterval ?? 60;\n }\n\n public async connect(timeout: number): Promise<this> {\n this.clearPollingInterval();\n\n await this.fetchConfigs(timeout);\n\n this.schedulePollingInterval(() => {\n void this.fetchConfigs(timeout).catch((error) => {\n this.logger.error(\"[PollingTransport] Error during polling:\", error);\n });\n });\n\n return this;\n }\n\n private async fetchConfigs(timeout: number) {\n if (this.fatalError) {\n this.logger.warn(\n \"[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.\",\n );\n return;\n }\n\n try {\n const response = await fetchWithTimeout(\n timeout,\n this.url,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n metaContext: this.options.metaContext,\n serverSdkKey: this.options.serverSdkKey,\n lastUpdateTimestamp: this.lastUpdateTimestamp,\n }),\n },\n this.logger,\n );\n\n await this.handleNonOkResponse(response);\n\n if (response.status === 200) {\n const json = JSON.parse(await response.text());\n this.lastUpdateTimestamp = json.timestamp;\n this.eventEmitter.emit(\"configBundleReceived\", json);\n }\n } catch (fetchError) {\n this.handleFetchError(fetchError);\n }\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","import type { TransportOptions } from \"./types\";\nimport { PollingTransport } from \"./PollingTransport\";\n\nexport class OneTimeTransport extends PollingTransport {\n constructor(options: TransportOptions) {\n super(options);\n this.pollingIntervalSeconds = 0;\n }\n}\n","import type { 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 type { 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 type { 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 type { 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 type { 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 type { 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 type {\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 = \"string\" | \"number\" | \"bigint\" | \"boolean\" | \"symbol\" | \"undefined\" | \"object\" | \"function\";\n\ntype ParseResult<T extends ConfigValueType> = {\n parsedValue: T;\n parsedValueId: string | null | undefined;\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 parsedValueId: undefined,\n requestedType,\n usedDefault: true,\n reason: \"value-missing\",\n };\n }\n\n if (configState.type === \"json\") {\n return parseJson(value, defaultValue, requestedType, configState.valueId);\n }\n\n if (typeof defaultValue === \"string\") {\n return {\n parsedValue: value as T,\n parsedValueId: configState.valueId,\n requestedType,\n usedDefault: false,\n reason: \"found-match\",\n };\n }\n\n if (\n typeof defaultValue === \"boolean\" &&\n (configState.type === \"boolean\" || configState.type === \"string\")\n ) {\n const boolValue = parseConfigBoolean(value);\n const hasBoolean = typeof boolValue === \"boolean\";\n return {\n parsedValue: (hasBoolean ? boolValue : defaultValue) as T,\n parsedValueId: hasBoolean ? configState.valueId : undefined,\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 parsedValueId: hasNumber ? configState.valueId : undefined,\n requestedType,\n usedDefault: !hasNumber,\n reason: hasNumber ? \"found-match\" : \"invalid-number\",\n };\n }\n if (\n isNumericNativeType(typeof defaultValue) &&\n (configState.type === \"float\" || configState.type === \"enum\" || configState.type === \"string\")\n ) {\n const numValue = parseConfigFloat(value);\n const hasNumber = typeof numValue === \"number\";\n return {\n parsedValue: (hasNumber ? numValue : defaultValue) as T,\n parsedValueId: hasNumber ? configState.valueId : undefined,\n requestedType,\n usedDefault: !hasNumber,\n reason: hasNumber ? \"found-match\" : \"invalid-number\",\n };\n }\n\n return {\n parsedValue: value as T,\n parsedValueId: configState.valueId,\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\nconst parseJson = <T extends ConfigValueType>(\n jsonString: string,\n defaultValue: T,\n requestedType: string,\n valueId: string | null | undefined,\n): ParseResult<T> => {\n if (typeof defaultValue === \"string\") {\n return {\n parsedValue: jsonString as T,\n parsedValueId: valueId,\n requestedType,\n usedDefault: false,\n reason: \"found-match\",\n };\n }\n\n try {\n return {\n parsedValue: JSON.parse(jsonString),\n parsedValueId: valueId,\n requestedType,\n usedDefault: false,\n reason: \"found-match\",\n };\n } catch {\n return {\n parsedValue: defaultValue,\n parsedValueId: undefined,\n requestedType,\n usedDefault: true,\n reason: \"invalid-json\",\n };\n }\n};\n","import type { 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 protected 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 type { 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 { ConfigDirectorValidationError } from \"./errors\";\n\nexport interface UrlLike {\n toString(): string;\n}\n\nexport type UrlFactory = (input: string, base?: UrlLike) => UrlLike;\n\nexport const defaultUrlFactory: UrlFactory = (input, base) => new URL(input, base?.toString());\n\nexport const 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 URL '${url}'. Parsing failed: ${error}`);\n }\n};\n","import type { ConfigType, ConfigValueType } from \"../types\";\nimport type { ValueIdGenerator, DroppedEvents } from \"./types\";\n\nconst CONFIG_VALUE_MAX_LENGTH = 500;\n\nexport type TelemetryValue = {\n value?: string;\n valueId?: string;\n type?: ConfigType;\n};\n\nexport type TelemetryEvaluationValue<TV extends ConfigValueType> = {\n value: TV;\n valueId?: string | null | undefined;\n type?: ConfigType;\n};\n\nexport const compactTelemetryValue = async (\n tv: TelemetryValue,\n valueIdGenerator: ValueIdGenerator,\n): Promise<TelemetryValue> => {\n if (tv.valueId) {\n return { valueId: tv.valueId };\n }\n if (tv.value) {\n if (tv.value.length <= CONFIG_VALUE_MAX_LENGTH && tv.type != \"json\") {\n return { value: tv.value };\n }\n\n return { valueId: await valueIdGenerator(tv.value) };\n }\n\n return tv;\n};\n\nexport const mapToTelemetryValue = <TV extends ConfigValueType>(\n ev: TelemetryEvaluationValue<TV>,\n): TelemetryValue => {\n if (ev.type === \"json\" || (ev.type == null && typeof ev.value === \"object\")) {\n if (ev.valueId) {\n return { valueId: ev.valueId, type: \"json\" };\n }\n try {\n const json = JSON.stringify(ev.value);\n return { value: json, type: \"json\" };\n } catch {\n return { value: ev.value.toString(), type: \"json\" };\n }\n }\n\n const stringValue = ev.value?.toString();\n if (stringValue && stringValue.length <= CONFIG_VALUE_MAX_LENGTH) {\n return { value: stringValue };\n }\n\n return ev.valueId ? { valueId: ev.valueId } : { value: stringValue };\n};\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] ?? 0);\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 { isDroppedEventsEmpty, isEventListEmpty } from \"@shared/telemetry/utils\";\nimport type { ConfigDirectorLogger } from \"../types\";\nimport type { EventReport, EventReporter, EventReporterPayload, ReporterResponse } from \"./types\";\nimport { fetchWithTimeout } from \"@shared/fetchWithTimeout\";\nimport {type UrlLike } from \"@shared/url\";\n\nexport type EventReporterOptions = {\n sdkKey: string;\n logger: ConfigDirectorLogger;\n baseUrl: UrlLike;\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.toString());\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 type { 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 type { 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 type { ConfigDirectorContext, ConfigDirectorLogger } from \"../types\";\nimport { EventAggregator } from \"./EventAggregator\";\nimport { EventQueue } from \"./EventQueue\";\nimport type { EventReporter, EventSnapshotPreprocessor, ReportableEvent, ReporterResponse, ValueIdGenerator } from \"./types\";\nimport type { UrlFactory, UrlLike } from \"../url\";\n\nexport type TelemetryEventCollectorOptions = {\n sdkKey: string;\n logger: ConfigDirectorLogger;\n baseUrl: UrlLike;\n urlFactory: UrlFactory;\n valueIdGenerator: ValueIdGenerator;\n flushIntervalDelay?: number;\n initialFlushIntervalDelay?: number;\n evaluationQueueLimit?: number;\n};\n\nexport abstract class TelemetryEventCollector<T extends ReportableEvent> {\n protected readonly logger: ConfigDirectorLogger;\n protected abstract readonly reporter: EventReporter | undefined;\n protected readonly evaluationEventQueue: EventQueue<T>;\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 protected abstract evaluationEventSnapshotPreprocessor: EventSnapshotPreprocessor<T>;\n\n constructor(options: TelemetryEventCollectorOptions) {\n this.logger = options.logger;\n this.evaluationEventQueue = new EventQueue(options.evaluationQueueLimit ?? 1_000);\n this.flushIntervalDelay = options.flushIntervalDelay ?? 30_000;\n const initialDelay = options.initialFlushIntervalDelay ?? 5_000;\n this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), initialDelay);\n }\n\n protected abstract flush(): Promise<ReporterResponse>;\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 cancelScheduledFlush() {\n clearTimeout(this.flushTimeout);\n }\n\n public async close() {\n this.collectEvents = false;\n clearTimeout(this.flushTimeout);\n await this.flush();\n this.evaluationEventQueue.clear();\n }\n\n public dispose() {\n this.close();\n }\n}\n","export class LimitedMap<K, V> extends Map<K, V> {\n private readonly limit: number;\n private _droppedCount: number = 0;\n\n constructor(limit: number, entries?: [K, V][]) {\n super(entries);\n this.limit = limit;\n this.enforceLimit();\n }\n\n public override set(key: K, value: V): this {\n super.set(key, value);\n this.enforceLimit();\n return this;\n }\n\n public get droppedCount(): number {\n return this._droppedCount;\n }\n\n public clearAndReset() {\n this.clear();\n this._droppedCount = 0;\n }\n\n private enforceLimit() {\n while (this.size > this.limit) {\n const oldestKey = this.keys().next().value;\n if (oldestKey !== undefined) {\n this.delete(oldestKey);\n this._droppedCount += 1;\n }\n }\n }\n}\n","import type { EvaluatedConfigEvent } from \"./telemetry-events\";\nimport type { EventQueueSnapshot, EventSnapshotPreprocessor, ValueIdGenerator } from \"./types\";\nimport type { TelemetryValue } from \"./utils\";\nimport { compactTelemetryValue } from \"./utils\";\n\nexport class EvaluatedConfigEventPreprocessor implements EventSnapshotPreprocessor<\n EvaluatedConfigEvent<TelemetryValue>\n> {\n constructor(private readonly valueIdGenerator: ValueIdGenerator) {\n this.valueIdGenerator = valueIdGenerator;\n }\n\n async process(\n snapshot: EventQueueSnapshot<EvaluatedConfigEvent<TelemetryValue>>,\n ): Promise<EventQueueSnapshot<EvaluatedConfigEvent<TelemetryValue>>> {\n const events = await Promise.all(\n snapshot.events.map(async (e) => {\n return {\n ...e,\n defaultValue: await compactTelemetryValue(e.defaultValue, this.valueIdGenerator),\n evaluatedValue: await compactTelemetryValue(e.evaluatedValue, this.valueIdGenerator),\n };\n }),\n );\n\n return { ...snapshot, events };\n }\n}\n","import type { ConfigDirectorContext, ConfigValueType } from \"../types\";\nimport { ServerEventReporter } from \"./ServerEventReporter\";\nimport type { EvaluatedConfigEvent } from \"@shared/telemetry/telemetry-events\";\nimport type {\n EvaluatedConfig,\n EventReporter,\n EventSnapshotPreprocessor,\n ReporterResponse,\n ServerTelemetryEventCollectorOptions,\n} from \"./types\";\nimport { TelemetryEventCollector } from \"@shared/telemetry/TelemetryEventCollector\";\nimport { LimitedMap } from \"@shared/LimitedMap\";\nimport { mapToTelemetryValue, type TelemetryValue } from \"@shared/telemetry/utils\";\nimport { EvaluatedConfigEventPreprocessor } from \"@shared/telemetry/EvaluatedConfigEventPreprocessor\";\n\nconst DEFAULT_CONTEXTS_LIMIT = 1_000;\n\nexport class ServerTelemetryEventCollector extends TelemetryEventCollector<\n EvaluatedConfigEvent<TelemetryValue>\n> {\n protected override evaluationEventSnapshotPreprocessor: EventSnapshotPreprocessor<\n EvaluatedConfigEvent<TelemetryValue>\n >;\n protected readonly reporter: EventReporter;\n protected _context?: ConfigDirectorContext;\n private readonly contexts: LimitedMap<string, ConfigDirectorContext>;\n\n constructor(options: ServerTelemetryEventCollectorOptions) {\n super(options);\n this.reporter = new ServerEventReporter(options);\n this.contexts = new LimitedMap(options.contextLimit ?? DEFAULT_CONTEXTS_LIMIT);\n this.evaluationEventSnapshotPreprocessor = new EvaluatedConfigEventPreprocessor(options.valueIdGenerator);\n }\n\n public evaluatedConfig<T extends ConfigValueType>(payload: EvaluatedConfig<T>): void {\n if (!this.collectEvents) {\n return;\n }\n if (payload.context?.id && !payload.context.anonymous) {\n this.contexts.set(payload.context.id, payload.context);\n }\n const event = {\n ...payload.evaluation,\n contextId: payload.context?.anonymous ? undefined : payload.context?.id,\n };\n\n this.evaluationEventQueue.push(this.sanitizeEvaluatedConfigEvent(event));\n }\n\n private sanitizeEvaluatedConfigEvent<T extends ConfigValueType>(\n event: EvaluatedConfigEvent<T>,\n ): EvaluatedConfigEvent<TelemetryValue> {\n return {\n ...event,\n defaultValue: mapToTelemetryValue({ value: event.defaultValue, type: event.type }),\n evaluatedValue: mapToTelemetryValue({\n value: event.evaluatedValue,\n valueId: event.evaluatedValueId,\n type: event.type,\n }),\n };\n }\n\n private snapshotContexts() {\n const contexts = Array.from(this.contexts.values());\n const droppedCount = this.contexts.droppedCount;\n this.contexts.clearAndReset();\n return { contexts, droppedCount };\n }\n\n protected override async flush(): Promise<ReporterResponse> {\n if (!this.reporter) {\n return { success: false, fatalError: false };\n }\n const evaluationSnapshot = await this.evaluationEventSnapshotPreprocessor.process(\n this.evaluationEventQueue.takeSnapshot(),\n );\n const contextsSnapshot = this.snapshotContexts();\n const response = await this.reporter?.report({\n context: this._context,\n discreteEvents: {\n capturedContexts: contextsSnapshot.contexts,\n },\n aggregatedEvents: {\n evaluatedConfig: this.aggregator.aggregate(evaluationSnapshot),\n },\n droppedEvents: {\n evaluatedConfig: evaluationSnapshot.droppedCount,\n capturedContexts: contextsSnapshot.droppedCount,\n },\n });\n return response;\n }\n}\n","const BASE62 = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n\nexport const toBase62 = (buf: Uint8Array, length: number): string => {\n const ZERO = BigInt(0);\n const BASE = BigInt(62);\n const hex = Array.from(buf)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n let n = BigInt(\"0x\" + hex);\n let result = \"\";\n while (n > ZERO) {\n result = BASE62[Number(n % BASE)] + result;\n n /= BASE;\n }\n return result.padStart(length, \"0\");\n};\n","export const DIGEST_BYTES = 16;\nexport const BASE62_LENGTH = Math.ceil((DIGEST_BYTES * 8) / Math.log2(62));\n","import { createHash } from \"node:crypto\";\nimport { toBase62 } from \"@shared/base62-encoder\";\nimport { DIGEST_BYTES, BASE62_LENGTH } from \"@shared/value-id\";\n\nexport const generateValueId = async (v: string | number | boolean | null | undefined): Promise<string> => {\n const hash = createHash(\"sha256\")\n .update(v?.toString() ?? \"\")\n .digest();\n return toBase62(hash.subarray(0, DIGEST_BYTES), BASE62_LENGTH);\n};\n","import { StreamingTransport } from \"./StreamingTransport\";\nimport { OneTimeTransport } from \"./OneTimeTransport\";\nimport { PollingTransport } from \"./PollingTransport\";\nimport { ConfigEvaluator } from \"@config-evaluator/ConfigEvaluator\";\nimport { getRequestedType, parseConfigValue } from \"@shared/value-parser\";\nimport type {\n ConfigDirectorContext,\n ConfigDirectorClientOptions,\n ConfigDirectorClient,\n ClientEvents,\n WatchHandler,\n ConfigValueType,\n ConfigDirectorLogger,\n Transport,\n IdentifyingSdkOptions,\n ConfigBundle,\n ConfigDefinition,\n ConfigState,\n ConnectionMode,\n ConfigEvaluation,\n HookHandler,\n} from \"./types\";\nimport { createDefaultLogger } from \"./logger\";\nimport { ConfigDirectorValidationError } from \"@shared/errors\";\nimport EventEmitter from \"node:events\";\nimport type { ConfigDirectorMetaContext, EvaluationReason } from \"@shared/types\";\nimport { defaultUrlFactory } from \"@shared/url\";\nimport { ServerTelemetryEventCollector } from \"./telemetry\";\nimport { generateValueId } from \"./telemetry/value-id-generator\";\n\nconst defaultBaseUrl = new URL(\"https://server-sdk-api.configdirector.com\");\nconst DEFAULT_FLUSH_INTERVAL = 30_000;\nconst DEFAULT_EVENT_QUEUE_LIMIT = 5_000;\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 connectionMode: ConnectionMode;\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.connectionMode = clientOptions?.connection?.mode ?? \"streaming\";\n const transportConstructor = this.getTransportConstructor(this.connectionMode);\n const queueLimit = clientOptions?.telemetry?.eventQueueLimit ?? DEFAULT_EVENT_QUEUE_LIMIT;\n this.usageEventCollector = new ServerTelemetryEventCollector({\n sdkKey: serverSdkKey,\n logger: this.logger,\n baseUrl,\n flushIntervalDelay: clientOptions?.telemetry?.flushInterval ?? DEFAULT_FLUSH_INTERVAL,\n evaluationQueueLimit: Math.ceil(queueLimit * 0.7),\n contextLimit: Math.floor(queueLimit * 0.3),\n urlFactory: defaultUrlFactory,\n valueIdGenerator: generateValueId,\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 pollingInterval: clientOptions?.connection?.pollingInterval,\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(\n `[ConfigDirectorClient] ConfigSet updated from server with ${configKeys.length} key(s)`,\n { keys: configKeys },\n );\n });\n\n this.registerHandler(\"clientReady\", clientOptions?.hooks?.clientReady);\n this.registerHandler(\"configEvaluated\", clientOptions?.hooks?.configEvaluated);\n this.registerHandler(\"configsUpdated\", clientOptions?.hooks?.configsUpdated);\n }\n\n private registerHandler<T extends keyof ClientEvents>(\n event: T,\n handler: HookHandler<T> | HookHandler<T>[] | undefined,\n ) {\n if (!handler) {\n return;\n }\n\n const handlers = Array.isArray(handler) ? handler : [handler];\n for (const h of handlers) {\n if (typeof h === \"function\") {\n this.on(event, h);\n }\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 =\n this.connectionMode === \"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 getTransportConstructor(mode: ConnectionMode) {\n switch (mode) {\n case \"one-time\":\n return OneTimeTransport;\n case \"polling\":\n return PollingTransport;\n default:\n return StreamingTransport;\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 context,\n evaluation: {\n key: configKey,\n type: configState.type,\n defaultValue: defaultValue,\n requestedType: parseResult.requestedType,\n evaluatedValue: parseResult.parsedValue,\n evaluatedValueId: parseResult.parsedValueId,\n usedDefault: parseResult.usedDefault,\n evaluationReason: parseResult.reason,\n },\n });\n this.dispatchEvaluationEvent({\n key: configKey,\n value: parseResult.parsedValue,\n valueId: parseResult.parsedValueId ?? undefined,\n isDefaultValue: parseResult.usedDefault,\n reason: parseResult.reason,\n context: context,\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 const reason: EvaluationReason = this.isReady ? \"config-state-missing\" : \"client-not-ready\";\n this.usageEventCollector.evaluatedConfig({\n context,\n evaluation: {\n key: configKey,\n defaultValue: defaultValue,\n requestedType: getRequestedType(defaultValue),\n evaluatedValue: defaultValue,\n usedDefault: true,\n evaluationReason: reason,\n },\n });\n this.dispatchEvaluationEvent({\n key: configKey,\n value: defaultValue,\n isDefaultValue: true,\n reason,\n context: context,\n });\n return defaultValue;\n }\n }\n\n private dispatchEvaluationEvent(event: ConfigEvaluation) {\n setTimeout(() => this.eventEmitter.emit(\"configEvaluated\", { evaluation: event }), 0);\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 getAllConfigs(options?: {\n context?: ConfigDirectorContext;\n configKeys?: string[];\n }): Record<string, ConfigState> {\n if (!this.configSet) return {};\n\n const entries = Object.entries(this.configSet.configs);\n const filtered = options?.configKeys\n ? entries.filter(([key]) => options.configKeys?.includes(key))\n : entries;\n\n return Object.fromEntries(\n filtered.map(([key, config]) => [\n key,\n this.configEvaluator.evaluate(config, { context: options?.context, metadata: this.metaContext }),\n ]),\n );\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 type { ConfigDirectorLoggingLevel, ConfigDirectorLogMessageDecorator } from \"@shared/types\";\nimport { DefaultConfigDirectorClient } from \"./DefaultConfigDirectorClient\";\nimport type { ConfigDirectorClient, ConfigDirectorClientOptions } from \"./types\";\nimport { createDefaultLogger } from \"./logger\";\n\n/**\n * Instantiates a ConfigDirector client to be used in a server environment. This function returns an\n * uninitialized client and does not make any network calls. You must call {@link ConfigDirectorClient.initialize}\n * before the client is ready to use:\n *\n * @param serverSdkKey string - The ConfigDirector server SDK key. This is a secret value, do not commit it\n * in your source code.\n * @param clientOptions {@link ConfigDirectorClientOptions} - Configuration options for the client (optional).\n * It is recommended that you include the {@link ConfigDirectorClientOptions.metadata} option including your `appName` and `appVersion` in order\n * to use those values in targeting rules.\n * @returns An uninitialized {@link ConfigDirectorClient} instance\n *\n * @example\n *\n * ```\n * import { createClient } from \"@configdirector/server-sdk\";\n *\n * const client = createClient(\"YOUR-SERVER-SDK-KEY\", {\n * metadata: { appName: \"my-awesome-app\", appVersion: \"1.0.0\" }\n * });\n *\n * await client.initialize();\n * ```\n *\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":"mNAAA,IAAa,EAAb,MAAa,UAAsC,KAAM,CAIvD,YAAY,EAAiB,EAAiB,CAC5C,MAAM,EAAQ,CAJhB,KAAyB,KAAe,gCAKtC,KAAK,OAAS,EAEd,OAAO,eAAe,KAAM,EAA8B,UAAU,GAI3D,EAAb,MAAa,UAAsC,KAAM,CAGvD,YAAY,EAAiB,CAC3B,MAAM,EAAQ,CAHhB,KAAyB,KAAe,gCAKtC,OAAO,eAAe,KAAM,EAA8B,UAAU,GClB3D,EAAb,MAAa,UAA0B,KAAM,CAG3C,YAAY,EAAiB,CAC3B,MAAM,EAAQ,CAHhB,KAAyB,KAAe,oBAKtC,OAAO,eAAe,KAAM,EAAkB,UAAU,GAI/C,EAAb,MAAa,UAAiC,KAAM,CAGlD,YAAY,EAAiB,CAC3B,MAAM,EAAQ,CAHhB,KAAyB,KAAe,2BAKtC,OAAO,eAAe,KAAM,EAAyB,UAAU,GAItD,EAAb,MAAa,UAA2B,KAAM,CAG5C,YAAY,EAAiB,CAC3B,MAAM,EAAQ,CAHhB,KAAyB,KAAe,qBAKtC,OAAO,eAAe,KAAM,EAAmB,UAAU,GAIhD,EAAb,MAAa,UAA6B,KAAM,CAG9C,YAAY,EAAiB,CAC3B,MAAM,EAAQ,CAHhB,KAAyB,KAAe,uBAKtC,OAAO,eAAe,KAAM,EAAqB,UAAU,GCxBlD,EAAb,KAA+B,CAS7B,YAAY,EAAmC,CAR/C,KAAQ,aAAe,GACvB,KAAQ,cAAgB,GACxB,KAAQ,aAAgD,CAAE,KAAM,GAAI,CAOlE,KAAK,aAAe,CAAE,KAAM,GAAI,CAChC,KAAK,QAAU,EAAQ,cAAkB,IACzC,KAAK,QAAU,EAAQ,QACvB,KAAK,UAAY,EAAQ,UAG3B,MAAa,EAAqB,CAChC,IAAI,EAAQ,EAER,KAAK,eACP,KAAK,aAAe,GAChB,EAAM,WAAW,IAAS,CAC5B,EAAQ,EAAM,MAAM,EAAE,CACb,EAAM,WAAW,MAAe,GACzC,EAAQ,EAAM,MAAM,EAAE,GAI1B,IAAM,EAAQ,KAAK,UAAU,KAAK,cAAgB,EAAM,CACxD,IAAK,IAAM,KAAQ,EACjB,KAAK,aAAa,EAAK,CAM3B,QAAsB,CACpB,KAAK,cAAgB,GAQvB,UAAkB,EAAwB,CACxC,IAAM,EAAkB,EAAE,CACtB,EAAY,EACZ,EAAI,EAER,KAAO,EAAI,EAAK,QAAQ,CACtB,IAAM,EAAK,EAAK,GACZ,IAAO,MAAQ,IAAO;GACxB,EAAM,KAAK,EAAK,MAAM,EAAW,EAAE,CAAC,CAEhC,IAAO,MAAQ,EAAK,EAAI,KAAO;GACjC,IAEF,IACA,EAAY,GAEZ,IAKJ,MADA,MAAK,cAAgB,EAAK,MAAM,EAAU,CACnC,EAGT,aAAqB,EAAoB,CACvC,GAAI,EAAK,WAAW,IAAI,CAAE,CACxB,KAAK,YAAY,KAAK,aAAa,EAAM,EAAE,CAAC,CAC5C,OAGF,GAAI,IAAS,GAAI,CACf,KAAK,WAAW,CAChB,OAGF,KAAK,WAAW,KAAK,WAAW,EAAK,CAAC,CAGxC,WAA0B,CACxB,IAAM,EAAO,KAAK,aAAa,KAAK,SAAS;EAAK,CAC9C,KAAK,aAAa,KAAK,MAAM,EAAG,GAAG,CACnC,KAAK,aAAa,KAElB,IAAS,IACX,KAAK,QAAQ,CAAE,GAAG,KAAK,aAAc,GAAI,KAAK,YAAa,OAAM,CAAC,CAGpE,KAAK,aAAe,CAAE,KAAM,GAAI,CAGlC,WAAmB,EAA2B,CAC5C,OAAQ,EAAO,MAAf,CACE,IAAK,QACH,KAAK,aAAa,KAAO,EAAO,MAChC,MACF,IAAK,OACH,KAAK,aAAa,MAAQ,EAAO,MAAQ;EACzC,MACF,IAAK,KAEE,EAAO,MAAM,SAAS,KAAK,GAC9B,KAAK,YAAc,EAAO,OAE5B,MACF,IAAK,QACC,QAAQ,KAAK,EAAO,MAAM,EAC5B,KAAK,UAAU,OAAO,SAAS,EAAO,MAAO,GAAG,CAAC,CAEnD,MACF,QACE,OAIN,WAAmB,EAA2B,CAC5C,IAAM,EAAQ,EAAK,QAAQ,IAAI,CAI/B,OAHI,IAAU,GACL,CAAE,MAAO,EAAM,MAAO,GAAI,CAE5B,CAAE,MAAO,EAAK,MAAM,EAAG,EAAM,CAAE,MAAO,KAAK,aAAa,EAAM,EAAQ,EAAE,CAAE,CAInF,aAAqB,EAAc,EAAsB,CACvD,OAAO,EAAK,KAAU,IAAM,EAAK,MAAM,EAAO,EAAE,CAAG,EAAK,MAAM,EAAK,GCtIvE,IAAY,EAAL,SAAA,EAAA,OACL,GAAA,KAAA,OACA,EAAA,OAAA,SACA,EAAA,WAAA,mBACD,CCID,IAAa,EAAb,KAA+B,CAqB7B,YAAY,EAAmC,CApB/C,KAAiB,6BAAiC,GAClD,KAAiB,sCAA0C,KAAK,uBAKhE,KAAQ,iBAA2B,EACnC,KAAQ,gBAAmC,IAAI,gBAE/C,KAAQ,YAA0B,EAAW,OAC7C,KAAQ,uBAAiC,IAWvC,KAAK,IAAM,EAAQ,IAAI,UAAU,CACjC,KAAK,YAAc,EAAQ,YAC3B,KAAK,QAAU,CACb,OAAQ,oBACR,GAAG,EAAQ,QACZ,CACD,KAAK,eAAiB,CACpB,KAAM,EAAQ,KACd,OAAQ,EAAQ,QAAU,MAC1B,KAAM,EAAQ,KACd,YAAa,EAAQ,YACrB,SAAU,EAAQ,SAClB,SAAU,EAAQ,SAClB,eAAgB,EAAQ,eACzB,CACD,KAAK,OAAS,EAAQ,SAAW,EAAK,IAAS,MAAM,EAAK,EAAK,EAC/D,KAAK,SAAW,KAAK,gBAAgB,EAAQ,YAAe,GAAI,UAAU,CAC1E,KAAK,WAAa,KAAK,gBAAgB,EAAQ,cAAiB,GAAI,YAAY,CAChF,KAAK,cAAgB,KAAK,gBAAgB,EAAQ,iBAAoB,GAAI,eAAe,CACzF,KAAK,WAAa,KAAK,gBAAgB,EAAQ,cAAiB,GAAI,YAAY,CAChF,KAAK,WAAa,KAAK,gBAAgB,EAAQ,cAAiB,GAAI,YAAY,CAChF,KAAK,iBAAmB,KAAK,gBAC3B,EAAQ,gBACR,KAAK,yBACL,kBACD,CACD,KAAK,yBAA2B,KAAK,gBACnC,EAAQ,wBACR,KAAK,kCACL,0BACD,CAGH,IAAW,QAAQ,EAA+B,CAChD,KAAK,SAAW,KAAK,gBAAgB,MAAa,GAAI,UAAU,CAGlE,IAAW,UAAU,EAAkC,CACrD,KAAK,WAAa,KAAK,gBAAgB,MAAa,GAAI,YAAY,CAGtE,IAAW,UAAU,EAAkC,CACrD,KAAK,WAAa,KAAK,gBAAgB,MAAa,GAAI,YAAY,CAGtE,IAAW,UAAU,EAAmB,CACtC,KAAK,WAAa,KAAK,gBAAgB,MAAa,GAAI,YAAY,CAGtE,IAAW,aAAa,EAAmB,CACzC,KAAK,cAAgB,KAAK,gBAAgB,MAAa,GAAI,eAAe,CAG5E,IAAW,gBAAgB,EAA8C,CACvE,KAAK,iBAAmB,KAAK,gBAAgB,EAAO,KAAK,yBAA0B,kBAAkB,CAGvG,IAAW,wBAAwB,EAA6C,CAC9E,KAAK,yBAA2B,KAAK,gBACnC,EACA,KAAK,kCACL,kBACD,CAGH,IAAW,YAAyB,CAClC,OAAO,KAAK,YAGd,SAAiB,CACX,KAAK,cAAgB,EAAW,SAIpC,KAAK,iBAAmB,EACxB,KAAK,oBAAoB,EAG3B,OAAe,CACb,aAAa,KAAK,iBAAiB,CACnC,KAAK,YAAc,EAAW,OAC9B,KAAK,gBAAgB,OAAO,CAG9B,oBAA6B,CAC3B,KAAK,gBAAkB,IAAI,gBAC3B,KAAK,YAAc,EAAW,WAE9B,KAAK,OAAO,KAAK,IAAK,CACpB,GAAG,KAAK,eACR,QAAS,KAAK,qBAAqB,CACnC,OAAQ,KAAK,gBAAgB,OAC7B,MAAO,WACR,CAAC,CACC,KAAM,GAAa,KAAK,oBAAoB,EAAS,CAAC,CACtD,MAAO,GAAiB,CACvB,GAAI,EAAM,OAAS,cAAgB,KAAK,gBAAgB,OAAO,QAAS,CACtE,KAAK,OAAO,CACZ,OAGF,KAAK,SAAS,EAAM,CACpB,KAAK,kBAAkB,CAAE,QAAO,CAAC,EACjC,CAGN,MAAc,oBAAoB,EAAoB,CACpD,GAAM,CAAE,OAAM,UAAW,EAEzB,GAAI,IAAW,IAAK,CAClB,KAAK,cAAc,CACnB,OAGF,GAAI,GAAU,IAAK,CACjB,KAAK,kBAAkB,CAAE,SAAQ,CAAC,CAClC,OAGF,GAAI,CAAC,EACH,MAAM,IAAI,EAAyB,0CAA0C,CAG/E,KAAK,YAAc,EAAW,KAC9B,KAAK,YAAY,CACjB,KAAK,iBAAmB,EAExB,IAAM,EAAS,IAAI,EAAkB,CACnC,QAAU,GAAU,CACd,OAAO,EAAM,IAAO,WACtB,KAAK,YAAc,EAAM,IAE3B,KAAK,WAAW,EAAM,EAExB,UAAY,GAAY,KAAK,WAAW,EAAQ,CAChD,QAAU,GAAgB,KAAK,uBAAyB,EACzD,CAAC,CAEF,MAAM,KAAK,mBAAmB,EAAM,EAAO,CAE3C,KAAK,kBAAkB,CACrB,SACA,MAAO,IAAI,EAAkB,wCAAwC,CACtE,CAAC,CAGJ,MAAc,mBAAmB,EAAkC,EAA2B,CAC5F,IAAM,EAAS,EAAK,WAAW,CACzB,EAAU,IAAI,YAEhB,EAAO,GACX,KAAO,CAAC,GAAM,CACZ,IAAM,EAAS,MAAM,EAAO,MAAM,CAC9B,EAAO,OACT,EAAO,MAAM,EAAQ,OAAO,EAAO,MAAO,CAAE,OAAQ,GAAM,CAAC,CAAC,CAE9D,EAAO,EAAO,KAGhB,EAAO,QAAQ,CAGjB,cAAuB,CACrB,aAAa,KAAK,iBAAiB,CACnC,KAAK,YAAc,EAAW,OAC9B,KAAK,eAAe,CAGtB,qBAAsD,CACpD,IAAM,EAAoB,KAAK,YAAc,CAAE,gBAAiB,KAAK,YAAa,CAAG,IAAA,GACrF,MAAO,CACL,GAAG,KAAK,QACR,GAAG,EACJ,CAGH,kBAA0B,EAA2C,CACnE,GAAI,KAAK,gBAAgB,OAAO,QAC9B,OAGF,aAAa,KAAK,iBAAiB,CACnC,KAAK,kBAAoB,EAEzB,IAAM,EAAuC,CAC3C,QAAS,KAAK,iBACd,OAAQ,EAAM,OACd,MAAO,EAAM,MACb,uBAAwB,KAAK,uBAC9B,CAED,GAAI,CAAC,KAAK,iBAAiB,EAAkB,CAAE,CAC7C,KAAK,cAAc,CACnB,OAGF,KAAK,YAAc,EAAW,WAC9B,IAAI,EAAQ,KAAK,yBAAyB,EAAkB,EAExD,CAAC,OAAO,SAAS,EAAM,EAAI,EAAQ,GAAK,EAAQ,IAAQ,GAAK,MAC/D,KAAK,SACH,IAAI,EACF,mDAAmD,EAAM,kBAAkB,KAAK,yBACjF,CACF,CACD,EAAQ,KAAK,wBAGf,KAAK,iBAAmB,eAAiB,KAAK,oBAAoB,CAAE,EAAM,CAG5E,gBACE,EACA,EACA,EACG,CACH,GAAI,GAAY,KAAM,OAAO,EAC7B,GAAI,OAAO,GAAa,WACtB,MAAM,IAAI,EAAmB,GAAG,EAAW,qBAAqB,CAElE,OAAO,IC9PX,MAAa,EAA2B,GAC/B,IAAI,EAAkB,EAAQ,CCUvC,IAAa,EAAb,KAAqD,CAMnD,YAAY,EAA4C,CAA3B,KAAA,QAAA,EAH7B,KAAQ,aAAe,IAAIA,EAIzB,KAAK,QAAU,EACf,KAAK,OAAS,EAAQ,OACtB,KAAK,IAAM,IAAI,IAAI,gBAAiB,EAAQ,QAAQ,CAGtD,MAAa,QAAQ,EAAgC,CAC/C,KAAK,aACP,KAAK,OAAO,CAGd,IAAM,EAAqB,IAAI,SAAe,EAAS,IAAW,CAChE,KAAK,YAAc,EAAwB,CACzC,IAAK,KAAK,IACV,OAAQ,OACR,QAAS,CAAE,eAAgB,mBAAoB,CAC/C,KAAM,KAAK,UAAU,CACnB,YAAa,KAAK,QAAQ,YAC1B,aAAc,KAAK,QAAQ,aAC5B,CAAC,CAEF,WAAY,CAAE,UAAW,CACvB,KAAK,gBAAgB,EAAK,EAE5B,cAAiB,CACf,KAAK,OAAO,MAAM,iCAAiC,CACnD,EAAQ,KAAK,EAEf,gBAAkB,GAAU,CAC1B,IAAM,EAAY,CAAC,KAAK,cAAc,EAAM,OAAO,CAInD,OAHK,GACH,EAAO,KAAK,kBAAkB,EAAM,OAAQ,EAAM,MAAM,CAAC,CAEpD,GAET,wBAA0B,GAAU,CAElC,IAAM,EADmB,GAAG,KAAK,IAAI,EAAM,QAAS,EAAsB,CAClD,IAClB,EAAa,sDAAsD,EAAM,QAAQ,MAAM,EAAM,KAMnG,OALI,EAAM,SAAW,EACnB,KAAK,OAAO,KAAK,EAAW,CAE5B,KAAK,OAAO,KAAK,EAAW,CAEvB,GAET,iBAAoB,CAClB,KAAK,OAAO,MAAM,oCAAoC,EAEzD,CAAC,CACF,KAAK,YAAY,SAAS,EAC1B,CACF,OAAO,QAAQ,KAAK,CAClB,EACA,IAAI,QAAe,GAAY,CAC7B,eAAiB,EAAQ,KAAK,CAAE,EAAQ,EACxC,CACH,CAAC,CAGJ,gBAAwB,EAAc,CACpC,GAAI,CACF,IAAM,EAAO,KAAK,MAAM,EAAK,CAC7B,KAAK,aAAa,KAAK,uBAAwB,EAAqB,OAC7D,EAAO,CACd,KAAK,OAAO,MAAM,0EAA2E,EAAM,EAIvG,kBACE,EACA,EAC+B,CAC/B,IAAM,EAAS,GAAkB,EAIjC,OAAO,IAAI,EADK,GAFC,kCAAkC,GAAkB,UAAU,KAC7D,EAAQ,WAAW,EAAM,GAAK,GACR,iEACU,EAAO,CAG3D,cAAsB,EAAqC,CACzD,MAAO,CAAC,CAAC,GAAU,GAAU,KAAO,EAAS,IAG/C,IAAW,aAAuB,CAChC,OAAO,KAAK,aAAa,YAAc,OAGzC,GACE,EACA,EACM,CACN,KAAK,aAAa,GAAG,EAAM,EAAQ,CAGrC,IACE,EACA,EACM,CACF,EACF,KAAK,aAAa,IAAI,EAAM,EAAQ,CAEpC,KAAK,aAAa,mBAAmB,EAAK,CAI9C,OAAqB,CACnB,KAAK,aAAa,oBAAoB,CAGxC,OAAe,CACb,KAAK,aAAa,OAAO,CAG3B,SAAuB,CACrB,KAAK,OAAO,CACZ,KAAK,OAAO,GCtIM,EAAtB,KAA+C,eAC7C,KAAU,WAAa,GAEvB,KAAU,uBAAiC,GAE3C,sBAAiC,CAC/B,cAAc,KAAK,gBAAgB,CAGrC,wBAAkC,EAAsB,CACtD,cAAc,KAAK,gBAAgB,CAC/B,OAAK,wBAA0B,KAGnC,KAAK,gBAAkB,YAAY,EAAU,KAAK,uBAAyB,IAAM,EAGnF,MAAgB,oBAAoB,EAAoB,CACtD,GAAI,CAAC,EAAS,GAKV,MAJE,KAAK,cAAc,EAAS,OAAO,EACrC,KAAK,WAAa,GACZ,KAAK,gCAAgC,EAAS,OAAQ,MAAM,EAAS,MAAM,CAAC,EAE5E,IAAI,EACR,kCAAkC,EAAS,SAC3C,EAAS,OACV,CAKP,iBAA2B,EAAqB,CAU5C,MATE,KAAK,kBAAkB,EAAW,EACpC,KAAK,WAAa,GAClB,KAAK,OAAO,CACN,IAAI,EACR,uCAAuC,EAAW,mEACnD,EACQ,aAAsB,YACzB,IAAI,EAA8B,iDAAiD,IAAa,CAEhG,IAAI,EAA8B,iCAAiC,EAAW,GAAG,CAI3F,gCACE,EACA,EAC+B,CAC/B,IAAM,EAAS,GAAkB,EAIjC,OAAO,IAAI,EADK,GAFC,kCAAkC,GAAkB,eACjD,GAAW,MAAM,EAAE,QAAU,GAAK,EAAI,KAAK,EAAU,GAAK,GACrC,mEACS,EAAO,CAG3D,cAAwB,EAAqC,CAC3D,MAAO,CAAC,CAAC,GAAU,GAAU,KAAO,EAAS,IAG/C,kBAA4B,EAA8B,CAMxD,OALK,GAAoB,OAAS,kBACzB,GACE,aAAsB,UAMnC,IAAW,aAAuB,CAChC,OAAO,KAAK,kBAAoB,IAAA,GAGlC,OAAe,CACb,cAAc,KAAK,gBAAgB,CACnC,KAAK,gBAAkB,IAAA,GAKzB,SAAuB,CACrB,KAAK,OAAO,CACZ,KAAK,OAAO,GCjFhB,MAAa,EAAmB,MAC9B,EACA,EACA,EACA,IACG,CACH,IAAM,EAAkB,IAAI,gBACtB,EAAiB,eAAiB,CACtC,EAAO,MAAM,uDAAuD,CACpE,EAAgB,OAAO,EACtB,EAAQ,CAEX,GAAI,CACF,IAAM,EAAW,MAAM,MAAM,OAAO,GAAa,UAAY,aAAoB,QAAU,EAAW,EAAS,UAAU,CAAE,CACzH,GAAG,EACH,OAAQ,EAAgB,OACzB,CAAC,CAEF,OADA,aAAa,EAAe,CACrB,QACA,EAAO,CAGd,MAFA,EAAO,KAAK,mCAAoC,EAAM,CACtD,aAAa,EAAe,CACtB,ICfV,IAAa,EAAb,cAAsC,CAA8C,CAMlF,YAAY,EAA4C,CACtD,OAAO,CADoB,KAAA,QAAA,EAJ7B,KAAQ,aAAe,IAAIC,EAMzB,KAAK,QAAU,EACf,KAAK,OAAS,EAAQ,OACtB,KAAK,IAAM,IAAI,IAAI,oBAAqB,EAAQ,QAAQ,CACxD,KAAK,uBAAyB,EAAQ,iBAAmB,GAG3D,MAAa,QAAQ,EAAgC,CAWnD,OAVA,KAAK,sBAAsB,CAE3B,MAAM,KAAK,aAAa,EAAQ,CAEhC,KAAK,4BAA8B,CAC5B,KAAK,aAAa,EAAQ,CAAC,MAAO,GAAU,CAC/C,KAAK,OAAO,MAAM,2CAA4C,EAAM,EACpE,EACF,CAEK,KAGT,MAAc,aAAa,EAAiB,CAC1C,GAAI,KAAK,WAAY,CACnB,KAAK,OAAO,KACV,2FACD,CACD,OAGF,GAAI,CACF,IAAM,EAAW,MAAM,EACrB,EACA,KAAK,IACL,CACE,OAAQ,OACR,QAAS,CAAE,eAAgB,mBAAoB,CAC/C,KAAM,KAAK,UAAU,CACnB,YAAa,KAAK,QAAQ,YAC1B,aAAc,KAAK,QAAQ,aAC3B,oBAAqB,KAAK,oBAC3B,CAAC,CACH,CACD,KAAK,OACN,CAID,GAFA,MAAM,KAAK,oBAAoB,EAAS,CAEpC,EAAS,SAAW,IAAK,CAC3B,IAAM,EAAO,KAAK,MAAM,MAAM,EAAS,MAAM,CAAC,CAC9C,KAAK,oBAAsB,EAAK,UAChC,KAAK,aAAa,KAAK,uBAAwB,EAAK,QAE/C,EAAY,CACnB,KAAK,iBAAiB,EAAW,EAIrC,GACE,EACA,EACM,CACN,KAAK,aAAa,GAAG,EAAM,EAAQ,CAGrC,IACE,EACA,EACM,CACF,EACF,KAAK,aAAa,IAAI,EAAM,EAAQ,CAEpC,KAAK,aAAa,mBAAmB,EAAK,CAI9C,OAAqB,CACnB,KAAK,aAAa,oBAAoB,GC1F7B,EAAb,cAAsC,CAAiB,CACrD,YAAY,EAA2B,CACrC,MAAM,EAAQ,CACd,KAAK,uBAAyB,ICJlC,MAAa,IAAe,EAAkC,EAAoB,IAA2B,CAC3G,OAAQ,EAAS,aAAa,CAA9B,CACE,IAAK,IACL,IAAK,SACH,OAAO,GAAS,EAAa,GAC/B,IAAK,KACL,IAAK,iBACH,OAAO,GAAS,EAAa,GAC/B,IAAK,YACH,OAAO,EAAa,SAAS,EAAa,CAC5C,IAAK,gBACH,MAAO,CAAC,EAAa,SAAS,EAAa,CAC7C,IAAK,qBACH,OAAO,EAAa,UAAW,GAAM,GAAO,WAAW,EAAE,CAAC,EAAI,EAChE,IAAK,6BACH,OAAO,EAAa,UAAW,GAAM,GAAO,WAAW,EAAE,CAAC,CAAG,EAC/D,IAAK,mBACH,OAAO,EAAa,UAAW,GAAM,GAAO,SAAS,EAAE,CAAC,EAAI,EAC9D,IAAK,2BACH,OAAO,EAAa,UAAW,GAAM,GAAO,SAAS,EAAE,CAAC,CAAG,EAC7D,IAAK,gBACH,OAAO,EAAa,EAAa,GAAI,EAAM,CAC7C,IAAK,uBACH,MAAO,CAAC,EAAa,EAAa,GAAI,EAAM,CAC9C,QACE,MAAO,KAIP,GAAgB,EAAqB,IAAqC,CAC9E,GAAI,CACF,OAAO,IAAI,OAAO,EAAY,CAAC,KAAK,EAAa,MAC3C,CACN,MAAO,KCjCE,GACX,EACA,EACA,IACG,CACH,GAAI,OAAO,GAAc,UACvB,MAAO,GAET,IAAM,EAAoB,EAAS,aAAa,CAChD,GAAI,GAAc,KAChB,OAAO,GAAqB,MAAQ,GAAqB,iBAG3D,GAAM,CAAE,QAAO,UAAW,EAAoB,EAAY,EAAa,GAAG,CAE1E,OAAQ,EAAR,CACE,IAAK,IACL,IAAK,SACH,OAAO,GAAS,EAClB,IAAK,KACL,IAAK,iBACH,OAAO,GAAS,EAClB,IAAK,IACH,OAAO,EAAQ,EACjB,IAAK,KACH,OAAO,GAAS,EAClB,IAAK,IACH,OAAO,EAAQ,EACjB,IAAK,KACH,OAAO,GAAS,EAClB,QACE,MAAO,KAIP,GAAuB,EAAiC,IACxD,OAAO,GAAS,SACX,CAAE,QAAO,OAAQ,EAAc,EAAY,CAAE,CAC3C,OAAO,GAAS,SAClB,CAAE,QAAO,OAAQ,OAAO,EAAY,CAAE,CAEtC,CAAE,MAAO,EAAc,EAAM,CAAE,OAAQ,EAAc,EAAY,CAAE,CAIxE,EAAiB,GAA4C,CACjE,IAAM,EAAI,OAAO,WAAW,EAAM,UAAU,CAAC,CAC7C,GAAI,MAAM,EAAE,EAAI,CAAC,SAAS,EAAE,CAC1B,MAAU,UAAU,0BAA0B,IAAQ,CAExD,OAAO,GClDI,GAAe,EAAkC,EAAoB,IAA2B,CAC3G,GAAI,GAAS,KACX,MAAO,GAGT,GAAI,CACF,IAAM,EAAY,IAAI,KAAK,EAAM,CAC3B,EAAa,IAAI,KAAK,EAAa,GAAG,CAC5C,OAAQ,EAAR,CACE,IAAK,WACH,OAAO,EAAY,EACrB,IAAK,YACH,OAAO,EAAY,EACrB,QACE,MAAO,SAEL,CACN,MAAO,KChBE,GAAiB,EAAkC,EAAoB,IAA2B,CAC7G,IAAM,EAAoB,EAAS,aAAa,CAChD,GAAI,GAAS,MAAQ,EAAM,MAAM,CAAC,QAAU,EAC1C,OAAO,GAAqB,gBAG9B,IAAM,EAAc,EAAO,OAAO,EAAM,EAAI,EACtC,EAAgB,EAAa,IAAK,GAAM,EAAO,OAAO,EAAE,EAAI,EAAE,CACpE,GAAI,CACF,OAAQ,EAAR,CACE,IAAK,IACH,OAAO,EAAW,EAAa,EAAc,GAAG,CAClD,IAAK,IACH,OAAO,EAAO,GAAG,EAAa,EAAc,GAAG,CACjD,IAAK,KACH,OAAO,EAAO,IAAI,EAAa,EAAc,GAAG,CAClD,IAAK,IACH,OAAO,EAAO,GAAG,EAAa,EAAc,GAAG,CACjD,IAAK,KACH,OAAO,EAAO,IAAI,EAAa,EAAc,GAAG,CAClD,IAAK,YACH,OAAO,EAAc,UAAW,GAAM,EAAW,EAAa,EAAE,CAAC,EAAI,EACvE,IAAK,gBACH,OAAO,EAAc,UAAW,GAAM,EAAW,EAAa,EAAE,CAAC,CAAG,EACtE,QACE,MAAO,SAEL,CACN,MAAO,KAIL,GAAc,EAA4B,IAAwC,CACtF,GAAI,CACF,OAAO,EAAO,GAAG,EAAI,EAAG,MAClB,CACN,MAAO,KCrCE,GAAgB,EAAiB,EAAoB,IAA2B,CAC3F,IAAM,EAAoB,EAAS,aAAa,CAChD,GAAI,CAAC,MAAM,QAAQ,EAAW,CAC5B,OAAO,GAAqB,0BAG9B,OAAQ,EAAR,CACE,IAAK,kBACH,OAAO,EAAW,KAAM,GAAM,EAAa,SAAS,EAAE,CAAC,CACzD,IAAK,0BACH,MAAO,CAAC,EAAW,KAAM,GAAM,EAAa,SAAS,EAAE,CAAC,CAC1D,QACE,MAAO,KCNb,IAAa,EAAb,KAAgC,CAC9B,SAAgB,EAAsB,EAAsC,CAC1E,GAAI,CAAC,KAAK,8BAA8B,EAAU,CAChD,MAAO,GAGT,OAAQ,EAAU,UAAlB,CACE,IAAK,aACH,OAAO,KAAK,QAAQ,GAAS,SAAS,GAAI,EAAU,CACtD,IAAK,OACH,OAAO,KAAK,QAAQ,GAAS,SAAS,KAAM,EAAU,CACxD,IAAK,UACH,OAAO,KAAK,QAAQ,GAAS,UAAU,QAAS,EAAU,CAC5D,IAAK,aACH,OAAO,KAAK,QAAQ,GAAS,UAAU,WAAY,EAAU,CAC/D,IAAK,SACH,GAAI,EAAU,MAAO,CACnB,IAAM,EAAQ,KAAK,eAAe,GAAS,SAAS,OAAQ,EAAU,MAAM,CAC5E,OAAO,KAAK,QAAQ,EAAO,EAAU,EAI3C,MAAO,GAGT,QACE,EACA,EACS,CACT,OAAQ,EAAU,WAAlB,CACE,IAAK,OACH,OAAO,GAAY,GAAO,UAAU,CAAE,EAAU,SAAU,EAAU,aAAa,CACnF,IAAK,SACH,OAAO,EAAe,EAAO,EAAU,SAAU,EAAU,aAAa,CAC1E,IAAK,WACH,OAAO,EAAY,GAAO,UAAU,CAAE,EAAU,SAAU,EAAU,aAAa,CACnF,IAAK,SACH,OAAO,EAAc,GAAO,UAAU,CAAE,EAAU,SAAU,EAAU,aAAa,CACrF,IAAK,QACH,OAAO,EAAa,EAAO,EAAU,SAAU,EAAU,aAAa,CAE1E,MAAO,GAGT,eAAuB,EAA6C,EAAc,CAChF,GAAI,CAEF,OADkB,EAAc,EAAM,EAAO,CAC5B,SACX,CACN,QAIJ,8BAAsC,EAA+B,CACnE,OAAO,MAAM,QAAQ,EAAU,aAAa,EAAI,EAAU,aAAa,OAAS,IC5DpF,MAEa,EAAoB,GAAgE,CAC/F,IAAM,EAAQ,GAAG,EAAK,kBAAkB,GAAG,EAAK,WAEhD,OAAO,OAAO,EAAU,EAAO,CAAE,KAAM,aAAM,CAAC,CAAG,MAAO,CAAG,ICkB7D,IAAa,EAAb,KAA6B,CAG3B,YAAY,EAA+C,CACzD,GAD2B,KAAA,OAAA,EAF7B,KAAiB,mBAAqB,IAAI,EAGpC,CAAC,GAAU,CAAC,EAAO,MAAQ,CAAC,EAAO,OAAS,CAAC,EAAO,KACtD,MAAU,UAAU,4DAA4D,CAGlF,KAAK,OAAS,EAGhB,SAAgB,EAAgB,EAA0C,CACxE,MAAO,CACL,GAAI,EAAO,GACX,IAAK,EAAO,IACZ,KAAM,EAAO,KACb,MAAO,KAAK,eAAe,EAAQ,EAAQ,CAC5C,CAGH,eAAuB,EAAgB,EAA6B,CAClE,IAAM,EAAQ,CAAC,GAAI,EAAO,QAAQ,OAAS,EAAE,CAAE,CAAC,MAC7C,EAAG,KAAO,EAAE,iBAAqC,EAAE,gBACrD,CACD,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAS,KAAK,aAAa,EAAM,EAAQ,EAAQ,CACvD,GAAI,EAAO,QACT,OAAO,EAAO,MAGlB,OAAO,EAAO,QAAQ,aAGxB,aAAqB,EAAY,EAAgB,EAAmD,CAClG,GAAI,CACF,GAAI,EAAK,MAAQ,aACf,OAAO,KAAK,uBAAuB,EAAM,EAAQ,EAAQ,IAChD,EAAK,MAAQ,cACtB,OAAO,KAAK,wBAAwB,EAAM,EAAQ,EAAQ,OAErD,EAAO,CACd,KAAK,OAAO,KAAK,yDAAyD,GAAM,GAAG,SAAS,GAAQ,IAAI,kCAAmC,CACzI,QACA,UAAW,GAAQ,IACnB,OAAQ,GAAM,GACf,CAAC,CAGJ,MAAO,CAAE,QAAS,GAAO,CAG3B,uBACE,EACA,EACA,EACsB,CACtB,OAAO,KAAK,mBAAmB,EAAK,aAAe,EAAE,CAAE,EAAQ,EAAQ,CAGzE,mBACE,EACA,EACA,EACsB,CACtB,IAAM,EAAqB,EAAiB,CAC1C,SAAU,EAAO,GACjB,kBAAmB,GAAS,SAAS,IAAM,OAAO,YAAY,CAC/D,CAAC,CACE,EAAQ,EACR,EAAM,EACN,EACJ,KAAO,CAAC,GAAU,EAAQ,EAAY,QAChC,GAAsB,EAAY,GAAO,WAAa,IACxD,EAAS,EAAY,IAGvB,GAAO,EAAY,GAAO,WAC1B,GAAS,EAMX,OAHI,GAAQ,OAAS,KAGd,CAAE,QAAS,GAAO,CAFhB,CAAE,QAAS,GAAM,MAAO,EAAO,MAAM,UAAU,CAAE,CAK5D,wBACE,EACA,EACA,EACsB,CACtB,IAAM,EAAa,EAAK,YAAc,EAAE,CACpC,EAAQ,EACR,EACJ,KAAO,CAAC,GAAa,EAAQ,EAAW,QAClC,KAAK,mBAAmB,SAAS,EAAW,GAAQ,EAAQ,GAC9D,EAAY,EAAW,IAEzB,GAAS,EAQX,OALI,GAAa,EAAK,QAAU,SAAW,EAAK,OAAS,KAChD,CAAE,QAAS,GAAM,MAAO,EAAK,MAAM,UAAU,CAAE,CAC7C,GAAa,EAAK,QAAU,aAC9B,KAAK,mBAAmB,EAAK,aAAe,EAAE,CAAE,EAAQ,EAAQ,CAElE,CAAE,QAAS,GAAO,GCvH7B,MAAa,EAA+C,GAA4B,CACtF,IAAM,EAAW,OAAO,EACxB,GAAI,IAAa,SACf,GAAI,CACF,OAAQ,EAAwB,aAAa,MAAQ,OAC/C,CACN,OAAO,UAEA,IAAa,WACtB,GAAI,CACF,IAAM,EAAgB,EAAqB,KAC3C,OAAO,EAAe,aAAa,IAAiB,OAC9C,CACN,OAAO,EAGX,OAAO,GAGH,EAAuB,GACpB,IAAkB,UAAY,IAAkB,SAG5C,GACX,EACA,IACmB,CACnB,IAAM,EAAQ,EAAY,MACpB,EAAgB,EAAiB,EAAa,CAEpD,GAAI,CAAC,EACH,MAAO,CACL,YAAa,EACb,cAAe,IAAA,GACf,gBACA,YAAa,GACb,OAAQ,gBACT,CAGH,GAAI,EAAY,OAAS,OACvB,OAAO,EAAU,EAAO,EAAc,EAAe,EAAY,QAAQ,CAG3E,GAAI,OAAO,GAAiB,SAC1B,MAAO,CACL,YAAa,EACb,cAAe,EAAY,QAC3B,gBACA,YAAa,GACb,OAAQ,cACT,CAGH,GACE,OAAO,GAAiB,YACvB,EAAY,OAAS,WAAa,EAAY,OAAS,UACxD,CACA,IAAM,EAAY,EAAmB,EAAM,CACrC,EAAa,OAAO,GAAc,UACxC,MAAO,CACL,YAAc,EAAa,EAAY,EACvC,cAAe,EAAa,EAAY,QAAU,IAAA,GAClD,gBACA,YAAa,CAAC,EACd,OAAQ,EAAa,cAAgB,kBACtC,CAGH,GAAI,EAAoB,OAAO,EAAa,EAAI,EAAY,OAAS,UAAW,CAC9E,IAAM,EAAW,EAAmB,EAAM,CACpC,EAAY,OAAO,GAAa,SACtC,MAAO,CACL,YAAc,EAAY,EAAW,EACrC,cAAe,EAAY,EAAY,QAAU,IAAA,GACjD,gBACA,YAAa,CAAC,EACd,OAAQ,EAAY,cAAgB,iBACrC,CAEH,GACE,EAAoB,OAAO,EAAa,GACvC,EAAY,OAAS,SAAW,EAAY,OAAS,QAAU,EAAY,OAAS,UACrF,CACA,IAAM,EAAW,EAAiB,EAAM,CAClC,EAAY,OAAO,GAAa,SACtC,MAAO,CACL,YAAc,EAAY,EAAW,EACrC,cAAe,EAAY,EAAY,QAAU,IAAA,GACjD,gBACA,YAAa,CAAC,EACd,OAAQ,EAAY,cAAgB,iBACrC,CAGH,MAAO,CACL,YAAa,EACb,cAAe,EAAY,QAC3B,gBACA,YAAa,GACb,OAAQ,cACT,EAGG,EAAsB,GAAuC,CACjE,GAAI,CAAC,EACH,OAEF,IAAM,EAAa,EAAM,aAAa,CAClC,QAAc,QAAU,GAAc,SAG1C,OAAO,IAAe,QAGlB,EAAsB,GAAsC,CAChE,GAAI,CAAC,EACH,OAEF,IAAM,EAAM,OAAO,SAAS,EAAM,CAC9B,WAAM,EAAI,EAAI,CAAC,SAAS,EAAI,EAGhC,OAAO,GAGH,EAAoB,GAAsC,CAC9D,GAAI,CAAC,EACH,OAEF,IAAM,EAAM,OAAO,WAAW,EAAM,CAChC,WAAM,EAAI,EAAI,CAAC,SAAS,EAAI,EAGhC,OAAO,GAGH,GACJ,EACA,EACA,EACA,IACmB,CACnB,GAAI,OAAO,GAAiB,SAC1B,MAAO,CACL,YAAa,EACb,cAAe,EACf,gBACA,YAAa,GACb,OAAQ,cACT,CAGH,GAAI,CACF,MAAO,CACL,YAAa,KAAK,MAAM,EAAW,CACnC,cAAe,EACf,gBACA,YAAa,GACb,OAAQ,cACT,MACK,CACN,MAAO,CACL,YAAa,EACb,cAAe,IAAA,GACf,gBACA,YAAa,GACb,OAAQ,eACT,GClLC,EAAqD,CACzD,IAAK,GACL,MAAO,EACP,KAAM,EACN,KAAM,EACN,MAAO,EACR,CAEK,MAA2B,CAC/B,IAAM,EAA0C,CAC9C,KAAM,UACN,MAAO,UACP,IAAK,UACL,KAAM,UACN,OAAQ,UACR,OAAQ,UACR,aAAc,QACf,CAED,GAAI,CACF,OAAO,IAAI,KAAK,eACd,IAAA,GACA,CAAE,GAAG,EAAa,uBAAwB,EAAG,CAC9C,MACK,CACN,OAAO,IAAI,KAAK,eAAe,IAAA,GAAW,EAAY,GAI1D,IAAa,GAAb,KAAkE,CAGhE,YAAY,EAAoD,EAA+D,CAAlG,KAAA,MAAA,EAAoD,KAAA,UAAA,EAC/E,KAAK,MAAQ,EACb,KAAK,UAAY,EACjB,KAAK,cAAgB,GAAoB,CAG3C,MAAM,EAAiB,GAAG,EAAiB,CACzC,KAAK,IAAI,QAAQ,MAAO,QAAS,EAAS,GAAG,EAAK,CAGpD,KAAK,EAAiB,GAAG,EAAiB,CACxC,KAAK,IAAI,QAAQ,KAAM,OAAQ,EAAS,GAAG,EAAK,CAGlD,KAAK,EAAiB,GAAG,EAAiB,CACxC,KAAK,IAAI,QAAQ,KAAM,OAAQ,EAAS,GAAG,EAAK,CAGlD,MAAM,EAAiB,GAAG,EAAiB,CACzC,KAAK,IAAI,QAAQ,MAAO,QAAS,EAAS,GAAG,EAAK,CAGpD,IAAc,EAAwC,EAAmC,EAAiB,GAAG,EAAW,CAClH,EAAO,KAAK,QAAU,EAAO,IAC/B,EAAe,IAAI,KAAK,cAAc,OAAO,IAAI,KAAO,CAAC,IAAI,KAAK,WAAW,gBAAgB,EAAQ,GAAI,GAAG,EAAK,GCvDjH,EAAN,KAAuE,CACrE,gBAAgB,EAAyB,CACvC,MAAO,kCAAkC,MAI7C,MAAa,GACX,EACA,IAEO,IAAI,GAAqB,GAAS,OAAQ,GAAoB,IAAI,EAAsB,CCLpF,GAAiC,EAAO,IAAS,IAAI,IAAI,EAAO,GAAM,UAAU,CAAC,CCSjF,EAAwB,MACnC,EACA,IAEI,EAAG,QACE,CAAE,QAAS,EAAG,QAAS,CAE5B,EAAG,MACD,EAAG,MAAM,QAAU,KAA2B,EAAG,MAAQ,OACpD,CAAE,MAAO,EAAG,MAAO,CAGrB,CAAE,QAAS,MAAM,EAAiB,EAAG,MAAM,CAAE,CAG/C,EAGI,EACX,GACmB,CACnB,GAAI,EAAG,OAAS,QAAW,EAAG,MAAQ,MAAQ,OAAO,EAAG,OAAU,SAAW,CAC3E,GAAI,EAAG,QACL,MAAO,CAAE,QAAS,EAAG,QAAS,KAAM,OAAQ,CAE9C,GAAI,CAEF,MAAO,CAAE,MADI,KAAK,UAAU,EAAG,MAAM,CACf,KAAM,OAAQ,MAC9B,CACN,MAAO,CAAE,MAAO,EAAG,MAAM,UAAU,CAAE,KAAM,OAAQ,EAIvD,IAAM,EAAc,EAAG,OAAO,UAAU,CAKxC,OAJI,GAAe,EAAY,QAAU,IAChC,CAAE,MAAO,EAAa,CAGxB,EAAG,QAAU,CAAE,QAAS,EAAG,QAAS,CAAG,CAAE,MAAO,EAAa,EAiBzD,EAA8D,GAA0B,CACnG,IAAM,EAAO,OAAO,KAAK,EAAU,CACnC,GAAI,EAAK,QAAU,EACjB,MAAO,GAGT,IAAK,IAAM,KAAO,EAChB,IAAK,EAAU,IAAM,QAAU,GAAK,EAClC,MAAO,GAIX,MAAO,IAGI,EAAwB,GAA2C,CAC9E,GAAI,CAAC,EACH,MAAO,GAET,IAAM,EAAO,OAAO,KAAK,EAAc,CACvC,GAAI,EAAK,QAAU,EACjB,MAAO,GAGT,IAAK,IAAM,KAAO,EAChB,IAAK,EAAc,IAAQ,GAAK,EAC9B,MAAO,GAIX,MAAO,IC1FT,IAAa,EAAb,KAA0D,CAMxD,YAAY,EAA+B,CAF3C,KAAQ,gBAAkB,GAGxB,KAAK,OAAS,EAAQ,OACtB,KAAK,OAAS,EAAQ,OACtB,KAAK,IAAM,IAAI,IAAI,sBAAuB,EAAQ,QAAQ,UAAU,CAAC,CAGvE,MAAa,OAAO,CAClB,iBACA,mBACA,iBACkD,CAClD,GAAI,CAAC,KAAK,gBACR,MAAO,CAAE,QAAS,GAAO,WAAY,GAAM,CAG7C,IAAM,EAA2B,CAC/B,aAAc,KAAK,OACnB,iBACA,mBACA,gBACD,CACD,GAAI,KAAK,cAAc,EAAY,CACjC,MAAO,CAAE,QAAS,GAAM,WAAY,GAAO,CAG7C,IAAM,EAAW,MAAM,KAAK,WAAW,EAAY,CAInD,OAHI,EAAS,aACX,KAAK,gBAAkB,IAElB,EAGT,cAAsB,EAA0B,CAC9C,OACE,EAAiB,EAAY,eAAe,EAC5C,EAAiB,EAAY,iBAAiB,EAC9C,EAAqB,EAAY,cAAc,CAInD,MAAc,WAAW,EAAqD,CAC5E,GAAI,CACF,IAAM,EAAW,MAAM,EACrB,IACA,KAAK,IACL,CACE,OAAQ,OACR,KAAM,KAAK,UAAU,EAAY,CAClC,CACD,KAAK,OACN,CAEK,EAAe,KAAK,cAAc,EAAS,OAAO,CAMxD,OALI,GACF,KAAK,OAAO,KACV,qDAAqD,EAAS,OAAO,qEACtE,CAEI,CAAE,QAAS,EAAS,GAAI,WAAY,EAAc,OAClD,EAAO,CAEd,OADA,KAAK,OAAO,KAAK,4DAA4D,IAAS,CAAE,QAAO,CAAC,CACzF,CAAE,QAAS,GAAO,WAAY,GAAO,EAIhD,cAAsB,EAAqC,CACzD,MAAO,CAAC,CAAC,GAAU,GAAU,KAAO,EAAS,MClFpC,EAAb,KAA6B,CAC3B,UAA4C,EAAuD,CACjG,GAAI,EAAS,OAAO,QAAU,EAC5B,MAAO,EAAE,CAGX,IAAM,EAAM,IAAI,IAChB,IAAK,IAAM,KAAS,EAAS,OAAQ,CACnC,IAAM,EAAkB,KAAK,UAAU,EAAM,CACvC,EAAQ,EAAI,IAAI,EAAgB,EAAE,OAAS,EACjD,EAAI,IAAI,EAAiB,CAAE,QAAO,MAAO,EAAQ,EAAG,CAAC,CAEvD,OAAO,MAAM,KAAK,EAAI,CAAC,KAAK,EAAG,MACtB,CACL,UAAW,EAAS,UACpB,QAAS,EAAS,QAClB,MAAO,EAAE,MACT,MAAO,EAAE,MACV,EACD,GCnBO,EAAb,KAAmD,CAMjD,YAAY,EAAgB,CAH5B,KAAQ,QAAe,EAAE,CACzB,KAAQ,mBAAqB,EAG3B,KAAK,MAAQ,GAAS,IAGxB,IAAW,QAAc,CACvB,OAAO,KAAK,QAGd,IAAW,cAAwB,CACjC,OAAO,KAAK,QAAQ,QAAU,KAAK,MAGrC,IAAW,mBAA4B,CACrC,OAAO,KAAK,mBAGd,KAAY,GAAG,EAAgB,CACxB,KAAK,YACR,KAAK,UAAY,IAAI,MAGvB,IAAM,EAAuB,KAAK,IAAI,EAAG,EAAU,OAAS,KAAK,MAAM,CACjE,EAAe,EAAU,MAAM,EAAsB,EAAU,OAAO,CAE5E,GADA,KAAK,oBAAsB,EACvB,KAAK,QAAQ,OAAS,EAAa,OAAS,KAAK,MAAO,CAC1D,IAAM,EAAmB,KAAK,QAAQ,OAAS,EAAa,OAAS,KAAK,MAC1E,KAAK,QAAQ,OAAO,EAAG,EAAiB,CACxC,KAAK,oBAAsB,EAE7B,OAAO,KAAK,QAAQ,KAAK,GAAG,EAAa,CAG3C,cAA6C,CAC3C,IAAM,EAAU,IAAI,KACd,EAAY,KAAK,WAAa,EAC9B,EAAiB,KAAK,QAAQ,OAAO,EAAE,CACvC,EAAe,KAAK,mBAG1B,MAFA,MAAK,UAAY,IAAA,GACjB,KAAK,mBAAqB,EACnB,CACL,YACA,UACA,OAAQ,EACR,eACD,CAGH,OAAe,CACb,KAAK,QAAU,EAAE,CACjB,KAAK,UAAY,IAAA,GACjB,KAAK,mBAAqB,ICzCR,EAAtB,KAAyE,CAWvE,YAAY,EAAyC,CAPrD,KAAmB,WAA8B,IAAI,EAGrD,KAAU,cAAgB,GAKxB,KAAK,OAAS,EAAQ,OACtB,KAAK,qBAAuB,IAAI,EAAW,EAAQ,sBAAwB,IAAM,CACjF,KAAK,mBAAqB,EAAQ,oBAAsB,IACxD,IAAM,EAAe,EAAQ,2BAA6B,IAC1D,KAAK,aAAe,eAAiB,KAAK,sBAAsB,CAAE,EAAa,CAKjF,MAAgB,sBAAuB,EACpB,MAAM,KAAK,OAAO,EACtB,YACX,KAAK,cAAgB,GACrB,KAAK,OAAO,CACZ,KAAK,OAAO,KACV,2GACD,EAED,KAAK,aAAe,eAAiB,KAAK,sBAAsB,CAAE,KAAK,mBAAmB,CAI9F,sBAAiC,CAC/B,aAAa,KAAK,aAAa,CAGjC,MAAa,OAAQ,CACnB,KAAK,cAAgB,GACrB,aAAa,KAAK,aAAa,CAC/B,MAAM,KAAK,OAAO,CAClB,KAAK,qBAAqB,OAAO,CAGnC,SAAiB,CACf,KAAK,OAAO,GC/DH,EAAb,cAAsC,GAAU,CAI9C,YAAY,EAAe,EAAoB,CAC7C,MAAM,EAAQ,CAHhB,KAAQ,cAAwB,EAI9B,KAAK,MAAQ,EACb,KAAK,cAAc,CAGrB,IAAoB,EAAQ,EAAgB,CAG1C,OAFA,MAAM,IAAI,EAAK,EAAM,CACrB,KAAK,cAAc,CACZ,KAGT,IAAW,cAAuB,CAChC,OAAO,KAAK,cAGd,eAAuB,CACrB,KAAK,OAAO,CACZ,KAAK,cAAgB,EAGvB,cAAuB,CACrB,KAAO,KAAK,KAAO,KAAK,OAAO,CAC7B,IAAM,EAAY,KAAK,MAAM,CAAC,MAAM,CAAC,MACjC,IAAc,IAAA,KAChB,KAAK,OAAO,EAAU,CACtB,KAAK,eAAiB,MCzBjB,GAAb,KAEE,CACA,YAAY,EAAqD,CAApC,KAAA,iBAAA,EAC3B,KAAK,iBAAmB,EAG1B,MAAM,QACJ,EACmE,CACnE,IAAM,EAAS,MAAM,QAAQ,IAC3B,EAAS,OAAO,IAAI,KAAO,KAClB,CACL,GAAG,EACH,aAAc,MAAM,EAAsB,EAAE,aAAc,KAAK,iBAAiB,CAChF,eAAgB,MAAM,EAAsB,EAAE,eAAgB,KAAK,iBAAiB,CACrF,EACD,CACH,CAED,MAAO,CAAE,GAAG,EAAU,SAAQ,GCRrB,GAAb,cAAmD,CAEjD,CAQA,YAAY,EAA+C,CACzD,MAAM,EAAQ,CACd,KAAK,SAAW,IAAI,EAAoB,EAAQ,CAChD,KAAK,SAAW,IAAI,EAAW,EAAQ,cAAgB,IAAuB,CAC9E,KAAK,oCAAsC,IAAI,GAAiC,EAAQ,iBAAiB,CAG3G,gBAAkD,EAAmC,CACnF,GAAI,CAAC,KAAK,cACR,OAEE,EAAQ,SAAS,IAAM,CAAC,EAAQ,QAAQ,WAC1C,KAAK,SAAS,IAAI,EAAQ,QAAQ,GAAI,EAAQ,QAAQ,CAExD,IAAM,EAAQ,CACZ,GAAG,EAAQ,WACX,UAAW,EAAQ,SAAS,UAAY,IAAA,GAAY,EAAQ,SAAS,GACtE,CAED,KAAK,qBAAqB,KAAK,KAAK,6BAA6B,EAAM,CAAC,CAG1E,6BACE,EACsC,CACtC,MAAO,CACL,GAAG,EACH,aAAc,EAAoB,CAAE,MAAO,EAAM,aAAc,KAAM,EAAM,KAAM,CAAC,CAClF,eAAgB,EAAoB,CAClC,MAAO,EAAM,eACb,QAAS,EAAM,iBACf,KAAM,EAAM,KACb,CAAC,CACH,CAGH,kBAA2B,CACzB,IAAM,EAAW,MAAM,KAAK,KAAK,SAAS,QAAQ,CAAC,CAC7C,EAAe,KAAK,SAAS,aAEnC,OADA,KAAK,SAAS,eAAe,CACtB,CAAE,WAAU,eAAc,CAGnC,MAAyB,OAAmC,CAC1D,GAAI,CAAC,KAAK,SACR,MAAO,CAAE,QAAS,GAAO,WAAY,GAAO,CAE9C,IAAM,EAAqB,MAAM,KAAK,oCAAoC,QACxE,KAAK,qBAAqB,cAAc,CACzC,CACK,EAAmB,KAAK,kBAAkB,CAchD,OAbiB,MAAM,KAAK,UAAU,OAAO,CAC3C,QAAS,KAAK,SACd,eAAgB,CACd,iBAAkB,EAAiB,SACpC,CACD,iBAAkB,CAChB,gBAAiB,KAAK,WAAW,UAAU,EAAmB,CAC/D,CACD,cAAe,CACb,gBAAiB,EAAmB,aACpC,iBAAkB,EAAiB,aACpC,CACF,CAAC,GC1FN,MAEa,IAAY,EAAiB,IAA2B,CACnE,IAAM,EAAO,OAAO,EAAE,CAChB,EAAO,OAAO,GAAG,CACjB,EAAM,MAAM,KAAK,EAAI,CACxB,IAAK,GAAM,EAAE,SAAS,GAAG,CAAC,SAAS,EAAG,IAAI,CAAC,CAC3C,KAAK,GAAG,CACP,EAAI,OAAO,KAAO,EAAI,CACtB,EAAS,GACb,KAAO,EAAI,GACT,EAAS,iEAAO,OAAO,EAAI,EAAK,EAAI,EACpC,GAAK,EAEP,OAAO,EAAO,SAAS,EAAQ,IAAI,ECbxB,GAAgB,KAAK,KAAA,IAA0B,KAAK,KAAK,GAAG,CAAC,CCG7D,GAAkB,KAAO,IAI7B,GAHM,EAAW,SAAS,CAC9B,OAAO,GAAG,UAAU,EAAI,GAAG,CAC3B,QAAQ,CACU,SAAS,EAAA,GAAgB,CAAE,GAAc,CCsB1D,EAAiB,IAAI,IAAI,4CAA4C,CAW3E,IAAa,GAAb,KAAyE,CAevE,YACE,EACA,EACA,EACA,CAfF,KAAQ,YAA2D,IAAI,IAEvE,KAAQ,aAAe,IAAI,EAE3B,KAAQ,MAAQ,GAYd,KAAK,OAAS,GAAe,QAAU,GAAqB,CAC5D,KAAK,QAAU,GAAe,YAAY,SAAW,IACrD,KAAK,YAAc,GAAe,UAAY,EAAE,CAChD,KAAK,gBAAkB,IAAI,EAAgB,KAAK,OAAO,CACvD,IAAM,EAAU,KAAK,SAAS,GAAe,YAAY,IAAI,EAAI,EACjE,KAAK,eAAiB,GAAe,YAAY,MAAQ,YACzD,IAAM,EAAuB,KAAK,wBAAwB,KAAK,eAAe,CACxE,EAAa,GAAe,WAAW,iBAAmB,IAChE,KAAK,oBAAsB,IAAI,GAA8B,CAC3D,OAAQ,EACR,OAAQ,KAAK,OACb,UACA,mBAAoB,GAAe,WAAW,eAAiB,IAC/D,qBAAsB,KAAK,KAAK,EAAa,GAAI,CACjD,aAAc,KAAK,MAAM,EAAa,GAAI,CAC1C,WAAY,EACZ,iBAAkB,GACnB,CAAC,CACF,KAAK,UAAY,IAAI,EAAqB,CACxC,eACA,UACA,YAAa,CACX,GAAG,GAAe,SAClB,QAAS,EAAW,QACpB,WAAY,EAAW,WACxB,CACD,OAAQ,KAAK,OACb,gBAAiB,GAAe,YAAY,gBAC7C,CAAC,CAEF,KAAK,UAAU,GAAG,uBAAyB,GAA+B,CACxE,KAAK,gBAAgB,CACrB,IAAM,EAAa,OAAO,KAAK,EAAa,QAAQ,CAChD,CAAC,KAAK,WAAa,EAAa,MAAQ,QAC1C,KAAK,UAAY,EACjB,KAAK,aAAa,KAAK,iBAAkB,CAAE,KAAM,EAAY,CAAC,CAC9D,KAAK,eAAe,EAAa,QAAQ,GAEzC,KAAK,UAAU,QAAU,CACvB,GAAG,KAAK,UAAU,QAClB,GAAG,EAAa,QACjB,CACD,KAAK,aAAa,KAAK,iBAAkB,CAAE,KAAM,EAAY,CAAC,CAC9D,KAAK,eAAe,EAAa,QAAQ,EAE3C,KAAK,OAAO,MACV,6DAA6D,EAAW,OAAO,SAC/E,CAAE,KAAM,EAAY,CACrB,EACD,CAEF,KAAK,gBAAgB,cAAe,GAAe,OAAO,YAAY,CACtE,KAAK,gBAAgB,kBAAmB,GAAe,OAAO,gBAAgB,CAC9E,KAAK,gBAAgB,iBAAkB,GAAe,OAAO,eAAe,CAG9E,gBACE,EACA,EACA,CACA,GAAI,CAAC,EACH,OAGF,IAAM,EAAW,MAAM,QAAQ,EAAQ,CAAG,EAAU,CAAC,EAAQ,CAC7D,IAAK,IAAM,KAAK,EACV,OAAO,GAAM,YACf,KAAK,GAAG,EAAO,EAAE,CAKvB,MAAa,YAAa,CACxB,GAAI,CACF,KAAK,MAAQ,GACb,KAAK,aAAe,IAAI,QAAe,GAAY,CACjD,KAAK,aAAe,GACpB,CAAC,SAAW,CACZ,KAAK,MAAQ,GACb,KAAK,aAAa,KAAK,cAAc,CACrC,KAAK,OAAO,MAAM,mFAAmF,EACrG,CACF,IAAM,EAAY,IAAI,MAAM,CAAC,SAAS,CACtC,MAAM,KAAK,UAAU,QAAQ,KAAK,QAAQ,CAC1C,IAAM,EAAc,IAAI,MAAM,CAAC,SAAS,CAAG,EACrC,EAAmB,KAAK,QAAU,EASxC,GARI,EAAmB,GACrB,MAAM,QAAQ,KAAK,CACjB,KAAK,aACL,IAAI,QAAe,GAAY,CAC7B,eAAiB,GAAS,CAAE,EAAiB,EAC7C,CACH,CAAC,CAEA,CAAC,KAAK,MAAO,CACf,IAAM,EACJ,KAAK,iBAAmB,YACpB,oJACA,iHACN,KAAK,OAAO,KACV,qEAAqE,KAAK,QAAQ,MAAM,IACzF,QAEI,EAAO,CACd,KAAK,OAAO,MAAM,mEAAoE,EAAM,EAIhG,wBAAgC,EAAsB,CACpD,OAAQ,EAAR,CACE,IAAK,WACH,OAAO,EACT,IAAK,UACH,OAAO,EACT,QACE,OAAO,GAIb,eAAuB,EAA8C,CACnE,OAAO,OAAO,EAAW,CAAC,QAAS,GAAM,KAAK,wBAAwB,EAAsB,CAAC,CAG/F,wBAAgC,EAAoC,CAClE,KAAK,YAAY,IAAI,EAAiB,IAAI,EAAE,QAAS,GAAM,CACzD,IAAM,EAAQ,KAAK,6BACjB,EAAiB,IACjB,EACA,EAAE,aACF,EAAE,QACH,CACD,EAAE,QAAQ,EAAM,EAChB,CAGJ,MACE,EACA,EACA,EACA,EACA,CACA,KAAK,qBAAqB,EAAa,CAEvC,IAAM,EAAW,KAAK,YAAY,IAAI,EAAU,CAC1C,EAAqB,CACzB,QAAS,EACT,eACA,cAAe,OAAO,EACtB,UACD,CAOD,OANI,EACF,EAAS,KAAK,EAAmB,CAEjC,KAAK,YAAY,IAAI,EAAW,CAAC,EAAmB,CAAC,KAG1C,KAAK,QAAQ,EAAW,EAAS,CAGhD,QAA0C,EAAmB,EAA4B,CACvF,IAAM,EAAW,KAAK,YAAY,IAAI,EAAU,CAC3C,KAIL,GAAI,EAAU,CACZ,IAAM,EAAQ,EAAS,UAAW,GAAM,EAAE,SAAW,EAAS,CAC1D,GAAS,GACX,GAAU,OAAO,EAAO,EAAE,MAG5B,EAAS,OAAO,EAAE,CAItB,SACE,EACA,EACA,EACG,CACH,KAAK,qBAAqB,EAAa,CAEvC,IAAM,EAAmB,KAAK,WAAW,QAAQ,GACjD,OAAO,KAAK,6BAA6B,EAAW,EAAkB,EAAc,EAAQ,CAG9F,6BACE,EACA,EACA,EACA,EACG,CACH,GAAI,EAAkB,CACpB,IAAM,EAAc,KAAK,gBAAgB,SAAS,EAAkB,CAClE,UACA,SAAU,KAAK,YAChB,CAAC,CACI,EAAc,EAAoB,EAAa,EAAa,CAuBlE,OAtBA,KAAK,oBAAoB,gBAAgB,CACvC,UACA,WAAY,CACV,IAAK,EACL,KAAM,EAAY,KACJ,eACd,cAAe,EAAY,cAC3B,eAAgB,EAAY,YAC5B,iBAAkB,EAAY,cAC9B,YAAa,EAAY,YACzB,iBAAkB,EAAY,OAC/B,CACF,CAAC,CACF,KAAK,wBAAwB,CAC3B,IAAK,EACL,MAAO,EAAY,YACnB,QAAS,EAAY,eAAiB,IAAA,GACtC,eAAgB,EAAY,YAC5B,OAAQ,EAAY,OACX,UACV,CAAC,CACF,KAAK,OAAO,MAAM,qCAAqC,EAAU,QAAQ,EAAY,YAAY,GAAG,CAC7F,EAAY,gBACd,CACL,KAAK,OAAO,MACV,qDAAqD,EAAU,8BAA8B,EAAa,GAC3G,CACD,IAAM,EAA2B,KAAK,QAAU,uBAAyB,mBAmBzE,OAlBA,KAAK,oBAAoB,gBAAgB,CACvC,UACA,WAAY,CACV,IAAK,EACS,eACd,cAAe,EAAiB,EAAa,CAC7C,eAAgB,EAChB,YAAa,GACb,iBAAkB,EACnB,CACF,CAAC,CACF,KAAK,wBAAwB,CAC3B,IAAK,EACL,MAAO,EACP,eAAgB,GAChB,SACS,UACV,CAAC,CACK,GAIX,wBAAgC,EAAyB,CACvD,eAAiB,KAAK,aAAa,KAAK,kBAAmB,CAAE,WAAY,EAAO,CAAC,CAAE,EAAE,CAGvF,SAAiB,EAA0C,CACpD,KAIL,GAAI,CACF,OAAO,IAAI,IAAI,EAAI,OACZ,EAAO,CACd,MAAM,IAAI,EAA8B,qBAAqB,EAAI,qBAAqB,IAAQ,EAIlG,qBAAwD,EAAiB,CACvE,GAAI,GAA+C,KACjD,MAAM,IAAI,EACR,sFACD,CAGH,GAAI,OAAO,GAAiB,WAC1B,MAAM,IAAI,EACR,8EACD,CAIL,IAAW,SAAmB,CAC5B,OAAO,KAAK,MAGd,GAAwC,EAAc,EAAgD,CACpG,KAAK,aAAa,GAAG,EAAW,EAAQ,CAG1C,IAAyC,EAAc,EAA2C,CAC5F,EACF,KAAK,aAAa,IAAI,EAAW,EAAQ,CAEzC,KAAK,aAAa,mBAAmB,EAAU,CAInD,oBAA4B,CAC1B,KAAK,OAAO,MAAM,sFAAsF,CACxG,KAAK,aAAa,oBAAoB,CACtC,KAAK,YAAY,OAAO,CAG1B,YAAoB,CAClB,KAAK,YAAY,OAAO,CAG1B,cAAqB,EAGW,CAC9B,GAAI,CAAC,KAAK,UAAW,MAAO,EAAE,CAE9B,IAAM,EAAU,OAAO,QAAQ,KAAK,UAAU,QAAQ,CAChD,EAAW,GAAS,WACtB,EAAQ,QAAQ,CAAC,KAAS,EAAQ,YAAY,SAAS,EAAI,CAAC,CAC5D,EAEJ,OAAO,OAAO,YACZ,EAAS,KAAK,CAAC,EAAK,KAAY,CAC9B,EACA,KAAK,gBAAgB,SAAS,EAAQ,CAAE,QAAS,GAAS,QAAS,SAAU,KAAK,YAAa,CAAC,CACjG,CAAC,CACH,CAGH,iBAAyB,CACvB,KAAK,OAAO,MACV,yFACD,CACD,KAAK,UAAU,OAAO,CACtB,KAAK,MAAQ,GAGf,SAAiB,CACf,KAAK,oBAAoB,CACzB,KAAK,iBAAiB,GC5W1B,MAAa,IACX,EACA,IAEO,IAAI,GACT,EACA,CAAE,QAAS,gBAAiB,WAAY,QAAe,CACvD,EACD,CAGU,IACX,EACA,IAEO,EAAoB,EAAO,EAAiB"}
|
package/package.json
CHANGED