@traffical/js-client 0.1.2

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.
Files changed (52) hide show
  1. package/README.md +209 -0
  2. package/dist/client.d.ts +167 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +429 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/error-boundary.d.ts +47 -0
  7. package/dist/error-boundary.d.ts.map +1 -0
  8. package/dist/error-boundary.js +118 -0
  9. package/dist/error-boundary.js.map +1 -0
  10. package/dist/event-logger.d.ts +75 -0
  11. package/dist/event-logger.d.ts.map +1 -0
  12. package/dist/event-logger.js +186 -0
  13. package/dist/event-logger.js.map +1 -0
  14. package/dist/exposure-dedup.d.ts +49 -0
  15. package/dist/exposure-dedup.d.ts.map +1 -0
  16. package/dist/exposure-dedup.js +94 -0
  17. package/dist/exposure-dedup.js.map +1 -0
  18. package/dist/global.d.ts +38 -0
  19. package/dist/global.d.ts.map +1 -0
  20. package/dist/global.js +67 -0
  21. package/dist/global.js.map +1 -0
  22. package/dist/index.d.ts +41 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +46 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/plugins/decision-tracking.d.ts +68 -0
  27. package/dist/plugins/decision-tracking.d.ts.map +1 -0
  28. package/dist/plugins/decision-tracking.js +83 -0
  29. package/dist/plugins/decision-tracking.js.map +1 -0
  30. package/dist/plugins/dom-binding.d.ts +48 -0
  31. package/dist/plugins/dom-binding.d.ts.map +1 -0
  32. package/dist/plugins/dom-binding.js +211 -0
  33. package/dist/plugins/dom-binding.js.map +1 -0
  34. package/dist/plugins/index.d.ts +70 -0
  35. package/dist/plugins/index.d.ts.map +1 -0
  36. package/dist/plugins/index.js +194 -0
  37. package/dist/plugins/index.js.map +1 -0
  38. package/dist/plugins/types.d.ts +62 -0
  39. package/dist/plugins/types.d.ts.map +1 -0
  40. package/dist/plugins/types.js +7 -0
  41. package/dist/plugins/types.js.map +1 -0
  42. package/dist/stable-id.d.ts +44 -0
  43. package/dist/stable-id.d.ts.map +1 -0
  44. package/dist/stable-id.js +129 -0
  45. package/dist/stable-id.js.map +1 -0
  46. package/dist/storage.d.ts +41 -0
  47. package/dist/storage.d.ts.map +1 -0
  48. package/dist/storage.js +144 -0
  49. package/dist/storage.js.map +1 -0
  50. package/dist/traffical.min.js +3 -0
  51. package/dist/traffical.min.js.map +7 -0
  52. package/package.json +51 -0
@@ -0,0 +1,3 @@
1
+ /* @traffical/js-client v0.1.2 */
2
+ "use strict";var Traffical=(()=>{var w=Object.defineProperty;var ce=Object.getOwnPropertyDescriptor;var de=Object.getOwnPropertyNames;var fe=Object.prototype.hasOwnProperty;var ge=(r,e,t)=>e in r?w(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t;var pe=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports),he=(r,e)=>{for(var t in e)w(r,t,{get:e[t],enumerable:!0})},me=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of de(e))!fe.call(r,i)&&i!==t&&w(r,i,{get:()=>e[i],enumerable:!(n=ce(e,i))||n.enumerable});return r};var ye=r=>me(w({},"__esModule",{value:!0}),r);var E=(r,e,t)=>(ge(r,typeof e!="symbol"?e+"":e,t),t);var Q=pe(()=>{});var dt={};he(dt,{TrafficalClient:()=>T,createDOMBindingPlugin:()=>ue,destroy:()=>ct,init:()=>at,initSync:()=>lt,instance:()=>ut});function x(r){let e=2166136261;for(let t=0;t<r.length;t++)e^=r.charCodeAt(t),e=Math.imul(e,16777619);return e>>>0}function S(r,e,t){let n=`${r}:${e}`;return x(n)%t}function U(r,e){return r>=e[0]&&r<=e[1]}function P(r,e){for(let t of e)if(U(r,t.bucketRange))return t;return null}function z(r,e){let{field:t,op:n,value:i,values:o}=r,s=Ee(e,t);switch(n){case"eq":return s===i;case"neq":return s!==i;case"in":return Array.isArray(o)?o.includes(s):!1;case"nin":return Array.isArray(o)?!o.includes(s):!0;case"gt":return typeof s=="number"&&s>i;case"gte":return typeof s=="number"&&s>=i;case"lt":return typeof s=="number"&&s<i;case"lte":return typeof s=="number"&&s<=i;case"contains":return typeof s=="string"&&typeof i=="string"&&s.includes(i);case"startsWith":return typeof s=="string"&&typeof i=="string"&&s.startsWith(i);case"endsWith":return typeof s=="string"&&typeof i=="string"&&s.endsWith(i);case"regex":if(typeof s!="string"||typeof i!="string")return!1;try{return new RegExp(i).test(s)}catch{return!1}case"exists":return s!=null;case"notExists":return s==null;default:return!1}}function B(r,e){return r.length===0?!0:r.every(t=>z(t,e))}function Ee(r,e){let t=e.split("."),n=r;for(let i of t){if(n==null)return;if(typeof n=="object")n=n[i];else return}return n}var xe=r=>crypto.getRandomValues(new Uint8Array(r)),Te=(r,e,t)=>{let n=(2<<Math.log2(r.length-1))-1,i=-~(1.6*n*e/r.length);return(o=e)=>{let s="";for(;;){let f=t(i),h=i|0;for(;h--;)if(s+=r[f[h]&n]||"",s.length>=o)return s}}},Z=(r,e=21)=>Te(r,e|0,xe);function A(r){let e=new Error(r);return e.source="ulid",e}var q="0123456789ABCDEFGHJKMNPQRSTVWXYZ",b=q.length,ee=Math.pow(2,48)-1,be=10,Ie=16;function Ce(r){let e=Math.floor(r()*b);return e===b&&(e=b-1),q.charAt(e)}function De(r,e){if(isNaN(r))throw new Error(r+" must be a number");if(r>ee)throw A("cannot encode time greater than "+ee);if(r<0)throw A("time must be positive");if(Number.isInteger(Number(r))===!1)throw A("time must be an integer");let t,n="";for(;e>0;e--)t=r%b,n=q.charAt(t)+n,r=(r-t)/b;return n}function ke(r,e){let t="";for(;r>0;r--)t=Ce(e)+t;return t}function we(r=!1,e){e||(e=typeof window<"u"?window:null);let t=e&&(e.crypto||e.msCrypto);if(t)return()=>{let n=new Uint8Array(1);return t.getRandomValues(n),n[0]/255};try{let n=Q();return()=>n.randomBytes(1).readUInt8()/255}catch{}if(r){try{console.error("secure crypto unusable, falling back to insecure Math.random()!")}catch{}return()=>Math.random()}throw A("secure crypto unusable, insecure Math.random not allowed")}function Se(r){return r||(r=we()),function(t){return isNaN(t)&&(t=Date.now()),De(t,be)+ke(Ie,r)}}var te=Se();var Pe="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",Be=8,Dt=Z(Pe,Be);function R(r){return`${r}_${te()}`}function I(){return R("dec")}function W(){return R("exp")}function H(){return R("trk")}function Ae(r,e){let t=new Set;for(let i of e)if(i.contextLogging?.allowedFields)for(let o of i.contextLogging.allowedFields)t.add(o);if(t.size===0)return;let n={};for(let i of t)i in r&&(n[i]=r[i]);return Object.keys(n).length>0?n:void 0}function Re(r,e){let t=[];for(let n of r){let i=e[n];if(i==null)return null;t.push(String(i))}return t.join("_")}function Me(r,e){if(r.length===0||r.length===1)return 0;let n=x(e)%1e4/1e4,i=0;for(let o=0;o<r.length;o++)if(i+=r[o],n<i)return o;return r.length-1}function ne(r){if(r<=0)return[];let e=1/r;return Array(r).fill(e)}function Oe(r,e,t,n){let i=r.entityState?.[e];if(!i)return ne(n);let o=i.entities[t];if(o&&o.weights.length===n)return o.weights;let s=i._global;return s&&s.weights.length===n?s.weights:ne(n)}function Ne(r,e,t,n){let i=e.entityConfig;if(!i)return null;let o=Re(i.entityKeys,t);if(!o)return null;let s,f;if(i.dynamicAllocations){let _=i.dynamicAllocations.countKey,y=t[_];if(typeof y!="number"||y<=0)return null;f=Math.floor(y),s=Array.from({length:f},(a,c)=>({id:`${e.id}_dynamic_${c}`,name:String(c),bucketRange:[0,0],overrides:{}}))}else s=e.allocations,f=s.length;if(f===0)return null;let h=Oe(r,e.id,o,f),g=`${o}:${n}:${e.id}`,u=Me(h,g);return{allocation:s[u],entityId:o}}function X(r,e){let t=e[r.hashing.unitKey];return t==null?null:String(t)}function re(r,e,t){let n={...t},i=[],o=[];if(!r)return{assignments:n,unitKeyValue:"",layers:i,matchedPolicies:o};let s=X(r,e);if(!s)return{assignments:n,unitKeyValue:"",layers:i,matchedPolicies:o};let f=new Set(Object.keys(t)),h=r.parameters.filter(u=>f.has(u.key));for(let u of h)u.key in n&&(n[u.key]=u.default);let g=new Map;for(let u of h){let _=g.get(u.layerId)||[];_.push(u),g.set(u.layerId,_)}for(let u of r.layers){let _=g.get(u.id);if(!_||_.length===0)continue;let y=S(s,u.id,r.hashing.bucketCount),a,c;for(let l of u.policies)if(l.state==="running"){if(l.eligibleBucketRange){let{start:d,end:m}=l.eligibleBucketRange;if(y<d||y>m)continue}if(B(l.conditions,e))if(l.entityConfig&&l.entityConfig.resolutionMode==="bundle"){let d=Ne(r,l,e,s);if(d){if(a=l,c=d.allocation,o.push(l),!l.entityConfig.dynamicAllocations)for(let[m,$]of Object.entries(d.allocation.overrides))m in n&&(n[m]=$);break}}else{if(l.entityConfig&&l.entityConfig.resolutionMode==="edge")continue;{let d=P(y,l.allocations);if(d){a=l,c=d,o.push(l);for(let[m,$]of Object.entries(d.overrides))m in n&&(n[m]=$);break}}}}i.push({layerId:u.id,bucket:y,policyId:a?.id,allocationId:c?.id,allocationName:c?.name})}return{assignments:n,unitKeyValue:s,layers:i,matchedPolicies:o}}function M(r,e,t){return re(r,e,t).assignments}function O(r,e,t){let{assignments:n,unitKeyValue:i,layers:o,matchedPolicies:s}=re(r,e,t),f=Ae(e,s);return{decisionId:I(),assignments:n,metadata:{timestamp:new Date().toISOString(),unitKeyValue:i,layers:o,filteredContext:f}}}var v=class r{constructor(e={}){E(this,"_seen",new Map);E(this,"_ttlMs");E(this,"_maxEntries");E(this,"_lastCleanup",Date.now());this._ttlMs=e.ttlMs??36e5,this._maxEntries=e.maxEntries??1e4}static hashAssignments(e){let t=Object.keys(e).sort(),n=[];for(let i of t){let o=e[i],s=typeof o=="object"?JSON.stringify(o):String(o);n.push(`${i}=${s}`)}return n.join("|")}static createKey(e,t){return`${e}:${t}`}checkAndMark(e,t){let n=r.createKey(e,t),i=Date.now(),o=this._seen.get(n);return o!==void 0&&i-o<this._ttlMs?!1:(this._seen.set(n,i),this._maybeCleanup(i),!0)}wouldBeNew(e,t){let n=r.createKey(e,t),i=Date.now(),o=this._seen.get(n);return o===void 0?!0:i-o>=this._ttlMs}clear(){this._seen.clear()}get size(){return this._seen.size}_maybeCleanup(e){(e-this._lastCleanup>this._ttlMs*.2||this._seen.size>this._maxEntries)&&(this._lastCleanup=e,this._cleanup(e))}_cleanup(e){let t=[];for(let[n,i]of this._seen.entries())e-i>=this._ttlMs&&t.push(n);for(let n of t)this._seen.delete(n);if(this._seen.size>this._maxEntries){let i=Array.from(this._seen.entries()).sort((o,s)=>o[1]-s[1]).slice(0,this._seen.size-this._maxEntries);for(let[o]of i)this._seen.delete(o)}}};var N=class{constructor(e={}){this._seen=new Set;this._lastError=null;this._options=e}capture(e,t,n){try{return t()}catch(i){return this._onError(e,i),n}}async captureAsync(e,t,n){try{return await t()}catch(i){return this._onError(e,i),n}}async swallow(e,t){try{await t()}catch(n){this._onError(e,n)}}getLastError(){let e=this._lastError;return this._lastError=null,e}clearSeen(){this._seen.clear()}_onError(e,t){let n=this._resolveError(t);this._lastError=n;let i=`${e}:${n.name}:${n.message}`;this._seen.has(i)||(this._seen.add(i),console.warn(`[Traffical] Error in ${e}:`,n.message),this._options.onError?.(e,n),this._options.reportErrors&&this._options.errorEndpoint&&this._reportError(e,n).catch(()=>{}))}async _reportError(e,t){if(this._options.errorEndpoint)try{await fetch(this._options.errorEndpoint,{method:"POST",headers:{"Content-Type":"application/json",...this._options.sdkKey&&{"X-Traffical-Key":this._options.sdkKey}},body:JSON.stringify({tag:e,error:t.name,message:t.message,stack:t.stack,timestamp:new Date().toISOString(),sdk:"@traffical/js-client",userAgent:typeof navigator<"u"?navigator.userAgent:void 0})})}catch{}}_resolveError(e){return e instanceof Error?e:typeof e=="string"?new Error(e):new Error("An unknown error occurred")}};var L="failed_events";var K=class{constructor(e){this._queue=[];this._flushTimer=null;this._isFlushing=!1;this._onPageHide=()=>{this.flushBeacon()};this._onVisibilityChange=()=>{document.visibilityState==="hidden"&&this.flushBeacon()};this._onBeforeUnload=()=>{this.flushBeacon()};this._endpoint=e.endpoint,this._apiKey=e.apiKey,this._storage=e.storage,this._batchSize=e.batchSize??10,this._flushIntervalMs=e.flushIntervalMs??3e4,this._onError=e.onError,this._setupListeners(),this._retryFailedEvents(),this._startFlushTimer()}log(e){this._queue.push(e),this._queue.length>=this._batchSize&&this.flush()}async flush(){if(this._isFlushing||this._queue.length===0)return;this._isFlushing=!0;let e=[...this._queue];this._queue=[];try{await this._sendEvents(e)}catch(t){this._persistFailedEvents(e),this._onError?.(t instanceof Error?t:new Error(String(t)))}finally{this._isFlushing=!1}}flushBeacon(){if(this._queue.length===0)return!0;if(typeof fetch>"u")return this.flush(),!1;let e=[...this._queue];return this._queue=[],fetch(this._endpoint,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this._apiKey}`},body:JSON.stringify({events:e}),keepalive:!0}).catch(()=>{this._persistFailedEvents(e)}),!0}get queueSize(){return this._queue.length}destroy(){this._flushTimer&&(clearInterval(this._flushTimer),this._flushTimer=null),this._removeListeners()}async _sendEvents(e){let t=await fetch(this._endpoint,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this._apiKey}`},body:JSON.stringify({events:e})});if(!t.ok)throw new Error(`HTTP ${t.status}: ${t.statusText}`)}_persistFailedEvents(e){let n=[...this._storage.get(L)??[],...e].slice(-100);this._storage.set(L,n)}_retryFailedEvents(){let e=this._storage.get(L);!e||e.length===0||(this._storage.remove(L),this._queue.push(...e))}_startFlushTimer(){this._flushIntervalMs<=0||(this._flushTimer=setInterval(()=>{this.flush().catch(()=>{})},this._flushIntervalMs))}_setupListeners(){typeof window>"u"||(window.addEventListener("pagehide",this._onPageHide),document.addEventListener("visibilitychange",this._onVisibilityChange),window.addEventListener("beforeunload",this._onBeforeUnload))}_removeListeners(){typeof window>"u"||(window.removeEventListener("pagehide",this._onPageHide),document.removeEventListener("visibilitychange",this._onVisibilityChange),window.removeEventListener("beforeunload",this._onBeforeUnload))}};var C="exposure_dedup";var V=class r{constructor(e){this._storage=e.storage,this._sessionTtlMs=e.sessionTtlMs??18e5,this._seen=new Set,this._sessionStart=Date.now(),this._restore()}static createKey(e,t,n){return`${e}:${t}:${n}`}shouldTrack(e){return this._isSessionExpired()&&this._resetSession(),this._seen.has(e)?!1:(this._seen.add(e),this._persist(),!0)}checkAndMark(e,t,n){let i=r.createKey(e,t,n);return this.shouldTrack(i)}clear(){this._seen.clear(),this._storage.remove(C)}get size(){return this._seen.size}_isSessionExpired(){return Date.now()-this._sessionStart>this._sessionTtlMs}_resetSession(){this._seen.clear(),this._sessionStart=Date.now(),this._storage.remove(C)}_persist(){let e={seen:Array.from(this._seen),sessionStart:this._sessionStart};this._storage.set(C,e,this._sessionTtlMs)}_restore(){let e=this._storage.get(C);if(!e)return;if(Date.now()-e.sessionStart>this._sessionTtlMs){this._storage.remove(C);return}this._seen=new Set(e.seen),this._sessionStart=e.sessionStart}};var D="stable_id",Qe="traffical_sid";var F=class{constructor(e){this._cachedId=null;this._storage=e.storage,this._useCookieFallback=e.useCookieFallback??!0,this._cookieName=e.cookieName??Qe}getId(){if(this._cachedId)return this._cachedId;let e=this._storage.get(D);return e?(this._cachedId=e,e):this._useCookieFallback&&(e=this._getCookie(),e)?(this._storage.set(D,e),this._cachedId=e,e):(e=this._generateId(),this._persist(e),this._cachedId=e,e)}setId(e){this._persist(e),this._cachedId=e}clear(){this._storage.remove(D),this._useCookieFallback&&this._deleteCookie(),this._cachedId=null}hasId(){return this._storage.get(D)!==null||this._getCookie()!==null}_persist(e){this._storage.set(D,e),this._useCookieFallback&&this._setCookie(e)}_generateId(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}_getCookie(){if(typeof document>"u")return null;try{let e=document.cookie.split(";");for(let t of e){let[n,i]=t.trim().split("=");if(n===this._cookieName&&i)return decodeURIComponent(i)}}catch{}return null}_setCookie(e){if(!(typeof document>"u"))try{document.cookie=`${this._cookieName}=${encodeURIComponent(e)}; max-age=31536000; path=/; SameSite=Lax`}catch{}}_deleteCookie(){if(!(typeof document>"u"))try{document.cookie=`${this._cookieName}=; max-age=0; path=/`}catch{}}};var k="traffical:",G=class{constructor(){this._available=this._checkAvailability()}get(e){if(!this._available)return null;try{let t=localStorage.getItem(k+e);if(!t)return null;let n=JSON.parse(t);return n.expiresAt&&Date.now()>n.expiresAt?(this.remove(e),null):n.value}catch{return null}}set(e,t,n){if(this._available)try{let i={value:t,...n&&{expiresAt:Date.now()+n}};localStorage.setItem(k+e,JSON.stringify(i))}catch{}}remove(e){if(this._available)try{localStorage.removeItem(k+e)}catch{}}clear(){if(this._available)try{let e=[];for(let t=0;t<localStorage.length;t++){let n=localStorage.key(t);n?.startsWith(k)&&e.push(n)}e.forEach(t=>localStorage.removeItem(t))}catch{}}_checkAvailability(){try{let e=k+"__test__";return localStorage.setItem(e,"test"),localStorage.removeItem(e),!0}catch{return!1}}},J=class{constructor(){this._store=new Map}get(e){let t=this._store.get(e);return t?t.expiresAt&&Date.now()>t.expiresAt?(this.remove(e),null):t.value:null}set(e,t,n){this._store.set(e,{value:t,...n&&{expiresAt:Date.now()+n}})}remove(e){this._store.delete(e)}clear(){this._store.clear()}};function ie(){let r=new G;return r.get("__check__")!==null||et()?r:new J}function et(){try{let r="__traffical_storage_test__";return localStorage.setItem(r,"test"),localStorage.removeItem(r),!0}catch{return!1}}var tt="js-client",nt="0.1.0";function Y(r,e){let t=new v({ttlMs:r.deduplicationTtlMs});return{name:"decision-tracking",onDecision(n){if(r.disabled)return;let i=n.metadata.unitKeyValue;if(!i)return;let o=v.hashAssignments(n.assignments);if(!t.checkAndMark(i,o))return;let s={type:"decision",id:n.decisionId,orgId:e.orgId,projectId:e.projectId,env:e.env,unitKey:i,timestamp:n.metadata.timestamp,assignments:n.assignments,layers:n.metadata.layers,context:n.metadata.filteredContext,sdkName:tt,sdkVersion:nt};e.log(s)},onDestroy(){t.clear()}}}var j=class{constructor(){this._plugins=[]}register(e){let t="plugin"in e?e.plugin:e,n="priority"in e?e.priority??0:0;if(this._plugins.some(i=>i.plugin.name===t.name)){console.warn(`[Traffical] Plugin "${t.name}" already registered, skipping.`);return}this._plugins.push({plugin:t,priority:n}),this._plugins.sort((i,o)=>o.priority-i.priority)}unregister(e){let t=this._plugins.findIndex(n=>n.plugin.name===e);return t===-1?!1:(this._plugins.splice(t,1),!0)}get(e){return this._plugins.find(t=>t.plugin.name===e)?.plugin}getAll(){return this._plugins.map(e=>e.plugin)}async runInitialize(){for(let{plugin:e}of this._plugins)if(e.onInitialize)try{await e.onInitialize()}catch(t){console.warn(`[Traffical] Plugin "${e.name}" onInitialize error:`,t)}}runConfigUpdate(e){for(let{plugin:t}of this._plugins)if(t.onConfigUpdate)try{t.onConfigUpdate(e)}catch(n){console.warn(`[Traffical] Plugin "${t.name}" onConfigUpdate error:`,n)}}runBeforeDecision(e){let t=e;for(let{plugin:n}of this._plugins)if(n.onBeforeDecision)try{let i=n.onBeforeDecision(t);i&&(t=i)}catch(i){console.warn(`[Traffical] Plugin "${n.name}" onBeforeDecision error:`,i)}return t}runDecision(e){for(let{plugin:t}of this._plugins)if(t.onDecision)try{t.onDecision(e)}catch(n){console.warn(`[Traffical] Plugin "${t.name}" onDecision error:`,n)}}runResolve(e){for(let{plugin:t}of this._plugins)if(t.onResolve)try{t.onResolve(e)}catch(n){console.warn(`[Traffical] Plugin "${t.name}" onResolve error:`,n)}}runExposure(e){for(let{plugin:t}of this._plugins)if(t.onExposure)try{if(t.onExposure(e)===!1)return!1}catch(n){console.warn(`[Traffical] Plugin "${t.name}" onExposure error:`,n)}return!0}runTrack(e){for(let{plugin:t}of this._plugins)if(t.onTrack)try{if(t.onTrack(e)===!1)return!1}catch(n){console.warn(`[Traffical] Plugin "${t.name}" onTrack error:`,n)}return!0}runDestroy(){for(let{plugin:e}of this._plugins)if(e.onDestroy)try{e.onDestroy()}catch(t){console.warn(`[Traffical] Plugin "${e.name}" onDestroy error:`,t)}}clear(){this._plugins=[]}};var oe="js-client",se="0.1.0",rt="https://sdk.traffical.io",it=6e4,ot=3e5,st=100,T=class{constructor(e){this._state={bundle:null,etag:null,lastFetchTime:0,lastOfflineWarning:0,refreshTimer:null,isInitialized:!1};this._decisionCache=new Map;if(this._options={orgId:e.orgId,projectId:e.projectId,env:e.env,apiKey:e.apiKey,baseUrl:e.baseUrl??rt,localConfig:e.localConfig,refreshIntervalMs:e.refreshIntervalMs??it},this._errorBoundary=new N(e.errorBoundary),this._storage=e.storage??ie(),this._eventLogger=new K({endpoint:`${this._options.baseUrl}/v1/events/batch`,apiKey:e.apiKey,storage:this._storage,batchSize:e.eventBatchSize,flushIntervalMs:e.eventFlushIntervalMs,onError:t=>{console.warn("[Traffical] Event logging error:",t.message)}}),this._exposureDedup=new V({storage:this._storage,sessionTtlMs:e.exposureSessionTtlMs}),this._stableId=new F({storage:this._storage}),this._plugins=new j,e.trackDecisions!==!1&&this._plugins.register({plugin:Y({deduplicationTtlMs:e.decisionDeduplicationTtlMs},{orgId:this._options.orgId,projectId:this._options.projectId,env:this._options.env,log:t=>this._eventLogger.log(t)}),priority:100}),e.plugins)for(let t of e.plugins)this._plugins.register(t);this._options.localConfig&&(this._state.bundle=this._options.localConfig,this._plugins.runConfigUpdate(this._options.localConfig))}async initialize(){await this._errorBoundary.captureAsync("initialize",async()=>{await this._fetchConfig(),this._startBackgroundRefresh(),this._state.isInitialized=!0,await this._plugins.runInitialize()},void 0)}get isInitialized(){return this._state.isInitialized}destroy(){this._state.refreshTimer&&(clearInterval(this._state.refreshTimer),this._state.refreshTimer=null),this._eventLogger.flushBeacon(),this._eventLogger.destroy(),this._plugins.runDestroy()}async refreshConfig(){await this._errorBoundary.swallow("refreshConfig",async()=>{await this._fetchConfig()})}getConfigVersion(){return this._state.bundle?.version??null}getParams(e){return this._errorBoundary.capture("getParams",()=>{let t=this._getEffectiveBundle(),n=this._enrichContext(e.context),i=M(t,n,e.defaults);return this._plugins.runResolve(i),i},e.defaults)}decide(e){return this._errorBoundary.capture("decide",()=>{let t=this._getEffectiveBundle(),n=this._enrichContext(e.context);n=this._plugins.runBeforeDecision(n);let i=O(t,n,e.defaults);return this._cacheDecision(i),this._plugins.runDecision(i),i},{decisionId:I(),assignments:e.defaults,metadata:{timestamp:new Date().toISOString(),unitKeyValue:"",layers:[]}})}trackExposure(e){this._errorBoundary.capture("trackExposure",()=>{let t=e.metadata.unitKeyValue;if(t)for(let n of e.metadata.layers){if(!n.policyId||!n.allocationName||!this._exposureDedup.checkAndMark(t,n.policyId,n.allocationName))continue;let o={type:"exposure",id:W(),decisionId:e.decisionId,orgId:this._options.orgId,projectId:this._options.projectId,env:this._options.env,unitKey:t,timestamp:new Date().toISOString(),assignments:e.assignments,layers:e.metadata.layers,context:e.metadata.filteredContext,sdkName:oe,sdkVersion:se};this._plugins.runExposure(o)&&this._eventLogger.log(o)}},void 0)}track(e,t,n){this._errorBoundary.capture("track",()=>{let i=n?.unitKey??this._stableId.getId(),o=typeof t?.value=="number"?t.value:void 0,s,f=n?.decisionId;if(f){let g=this._decisionCache.get(f);g&&(s=g.metadata.layers.filter(u=>u.policyId&&u.allocationName).map(u=>({layerId:u.layerId,policyId:u.policyId,allocationName:u.allocationName})))}let h={type:"track",id:H(),orgId:this._options.orgId,projectId:this._options.projectId,env:this._options.env,unitKey:i,timestamp:new Date().toISOString(),event:e,value:o,properties:t,decisionId:f,attribution:s,sdkName:oe,sdkVersion:se};this._plugins.runTrack(h)&&this._eventLogger.log(h)},void 0)}async flushEvents(){await this._errorBoundary.swallow("flushEvents",async()=>{await this._eventLogger.flush()})}use(e){return this._plugins.register(e),this}getPlugin(e){return this._plugins.get(e)}getStableId(){return this._stableId.getId()}setStableId(e){this._stableId.setId(e)}_getEffectiveBundle(){return this._state.bundle??this._options.localConfig??null}_enrichContext(e){let n=this._getEffectiveBundle()?.hashing?.unitKey??"userId";return e[n]?e:{...e,[n]:this._stableId.getId()}}async _fetchConfig(){let e=`${this._options.baseUrl}/v1/config/${this._options.projectId}?env=${this._options.env}`,t={"Content-Type":"application/json",Authorization:`Bearer ${this._options.apiKey}`};this._state.etag&&(t["If-None-Match"]=this._state.etag);try{let n=await fetch(e,{method:"GET",headers:t});if(n.status===304){this._state.lastFetchTime=Date.now();return}if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);let i=await n.json(),o=n.headers.get("ETag");this._state.bundle=i,this._state.etag=o,this._state.lastFetchTime=Date.now(),this._plugins.runConfigUpdate(i)}catch(n){this._logOfflineWarning(n)}}_startBackgroundRefresh(){this._options.refreshIntervalMs<=0||(this._state.refreshTimer=setInterval(()=>{this._fetchConfig().catch(()=>{})},this._options.refreshIntervalMs))}_logOfflineWarning(e){let t=Date.now();t-this._state.lastOfflineWarning>ot&&(console.warn(`[Traffical] Failed to fetch config: ${e instanceof Error?e.message:String(e)}. Using ${this._state.bundle?"cached":"local"} config.`),this._state.lastOfflineWarning=t)}_cacheDecision(e){if(this._decisionCache.size>=st){let t=this._decisionCache.keys().next().value;t&&this._decisionCache.delete(t)}this._decisionCache.set(e.decisionId,e)}};async function ae(r){let e=new T(r);return await e.initialize(),e}function le(r){return new T(r)}function ue(r={}){let e={observeMutations:r.observeMutations??!0,debounceMs:r.debounceMs??100},t=[],n={},i=null,o=null;function s(a,c){try{return new RegExp(a).test(c)}catch{return c===a}}function f(a,c,l){if(c==="innerHTML")a.innerHTML=l;else if(c==="textContent")a.textContent=l;else if(c==="src"&&"src"in a)a.src=l;else if(c==="href"&&"href"in a)a.href=l;else if(c.startsWith("style.")){let d=c.slice(6);a.style[d]=l}else a.setAttribute(c,l)}function h(a,c){let l=String(c);try{let d=document.querySelectorAll(a.selector);for(let m of d)f(m,a.property,l)}catch(d){console.warn(`[Traffical DOM Binding] Failed to apply binding for ${a.parameterKey}:`,d)}}function g(a,c=!1){n=a;let l=typeof window<"u"?window.location.pathname:"";for(let d of t){if(!c&&!s(d.urlPattern,l))continue;let m=a[d.parameterKey];m!==void 0&&h(d,m)}}function u(){o&&clearTimeout(o),o=setTimeout(()=>{g(n)},e.debounceMs)}function _(){i||typeof MutationObserver>"u"||typeof document>"u"||(i=new MutationObserver(()=>{u()}),i.observe(document.body,{childList:!0,subtree:!0}))}function y(){i&&(i.disconnect(),i=null),o&&(clearTimeout(o),o=null)}return{name:"dom-binding",onInitialize(){e.observeMutations&&_()},onConfigUpdate(a){t=a.domBindings??[]},onResolve(a){g(a)},onDecision(a){g(a.assignments)},onDestroy(){y(),t=[],n={}},applyBindings(a){g(a||n)},getBindings(){return t}}}var p=null;async function at(r){return p?(console.warn("[Traffical] Client already initialized. Returning existing instance."),p):(p=await ae(r),p)}function lt(r){return p?(console.warn("[Traffical] Client already initialized. Returning existing instance."),p):(p=le(r),p.initialize().catch(e=>{console.warn("[Traffical] Initialization error:",e)}),p)}function ut(){return p}function ct(){p&&(p.destroy(),p=null)}return ye(dt);})();
3
+ //# sourceMappingURL=traffical.min.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../node_modules/.bun/ulid@2.4.0/node_modules/ulid/stubs/crypto.js", "../src/global.ts", "../../core/src/hashing/fnv1a.ts", "../../core/src/hashing/bucket.ts", "../../core/src/resolution/conditions.ts", "../../../node_modules/.bun/nanoid@5.1.6/node_modules/nanoid/index.browser.js", "../../../node_modules/.bun/ulid@2.4.0/node_modules/ulid/dist/index.esm.js", "../../core/src/ids/index.ts", "../../core/src/resolution/engine.ts", "../../core/src/dedup/decision-dedup.ts", "../src/error-boundary.ts", "../src/event-logger.ts", "../src/exposure-dedup.ts", "../src/stable-id.ts", "../src/storage.ts", "../src/plugins/decision-tracking.ts", "../src/plugins/index.ts", "../src/client.ts", "../src/plugins/dom-binding.ts"],
4
+ "sourcesContent": ["", "/**\n * Global entry point for IIFE bundle.\n *\n * Exports `window.Traffical` for script tag usage:\n *\n * ```html\n * <script src=\"https://cdn.traffical.io/js-client/v1/traffical.min.js\"></script>\n * <script>\n * Traffical.init({ ... }).then(function(client) {\n * var params = client.getParams({ ... });\n * });\n * </script>\n * ```\n */\n\nimport {\n TrafficalClient,\n createTrafficalClient,\n createTrafficalClientSync,\n type TrafficalClientOptions,\n} from \"./client.js\";\nimport type { TrafficalPlugin } from \"./plugins/index.js\";\nimport {\n createDOMBindingPlugin,\n type DOMBindingPlugin,\n type DOMBindingPluginOptions,\n} from \"./plugins/dom-binding.js\";\n\n// Global state for singleton pattern\nlet _instance: TrafficalClient | null = null;\n\n/**\n * Initialize the Traffical client (async).\n * Returns the client instance.\n */\nasync function init(options: TrafficalClientOptions): Promise<TrafficalClient> {\n if (_instance) {\n console.warn(\"[Traffical] Client already initialized. Returning existing instance.\");\n return _instance;\n }\n\n _instance = await createTrafficalClient(options);\n return _instance;\n}\n\n/**\n * Initialize the Traffical client (sync).\n * Returns the client instance immediately, but config fetch happens async.\n */\nfunction initSync(options: TrafficalClientOptions): TrafficalClient {\n if (_instance) {\n console.warn(\"[Traffical] Client already initialized. Returning existing instance.\");\n return _instance;\n }\n\n _instance = createTrafficalClientSync(options);\n\n // Start async initialization in background\n _instance.initialize().catch((error) => {\n console.warn(\"[Traffical] Initialization error:\", error);\n });\n\n return _instance;\n}\n\n/**\n * Get the singleton client instance.\n * Returns null if not initialized.\n */\nfunction instance(): TrafficalClient | null {\n return _instance;\n}\n\n/**\n * Destroy the singleton instance.\n */\nfunction destroy(): void {\n if (_instance) {\n _instance.destroy();\n _instance = null;\n }\n}\n\n// Export the Traffical global object\nexport {\n init,\n initSync,\n instance,\n destroy,\n TrafficalClient,\n type TrafficalClientOptions,\n type TrafficalPlugin,\n // DOM binding plugin\n createDOMBindingPlugin,\n type DOMBindingPlugin,\n type DOMBindingPluginOptions,\n};\n\n", "/**\n * FNV-1a Hash Function\n *\n * A simple, fast hash function with good distribution properties.\n * Used by Google's experimentation system for bucket assignment.\n *\n * This implementation produces consistent results across all platforms\n * and is the canonical hash for Traffical SDKs.\n */\n\nconst FNV_OFFSET_BASIS = 2166136261;\nconst FNV_PRIME = 16777619;\n\n/**\n * Computes the FNV-1a hash of a string.\n *\n * @param input - The string to hash\n * @returns Unsigned 32-bit integer hash\n */\nexport function fnv1a(input: string): number {\n let hash = FNV_OFFSET_BASIS;\n\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i);\n hash = Math.imul(hash, FNV_PRIME);\n }\n\n // Convert to unsigned 32-bit integer\n return hash >>> 0;\n}\n\n", "/**\n * Bucket Computation\n *\n * Deterministic bucket assignment for traffic splitting.\n * The bucket is computed as: hash(unitKeyValue + \":\" + layerId) % bucketCount\n *\n * This ensures:\n * - Same user always gets same bucket for a given layer\n * - Different layers can have independent bucketing (orthogonality)\n * - Deterministic results across SDK and server\n */\n\nimport { fnv1a } from \"./fnv1a.js\";\n\n/**\n * Computes the bucket for a given unit and layer.\n *\n * @param unitKeyValue - The value of the unit key (e.g., userId value)\n * @param layerId - The layer ID for orthogonal bucketing\n * @param bucketCount - Total number of buckets (e.g., 1000)\n * @returns Bucket number in range [0, bucketCount - 1]\n */\nexport function computeBucket(\n unitKeyValue: string,\n layerId: string,\n bucketCount: number\n): number {\n // Concatenate unit key and layer ID with separator\n const input = `${unitKeyValue}:${layerId}`;\n\n // Use FNV-1a for consistent hashing\n const hash = fnv1a(input);\n\n // Map to bucket range\n return hash % bucketCount;\n}\n\n/**\n * Checks if a bucket falls within a range.\n *\n * @param bucket - The computed bucket\n * @param range - [start, end] inclusive range\n * @returns True if bucket is in range\n */\nexport function isInBucketRange(\n bucket: number,\n range: [number, number]\n): boolean {\n return bucket >= range[0] && bucket <= range[1];\n}\n\n/**\n * Finds which allocation matches a given bucket.\n *\n * @param bucket - The computed bucket\n * @param allocations - Array of allocations with bucket ranges\n * @returns The matching allocation, or null if none match\n */\nexport function findMatchingAllocation<\n T extends { bucketRange: [number, number] }\n>(bucket: number, allocations: T[]): T | null {\n for (const allocation of allocations) {\n if (isInBucketRange(bucket, allocation.bucketRange)) {\n return allocation;\n }\n }\n return null;\n}\n\n/**\n * Converts a percentage to a bucket range.\n *\n * @param percentage - Traffic percentage (0-100)\n * @param bucketCount - Total buckets\n * @param startBucket - Starting bucket (default 0)\n * @returns [start, end] bucket range\n */\nexport function percentageToBucketRange(\n percentage: number,\n bucketCount: number,\n startBucket = 0\n): [number, number] {\n const bucketsNeeded = Math.floor((percentage / 100) * bucketCount);\n const endBucket = Math.min(startBucket + bucketsNeeded - 1, bucketCount - 1);\n return [startBucket, endBucket];\n}\n\n/**\n * Creates non-overlapping bucket ranges for multiple variants.\n *\n * @param percentages - Array of percentages that should sum to <= 100\n * @param bucketCount - Total buckets\n * @returns Array of [start, end] bucket ranges\n */\nexport function createBucketRanges(\n percentages: number[],\n bucketCount: number\n): [number, number][] {\n const ranges: [number, number][] = [];\n let currentBucket = 0;\n\n for (const percentage of percentages) {\n if (percentage <= 0) continue;\n\n const bucketsNeeded = Math.floor((percentage / 100) * bucketCount);\n if (bucketsNeeded > 0) {\n const endBucket = currentBucket + bucketsNeeded - 1;\n ranges.push([currentBucket, endBucket]);\n currentBucket = endBucket + 1;\n }\n }\n\n return ranges;\n}\n\n", "/**\n * Condition Evaluation\n *\n * Evaluates context predicates to determine policy eligibility.\n * Conditions are AND-ed together: all must match for a policy to apply.\n */\n\nimport type { Context, BundleCondition } from \"../types/index.js\";\n\n/**\n * Evaluates a single condition against a context.\n *\n * @param condition - The condition to evaluate\n * @param context - The context to evaluate against\n * @returns True if the condition matches\n */\nexport function evaluateCondition(\n condition: BundleCondition,\n context: Context\n): boolean {\n const { field, op, value, values } = condition;\n\n // Get the context value using dot notation\n const contextValue = getNestedValue(context, field);\n\n switch (op) {\n case \"eq\":\n return contextValue === value;\n\n case \"neq\":\n return contextValue !== value;\n\n case \"in\":\n if (!Array.isArray(values)) return false;\n return values.includes(contextValue);\n\n case \"nin\":\n if (!Array.isArray(values)) return true;\n return !values.includes(contextValue);\n\n case \"gt\":\n return (\n typeof contextValue === \"number\" && contextValue > (value as number)\n );\n\n case \"gte\":\n return (\n typeof contextValue === \"number\" && contextValue >= (value as number)\n );\n\n case \"lt\":\n return (\n typeof contextValue === \"number\" && contextValue < (value as number)\n );\n\n case \"lte\":\n return (\n typeof contextValue === \"number\" && contextValue <= (value as number)\n );\n\n case \"contains\":\n return (\n typeof contextValue === \"string\" &&\n typeof value === \"string\" &&\n contextValue.includes(value)\n );\n\n case \"startsWith\":\n return (\n typeof contextValue === \"string\" &&\n typeof value === \"string\" &&\n contextValue.startsWith(value)\n );\n\n case \"endsWith\":\n return (\n typeof contextValue === \"string\" &&\n typeof value === \"string\" &&\n contextValue.endsWith(value)\n );\n\n case \"regex\":\n if (typeof contextValue !== \"string\" || typeof value !== \"string\") {\n return false;\n }\n try {\n const regex = new RegExp(value);\n return regex.test(contextValue);\n } catch {\n return false;\n }\n\n case \"exists\":\n return contextValue !== undefined && contextValue !== null;\n\n case \"notExists\":\n return contextValue === undefined || contextValue === null;\n\n default:\n // Unknown operator, fail safe by not matching\n return false;\n }\n}\n\n/**\n * Evaluates all conditions against a context.\n * All conditions must match (AND logic).\n *\n * @param conditions - Array of conditions\n * @param context - The context to evaluate against\n * @returns True if all conditions match (or if there are no conditions)\n */\nexport function evaluateConditions(\n conditions: BundleCondition[],\n context: Context\n): boolean {\n // Empty conditions = always match\n if (conditions.length === 0) {\n return true;\n }\n\n // All conditions must match (AND)\n return conditions.every((condition) => evaluateCondition(condition, context));\n}\n\n/**\n * Gets a nested value from an object using dot notation.\n *\n * @example\n * getNestedValue({ user: { name: \"Alice\" } }, \"user.name\") // \"Alice\"\n * getNestedValue({ tags: [\"a\", \"b\"] }, \"tags.0\") // \"a\"\n */\nfunction getNestedValue(obj: Record<string, unknown>, path: string): unknown {\n const parts = path.split(\".\");\n let current: unknown = obj;\n\n for (const part of parts) {\n if (current === null || current === undefined) {\n return undefined;\n }\n\n if (typeof current === \"object\") {\n current = (current as Record<string, unknown>)[part];\n } else {\n return undefined;\n }\n }\n\n return current;\n}\n\n// =============================================================================\n// Condition Builder Helpers\n// =============================================================================\n\n/**\n * Creates an equality condition.\n */\nexport function eq(field: string, value: unknown): BundleCondition {\n return { field, op: \"eq\", value };\n}\n\n/**\n * Creates a not-equal condition.\n */\nexport function neq(field: string, value: unknown): BundleCondition {\n return { field, op: \"neq\", value };\n}\n\n/**\n * Creates an \"in\" condition.\n */\nexport function inValues(field: string, values: unknown[]): BundleCondition {\n return { field, op: \"in\", values };\n}\n\n/**\n * Creates a \"not in\" condition.\n */\nexport function notIn(field: string, values: unknown[]): BundleCondition {\n return { field, op: \"nin\", values };\n}\n\n/**\n * Creates a greater-than condition.\n */\nexport function gt(field: string, value: number): BundleCondition {\n return { field, op: \"gt\", value };\n}\n\n/**\n * Creates a greater-than-or-equal condition.\n */\nexport function gte(field: string, value: number): BundleCondition {\n return { field, op: \"gte\", value };\n}\n\n/**\n * Creates a less-than condition.\n */\nexport function lt(field: string, value: number): BundleCondition {\n return { field, op: \"lt\", value };\n}\n\n/**\n * Creates a less-than-or-equal condition.\n */\nexport function lte(field: string, value: number): BundleCondition {\n return { field, op: \"lte\", value };\n}\n\n/**\n * Creates a string contains condition.\n */\nexport function contains(field: string, value: string): BundleCondition {\n return { field, op: \"contains\", value };\n}\n\n/**\n * Creates a string starts-with condition.\n */\nexport function startsWith(field: string, value: string): BundleCondition {\n return { field, op: \"startsWith\", value };\n}\n\n/**\n * Creates a string ends-with condition.\n */\nexport function endsWith(field: string, value: string): BundleCondition {\n return { field, op: \"endsWith\", value };\n}\n\n/**\n * Creates a regex match condition.\n */\nexport function regex(field: string, pattern: string): BundleCondition {\n return { field, op: \"regex\", value: pattern };\n}\n\n/**\n * Creates an exists condition.\n */\nexport function exists(field: string): BundleCondition {\n return { field, op: \"exists\" };\n}\n\n/**\n * Creates a not-exists condition.\n */\nexport function notExists(field: string): BundleCondition {\n return { field, op: \"notExists\" };\n}\n\n", "/* @ts-self-types=\"./index.d.ts\" */\nimport { urlAlphabet as scopedUrlAlphabet } from './url-alphabet/index.js'\nexport { urlAlphabet } from './url-alphabet/index.js'\nexport let random = bytes => crypto.getRandomValues(new Uint8Array(bytes))\nexport let customRandom = (alphabet, defaultSize, getRandom) => {\n let mask = (2 << Math.log2(alphabet.length - 1)) - 1\n let step = -~((1.6 * mask * defaultSize) / alphabet.length)\n return (size = defaultSize) => {\n let id = ''\n while (true) {\n let bytes = getRandom(step)\n let j = step | 0\n while (j--) {\n id += alphabet[bytes[j] & mask] || ''\n if (id.length >= size) return id\n }\n }\n }\n}\nexport let customAlphabet = (alphabet, size = 21) =>\n customRandom(alphabet, size | 0, random)\nexport let nanoid = (size = 21) => {\n let id = ''\n let bytes = crypto.getRandomValues(new Uint8Array((size |= 0)))\n while (size--) {\n id += scopedUrlAlphabet[bytes[size] & 63]\n }\n return id\n}\n", "function createError(message) {\n const err = new Error(message);\n err.source = \"ulid\";\n return err;\n}\n// These values should NEVER change. If\n// they do, we're no longer making ulids!\nconst ENCODING = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\"; // Crockford's Base32\nconst ENCODING_LEN = ENCODING.length;\nconst TIME_MAX = Math.pow(2, 48) - 1;\nconst TIME_LEN = 10;\nconst RANDOM_LEN = 16;\nfunction replaceCharAt(str, index, char) {\n if (index > str.length - 1) {\n return str;\n }\n return str.substr(0, index) + char + str.substr(index + 1);\n}\nfunction incrementBase32(str) {\n let done = undefined;\n let index = str.length;\n let char;\n let charIndex;\n const maxCharIndex = ENCODING_LEN - 1;\n while (!done && index-- >= 0) {\n char = str[index];\n charIndex = ENCODING.indexOf(char);\n if (charIndex === -1) {\n throw createError(\"incorrectly encoded string\");\n }\n if (charIndex === maxCharIndex) {\n str = replaceCharAt(str, index, ENCODING[0]);\n continue;\n }\n done = replaceCharAt(str, index, ENCODING[charIndex + 1]);\n }\n if (typeof done === \"string\") {\n return done;\n }\n throw createError(\"cannot increment this string\");\n}\nfunction randomChar(prng) {\n let rand = Math.floor(prng() * ENCODING_LEN);\n if (rand === ENCODING_LEN) {\n rand = ENCODING_LEN - 1;\n }\n return ENCODING.charAt(rand);\n}\nfunction encodeTime(now, len) {\n if (isNaN(now)) {\n throw new Error(now + \" must be a number\");\n }\n if (now > TIME_MAX) {\n throw createError(\"cannot encode time greater than \" + TIME_MAX);\n }\n if (now < 0) {\n throw createError(\"time must be positive\");\n }\n if (Number.isInteger(Number(now)) === false) {\n throw createError(\"time must be an integer\");\n }\n let mod;\n let str = \"\";\n for (; len > 0; len--) {\n mod = now % ENCODING_LEN;\n str = ENCODING.charAt(mod) + str;\n now = (now - mod) / ENCODING_LEN;\n }\n return str;\n}\nfunction encodeRandom(len, prng) {\n let str = \"\";\n for (; len > 0; len--) {\n str = randomChar(prng) + str;\n }\n return str;\n}\nfunction decodeTime(id) {\n if (id.length !== TIME_LEN + RANDOM_LEN) {\n throw createError(\"malformed ulid\");\n }\n var time = id\n .substr(0, TIME_LEN)\n .split(\"\")\n .reverse()\n .reduce((carry, char, index) => {\n const encodingIndex = ENCODING.indexOf(char);\n if (encodingIndex === -1) {\n throw createError(\"invalid character found: \" + char);\n }\n return (carry += encodingIndex * Math.pow(ENCODING_LEN, index));\n }, 0);\n if (time > TIME_MAX) {\n throw createError(\"malformed ulid, timestamp too large\");\n }\n return time;\n}\nfunction detectPrng(allowInsecure = false, root) {\n if (!root) {\n root = typeof window !== \"undefined\" ? window : null;\n }\n const browserCrypto = root && (root.crypto || root.msCrypto);\n if (browserCrypto) {\n return () => {\n const buffer = new Uint8Array(1);\n browserCrypto.getRandomValues(buffer);\n return buffer[0] / 0xff;\n };\n }\n else {\n try {\n const nodeCrypto = require(\"crypto\");\n return () => nodeCrypto.randomBytes(1).readUInt8() / 0xff;\n }\n catch (e) { }\n }\n if (allowInsecure) {\n try {\n console.error(\"secure crypto unusable, falling back to insecure Math.random()!\");\n }\n catch (e) { }\n return () => Math.random();\n }\n throw createError(\"secure crypto unusable, insecure Math.random not allowed\");\n}\nfunction factory(currPrng) {\n if (!currPrng) {\n currPrng = detectPrng();\n }\n return function ulid(seedTime) {\n if (isNaN(seedTime)) {\n seedTime = Date.now();\n }\n return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN, currPrng);\n };\n}\nfunction monotonicFactory(currPrng) {\n if (!currPrng) {\n currPrng = detectPrng();\n }\n let lastTime = 0;\n let lastRandom;\n return function ulid(seedTime) {\n if (isNaN(seedTime)) {\n seedTime = Date.now();\n }\n if (seedTime <= lastTime) {\n const incrementedRandom = (lastRandom = incrementBase32(lastRandom));\n return encodeTime(lastTime, TIME_LEN) + incrementedRandom;\n }\n lastTime = seedTime;\n const newRandom = (lastRandom = encodeRandom(RANDOM_LEN, currPrng));\n return encodeTime(seedTime, TIME_LEN) + newRandom;\n };\n}\nconst ulid = factory();\n\nexport { decodeTime, detectPrng, encodeRandom, encodeTime, factory, incrementBase32, monotonicFactory, randomChar, replaceCharAt, ulid };\n", "/**\n * ID Generation Utilities\n *\n * Provides consistent ID generation with type prefixes for all entities and events.\n *\n * Entity IDs: 8-character NanoID with prefix (e.g., \"proj_hVF1cCoC\")\n * - Compact and URL-friendly\n * - 64^8 = 281 trillion combinations\n * - With DB constraints, collisions are handled via retry\n *\n * Event IDs: ULID with prefix (e.g., \"dec_01JHFK1WWMMG7M0XPEBTYXZEBW\")\n * - Lexicographically sortable (time-ordered)\n * - Contains millisecond timestamp for analytics\n */\n\nimport { customAlphabet } from \"nanoid\";\nimport { ulid } from \"ulid\";\n\n// =============================================================================\n// NanoID Configuration\n// =============================================================================\n\n/**\n * URL-safe alphabet for NanoID (64 characters).\n * Includes: 0-9, A-Z, a-z (no special chars to avoid URL encoding issues)\n */\nconst NANOID_ALPHABET = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n\n/**\n * Default length for entity IDs (without prefix).\n * 64^8 = 281,474,976,710,656 (~281 trillion) combinations.\n */\nconst ENTITY_ID_LENGTH = 8;\n\n/**\n * NanoID generator with custom alphabet.\n */\nconst nanoid = customAlphabet(NANOID_ALPHABET, ENTITY_ID_LENGTH);\n\n// =============================================================================\n// ID Prefixes\n// =============================================================================\n\n/**\n * Entity ID prefixes for each entity type.\n */\nexport type EntityIdPrefix =\n | \"org\" // Organization\n | \"proj\" // Project\n | \"env\" // Environment\n | \"ns\" // Namespace\n | \"lay\" // Layer\n | \"pol\" // Policy\n | \"alloc\" // Allocation\n | \"param\" // Parameter\n | \"dom\" // DOM Binding\n | \"ovr\" // Environment Override\n | \"ak\"; // API Key\n\n/**\n * Event ID prefixes for each event type.\n */\nexport type EventIdPrefix = \"dec\" | \"exp\" | \"trk\";\n\n// =============================================================================\n// Generic ID Generation\n// =============================================================================\n\n/**\n * Generates a prefixed 8-char NanoID for the specified entity type.\n *\n * @param prefix - The entity type prefix\n * @returns A prefixed NanoID string (e.g., \"proj_hVF1cCoC\")\n */\nexport function generateEntityId(prefix: EntityIdPrefix): string {\n return `${prefix}_${nanoid()}`;\n}\n\n/**\n * Generates a prefixed ULID for the specified event type.\n * Events use ULID for time-sortability in analytics.\n *\n * @param prefix - The event type prefix\n * @returns A prefixed ULID string (e.g., \"dec_01JHFK1WWMMG7M0XPEBTYXZEBW\")\n */\nexport function generateEventId(prefix: EventIdPrefix): string {\n return `${prefix}_${ulid()}`;\n}\n\n/**\n * Generates a plain 8-char NanoID without prefix.\n * Used for internal IDs that don't need type identification.\n */\nexport function generateShortId(): string {\n return nanoid();\n}\n\n// =============================================================================\n// Entity ID Convenience Functions\n// =============================================================================\n\n/** Generates an Organization ID with \"org_\" prefix */\nexport function generateOrgId(): string {\n return generateEntityId(\"org\");\n}\n\n/** Generates a Project ID with \"proj_\" prefix */\nexport function generateProjectId(): string {\n return generateEntityId(\"proj\");\n}\n\n/** Generates an Environment ID with \"env_\" prefix */\nexport function generateEnvironmentId(): string {\n return generateEntityId(\"env\");\n}\n\n/** Generates a Namespace ID with \"ns_\" prefix */\nexport function generateNamespaceId(): string {\n return generateEntityId(\"ns\");\n}\n\n/** Generates a Layer ID with \"lay_\" prefix */\nexport function generateLayerId(): string {\n return generateEntityId(\"lay\");\n}\n\n/** Generates a Policy ID with \"pol_\" prefix */\nexport function generatePolicyId(): string {\n return generateEntityId(\"pol\");\n}\n\n/** Generates an Allocation ID with \"alloc_\" prefix */\nexport function generateAllocationId(): string {\n return generateEntityId(\"alloc\");\n}\n\n/** Generates a Parameter ID with \"param_\" prefix */\nexport function generateParameterId(): string {\n return generateEntityId(\"param\");\n}\n\n/** Generates a DOM Binding ID with \"dom_\" prefix */\nexport function generateDomBindingId(): string {\n return generateEntityId(\"dom\");\n}\n\n/** Generates an Environment Override ID with \"ovr_\" prefix */\nexport function generateOverrideId(): string {\n return generateEntityId(\"ovr\");\n}\n\n/** Generates an API Key ID with \"ak_\" prefix */\nexport function generateApiKeyId(): string {\n return generateEntityId(\"ak\");\n}\n\n// =============================================================================\n// Event ID Convenience Functions (Keep ULID for time-sortability)\n// =============================================================================\n\n/** Generates a Decision event ID with \"dec_\" prefix (ULID) */\nexport function generateDecisionId(): string {\n return generateEventId(\"dec\");\n}\n\n/** Generates an Exposure event ID with \"exp_\" prefix (ULID) */\nexport function generateExposureId(): string {\n return generateEventId(\"exp\");\n}\n\n/** Generates a Track event ID with \"trk_\" prefix (ULID) */\nexport function generateTrackEventId(): string {\n return generateEventId(\"trk\");\n}\n\n// =============================================================================\n// Utilities\n// =============================================================================\n\n/**\n * Extracts the timestamp from a ULID-based ID.\n * Only works for event IDs (ULID format).\n *\n * @param id - A prefixed ULID (e.g., \"dec_01JHFK1WWMMG7M0XPEBTYXZEBW\")\n * @returns The timestamp as a Date, or null if invalid\n */\nexport function getIdTimestamp(id: string): Date | null {\n // Extract the ULID part (after the prefix and underscore)\n const parts = id.split(\"_\");\n if (parts.length < 2) {\n return null;\n }\n\n const ulidPart = parts[1];\n if (!ulidPart || ulidPart.length !== 26) {\n return null;\n }\n\n // ULID timestamp is encoded in the first 10 characters (Crockford's Base32)\n const ENCODING = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\n const TIME_LEN = 10;\n\n let time = 0;\n for (let i = 0; i < TIME_LEN; i++) {\n const char = ulidPart.charAt(i).toUpperCase();\n const index = ENCODING.indexOf(char);\n if (index === -1) {\n return null;\n }\n time = time * 32 + index;\n }\n\n return new Date(time);\n}\n\n/**\n * @deprecated Use getIdTimestamp instead\n */\nexport function getEventIdTimestamp(eventId: string): Date | null {\n return getIdTimestamp(eventId);\n}\n", "/**\n * Resolution Engine\n *\n * Pure functions for parameter resolution using layered config and policies.\n * Implements the Google-inspired layering system where:\n * - Parameters are partitioned into layers\n * - Within a layer, only one policy can be active for a unit\n * - Across layers, policies overlap freely (different parameters)\n *\n * Resolution order (lowest to highest priority):\n * 1. Caller defaults (always safe fallback)\n * 2. Parameter defaults (from bundle)\n * 3. Layer policies (each parameter belongs to exactly one layer)\n */\n\nimport type {\n ConfigBundle,\n BundleParameter,\n BundlePolicy,\n BundleAllocation,\n Context,\n ParameterValue,\n DecisionResult,\n LayerResolution,\n Id,\n} from \"../types/index.js\";\nimport { computeBucket, findMatchingAllocation } from \"../hashing/bucket.js\";\nimport { evaluateConditions } from \"./conditions.js\";\nimport { generateDecisionId } from \"../ids/index.js\";\nimport { fnv1a } from \"../hashing/fnv1a.js\";\n\n/**\n * Filters context to only include fields allowed by matched policies.\n * Collects the union of all allowed fields from policies with contextLogging config.\n *\n * @param context - The full evaluation context\n * @param policies - Matched policies from resolution\n * @returns Filtered context with only allowed fields, or undefined if no fields allowed\n */\nfunction filterContext(\n context: Context,\n policies: BundlePolicy[]\n): Context | undefined {\n // Collect union of all allowed fields from matched policies\n const allowedFields = new Set<string>();\n for (const policy of policies) {\n if (policy.contextLogging?.allowedFields) {\n for (const field of policy.contextLogging.allowedFields) {\n allowedFields.add(field);\n }\n }\n }\n\n // If no fields are allowed, return undefined\n if (allowedFields.size === 0) {\n return undefined;\n }\n\n // Filter context to only include allowed fields\n const filtered: Context = {};\n for (const field of allowedFields) {\n if (field in context) {\n filtered[field] = context[field];\n }\n }\n\n // Return undefined if no fields matched\n return Object.keys(filtered).length > 0 ? filtered : undefined;\n}\n\n// =============================================================================\n// Per-Entity Resolution Helpers\n// =============================================================================\n\n/**\n * Builds an entity ID from context using the policy's entityKeys.\n *\n * @param entityKeys - Array of context keys that identify the entity\n * @param context - The evaluation context\n * @returns Entity ID string, or null if any key is missing\n */\nfunction buildEntityId(entityKeys: string[], context: Context): string | null {\n const parts: string[] = [];\n for (const key of entityKeys) {\n const value = context[key];\n if (value === undefined || value === null) {\n return null;\n }\n parts.push(String(value));\n }\n return parts.join(\"_\");\n}\n\n/**\n * Performs deterministic weighted selection using a hash.\n *\n * Uses the entity ID + unit key + policy ID to deterministically select\n * an allocation based on weights. This ensures the same entity always\n * gets the same allocation for a given weight distribution.\n *\n * @param weights - Array of weights (should sum to 1.0)\n * @param seed - Seed string for deterministic hashing\n * @returns Index of selected allocation\n */\nfunction weightedSelection(weights: number[], seed: string): number {\n if (weights.length === 0) return 0;\n if (weights.length === 1) return 0;\n\n // Compute a deterministic random value in [0, 1) using hash\n const hash = fnv1a(seed);\n const random = (hash % 10000) / 10000;\n\n // Select based on cumulative weights\n let cumulative = 0;\n for (let i = 0; i < weights.length; i++) {\n cumulative += weights[i];\n if (random < cumulative) {\n return i;\n }\n }\n\n // Fallback to last allocation (handles floating point edge cases)\n return weights.length - 1;\n}\n\n/**\n * Creates uniform weights for dynamic allocations.\n *\n * @param count - Number of allocations\n * @returns Array of equal weights summing to 1.0\n */\nfunction createUniformWeights(count: number): number[] {\n if (count <= 0) return [];\n const weight = 1 / count;\n return Array(count).fill(weight);\n}\n\n/**\n * Gets entity weights from the bundle's entityState.\n *\n * @param bundle - The config bundle\n * @param policyId - The policy ID\n * @param entityId - The entity ID\n * @param allocationCount - Number of allocations (for dynamic allocations)\n * @returns Entity weights or uniform weights for cold start\n */\nfunction getEntityWeights(\n bundle: ConfigBundle,\n policyId: Id,\n entityId: string,\n allocationCount: number\n): number[] {\n const policyState = bundle.entityState?.[policyId];\n\n if (!policyState) {\n // No state for this policy - use uniform weights\n return createUniformWeights(allocationCount);\n }\n\n // Try entity-specific weights first\n const entityWeights = policyState.entities[entityId];\n if (entityWeights && entityWeights.weights.length === allocationCount) {\n return entityWeights.weights;\n }\n\n // Fall back to global prior\n const globalWeights = policyState._global;\n if (globalWeights && globalWeights.weights.length === allocationCount) {\n return globalWeights.weights;\n }\n\n // Last resort: uniform weights\n return createUniformWeights(allocationCount);\n}\n\n/**\n * Resolves a per-entity policy using weighted selection.\n *\n * @param bundle - The config bundle\n * @param policy - The policy with entityConfig\n * @param context - The evaluation context\n * @param unitKeyValue - The unit key value for hashing\n * @returns The selected allocation and entity ID, or null if cannot resolve\n */\nfunction resolvePerEntityPolicy(\n bundle: ConfigBundle,\n policy: BundlePolicy,\n context: Context,\n unitKeyValue: string\n): { allocation: BundleAllocation; entityId: string } | null {\n const entityConfig = policy.entityConfig;\n if (!entityConfig) return null;\n\n // Build entity ID from context\n const entityId = buildEntityId(entityConfig.entityKeys, context);\n if (!entityId) {\n // Missing entity keys - cannot resolve\n return null;\n }\n\n // Determine allocations\n let allocations: BundleAllocation[];\n let allocationCount: number;\n\n if (entityConfig.dynamicAllocations) {\n // Dynamic allocations from context\n const countKey = entityConfig.dynamicAllocations.countKey;\n const count = context[countKey];\n if (typeof count !== \"number\" || count <= 0) {\n return null;\n }\n allocationCount = Math.floor(count);\n\n // Create synthetic allocations for dynamic mode\n // Each allocation is an index (0, 1, 2, ..., count-1)\n allocations = Array.from({ length: allocationCount }, (_, i) => ({\n id: `${policy.id}_dynamic_${i}`,\n name: String(i),\n bucketRange: [0, 0] as [number, number], // Not used for per-entity\n overrides: {}, // Overrides are applied differently for dynamic\n }));\n } else {\n // Fixed allocations from policy\n allocations = policy.allocations;\n allocationCount = allocations.length;\n }\n\n if (allocationCount === 0) return null;\n\n // Get weights for this entity\n const weights = getEntityWeights(bundle, policy.id, entityId, allocationCount);\n\n // Deterministic weighted selection\n const seed = `${entityId}:${unitKeyValue}:${policy.id}`;\n const selectedIndex = weightedSelection(weights, seed);\n\n return {\n allocation: allocations[selectedIndex],\n entityId,\n };\n}\n\n/**\n * Extracts the unit key value from context using the bundle's hashing config.\n *\n * @param bundle - The config bundle\n * @param context - The evaluation context\n * @returns The unit key value as a string, or null if not found\n */\nexport function getUnitKeyValue(\n bundle: ConfigBundle,\n context: Context\n): string | null {\n const value = context[bundle.hashing.unitKey];\n\n if (value === undefined || value === null) {\n return null;\n }\n\n return String(value);\n}\n\n/**\n * Internal resolution result with metadata.\n */\ninterface ResolutionResult<T> {\n assignments: T;\n unitKeyValue: string;\n layers: LayerResolution[];\n /** Matched policies with context logging config */\n matchedPolicies: BundlePolicy[];\n}\n\n/**\n * Internal function that performs parameter resolution with metadata tracking.\n * This is the single source of truth for resolution logic.\n *\n * @param bundle - The config bundle (can be null if unavailable)\n * @param context - The evaluation context\n * @param defaults - Default values for parameters (required, used as fallback)\n * @returns Resolution result with assignments and metadata\n */\nfunction resolveInternal<T extends Record<string, ParameterValue>>(\n bundle: ConfigBundle | null,\n context: Context,\n defaults: T\n): ResolutionResult<T> {\n // Start with caller defaults (always safe)\n const assignments = { ...defaults } as Record<string, ParameterValue>;\n const layers: LayerResolution[] = [];\n const matchedPolicies: BundlePolicy[] = [];\n\n // If no bundle, return defaults with empty metadata\n if (!bundle) {\n return { assignments: assignments as T, unitKeyValue: \"\", layers, matchedPolicies };\n }\n\n // Try to get unit key\n const unitKeyValue = getUnitKeyValue(bundle, context);\n if (!unitKeyValue) {\n // Missing unit key - return defaults\n return { assignments: assignments as T, unitKeyValue: \"\", layers, matchedPolicies };\n }\n\n // Get requested parameter keys from defaults\n const requestedKeys = new Set(Object.keys(defaults));\n\n // Filter bundle parameters to only those requested\n const params = bundle.parameters.filter((p) => requestedKeys.has(p.key));\n\n // Apply bundle defaults (overrides caller defaults)\n for (const param of params) {\n if (param.key in assignments) {\n assignments[param.key] = param.default;\n }\n }\n\n // Group by layer\n const paramsByLayer = new Map<Id, BundleParameter[]>();\n for (const param of params) {\n const existing = paramsByLayer.get(param.layerId) || [];\n existing.push(param);\n paramsByLayer.set(param.layerId, existing);\n }\n\n // Process each layer (order doesn't matter since params are partitioned)\n for (const layer of bundle.layers) {\n const layerParams = paramsByLayer.get(layer.id);\n if (!layerParams || layerParams.length === 0) continue;\n\n // Compute bucket\n const bucket = computeBucket(\n unitKeyValue,\n layer.id,\n bundle.hashing.bucketCount\n );\n\n let matchedPolicy: BundlePolicy | undefined;\n let matchedAllocation: BundleAllocation | undefined;\n\n // Find matching policy\n for (const policy of layer.policies) {\n if (policy.state !== \"running\") continue;\n\n // Check bucket eligibility BEFORE conditions (performance optimization)\n // This enables non-overlapping experiments within a layer\n if (policy.eligibleBucketRange) {\n const { start, end } = policy.eligibleBucketRange;\n if (bucket < start || bucket > end) {\n continue; // User's bucket not eligible for this policy\n }\n }\n\n if (!evaluateConditions(policy.conditions, context)) continue;\n\n // Check if this is a per-entity policy\n if (policy.entityConfig && policy.entityConfig.resolutionMode === \"bundle\") {\n const result = resolvePerEntityPolicy(bundle, policy, context, unitKeyValue);\n if (result) {\n matchedPolicy = policy;\n matchedAllocation = result.allocation;\n\n // Track matched policy for context filtering\n matchedPolicies.push(policy);\n\n // Apply overrides\n // For dynamic allocations, the allocation name IS the value\n if (policy.entityConfig.dynamicAllocations) {\n // For per-entity dynamic policies, we return the selected index\n // The SDK caller should use metadata.allocationName to get the index\n // No parameter overrides to apply in this mode\n } else {\n // For fixed allocations, apply normal overrides\n for (const [key, value] of Object.entries(result.allocation.overrides)) {\n if (key in assignments) {\n assignments[key] = value;\n }\n }\n }\n break; // Only one policy per layer\n }\n } else if (policy.entityConfig && policy.entityConfig.resolutionMode === \"edge\") {\n // Edge mode: skip for now, will be handled by SDK's async resolution\n // In synchronous resolution, we fall through to bucket-based resolution\n // The SDK should use async decide() for edge mode policies\n continue;\n } else {\n // Standard bucket-based resolution\n const allocation = findMatchingAllocation(bucket, policy.allocations);\n if (allocation) {\n matchedPolicy = policy;\n matchedAllocation = allocation;\n\n // Track matched policy for context filtering\n matchedPolicies.push(policy);\n\n // Apply overrides\n for (const [key, value] of Object.entries(allocation.overrides)) {\n if (key in assignments) {\n assignments[key] = value;\n }\n }\n break; // Only one policy per layer\n }\n }\n }\n\n layers.push({\n layerId: layer.id,\n bucket,\n policyId: matchedPolicy?.id,\n allocationId: matchedAllocation?.id,\n allocationName: matchedAllocation?.name,\n });\n }\n\n return { assignments: assignments as T, unitKeyValue, layers, matchedPolicies };\n}\n\n/**\n * Resolves parameters with required defaults as fallback.\n * This is the primary SDK function that guarantees safe defaults.\n *\n * Resolution priority (highest wins):\n * 1. Policy overrides (from bundle)\n * 2. Parameter defaults (from bundle)\n * 3. Caller defaults (always safe fallback)\n *\n * @param bundle - The config bundle (can be null if unavailable)\n * @param context - The evaluation context\n * @param defaults - Default values for parameters (required, used as fallback)\n * @returns Resolved parameter assignments (always returns safe values with inferred types)\n */\nexport function resolveParameters<T extends Record<string, ParameterValue>>(\n bundle: ConfigBundle | null,\n context: Context,\n defaults: T\n): T {\n return resolveInternal(bundle, context, defaults).assignments;\n}\n\n/**\n * Makes a decision with full metadata for tracking.\n * Requires defaults for graceful degradation.\n *\n * Resolution priority (highest wins):\n * 1. Policy overrides (from bundle)\n * 2. Parameter defaults (from bundle)\n * 3. Caller defaults (always safe fallback)\n *\n * @param bundle - The config bundle (can be null if unavailable)\n * @param context - The evaluation context\n * @param defaults - Default values for parameters (required, used as fallback)\n * @returns Decision result with metadata (always returns safe values)\n */\nexport function decide<T extends Record<string, ParameterValue>>(\n bundle: ConfigBundle | null,\n context: Context,\n defaults: T\n): DecisionResult {\n const { assignments, unitKeyValue, layers, matchedPolicies } = resolveInternal(\n bundle,\n context,\n defaults\n );\n\n // Filter context based on matched policies' contextLogging config\n const filteredContext = filterContext(context, matchedPolicies);\n\n return {\n decisionId: generateDecisionId(),\n assignments,\n metadata: {\n timestamp: new Date().toISOString(),\n unitKeyValue,\n layers,\n filteredContext,\n },\n };\n}\n", "/**\n * DecisionDeduplicator - Pure decision deduplication logic.\n *\n * Tracks which user+assignment combinations have been seen to avoid\n * sending duplicate decision events. This enables efficient decision\n * tracking without overwhelming the event pipeline.\n *\n * Key differences from ExposureDeduplicator:\n * - Pure in-memory (no I/O, no storage dependency)\n * - Deduplicates on unitKey + assignment hash (not policy/variant)\n * - Suitable for use in any JavaScript environment\n */\n\nimport type { ParameterValue } from \"../types/index.js\";\n\nconst DEFAULT_TTL_MS = 3600_000; // 1 hour\nconst DEFAULT_MAX_ENTRIES = 10_000;\nconst CLEANUP_THRESHOLD = 0.2; // Clean when 20% of entries are expired\n\nexport interface DecisionDeduplicatorOptions {\n /**\n * Time-to-live for deduplication entries in milliseconds.\n * After this time, the same decision can be tracked again.\n * Default: 1 hour (3600000 ms)\n */\n ttlMs?: number;\n /**\n * Maximum number of entries to store.\n * When exceeded, oldest entries are removed.\n * Default: 10000\n */\n maxEntries?: number;\n}\n\nexport class DecisionDeduplicator {\n private _seen = new Map<string, number>(); // key -> timestamp\n private readonly _ttlMs: number;\n private readonly _maxEntries: number;\n private _lastCleanup = Date.now();\n\n constructor(options: DecisionDeduplicatorOptions = {}) {\n this._ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;\n this._maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;\n }\n\n /**\n * Generate a stable hash for assignment values.\n * Used to create a deduplication key from assignments.\n */\n static hashAssignments(assignments: Record<string, ParameterValue>): string {\n // Sort keys for deterministic ordering\n const sortedKeys = Object.keys(assignments).sort();\n const parts: string[] = [];\n\n for (const key of sortedKeys) {\n const value = assignments[key];\n // Simple string representation that's stable\n const valueStr = typeof value === \"object\" ? JSON.stringify(value) : String(value);\n parts.push(`${key}=${valueStr}`);\n }\n\n return parts.join(\"|\");\n }\n\n /**\n * Create a deduplication key from unitKey and assignment hash.\n */\n static createKey(unitKey: string, assignmentHash: string): string {\n return `${unitKey}:${assignmentHash}`;\n }\n\n /**\n * Check if this decision is new (not seen before within TTL).\n * If new, marks it as seen.\n *\n * @param unitKey - The unit key (user identifier)\n * @param assignmentHash - Hash of the assignments (from hashAssignments)\n * @returns true if this is a new decision, false if duplicate\n */\n checkAndMark(unitKey: string, assignmentHash: string): boolean {\n const key = DecisionDeduplicator.createKey(unitKey, assignmentHash);\n const now = Date.now();\n const lastSeen = this._seen.get(key);\n\n // Check if we've seen this within TTL\n if (lastSeen !== undefined && now - lastSeen < this._ttlMs) {\n return false; // Duplicate\n }\n\n // Mark as seen\n this._seen.set(key, now);\n\n // Periodic cleanup\n this._maybeCleanup(now);\n\n return true; // New decision\n }\n\n /**\n * Check if a decision would be considered new (without marking it).\n */\n wouldBeNew(unitKey: string, assignmentHash: string): boolean {\n const key = DecisionDeduplicator.createKey(unitKey, assignmentHash);\n const now = Date.now();\n const lastSeen = this._seen.get(key);\n\n if (lastSeen === undefined) {\n return true;\n }\n\n return now - lastSeen >= this._ttlMs;\n }\n\n /**\n * Clear all seen decisions.\n */\n clear(): void {\n this._seen.clear();\n }\n\n /**\n * Get the number of entries in the deduplication cache.\n */\n get size(): number {\n return this._seen.size;\n }\n\n /**\n * Perform cleanup of expired entries.\n * Called periodically based on CLEANUP_THRESHOLD.\n */\n private _maybeCleanup(now: number): void {\n // Only cleanup periodically, not on every call\n const timeSinceCleanup = now - this._lastCleanup;\n const shouldCleanup =\n timeSinceCleanup > this._ttlMs * CLEANUP_THRESHOLD || this._seen.size > this._maxEntries;\n\n if (!shouldCleanup) {\n return;\n }\n\n this._lastCleanup = now;\n this._cleanup(now);\n }\n\n /**\n * Remove expired entries and enforce max size.\n */\n private _cleanup(now: number): void {\n const expiredKeys: string[] = [];\n\n // Find expired entries\n for (const [key, timestamp] of this._seen.entries()) {\n if (now - timestamp >= this._ttlMs) {\n expiredKeys.push(key);\n }\n }\n\n // Remove expired entries\n for (const key of expiredKeys) {\n this._seen.delete(key);\n }\n\n // If still over max, remove oldest entries\n if (this._seen.size > this._maxEntries) {\n const entries = Array.from(this._seen.entries()).sort((a, b) => a[1] - b[1]); // Sort by timestamp\n\n const toRemove = entries.slice(0, this._seen.size - this._maxEntries);\n for (const [key] of toRemove) {\n this._seen.delete(key);\n }\n }\n }\n}\n\n", "/**\n * ErrorBoundary - Ensures SDK never crashes customer's application.\n *\n * Wraps all public methods to catch errors and return safe defaults.\n * Optionally reports errors to Traffical backend for monitoring.\n */\n\nexport interface ErrorBoundaryOptions {\n /** Whether to report errors to Traffical backend */\n reportErrors?: boolean;\n /** Endpoint for error reporting */\n errorEndpoint?: string;\n /** SDK key for identification */\n sdkKey?: string;\n /** Callback when error occurs */\n onError?: (tag: string, error: Error) => void;\n}\n\nexport class ErrorBoundary {\n private _seen = new Set<string>();\n private _options: ErrorBoundaryOptions;\n private _lastError: Error | null = null;\n\n constructor(options: ErrorBoundaryOptions = {}) {\n this._options = options;\n }\n\n /**\n * Wrap a synchronous function to catch errors and return fallback.\n */\n capture<T>(tag: string, fn: () => T, fallback: T): T {\n try {\n return fn();\n } catch (error) {\n this._onError(tag, error);\n return fallback;\n }\n }\n\n /**\n * Wrap an async function to catch errors and return fallback.\n */\n async captureAsync<T>(tag: string, fn: () => Promise<T>, fallback: T): Promise<T> {\n try {\n return await fn();\n } catch (error) {\n this._onError(tag, error);\n return fallback;\n }\n }\n\n /**\n * Execute an async operation without expecting a return value.\n * Used for fire-and-forget operations like event tracking.\n */\n async swallow(tag: string, fn: () => Promise<void>): Promise<void> {\n try {\n await fn();\n } catch (error) {\n this._onError(tag, error);\n }\n }\n\n /**\n * Get the last error that occurred (for debugging).\n */\n getLastError(): Error | null {\n const error = this._lastError;\n this._lastError = null;\n return error;\n }\n\n /**\n * Clear the seen errors set (for testing or session reset).\n */\n clearSeen(): void {\n this._seen.clear();\n }\n\n private _onError(tag: string, error: unknown): void {\n const resolvedError = this._resolveError(error);\n this._lastError = resolvedError;\n\n // Deduplicate - only handle each unique error once\n const errorKey = `${tag}:${resolvedError.name}:${resolvedError.message}`;\n if (this._seen.has(errorKey)) {\n return;\n }\n this._seen.add(errorKey);\n\n // Log to console (development)\n console.warn(`[Traffical] Error in ${tag}:`, resolvedError.message);\n\n // Call user-provided callback\n this._options.onError?.(tag, resolvedError);\n\n // Optionally report to backend\n if (this._options.reportErrors && this._options.errorEndpoint) {\n this._reportError(tag, resolvedError).catch(() => {\n // Silently fail - we don't want error reporting to cause errors\n });\n }\n }\n\n private async _reportError(tag: string, error: Error): Promise<void> {\n if (!this._options.errorEndpoint) return;\n\n try {\n await fetch(this._options.errorEndpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(this._options.sdkKey && { \"X-Traffical-Key\": this._options.sdkKey }),\n },\n body: JSON.stringify({\n tag,\n error: error.name,\n message: error.message,\n stack: error.stack,\n timestamp: new Date().toISOString(),\n sdk: \"@traffical/js-client\",\n userAgent: typeof navigator !== \"undefined\" ? navigator.userAgent : undefined,\n }),\n });\n } catch {\n // Silently fail\n }\n }\n\n private _resolveError(error: unknown): Error {\n if (error instanceof Error) {\n return error;\n }\n if (typeof error === \"string\") {\n return new Error(error);\n }\n return new Error(\"An unknown error occurred\");\n }\n}\n\n", "/**\n * EventLogger - Smart event batching with browser-specific features.\n *\n * Features:\n * - Batches events (flush every N events or M seconds)\n * - Uses navigator.sendBeacon() on page unload\n * - Persists failed events to localStorage\n * - Retries failed events on next session\n * - Visibility-aware: flushes on visibilitychange to hidden\n */\n\nimport type { TrackableEvent } from \"@traffical/core\";\nimport type { StorageProvider } from \"./storage.js\";\n\nconst FAILED_EVENTS_KEY = \"failed_events\";\nconst DEFAULT_BATCH_SIZE = 10;\nconst DEFAULT_FLUSH_INTERVAL_MS = 30_000; // 30 seconds\nconst MAX_FAILED_EVENTS = 100;\n\nexport interface EventLoggerOptions {\n /** API endpoint for events */\n endpoint: string;\n /** API key for authentication */\n apiKey: string;\n /** Storage provider for failed events */\n storage: StorageProvider;\n /** Max events before auto-flush (default: 10) */\n batchSize?: number;\n /** Auto-flush interval in ms (default: 30000) */\n flushIntervalMs?: number;\n /** Callback on flush error */\n onError?: (error: Error) => void;\n}\n\nexport class EventLogger {\n private _endpoint: string;\n private _apiKey: string;\n private _storage: StorageProvider;\n private _batchSize: number;\n private _flushIntervalMs: number;\n private _onError?: (error: Error) => void;\n\n private _queue: TrackableEvent[] = [];\n private _flushTimer: ReturnType<typeof setTimeout> | null = null;\n private _isFlushing = false;\n\n constructor(options: EventLoggerOptions) {\n this._endpoint = options.endpoint;\n this._apiKey = options.apiKey;\n this._storage = options.storage;\n this._batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;\n this._flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;\n this._onError = options.onError;\n\n // Set up browser event listeners\n this._setupListeners();\n\n // Retry failed events from previous session\n this._retryFailedEvents();\n\n // Start flush timer\n this._startFlushTimer();\n }\n\n /**\n * Log an event (added to batch queue).\n */\n log(event: TrackableEvent): void {\n this._queue.push(event);\n\n // Auto-flush if batch is full\n if (this._queue.length >= this._batchSize) {\n this.flush();\n }\n }\n\n /**\n * Flush all queued events immediately.\n */\n async flush(): Promise<void> {\n if (this._isFlushing || this._queue.length === 0) {\n return;\n }\n\n this._isFlushing = true;\n\n // Take current queue\n const events = [...this._queue];\n this._queue = [];\n\n try {\n await this._sendEvents(events);\n } catch (error) {\n // Persist failed events for retry\n this._persistFailedEvents(events);\n this._onError?.(error instanceof Error ? error : new Error(String(error)));\n } finally {\n this._isFlushing = false;\n }\n }\n\n /**\n * Flush using fetch with keepalive (for page unload).\n * \n * We use fetch with keepalive: true instead of sendBeacon because:\n * - sendBeacon cannot send custom headers (like Authorization)\n * - keepalive ensures the request completes even as the page unloads\n * - Same reliability guarantees as sendBeacon\n * \n * Returns true if request was initiated, false otherwise.\n */\n flushBeacon(): boolean {\n if (this._queue.length === 0) {\n return true;\n }\n\n if (typeof fetch === \"undefined\") {\n // Fetch not available - try async flush\n this.flush();\n return false;\n }\n\n const events = [...this._queue];\n this._queue = [];\n\n // Use fetch with keepalive instead of sendBeacon\n // This allows us to include the Authorization header\n fetch(this._endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this._apiKey}`,\n },\n body: JSON.stringify({ events }),\n keepalive: true,\n }).catch(() => {\n // Persist for retry on next session\n this._persistFailedEvents(events);\n });\n\n return true;\n }\n\n /**\n * Get the number of events in the queue.\n */\n get queueSize(): number {\n return this._queue.length;\n }\n\n /**\n * Destroy the logger (cleanup timers and listeners).\n */\n destroy(): void {\n if (this._flushTimer) {\n clearInterval(this._flushTimer);\n this._flushTimer = null;\n }\n this._removeListeners();\n }\n\n private async _sendEvents(events: TrackableEvent[]): Promise<void> {\n const response = await fetch(this._endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this._apiKey}`,\n },\n body: JSON.stringify({ events }),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n }\n\n private _persistFailedEvents(events: TrackableEvent[]): void {\n const existing = this._storage.get<TrackableEvent[]>(FAILED_EVENTS_KEY) ?? [];\n\n // Limit total stored events\n const combined = [...existing, ...events].slice(-MAX_FAILED_EVENTS);\n\n this._storage.set(FAILED_EVENTS_KEY, combined);\n }\n\n private _retryFailedEvents(): void {\n const failed = this._storage.get<TrackableEvent[]>(FAILED_EVENTS_KEY);\n if (!failed || failed.length === 0) {\n return;\n }\n\n // Clear stored events\n this._storage.remove(FAILED_EVENTS_KEY);\n\n // Add to queue for retry\n this._queue.push(...failed);\n }\n\n private _startFlushTimer(): void {\n if (this._flushIntervalMs <= 0) return;\n\n this._flushTimer = setInterval(() => {\n this.flush().catch(() => {\n // Errors handled in flush\n });\n }, this._flushIntervalMs);\n }\n\n private _setupListeners(): void {\n if (typeof window === \"undefined\") return;\n\n // Flush on page hide (covers tab close, navigation, etc.)\n window.addEventListener(\"pagehide\", this._onPageHide);\n\n // Flush when page becomes hidden (tab switch, minimize)\n document.addEventListener(\"visibilitychange\", this._onVisibilityChange);\n\n // Fallback: beforeunload for older browsers\n window.addEventListener(\"beforeunload\", this._onBeforeUnload);\n }\n\n private _removeListeners(): void {\n if (typeof window === \"undefined\") return;\n\n window.removeEventListener(\"pagehide\", this._onPageHide);\n document.removeEventListener(\"visibilitychange\", this._onVisibilityChange);\n window.removeEventListener(\"beforeunload\", this._onBeforeUnload);\n }\n\n private _onPageHide = (): void => {\n this.flushBeacon();\n };\n\n private _onVisibilityChange = (): void => {\n if (document.visibilityState === \"hidden\") {\n this.flushBeacon();\n }\n };\n\n private _onBeforeUnload = (): void => {\n this.flushBeacon();\n };\n}\n\n", "/**\n * ExposureDeduplicator - Prevents duplicate exposure events.\n *\n * Same user seeing same variant should only count as 1 exposure.\n * Uses session-based deduplication with localStorage persistence.\n */\n\nimport type { StorageProvider } from \"./storage.js\";\n\nconst STORAGE_KEY = \"exposure_dedup\";\nconst DEFAULT_SESSION_TTL_MS = 30 * 60 * 1000; // 30 minutes\n\nexport interface ExposureDeduplicatorOptions {\n /** Storage provider for persistence */\n storage: StorageProvider;\n /** Session TTL in milliseconds (default: 30 minutes) */\n sessionTtlMs?: number;\n}\n\ninterface DeduplicationState {\n /** Set of seen exposure keys */\n seen: string[];\n /** Session start timestamp */\n sessionStart: number;\n}\n\nexport class ExposureDeduplicator {\n private _storage: StorageProvider;\n private _sessionTtlMs: number;\n private _seen: Set<string>;\n private _sessionStart: number;\n\n constructor(options: ExposureDeduplicatorOptions) {\n this._storage = options.storage;\n this._sessionTtlMs = options.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS;\n this._seen = new Set();\n this._sessionStart = Date.now();\n\n // Restore from storage\n this._restore();\n }\n\n /**\n * Generate a deduplication key for an exposure.\n *\n * Key format: {unitKey}:{policyId}:{variant}\n */\n static createKey(unitKey: string, policyId: string, variant: string): string {\n return `${unitKey}:${policyId}:${variant}`;\n }\n\n /**\n * Check if an exposure should be tracked (not a duplicate).\n * Returns true if this is a new exposure, false if duplicate.\n */\n shouldTrack(key: string): boolean {\n // Check if session has expired\n if (this._isSessionExpired()) {\n this._resetSession();\n }\n\n if (this._seen.has(key)) {\n return false;\n }\n\n // Mark as seen\n this._seen.add(key);\n this._persist();\n\n return true;\n }\n\n /**\n * Check and mark in one operation.\n * Returns true if this was a new exposure (and is now marked as seen).\n */\n checkAndMark(unitKey: string, policyId: string, variant: string): boolean {\n const key = ExposureDeduplicator.createKey(unitKey, policyId, variant);\n return this.shouldTrack(key);\n }\n\n /**\n * Clear all seen exposures (useful for testing or logout).\n */\n clear(): void {\n this._seen.clear();\n this._storage.remove(STORAGE_KEY);\n }\n\n /**\n * Get the number of unique exposures in the current session.\n */\n get size(): number {\n return this._seen.size;\n }\n\n private _isSessionExpired(): boolean {\n return Date.now() - this._sessionStart > this._sessionTtlMs;\n }\n\n private _resetSession(): void {\n this._seen.clear();\n this._sessionStart = Date.now();\n this._storage.remove(STORAGE_KEY);\n }\n\n private _persist(): void {\n const state: DeduplicationState = {\n seen: Array.from(this._seen),\n sessionStart: this._sessionStart,\n };\n this._storage.set(STORAGE_KEY, state, this._sessionTtlMs);\n }\n\n private _restore(): void {\n const state = this._storage.get<DeduplicationState>(STORAGE_KEY);\n if (!state) return;\n\n // Check if stored session is still valid\n const sessionAge = Date.now() - state.sessionStart;\n if (sessionAge > this._sessionTtlMs) {\n this._storage.remove(STORAGE_KEY);\n return;\n }\n\n // Restore state\n this._seen = new Set(state.seen);\n this._sessionStart = state.sessionStart;\n }\n}\n\n", "/**\n * StableIdProvider - Anonymous user identification for experimentation.\n *\n * Generates a stable ID on first visit and persists it across sessions.\n * Uses localStorage as primary storage with cookie fallback.\n */\n\nimport type { StorageProvider } from \"./storage.js\";\n\nconst STORAGE_KEY = \"stable_id\";\nconst COOKIE_NAME = \"traffical_sid\";\nconst COOKIE_MAX_AGE_DAYS = 365;\n\nexport interface StableIdProviderOptions {\n /** Storage provider (localStorage) */\n storage: StorageProvider;\n /** Whether to use cookie fallback (default: true) */\n useCookieFallback?: boolean;\n /** Custom cookie name (default: traffical_sid) */\n cookieName?: string;\n}\n\nexport class StableIdProvider {\n private _storage: StorageProvider;\n private _useCookieFallback: boolean;\n private _cookieName: string;\n private _cachedId: string | null = null;\n\n constructor(options: StableIdProviderOptions) {\n this._storage = options.storage;\n this._useCookieFallback = options.useCookieFallback ?? true;\n this._cookieName = options.cookieName ?? COOKIE_NAME;\n }\n\n /**\n * Get the stable ID, creating one if it doesn't exist.\n */\n getId(): string {\n // Check cache first\n if (this._cachedId) {\n return this._cachedId;\n }\n\n // Try localStorage\n let id = this._storage.get<string>(STORAGE_KEY);\n if (id) {\n this._cachedId = id;\n return id;\n }\n\n // Try cookie fallback\n if (this._useCookieFallback) {\n id = this._getCookie();\n if (id) {\n // Sync back to localStorage\n this._storage.set(STORAGE_KEY, id);\n this._cachedId = id;\n return id;\n }\n }\n\n // Generate new ID\n id = this._generateId();\n this._persist(id);\n this._cachedId = id;\n\n return id;\n }\n\n /**\n * Set a custom stable ID (e.g., when user logs in).\n */\n setId(id: string): void {\n this._persist(id);\n this._cachedId = id;\n }\n\n /**\n * Clear the stable ID (e.g., on logout).\n */\n clear(): void {\n this._storage.remove(STORAGE_KEY);\n if (this._useCookieFallback) {\n this._deleteCookie();\n }\n this._cachedId = null;\n }\n\n /**\n * Check if a stable ID exists.\n */\n hasId(): boolean {\n return this._storage.get<string>(STORAGE_KEY) !== null || this._getCookie() !== null;\n }\n\n private _persist(id: string): void {\n // Save to localStorage (no TTL - permanent)\n this._storage.set(STORAGE_KEY, id);\n\n // Save to cookie as fallback\n if (this._useCookieFallback) {\n this._setCookie(id);\n }\n }\n\n private _generateId(): string {\n // Use crypto.randomUUID if available (modern browsers)\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n\n // Fallback to manual UUID v4 generation\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n }\n\n private _getCookie(): string | null {\n if (typeof document === \"undefined\") return null;\n\n try {\n const cookies = document.cookie.split(\";\");\n for (const cookie of cookies) {\n const [name, value] = cookie.trim().split(\"=\");\n if (name === this._cookieName && value) {\n return decodeURIComponent(value);\n }\n }\n } catch {\n // Cookie access failed (e.g., cross-origin iframe)\n }\n\n return null;\n }\n\n private _setCookie(value: string): void {\n if (typeof document === \"undefined\") return;\n\n try {\n const maxAge = COOKIE_MAX_AGE_DAYS * 24 * 60 * 60;\n document.cookie = `${this._cookieName}=${encodeURIComponent(value)}; max-age=${maxAge}; path=/; SameSite=Lax`;\n } catch {\n // Cookie access failed\n }\n }\n\n private _deleteCookie(): void {\n if (typeof document === \"undefined\") return;\n\n try {\n document.cookie = `${this._cookieName}=; max-age=0; path=/`;\n } catch {\n // Cookie access failed\n }\n }\n}\n\n", "/**\n * Storage abstraction for browser environments.\n *\n * Provides a safe wrapper around localStorage with:\n * - Automatic JSON serialization/deserialization\n * - Graceful fallback when localStorage is unavailable\n * - TTL support for expiring entries\n */\n\nexport interface StorageProvider {\n get<T>(key: string): T | null;\n set<T>(key: string, value: T, ttlMs?: number): void;\n remove(key: string): void;\n clear(): void;\n}\n\ninterface StoredValue<T> {\n value: T;\n expiresAt?: number;\n}\n\nconst STORAGE_PREFIX = \"traffical:\";\n\n/**\n * localStorage-based storage provider.\n */\nexport class LocalStorageProvider implements StorageProvider {\n private _available: boolean;\n\n constructor() {\n this._available = this._checkAvailability();\n }\n\n get<T>(key: string): T | null {\n if (!this._available) return null;\n\n try {\n const raw = localStorage.getItem(STORAGE_PREFIX + key);\n if (!raw) return null;\n\n const stored = JSON.parse(raw) as StoredValue<T>;\n\n // Check TTL\n if (stored.expiresAt && Date.now() > stored.expiresAt) {\n this.remove(key);\n return null;\n }\n\n return stored.value;\n } catch {\n return null;\n }\n }\n\n set<T>(key: string, value: T, ttlMs?: number): void {\n if (!this._available) return;\n\n try {\n const stored: StoredValue<T> = {\n value,\n ...(ttlMs && { expiresAt: Date.now() + ttlMs }),\n };\n localStorage.setItem(STORAGE_PREFIX + key, JSON.stringify(stored));\n } catch {\n // Storage full or unavailable - silently fail\n }\n }\n\n remove(key: string): void {\n if (!this._available) return;\n\n try {\n localStorage.removeItem(STORAGE_PREFIX + key);\n } catch {\n // Silently fail\n }\n }\n\n clear(): void {\n if (!this._available) return;\n\n try {\n // Only clear traffical-prefixed keys\n const keysToRemove: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(STORAGE_PREFIX)) {\n keysToRemove.push(key);\n }\n }\n keysToRemove.forEach((key) => localStorage.removeItem(key));\n } catch {\n // Silently fail\n }\n }\n\n private _checkAvailability(): boolean {\n try {\n const testKey = STORAGE_PREFIX + \"__test__\";\n localStorage.setItem(testKey, \"test\");\n localStorage.removeItem(testKey);\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/**\n * In-memory storage fallback when localStorage is unavailable.\n */\nexport class MemoryStorageProvider implements StorageProvider {\n private _store = new Map<string, StoredValue<unknown>>();\n\n get<T>(key: string): T | null {\n const stored = this._store.get(key) as StoredValue<T> | undefined;\n if (!stored) return null;\n\n // Check TTL\n if (stored.expiresAt && Date.now() > stored.expiresAt) {\n this.remove(key);\n return null;\n }\n\n return stored.value;\n }\n\n set<T>(key: string, value: T, ttlMs?: number): void {\n this._store.set(key, {\n value,\n ...(ttlMs && { expiresAt: Date.now() + ttlMs }),\n });\n }\n\n remove(key: string): void {\n this._store.delete(key);\n }\n\n clear(): void {\n this._store.clear();\n }\n}\n\n/**\n * Creates the appropriate storage provider for the current environment.\n */\nexport function createStorageProvider(): StorageProvider {\n // Try localStorage first\n const localProvider = new LocalStorageProvider();\n if (localProvider.get(\"__check__\") !== null || localStorageAvailable()) {\n return localProvider;\n }\n // Fall back to in-memory\n return new MemoryStorageProvider();\n}\n\nfunction localStorageAvailable(): boolean {\n try {\n const testKey = \"__traffical_storage_test__\";\n localStorage.setItem(testKey, \"test\");\n localStorage.removeItem(testKey);\n return true;\n } catch {\n return false;\n }\n}\n\n", "/**\n * DecisionTrackingPlugin - Automatically tracks decision events.\n *\n * This plugin hooks into the SDK's decision lifecycle and sends a DecisionEvent\n * to the control plane whenever decide() is called. This enables:\n * - Intent-to-treat analysis: tracking all assignments, not just exposures\n * - Debugging: understanding why specific values were computed\n * - Audit trail: tracking all decisions made by the SDK\n *\n * Decision events are deduplicated: the same user seeing the same assignment\n * will only trigger one event within the deduplication TTL.\n */\n\nimport type { TrafficalPlugin } from \"./types.js\";\nimport type { DecisionResult, DecisionEvent, ParameterValue } from \"@traffical/core\";\nimport { DecisionDeduplicator } from \"@traffical/core\";\n\nconst SDK_NAME = \"js-client\";\nconst SDK_VERSION = \"0.1.0\"; // Should match package.json version\n\n/**\n * Options for the DecisionTrackingPlugin.\n */\nexport interface DecisionTrackingPluginOptions {\n /**\n * Disable decision tracking entirely.\n * Default: false (tracking enabled)\n */\n disabled?: boolean;\n\n /**\n * Time-to-live for deduplication in milliseconds.\n * Same user+assignment combination won't be tracked again within this window.\n * Default: 1 hour (3600000 ms)\n */\n deduplicationTtlMs?: number;\n}\n\n/**\n * Dependencies injected by the SDK client.\n */\nexport interface DecisionTrackingPluginDeps {\n /** Organization ID */\n orgId: string;\n /** Project ID */\n projectId: string;\n /** Environment */\n env: string;\n /**\n * Function to log a decision event.\n * This is typically the EventLogger.log() method.\n */\n log: (event: DecisionEvent) => void;\n}\n\n/**\n * Creates a DecisionTrackingPlugin instance.\n *\n * @param options - Plugin configuration options\n * @param deps - Dependencies injected by the SDK client\n * @returns A TrafficalPlugin that tracks decision events\n *\n * @example\n * ```typescript\n * const plugin = createDecisionTrackingPlugin(\n * { disabled: false },\n * {\n * orgId: \"org_123\",\n * projectId: \"proj_456\",\n * env: \"production\",\n * log: (event) => eventLogger.log(event),\n * }\n * );\n * ```\n */\nexport function createDecisionTrackingPlugin(\n options: DecisionTrackingPluginOptions,\n deps: DecisionTrackingPluginDeps\n): TrafficalPlugin {\n const dedup = new DecisionDeduplicator({\n ttlMs: options.deduplicationTtlMs,\n });\n\n return {\n name: \"decision-tracking\",\n\n onDecision(decision: DecisionResult): void {\n // Skip if disabled\n if (options.disabled) {\n return;\n }\n\n // Skip if no unit key (can't attribute)\n const unitKey = decision.metadata.unitKeyValue;\n if (!unitKey) {\n return;\n }\n\n // Hash assignments for deduplication\n const hash = DecisionDeduplicator.hashAssignments(\n decision.assignments as Record<string, ParameterValue>\n );\n\n // Check deduplication\n if (!dedup.checkAndMark(unitKey, hash)) {\n return; // Duplicate, skip\n }\n\n // Build the decision event\n const event: DecisionEvent = {\n type: \"decision\",\n id: decision.decisionId,\n orgId: deps.orgId,\n projectId: deps.projectId,\n env: deps.env,\n unitKey,\n timestamp: decision.metadata.timestamp,\n assignments: decision.assignments,\n layers: decision.metadata.layers,\n // Include filtered context if available (for contextual bandit training)\n context: decision.metadata.filteredContext,\n sdkName: SDK_NAME,\n sdkVersion: SDK_VERSION,\n };\n\n // Log the event\n deps.log(event);\n },\n\n onDestroy(): void {\n // Clear the deduplication cache\n dedup.clear();\n },\n };\n}\n\n", "/**\n * PluginManager - Manages plugin lifecycle and hook execution.\n *\n * Provides a minimal hook-based system for extending SDK functionality.\n */\n\nimport type {\n ConfigBundle,\n DecisionResult,\n ExposureEvent,\n TrackEvent,\n Context,\n ParameterValue,\n} from \"@traffical/core\";\nimport type { TrafficalPlugin, PluginOptions } from \"./types.js\";\n\ninterface RegisteredPlugin {\n plugin: TrafficalPlugin;\n priority: number;\n}\n\nexport class PluginManager {\n private _plugins: RegisteredPlugin[] = [];\n\n /**\n * Register a plugin.\n */\n register(options: PluginOptions | TrafficalPlugin): void {\n const plugin = \"plugin\" in options ? options.plugin : options;\n const priority = \"priority\" in options ? (options.priority ?? 0) : 0;\n\n // Check for duplicate names\n if (this._plugins.some((p) => p.plugin.name === plugin.name)) {\n console.warn(`[Traffical] Plugin \"${plugin.name}\" already registered, skipping.`);\n return;\n }\n\n this._plugins.push({ plugin, priority });\n\n // Sort by priority (descending - higher priority first)\n this._plugins.sort((a, b) => b.priority - a.priority);\n }\n\n /**\n * Unregister a plugin by name.\n */\n unregister(name: string): boolean {\n const index = this._plugins.findIndex((p) => p.plugin.name === name);\n if (index === -1) return false;\n\n this._plugins.splice(index, 1);\n return true;\n }\n\n /**\n * Get a registered plugin by name.\n */\n get(name: string): TrafficalPlugin | undefined {\n return this._plugins.find((p) => p.plugin.name === name)?.plugin;\n }\n\n /**\n * Get all registered plugins.\n */\n getAll(): TrafficalPlugin[] {\n return this._plugins.map((p) => p.plugin);\n }\n\n /**\n * Run onInitialize hooks.\n */\n async runInitialize(): Promise<void> {\n for (const { plugin } of this._plugins) {\n if (plugin.onInitialize) {\n try {\n await plugin.onInitialize();\n } catch (error) {\n console.warn(`[Traffical] Plugin \"${plugin.name}\" onInitialize error:`, error);\n }\n }\n }\n }\n\n /**\n * Run onConfigUpdate hooks.\n * Called when config bundle is fetched or refreshed.\n */\n runConfigUpdate(bundle: ConfigBundle): void {\n for (const { plugin } of this._plugins) {\n if (plugin.onConfigUpdate) {\n try {\n plugin.onConfigUpdate(bundle);\n } catch (error) {\n console.warn(`[Traffical] Plugin \"${plugin.name}\" onConfigUpdate error:`, error);\n }\n }\n }\n }\n\n /**\n * Run onBeforeDecision hooks.\n * Returns potentially modified context.\n */\n runBeforeDecision(context: Context): Context {\n let result = context;\n\n for (const { plugin } of this._plugins) {\n if (plugin.onBeforeDecision) {\n try {\n const modified = plugin.onBeforeDecision(result);\n if (modified) {\n result = modified;\n }\n } catch (error) {\n console.warn(`[Traffical] Plugin \"${plugin.name}\" onBeforeDecision error:`, error);\n }\n }\n }\n\n return result;\n }\n\n /**\n * Run onDecision hooks.\n */\n runDecision(decision: DecisionResult): void {\n for (const { plugin } of this._plugins) {\n if (plugin.onDecision) {\n try {\n plugin.onDecision(decision);\n } catch (error) {\n console.warn(`[Traffical] Plugin \"${plugin.name}\" onDecision error:`, error);\n }\n }\n }\n }\n\n /**\n * Run onResolve hooks.\n * Called after getParams() resolves parameters.\n */\n runResolve(params: Record<string, ParameterValue>): void {\n for (const { plugin } of this._plugins) {\n if (plugin.onResolve) {\n try {\n plugin.onResolve(params);\n } catch (error) {\n console.warn(`[Traffical] Plugin \"${plugin.name}\" onResolve error:`, error);\n }\n }\n }\n }\n\n /**\n * Run onExposure hooks.\n * Returns false if any plugin cancels the exposure.\n */\n runExposure(event: ExposureEvent): boolean {\n for (const { plugin } of this._plugins) {\n if (plugin.onExposure) {\n try {\n const result = plugin.onExposure(event);\n if (result === false) {\n return false;\n }\n } catch (error) {\n console.warn(`[Traffical] Plugin \"${plugin.name}\" onExposure error:`, error);\n }\n }\n }\n\n return true;\n }\n\n /**\n * Run onTrack hooks.\n * Returns false if any plugin cancels the track event.\n */\n runTrack(event: TrackEvent): boolean {\n for (const { plugin } of this._plugins) {\n if (plugin.onTrack) {\n try {\n const result = plugin.onTrack(event);\n if (result === false) {\n return false;\n }\n } catch (error) {\n console.warn(`[Traffical] Plugin \"${plugin.name}\" onTrack error:`, error);\n }\n }\n }\n\n return true;\n }\n\n /**\n * Run onDestroy hooks.\n */\n runDestroy(): void {\n for (const { plugin } of this._plugins) {\n if (plugin.onDestroy) {\n try {\n plugin.onDestroy();\n } catch (error) {\n console.warn(`[Traffical] Plugin \"${plugin.name}\" onDestroy error:`, error);\n }\n }\n }\n }\n\n /**\n * Clear all plugins.\n */\n clear(): void {\n this._plugins = [];\n }\n}\n\n// Re-export types\nexport type { TrafficalPlugin, PluginOptions } from \"./types.js\";\n\n// Re-export plugins\nexport {\n createDecisionTrackingPlugin,\n type DecisionTrackingPluginOptions,\n type DecisionTrackingPluginDeps,\n} from \"./decision-tracking.js\";\n\n", "/**\n * TrafficalClient - JavaScript SDK for browser environments.\n *\n * Features:\n * - Same API as Node SDK: getParams(), decide(), trackExposure(), track()\n * - Error boundary wrapping (P0)\n * - Exposure deduplication (P0)\n * - Smart event batching with beacon on unload (P1)\n * - Plugin system (P2)\n * - Auto stable ID for anonymous users\n */\n\nimport {\n type ConfigBundle,\n type Context,\n type DecisionResult,\n type ParameterValue,\n type ExposureEvent,\n type TrackEvent,\n type TrackAttribution,\n type DecisionEvent,\n resolveParameters,\n decide as coreDecide,\n generateExposureId,\n generateTrackEventId,\n generateDecisionId,\n} from \"@traffical/core\";\n\nimport { ErrorBoundary, type ErrorBoundaryOptions } from \"./error-boundary.js\";\nimport { EventLogger } from \"./event-logger.js\";\nimport { ExposureDeduplicator } from \"./exposure-dedup.js\";\nimport { StableIdProvider } from \"./stable-id.js\";\nimport { createStorageProvider, type StorageProvider } from \"./storage.js\";\nimport { PluginManager, type TrafficalPlugin, createDecisionTrackingPlugin } from \"./plugins/index.js\";\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst SDK_NAME = \"js-client\";\nconst SDK_VERSION = \"0.1.0\"; // Should match package.json version\n\nconst DEFAULT_BASE_URL = \"https://sdk.traffical.io\";\nconst DEFAULT_REFRESH_INTERVAL_MS = 60_000; // 1 minute\nconst OFFLINE_WARNING_INTERVAL_MS = 300_000; // 5 minutes\nconst DECISION_CACHE_MAX_SIZE = 100; // Max decisions to cache for attribution lookup\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface TrafficalClientOptions {\n /** Organization ID */\n orgId: string;\n /** Project ID */\n projectId: string;\n /** Environment (e.g., \"production\", \"staging\") */\n env: string;\n /** API key for authentication */\n apiKey: string;\n /** Base URL for the SDK API (edge worker) */\n baseUrl?: string;\n /** Local config bundle for offline fallback */\n localConfig?: ConfigBundle;\n /** Refresh interval in milliseconds (default: 60000) */\n refreshIntervalMs?: number;\n /** Error boundary options */\n errorBoundary?: ErrorBoundaryOptions;\n /** Event batching options */\n eventBatchSize?: number;\n eventFlushIntervalMs?: number;\n /** Exposure deduplication session TTL */\n exposureSessionTtlMs?: number;\n /**\n * Whether to automatically track decision events (default: true).\n * When enabled, every call to decide() automatically sends a DecisionEvent\n * to the backend, enabling intent-to-treat analysis.\n */\n trackDecisions?: boolean;\n /**\n * Decision deduplication TTL in milliseconds (default: 1 hour).\n * Same user+assignment combination won't be tracked again within this window.\n */\n decisionDeduplicationTtlMs?: number;\n /** Plugins to register on init */\n plugins?: TrafficalPlugin[];\n /** Custom storage provider (default: localStorage) */\n storage?: StorageProvider;\n /** Disable automatic stable ID generation */\n disableAutoStableId?: boolean;\n}\n\ninterface ClientState {\n bundle: ConfigBundle | null;\n etag: string | null;\n lastFetchTime: number;\n lastOfflineWarning: number;\n refreshTimer: ReturnType<typeof setInterval> | null;\n isInitialized: boolean;\n}\n\n// =============================================================================\n// TrafficalClient Class\n// =============================================================================\n\nexport class TrafficalClient {\n private readonly _options: Required<\n Pick<TrafficalClientOptions, \"orgId\" | \"projectId\" | \"env\" | \"apiKey\" | \"baseUrl\" | \"refreshIntervalMs\">\n > & { localConfig?: ConfigBundle };\n\n private _state: ClientState = {\n bundle: null,\n etag: null,\n lastFetchTime: 0,\n lastOfflineWarning: 0,\n refreshTimer: null,\n isInitialized: false,\n };\n\n private readonly _errorBoundary: ErrorBoundary;\n private readonly _storage: StorageProvider;\n private readonly _eventLogger: EventLogger;\n private readonly _exposureDedup: ExposureDeduplicator;\n private readonly _stableId: StableIdProvider;\n private readonly _plugins: PluginManager;\n /** Cache of recent decisions for attribution lookup when track() is called */\n private readonly _decisionCache: Map<string, DecisionResult> = new Map();\n\n constructor(options: TrafficalClientOptions) {\n this._options = {\n orgId: options.orgId,\n projectId: options.projectId,\n env: options.env,\n apiKey: options.apiKey,\n baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,\n localConfig: options.localConfig,\n refreshIntervalMs: options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS,\n };\n\n // Initialize components\n this._errorBoundary = new ErrorBoundary(options.errorBoundary);\n this._storage = options.storage ?? createStorageProvider();\n\n this._eventLogger = new EventLogger({\n endpoint: `${this._options.baseUrl}/v1/events/batch`,\n apiKey: options.apiKey,\n storage: this._storage,\n batchSize: options.eventBatchSize,\n flushIntervalMs: options.eventFlushIntervalMs,\n onError: (error) => {\n console.warn(\"[Traffical] Event logging error:\", error.message);\n },\n });\n\n this._exposureDedup = new ExposureDeduplicator({\n storage: this._storage,\n sessionTtlMs: options.exposureSessionTtlMs,\n });\n\n this._stableId = new StableIdProvider({\n storage: this._storage,\n });\n\n this._plugins = new PluginManager();\n\n // Register decision tracking plugin (enabled by default)\n if (options.trackDecisions !== false) {\n this._plugins.register({\n plugin: createDecisionTrackingPlugin(\n { deduplicationTtlMs: options.decisionDeduplicationTtlMs },\n {\n orgId: this._options.orgId,\n projectId: this._options.projectId,\n env: this._options.env,\n log: (event: DecisionEvent) => this._eventLogger.log(event),\n }\n ),\n priority: 100, // High priority so it runs before user plugins\n });\n }\n\n // Register user-provided plugins\n if (options.plugins) {\n for (const plugin of options.plugins) {\n this._plugins.register(plugin);\n }\n }\n\n // Initialize with local config if provided\n if (this._options.localConfig) {\n this._state.bundle = this._options.localConfig;\n // Notify plugins about the local config\n this._plugins.runConfigUpdate(this._options.localConfig);\n }\n }\n\n // ===========================================================================\n // Initialization\n // ===========================================================================\n\n /**\n * Initializes the client by fetching the config bundle.\n */\n async initialize(): Promise<void> {\n await this._errorBoundary.captureAsync(\n \"initialize\",\n async () => {\n await this._fetchConfig();\n this._startBackgroundRefresh();\n this._state.isInitialized = true;\n\n // Run plugin onInitialize hooks\n await this._plugins.runInitialize();\n },\n undefined\n );\n }\n\n /**\n * Check if the client is initialized.\n */\n get isInitialized(): boolean {\n return this._state.isInitialized;\n }\n\n /**\n * Stops background refresh and cleans up resources.\n */\n destroy(): void {\n if (this._state.refreshTimer) {\n clearInterval(this._state.refreshTimer);\n this._state.refreshTimer = null;\n }\n\n // Flush any remaining events\n this._eventLogger.flushBeacon();\n this._eventLogger.destroy();\n\n // Run plugin onDestroy hooks\n this._plugins.runDestroy();\n }\n\n // ===========================================================================\n // Config Management\n // ===========================================================================\n\n /**\n * Manually refreshes the config bundle.\n */\n async refreshConfig(): Promise<void> {\n await this._errorBoundary.swallow(\"refreshConfig\", async () => {\n await this._fetchConfig();\n });\n }\n\n /**\n * Gets the current config bundle version.\n */\n getConfigVersion(): string | null {\n return this._state.bundle?.version ?? null;\n }\n\n // ===========================================================================\n // Parameter Resolution\n // ===========================================================================\n\n /**\n * Resolves parameters with defaults as fallback.\n */\n getParams<T extends Record<string, ParameterValue>>(options: { context: Context; defaults: T }): T {\n return this._errorBoundary.capture(\n \"getParams\",\n () => {\n const bundle = this._getEffectiveBundle();\n const context = this._enrichContext(options.context);\n const params = resolveParameters<T>(bundle, context, options.defaults);\n\n // Run plugin onResolve hooks (e.g., DOM binding plugin)\n this._plugins.runResolve(params);\n\n return params;\n },\n options.defaults\n );\n }\n\n /**\n * Makes a decision with full metadata for tracking.\n */\n decide<T extends Record<string, ParameterValue>>(options: { context: Context; defaults: T }): DecisionResult {\n return this._errorBoundary.capture(\n \"decide\",\n () => {\n const bundle = this._getEffectiveBundle();\n\n // Run plugin onBeforeDecision hooks\n let context = this._enrichContext(options.context);\n context = this._plugins.runBeforeDecision(context);\n\n const decision = coreDecide<T>(bundle, context, options.defaults);\n\n // Cache decision for attribution lookup when track() is called\n this._cacheDecision(decision);\n\n // Run plugin onDecision hooks (e.g., DOM binding plugin)\n this._plugins.runDecision(decision);\n\n return decision;\n },\n {\n decisionId: generateDecisionId(),\n assignments: options.defaults,\n metadata: {\n timestamp: new Date().toISOString(),\n unitKeyValue: \"\",\n layers: [],\n },\n }\n );\n }\n\n // ===========================================================================\n // Event Tracking\n // ===========================================================================\n\n /**\n * Tracks an exposure event.\n * Automatically deduplicates exposures for the same user/variant.\n */\n trackExposure(decision: DecisionResult): void {\n this._errorBoundary.capture(\n \"trackExposure\",\n () => {\n const unitKey = decision.metadata.unitKeyValue;\n if (!unitKey) return;\n\n // Check each layer for deduplication\n for (const layer of decision.metadata.layers) {\n if (!layer.policyId || !layer.allocationName) continue;\n\n // Deduplicate\n const isNew = this._exposureDedup.checkAndMark(unitKey, layer.policyId, layer.allocationName);\n if (!isNew) continue;\n\n const event: ExposureEvent = {\n type: \"exposure\",\n id: generateExposureId(), // Unique exposure ID (not same as decision)\n decisionId: decision.decisionId,\n orgId: this._options.orgId,\n projectId: this._options.projectId,\n env: this._options.env,\n unitKey,\n timestamp: new Date().toISOString(),\n assignments: decision.assignments,\n layers: decision.metadata.layers,\n context: decision.metadata.filteredContext,\n sdkName: SDK_NAME,\n sdkVersion: SDK_VERSION,\n };\n\n // Run plugin onExposure hooks\n if (!this._plugins.runExposure(event)) {\n continue;\n }\n\n this._eventLogger.log(event);\n }\n },\n undefined\n );\n }\n\n /**\n * Tracks a user event.\n * \n * @param eventName - The event name (e.g., 'purchase', 'add_to_cart')\n * @param properties - Optional event properties (including value for optimization)\n * @param options - Optional tracking options (decisionId, unitKey)\n * \n * @example\n * // Track a purchase with revenue\n * client.track('purchase', { value: 99.99, orderId: 'ord_123' });\n * \n * // Track a simple event\n * client.track('add_to_cart', { itemId: 'sku_456' });\n * \n * // Track with explicit decision attribution\n * client.track('checkout_complete', { value: 1 }, { decisionId: decision.decisionId });\n */\n track(\n eventName: string,\n properties?: Record<string, unknown>,\n options?: { decisionId?: string; unitKey?: string }\n ): void {\n this._errorBoundary.capture(\n \"track\",\n () => {\n const unitKey = options?.unitKey ?? this._stableId.getId();\n const value = typeof properties?.value === 'number' ? properties.value : undefined;\n\n // Auto-populate attribution from cached decision if available\n let attribution: TrackAttribution[] | undefined;\n const decisionId = options?.decisionId;\n if (decisionId) {\n const cachedDecision = this._decisionCache.get(decisionId);\n if (cachedDecision) {\n attribution = cachedDecision.metadata.layers\n .filter((l) => l.policyId && l.allocationName)\n .map((l) => ({\n layerId: l.layerId,\n policyId: l.policyId!,\n allocationName: l.allocationName!,\n }));\n }\n }\n\n const event: TrackEvent = {\n type: \"track\",\n id: generateTrackEventId(),\n orgId: this._options.orgId,\n projectId: this._options.projectId,\n env: this._options.env,\n unitKey,\n timestamp: new Date().toISOString(),\n event: eventName,\n value,\n properties,\n decisionId,\n attribution,\n sdkName: SDK_NAME,\n sdkVersion: SDK_VERSION,\n };\n\n // Run plugin onTrack hooks\n if (!this._plugins.runTrack(event)) {\n return;\n }\n\n this._eventLogger.log(event);\n },\n undefined\n );\n }\n\n /**\n * Flush pending events immediately.\n */\n async flushEvents(): Promise<void> {\n await this._errorBoundary.swallow(\"flushEvents\", async () => {\n await this._eventLogger.flush();\n });\n }\n\n // ===========================================================================\n // Plugin Management\n // ===========================================================================\n\n /**\n * Register a plugin.\n */\n use(plugin: TrafficalPlugin): this {\n this._plugins.register(plugin);\n return this;\n }\n\n /**\n * Get a registered plugin by name.\n */\n getPlugin(name: string): TrafficalPlugin | undefined {\n return this._plugins.get(name);\n }\n\n // ===========================================================================\n // Stable ID\n // ===========================================================================\n\n /**\n * Get the stable ID for the current user.\n */\n getStableId(): string {\n return this._stableId.getId();\n }\n\n /**\n * Set a custom stable ID (e.g., when user logs in).\n */\n setStableId(id: string): void {\n this._stableId.setId(id);\n }\n\n // ===========================================================================\n // Private Methods\n // ===========================================================================\n\n private _getEffectiveBundle(): ConfigBundle | null {\n return this._state.bundle ?? this._options.localConfig ?? null;\n }\n\n private _enrichContext(context: Context): Context {\n // Add stable ID if not already present\n const bundle = this._getEffectiveBundle();\n const unitKey = bundle?.hashing?.unitKey ?? \"userId\";\n\n if (!context[unitKey]) {\n return {\n ...context,\n [unitKey]: this._stableId.getId(),\n };\n }\n\n return context;\n }\n\n private async _fetchConfig(): Promise<void> {\n const url = `${this._options.baseUrl}/v1/config/${this._options.projectId}?env=${this._options.env}`;\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this._options.apiKey}`,\n };\n\n if (this._state.etag) {\n headers[\"If-None-Match\"] = this._state.etag;\n }\n\n try {\n const response = await fetch(url, { method: \"GET\", headers });\n\n if (response.status === 304) {\n this._state.lastFetchTime = Date.now();\n return;\n }\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const bundle = (await response.json()) as ConfigBundle;\n const etag = response.headers.get(\"ETag\");\n\n this._state.bundle = bundle;\n this._state.etag = etag;\n this._state.lastFetchTime = Date.now();\n\n // Run plugin onConfigUpdate hooks (e.g., DOM binding plugin)\n this._plugins.runConfigUpdate(bundle);\n } catch (error) {\n this._logOfflineWarning(error);\n }\n }\n\n private _startBackgroundRefresh(): void {\n if (this._options.refreshIntervalMs <= 0) return;\n\n this._state.refreshTimer = setInterval(() => {\n this._fetchConfig().catch(() => {\n // Errors logged in _fetchConfig\n });\n }, this._options.refreshIntervalMs);\n }\n\n private _logOfflineWarning(error: unknown): void {\n const now = Date.now();\n if (now - this._state.lastOfflineWarning > OFFLINE_WARNING_INTERVAL_MS) {\n console.warn(\n `[Traffical] Failed to fetch config: ${error instanceof Error ? error.message : String(error)}. Using ${this._state.bundle ? \"cached\" : \"local\"} config.`\n );\n this._state.lastOfflineWarning = now;\n }\n }\n\n /**\n * Caches a decision for attribution lookup when track() is called.\n * Maintains a bounded cache to prevent memory leaks.\n */\n private _cacheDecision(decision: DecisionResult): void {\n // Evict oldest entries if cache is full\n if (this._decisionCache.size >= DECISION_CACHE_MAX_SIZE) {\n // Get first (oldest) key and delete it\n const firstKey = this._decisionCache.keys().next().value;\n if (firstKey) {\n this._decisionCache.delete(firstKey);\n }\n }\n this._decisionCache.set(decision.decisionId, decision);\n }\n}\n\n// =============================================================================\n// Factory Functions\n// =============================================================================\n\n/**\n * Creates and initializes a Traffical client.\n */\nexport async function createTrafficalClient(options: TrafficalClientOptions): Promise<TrafficalClient> {\n const client = new TrafficalClient(options);\n await client.initialize();\n return client;\n}\n\n/**\n * Creates a Traffical client without initializing (synchronous).\n */\nexport function createTrafficalClientSync(options: TrafficalClientOptions): TrafficalClient {\n return new TrafficalClient(options);\n}\n\n", "/**\n * DOM Binding Plugin\n *\n * Automatically applies parameter values to DOM elements based on bindings\n * configured in Traffical via the visual editor.\n *\n * Features:\n * - URL pattern matching to apply bindings only on matching pages\n * - MutationObserver for dynamic content (SPA support)\n * - Supports multiple property types: innerHTML, textContent, src, href, style.*\n *\n * @example\n * ```typescript\n * import { createTrafficalClient, createDOMBindingPlugin } from '@traffical/js-client';\n *\n * const client = await createTrafficalClient({\n * // ... config\n * plugins: [createDOMBindingPlugin()],\n * });\n * ```\n */\n\nimport type { ConfigBundle, BundleDOMBinding, ParameterValue } from \"@traffical/core\";\nimport type { TrafficalPlugin } from \"./types.js\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface DOMBindingPluginOptions {\n /**\n * Whether to start observing DOM mutations automatically.\n * Useful for SPAs where content is dynamically loaded.\n * @default true\n */\n observeMutations?: boolean;\n\n /**\n * Debounce time in ms for mutation-triggered reapplication.\n * @default 100\n */\n debounceMs?: number;\n}\n\n// =============================================================================\n// DOM Binding Plugin\n// =============================================================================\n\n/**\n * Creates a DOM binding plugin instance.\n *\n * This plugin applies parameter values to DOM elements based on bindings\n * defined via the visual editor.\n */\nexport function createDOMBindingPlugin(\n options: DOMBindingPluginOptions = {}\n): TrafficalPlugin & { applyBindings: (params?: Record<string, unknown>) => void; getBindings: () => BundleDOMBinding[] } {\n const config = {\n observeMutations: options.observeMutations ?? true,\n debounceMs: options.debounceMs ?? 100,\n };\n\n // Internal state\n let bindings: BundleDOMBinding[] = [];\n let lastParams: Record<string, unknown> = {};\n let observer: MutationObserver | null = null;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n // ==========================================================================\n // Core binding logic\n // ==========================================================================\n\n /**\n * Check if URL matches the binding's pattern.\n */\n function matchesUrlPattern(pattern: string, path: string): boolean {\n try {\n const regex = new RegExp(pattern);\n return regex.test(path);\n } catch {\n // Invalid regex - fall back to exact match\n return path === pattern;\n }\n }\n\n /**\n * Set a property on an element.\n * Supports: innerHTML, textContent, src, href, style.*\n */\n function setProperty(element: HTMLElement, property: string, value: string): void {\n if (property === \"innerHTML\") {\n element.innerHTML = value;\n } else if (property === \"textContent\") {\n element.textContent = value;\n } else if (property === \"src\" && \"src\" in element) {\n (element as HTMLImageElement).src = value;\n } else if (property === \"href\" && \"href\" in element) {\n (element as HTMLAnchorElement).href = value;\n } else if (property.startsWith(\"style.\")) {\n const styleProp = property.slice(6); // Remove \"style.\" prefix\n (element.style as unknown as Record<string, string>)[styleProp] = value;\n } else {\n // Generic attribute setter for other properties\n element.setAttribute(property, value);\n }\n }\n\n /**\n * Apply a single binding to matching elements.\n */\n function applyBinding(binding: BundleDOMBinding, value: unknown): void {\n const stringValue = String(value);\n\n try {\n const elements = document.querySelectorAll(binding.selector);\n\n for (const element of elements) {\n setProperty(element as HTMLElement, binding.property, stringValue);\n }\n } catch (error) {\n // Invalid selector or other DOM error - silently warn\n console.warn(\n `[Traffical DOM Binding] Failed to apply binding for ${binding.parameterKey}:`,\n error\n );\n }\n }\n\n /**\n * Apply parameter values to matching DOM elements.\n */\n function apply(params: Record<string, unknown>, forceAll = false): void {\n lastParams = params;\n\n const currentPath = typeof window !== \"undefined\" ? window.location.pathname : \"\";\n\n for (const binding of bindings) {\n // Check URL pattern match\n if (!forceAll && !matchesUrlPattern(binding.urlPattern, currentPath)) {\n continue;\n }\n\n // Get parameter value\n const value = params[binding.parameterKey];\n if (value === undefined) {\n continue;\n }\n\n // Apply to matching elements\n applyBinding(binding, value);\n }\n }\n\n /**\n * Debounced reapplication of bindings after DOM mutations.\n */\n function debouncedApply(): void {\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n }\n\n debounceTimer = setTimeout(() => {\n apply(lastParams);\n }, config.debounceMs);\n }\n\n /**\n * Start observing DOM mutations.\n */\n function startObserving(): void {\n if (observer || typeof MutationObserver === \"undefined\" || typeof document === \"undefined\") {\n return;\n }\n\n observer = new MutationObserver(() => {\n debouncedApply();\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n }\n\n /**\n * Stop observing DOM mutations.\n */\n function stopObserving(): void {\n if (observer) {\n observer.disconnect();\n observer = null;\n }\n\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n debounceTimer = null;\n }\n }\n\n // ==========================================================================\n // Plugin implementation\n // ==========================================================================\n\n return {\n name: \"dom-binding\",\n\n onInitialize() {\n // Start observing if enabled\n if (config.observeMutations) {\n startObserving();\n }\n },\n\n onConfigUpdate(bundle: ConfigBundle) {\n // Update bindings from bundle\n bindings = bundle.domBindings ?? [];\n },\n\n onResolve(params: Record<string, ParameterValue>) {\n // Apply bindings after parameters are resolved\n apply(params as Record<string, unknown>);\n },\n\n onDecision(decision) {\n // Also apply after decide() calls\n apply(decision.assignments as Record<string, unknown>);\n },\n\n onDestroy() {\n stopObserving();\n bindings = [];\n lastParams = {};\n },\n\n // ==========================================================================\n // Public API (exposed on plugin instance)\n // ==========================================================================\n\n /**\n * Manually apply DOM bindings with the given parameter values.\n * Use this to re-trigger bindings after dynamic content changes.\n *\n * @param params - Parameter values to apply. If omitted, uses last known params.\n */\n applyBindings(params?: Record<string, unknown>): void {\n if (params) {\n apply(params);\n } else {\n apply(lastParams);\n }\n },\n\n /**\n * Get the current DOM bindings from the config bundle.\n */\n getBindings(): BundleDOMBinding[] {\n return bindings;\n },\n };\n}\n\n// Type for the plugin with its public API\nexport type DOMBindingPlugin = ReturnType<typeof createDOMBindingPlugin>;\n\n"],
5
+ "mappings": "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",
6
+ "names": ["require_crypto", "__commonJSMin", "global_exports", "__export", "TrafficalClient", "createDOMBindingPlugin", "destroy", "init", "initSync", "instance", "fnv1a", "input", "hash", "i", "computeBucket", "unitKeyValue", "layerId", "bucketCount", "input", "fnv1a", "isInBucketRange", "bucket", "range", "findMatchingAllocation", "allocations", "allocation", "evaluateCondition", "condition", "context", "field", "op", "value", "values", "contextValue", "getNestedValue", "evaluateConditions", "conditions", "obj", "path", "parts", "current", "part", "random", "bytes", "customRandom", "alphabet", "defaultSize", "getRandom", "mask", "step", "size", "id", "j", "customAlphabet", "createError", "message", "err", "ENCODING", "ENCODING_LEN", "TIME_MAX", "TIME_LEN", "RANDOM_LEN", "randomChar", "prng", "rand", "ENCODING_LEN", "ENCODING", "encodeTime", "now", "len", "TIME_MAX", "createError", "mod", "str", "encodeRandom", "detectPrng", "allowInsecure", "root", "browserCrypto", "buffer", "nodeCrypto", "createError", "factory", "currPrng", "seedTime", "encodeTime", "TIME_LEN", "encodeRandom", "RANDOM_LEN", "ulid", "factory", "NANOID_ALPHABET", "ENTITY_ID_LENGTH", "nanoid", "customAlphabet", "generateEventId", "prefix", "ulid", "generateDecisionId", "generateEventId", "generateExposureId", "generateTrackEventId", "filterContext", "context", "policies", "allowedFields", "policy", "field", "filtered", "buildEntityId", "entityKeys", "parts", "key", "value", "weightedSelection", "weights", "seed", "random", "fnv1a", "cumulative", "i", "createUniformWeights", "count", "weight", "getEntityWeights", "bundle", "policyId", "entityId", "allocationCount", "policyState", "entityWeights", "globalWeights", "resolvePerEntityPolicy", "unitKeyValue", "entityConfig", "allocations", "countKey", "_", "selectedIndex", "getUnitKeyValue", "resolveInternal", "defaults", "assignments", "layers", "matchedPolicies", "requestedKeys", "params", "p", "param", "paramsByLayer", "existing", "layer", "layerParams", "bucket", "computeBucket", "matchedPolicy", "matchedAllocation", "start", "end", "evaluateConditions", "result", "allocation", "findMatchingAllocation", "resolveParameters", "decide", "filteredContext", "generateDecisionId", "DecisionDeduplicator", "_DecisionDeduplicator", "options", "__publicField", "assignments", "sortedKeys", "parts", "key", "value", "valueStr", "unitKey", "assignmentHash", "now", "lastSeen", "expiredKeys", "timestamp", "toRemove", "a", "b", "ErrorBoundary", "options", "tag", "fn", "fallback", "error", "resolvedError", "errorKey", "FAILED_EVENTS_KEY", "EventLogger", "options", "event", "events", "error", "response", "combined", "FAILED_EVENTS_KEY", "failed", "STORAGE_KEY", "ExposureDeduplicator", "_ExposureDeduplicator", "options", "unitKey", "policyId", "variant", "key", "STORAGE_KEY", "state", "STORAGE_KEY", "COOKIE_NAME", "StableIdProvider", "options", "COOKIE_NAME", "id", "STORAGE_KEY", "c", "r", "cookies", "cookie", "name", "value", "STORAGE_PREFIX", "LocalStorageProvider", "key", "raw", "stored", "value", "ttlMs", "keysToRemove", "i", "testKey", "MemoryStorageProvider", "createStorageProvider", "localProvider", "localStorageAvailable", "SDK_NAME", "SDK_VERSION", "createDecisionTrackingPlugin", "options", "deps", "dedup", "DecisionDeduplicator", "decision", "unitKey", "hash", "event", "PluginManager", "options", "plugin", "priority", "p", "a", "b", "name", "index", "error", "bundle", "context", "result", "modified", "decision", "params", "event", "SDK_NAME", "SDK_VERSION", "DEFAULT_BASE_URL", "DEFAULT_REFRESH_INTERVAL_MS", "OFFLINE_WARNING_INTERVAL_MS", "DECISION_CACHE_MAX_SIZE", "TrafficalClient", "options", "ErrorBoundary", "createStorageProvider", "EventLogger", "error", "ExposureDeduplicator", "StableIdProvider", "PluginManager", "createDecisionTrackingPlugin", "event", "plugin", "bundle", "context", "params", "resolveParameters", "decision", "decide", "generateDecisionId", "unitKey", "layer", "generateExposureId", "eventName", "properties", "value", "attribution", "decisionId", "cachedDecision", "l", "generateTrackEventId", "name", "id", "url", "headers", "response", "etag", "now", "firstKey", "createTrafficalClient", "client", "createTrafficalClientSync", "createDOMBindingPlugin", "options", "config", "bindings", "lastParams", "observer", "debounceTimer", "matchesUrlPattern", "pattern", "path", "setProperty", "element", "property", "value", "styleProp", "applyBinding", "binding", "stringValue", "elements", "error", "apply", "params", "forceAll", "currentPath", "debouncedApply", "startObserving", "stopObserving", "bundle", "decision", "_instance", "init", "options", "createTrafficalClient", "initSync", "createTrafficalClientSync", "error", "instance", "destroy"]
7
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@traffical/js-client",
3
+ "version": "0.1.2",
4
+ "description": "Traffical JavaScript SDK for browser environments",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "bun run build:esm && bun run build:iife",
20
+ "build:esm": "tsc",
21
+ "build:iife": "bun run esbuild.config.ts",
22
+ "dev": "tsc --watch",
23
+ "test": "bun test",
24
+ "typecheck": "tsc --noEmit",
25
+ "release": "bun scripts/release.ts"
26
+ },
27
+ "dependencies": {
28
+ "@traffical/core": "workspace:^"
29
+ },
30
+ "devDependencies": {
31
+ "@types/bun": "latest",
32
+ "esbuild": "^0.20.0",
33
+ "typescript": "^5.3.0"
34
+ },
35
+ "keywords": [
36
+ "traffical",
37
+ "feature-flags",
38
+ "experimentation",
39
+ "a/b-testing",
40
+ "javascript",
41
+ "browser"
42
+ ],
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/traffical/js-sdk"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }