@schematichq/schematic-js 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,9 @@ pnpm add @schematichq/schematic-js
14
14
 
15
15
  ## Usage
16
16
 
17
- You can use Schematic to identify users; after this, your subsequent track events and flag checks will be associated with this user.
17
+ You can use Schematic to identify users; after this, your subsequent track events and flag checks will be associated with this user.
18
+
19
+ A number of these examples use `keys` to identify companies and users. Learn more about keys [here](https://docs.schematichq.com/developer_resources/key_management).
18
20
 
19
21
  ```typescript
20
22
  import { Schematic } from "@schematichq/schematic-js";
@@ -42,9 +44,11 @@ schematic.identify({
42
44
 
43
45
  // Send a track event to record usage
44
46
  schematic.track({ event: "query" });
47
+ // OR, Send a track event with a quantity to record multiple units of usage
48
+ schematic.track({ event: "query", quantity: 10 });
45
49
 
46
50
  // Check a flag
47
- await schematic.checkFlag("some-flag-key");
51
+ await schematic.checkFlag({ key: "some-flag-key" });
48
52
  ```
49
53
 
50
54
  By default, `checkFlag` will perform a network request to get the flag value for this user. If you'd like to check all flags at once in order to minimize network requests, you can use `checkFlags`:
@@ -1,3 +1,3 @@
1
- "use strict";(()=>{var ne=Object.create;var M=Object.defineProperty;var se=Object.getOwnPropertyDescriptor;var ae=Object.getOwnPropertyNames;var ie=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var le=(r,t)=>()=>(t||r((t={exports:{}}).exports,t),t.exports);var ce=(r,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of ae(t))!oe.call(r,i)&&i!==n&&M(r,i,{get:()=>t[i],enumerable:!(s=se(t,i))||s.enumerable});return r};var ue=(r,t,n)=>(n=r!=null?ne(ie(r)):{},ce(t||!r||!r.__esModule?M(n,"default",{value:r,enumerable:!0}):n,r));var z=le(K=>{(function(r){var t=function(n){var s=typeof globalThis<"u"&&globalThis||typeof r<"u"&&r||typeof global<"u"&&global||{},i={searchParams:"URLSearchParams"in s,iterable:"Symbol"in s&&"iterator"in Symbol,blob:"FileReader"in s&&"Blob"in s&&function(){try{return new Blob,!0}catch{return!1}}(),formData:"FormData"in s,arrayBuffer:"ArrayBuffer"in s};function c(e){return e&&DataView.prototype.isPrototypeOf(e)}if(i.arrayBuffer)var u=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],f=ArrayBuffer.isView||function(e){return e&&u.indexOf(Object.prototype.toString.call(e))>-1};function p(e){if(typeof e!="string"&&(e=String(e)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(e)||e==="")throw new TypeError('Invalid character in header field name: "'+e+'"');return e.toLowerCase()}function k(e){return typeof e!="string"&&(e=String(e)),e}function E(e){var a={next:function(){var o=e.shift();return{done:o===void 0,value:o}}};return i.iterable&&(a[Symbol.iterator]=function(){return a}),a}function d(e){this.map={},e instanceof d?e.forEach(function(a,o){this.append(o,a)},this):Array.isArray(e)?e.forEach(function(a){if(a.length!=2)throw new TypeError("Headers constructor: expected name/value pair to be length 2, found"+a.length);this.append(a[0],a[1])},this):e&&Object.getOwnPropertyNames(e).forEach(function(a){this.append(a,e[a])},this)}d.prototype.append=function(e,a){e=p(e),a=k(a);var o=this.map[e];this.map[e]=o?o+", "+a:a},d.prototype.delete=function(e){delete this.map[p(e)]},d.prototype.get=function(e){return e=p(e),this.has(e)?this.map[e]:null},d.prototype.has=function(e){return this.map.hasOwnProperty(p(e))},d.prototype.set=function(e,a){this.map[p(e)]=k(a)},d.prototype.forEach=function(e,a){for(var o in this.map)this.map.hasOwnProperty(o)&&e.call(a,this.map[o],o,this)},d.prototype.keys=function(){var e=[];return this.forEach(function(a,o){e.push(o)}),E(e)},d.prototype.values=function(){var e=[];return this.forEach(function(a){e.push(a)}),E(e)},d.prototype.entries=function(){var e=[];return this.forEach(function(a,o){e.push([o,a])}),E(e)},i.iterable&&(d.prototype[Symbol.iterator]=d.prototype.entries);function F(e){if(!e._noBody){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}}function b(e){return new Promise(function(a,o){e.onload=function(){a(e.result)},e.onerror=function(){o(e.error)}})}function V(e){var a=new FileReader,o=b(a);return a.readAsArrayBuffer(e),o}function Q(e){var a=new FileReader,o=b(a),h=/charset=([A-Za-z0-9_-]+)/.exec(e.type),g=h?h[1]:"utf-8";return a.readAsText(e,g),o}function Y(e){for(var a=new Uint8Array(e),o=new Array(a.length),h=0;h<a.length;h++)o[h]=String.fromCharCode(a[h]);return o.join("")}function q(e){if(e.slice)return e.slice(0);var a=new Uint8Array(e.byteLength);return a.set(new Uint8Array(e)),a.buffer}function H(){return this.bodyUsed=!1,this._initBody=function(e){this.bodyUsed=this.bodyUsed,this._bodyInit=e,e?typeof e=="string"?this._bodyText=e:i.blob&&Blob.prototype.isPrototypeOf(e)?this._bodyBlob=e:i.formData&&FormData.prototype.isPrototypeOf(e)?this._bodyFormData=e:i.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)?this._bodyText=e.toString():i.arrayBuffer&&i.blob&&c(e)?(this._bodyArrayBuffer=q(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):i.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(e)||f(e))?this._bodyArrayBuffer=q(e):this._bodyText=e=Object.prototype.toString.call(e):(this._noBody=!0,this._bodyText=""),this.headers.get("content-type")||(typeof e=="string"?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):i.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},i.blob&&(this.blob=function(){var e=F(this);if(e)return e;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))}),this.arrayBuffer=function(){if(this._bodyArrayBuffer){var e=F(this);return e||(ArrayBuffer.isView(this._bodyArrayBuffer)?Promise.resolve(this._bodyArrayBuffer.buffer.slice(this._bodyArrayBuffer.byteOffset,this._bodyArrayBuffer.byteOffset+this._bodyArrayBuffer.byteLength)):Promise.resolve(this._bodyArrayBuffer))}else{if(i.blob)return this.blob().then(V);throw new Error("could not read as ArrayBuffer")}},this.text=function(){var e=F(this);if(e)return e;if(this._bodyBlob)return Q(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(Y(this._bodyArrayBuffer));if(this._bodyFormData)throw new Error("could not read FormData body as text");return Promise.resolve(this._bodyText)},i.formData&&(this.formData=function(){return this.text().then(ee)}),this.json=function(){return this.text().then(JSON.parse)},this}var Z=["CONNECT","DELETE","GET","HEAD","OPTIONS","PATCH","POST","PUT","TRACE"];function j(e){var a=e.toUpperCase();return Z.indexOf(a)>-1?a:e}function C(e,a){if(!(this instanceof C))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');a=a||{};var o=a.body;if(e instanceof C){if(e.bodyUsed)throw new TypeError("Already read");this.url=e.url,this.credentials=e.credentials,a.headers||(this.headers=new d(e.headers)),this.method=e.method,this.mode=e.mode,this.signal=e.signal,!o&&e._bodyInit!=null&&(o=e._bodyInit,e.bodyUsed=!0)}else this.url=String(e);if(this.credentials=a.credentials||this.credentials||"same-origin",(a.headers||!this.headers)&&(this.headers=new d(a.headers)),this.method=j(a.method||this.method||"GET"),this.mode=a.mode||this.mode||null,this.signal=a.signal||this.signal||function(){if("AbortController"in s){var l=new AbortController;return l.signal}}(),this.referrer=null,(this.method==="GET"||this.method==="HEAD")&&o)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(o),(this.method==="GET"||this.method==="HEAD")&&(a.cache==="no-store"||a.cache==="no-cache")){var h=/([?&])_=[^&]*/;if(h.test(this.url))this.url=this.url.replace(h,"$1_="+new Date().getTime());else{var g=/\?/;this.url+=(g.test(this.url)?"&":"?")+"_="+new Date().getTime()}}}C.prototype.clone=function(){return new C(this,{body:this._bodyInit})};function ee(e){var a=new FormData;return e.trim().split("&").forEach(function(o){if(o){var h=o.split("="),g=h.shift().replace(/\+/g," "),l=h.join("=").replace(/\+/g," ");a.append(decodeURIComponent(g),decodeURIComponent(l))}}),a}function te(e){var a=new d,o=e.replace(/\r?\n[\t ]+/g," ");return o.split("\r").map(function(h){return h.indexOf(`
2
- `)===0?h.substr(1,h.length):h}).forEach(function(h){var g=h.split(":"),l=g.shift().trim();if(l){var x=g.join(":").trim();try{a.append(l,x)}catch(U){console.warn("Response "+U.message)}}}),a}H.call(C.prototype);function v(e,a){if(!(this instanceof v))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');if(a||(a={}),this.type="default",this.status=a.status===void 0?200:a.status,this.status<200||this.status>599)throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].");this.ok=this.status>=200&&this.status<300,this.statusText=a.statusText===void 0?"":""+a.statusText,this.headers=new d(a.headers),this.url=a.url||"",this._initBody(e)}H.call(v.prototype),v.prototype.clone=function(){return new v(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new d(this.headers),url:this.url})},v.error=function(){var e=new v(null,{status:200,statusText:""});return e.ok=!1,e.status=0,e.type="error",e};var re=[301,302,303,307,308];v.redirect=function(e,a){if(re.indexOf(a)===-1)throw new RangeError("Invalid status code");return new v(null,{status:a,headers:{location:e}})},n.DOMException=s.DOMException;try{new n.DOMException}catch{n.DOMException=function(a,o){this.message=a,this.name=o;var h=Error(a);this.stack=h.stack},n.DOMException.prototype=Object.create(Error.prototype),n.DOMException.prototype.constructor=n.DOMException}function I(e,a){return new Promise(function(o,h){var g=new C(e,a);if(g.signal&&g.signal.aborted)return h(new n.DOMException("Aborted","AbortError"));var l=new XMLHttpRequest;function x(){l.abort()}l.onload=function(){var y={statusText:l.statusText,headers:te(l.getAllResponseHeaders()||"")};g.url.indexOf("file://")===0&&(l.status<200||l.status>599)?y.status=200:y.status=l.status,y.url="responseURL"in l?l.responseURL:y.headers.get("X-Request-URL");var R="response"in l?l.response:l.responseText;setTimeout(function(){o(new v(R,y))},0)},l.onerror=function(){setTimeout(function(){h(new TypeError("Network request failed"))},0)},l.ontimeout=function(){setTimeout(function(){h(new TypeError("Network request timed out"))},0)},l.onabort=function(){setTimeout(function(){h(new n.DOMException("Aborted","AbortError"))},0)};function U(y){try{return y===""&&s.location.href?s.location.href:y}catch{return y}}if(l.open(g.method,U(g.url),!0),g.credentials==="include"?l.withCredentials=!0:g.credentials==="omit"&&(l.withCredentials=!1),"responseType"in l&&(i.blob?l.responseType="blob":i.arrayBuffer&&(l.responseType="arraybuffer")),a&&typeof a.headers=="object"&&!(a.headers instanceof d||s.Headers&&a.headers instanceof s.Headers)){var W=[];Object.getOwnPropertyNames(a.headers).forEach(function(y){W.push(p(y)),l.setRequestHeader(y,k(a.headers[y]))}),g.headers.forEach(function(y,R){W.indexOf(R)===-1&&l.setRequestHeader(R,y)})}else g.headers.forEach(function(y,R){l.setRequestHeader(R,y)});g.signal&&(g.signal.addEventListener("abort",x),l.onreadystatechange=function(){l.readyState===4&&g.signal.removeEventListener("abort",x)}),l.send(typeof g._bodyInit>"u"?null:g._bodyInit)})}return I.polyfill=!0,s.fetch||(s.fetch=I,s.Headers=d,s.Request=C,s.Response=v),n.Headers=d,n.Request=C,n.Response=v,n.fetch=I,n}({})})(typeof self<"u"?self:K)});var m=[];for(let r=0;r<256;++r)m.push((r+256).toString(16).slice(1));function $(r,t=0){return(m[r[t+0]]+m[r[t+1]]+m[r[t+2]]+m[r[t+3]]+"-"+m[r[t+4]]+m[r[t+5]]+"-"+m[r[t+6]]+m[r[t+7]]+"-"+m[r[t+8]]+m[r[t+9]]+"-"+m[r[t+10]]+m[r[t+11]]+m[r[t+12]]+m[r[t+13]]+m[r[t+14]]+m[r[t+15]]).toLowerCase()}var P,de=new Uint8Array(16);function A(){if(!P){if(typeof crypto>"u"||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");P=crypto.getRandomValues.bind(crypto)}return P(de)}var fe=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),L={randomUUID:fe};function he(r,t,n){if(L.randomUUID&&!t&&!r)return L.randomUUID();r=r||{};let s=r.random??r.rng?.()??A();if(s.length<16)throw new Error("Random bytes length must be >= 16");if(s[6]=s[6]&15|64,s[8]=s[8]&63|128,t){if(n=n||0,n<0||n+16>t.length)throw new RangeError(`UUID byte range ${n}:${n+15} is out of buffer bounds`);for(let i=0;i<16;++i)t[n+i]=s[i];return t}return $(s)}var _=he;var rt=ue(z());function w(r){return ge(r,!1)}function ge(r,t){return r==null?r:{companyId:r.company_id==null?void 0:r.company_id,error:r.error==null?void 0:r.error,featureAllocation:r.feature_allocation==null?void 0:r.feature_allocation,featureUsage:r.feature_usage==null?void 0:r.feature_usage,featureUsagePeriod:r.feature_usage_period==null?void 0:r.feature_usage_period,featureUsageResetAt:r.feature_usage_reset_at==null?void 0:new Date(r.feature_usage_reset_at),flag:r.flag,flagId:r.flag_id==null?void 0:r.flag_id,reason:r.reason,ruleId:r.rule_id==null?void 0:r.rule_id,ruleType:r.rule_type==null?void 0:r.rule_type,userId:r.user_id==null?void 0:r.user_id,value:r.value}}function B(r){return ye(r,!1)}function ye(r,t=!1){return r==null?r:{company_id:r.companyId,error:r.error,flag_id:r.flagId,flag_key:r.flagKey,reason:r.reason,req_company:r.reqCompany,req_user:r.reqUser,rule_id:r.ruleId,user_id:r.userId,value:r.value}}function T(r){return be(r,!1)}function be(r,t){return r==null?r:{data:w(r.data),params:r.params}}function X(r){return ke(r,!1)}function ke(r,t){return r==null?r:{flags:r.flags.map(w)}}function N(r){return Fe(r,!1)}function Fe(r,t){return r==null?r:{data:X(r.data),params:r.params}}var O=r=>{let{companyId:t,error:n,featureAllocation:s,featureUsage:i,featureUsagePeriod:c,featureUsageResetAt:u,flag:f,flagId:p,reason:k,ruleId:E,ruleType:d,userId:F,value:b}=w(r);return{featureUsageExceeded:!b&&(d=="company_override_usage_exceeded"||d=="plan_entitlement_usage_exceeded"),companyId:t??void 0,error:n??void 0,featureAllocation:s??void 0,featureUsage:i??void 0,featureUsagePeriod:c??void 0,featureUsageResetAt:u??void 0,flag:f,flagId:p??void 0,reason:k,ruleId:E??void 0,ruleType:d??void 0,userId:F??void 0,value:b}};function S(r){let t=Object.keys(r).reduce((n,s)=>{let c=Object.keys(r[s]||{}).sort().reduce((u,f)=>(u[f]=r[s][f],u),{});return n[s]=c,n},{});return JSON.stringify(t)}var J="1.2.2";var G="schematicId";var D=class{additionalHeaders={};apiKey;apiUrl="https://api.schematichq.com";conn=null;context={};debugEnabled=!1;offlineEnabled=!1;eventQueue;eventUrl="https://c.schematichq.com";flagCheckListeners={};flagValueListeners={};isPending=!0;isPendingListeners=new Set;storage;useWebSocket=!1;checks={};webSocketUrl="wss://api.schematichq.com";constructor(t,n){if(this.apiKey=t,this.eventQueue=[],this.useWebSocket=n?.useWebSocket??!1,this.debugEnabled=n?.debug??!1,this.offlineEnabled=n?.offline??!1,typeof window<"u"&&typeof window.location<"u"){let s=new URLSearchParams(window.location.search),i=s.get("schematic_debug");i!==null&&(i===""||i==="true"||i==="1")&&(this.debugEnabled=!0);let c=s.get("schematic_offline");c!==null&&(c===""||c==="true"||c==="1")&&(this.offlineEnabled=!0,this.debugEnabled=!0)}this.offlineEnabled&&n?.debug!==!1&&(this.debugEnabled=!0),this.offlineEnabled&&this.setIsPending(!1),this.additionalHeaders={"X-Schematic-Client-Version":`schematic-js@${J}`,...n?.additionalHeaders??{}},n?.storage?this.storage=n.storage:typeof localStorage<"u"&&(this.storage=localStorage),n?.apiUrl!==void 0&&(this.apiUrl=n.apiUrl),n?.eventUrl!==void 0&&(this.eventUrl=n.eventUrl),n?.webSocketUrl!==void 0&&(this.webSocketUrl=n.webSocketUrl),typeof window<"u"&&window?.addEventListener&&window.addEventListener("beforeunload",()=>{this.flushEventQueue()}),this.offlineEnabled?this.debug("Initialized with offline mode enabled - no network requests will be made"):this.debugEnabled&&this.debug("Initialized with debug mode enabled")}async checkFlag(t){let{fallback:n=!1,key:s}=t,i=t.context||this.context,c=S(i);if(this.debug(`checkFlag: ${s}`,{context:i,fallback:n}),this.isOffline())return this.debug(`checkFlag offline result: ${s}`,{value:n,offlineMode:!0}),n;if(!this.useWebSocket){let u=`${this.apiUrl}/flags/${s}/check`;return fetch(u,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(i)}).then(f=>{if(!f.ok)throw new Error("Network response was not ok");return f.json()}).then(f=>{let p=T(f);this.debug(`checkFlag result: ${s}`,p);let k=O(p.data);return this.submitFlagCheckEvent(s,k,i),k.value}).catch(f=>{console.error("There was a problem with the fetch operation:",f);let p={flag:s,value:n,reason:"API request failed",error:f instanceof Error?f.message:String(f)};return this.submitFlagCheckEvent(s,p,i),n})}try{let u=this.checks[c];if(this.conn!==null&&typeof u<"u"&&typeof u[s]<"u")return this.debug(`checkFlag cached result: ${s}`,u[s]),u[s].value;if(this.isOffline())return n;try{await this.setContext(i)}catch(E){return console.error("WebSocket connection failed, falling back to REST:",E),this.fallbackToRest(s,i,n)}let p=(this.checks[c]??{})[s],k=p?.value??n;return this.debug(`checkFlag WebSocket result: ${s}`,typeof p<"u"?p:{value:n,fallbackUsed:!0}),typeof p<"u"&&this.submitFlagCheckEvent(s,p,i),k}catch(u){console.error("Unexpected error in checkFlag:",u);let f={flag:s,value:n,reason:"Unexpected error in flag check",error:u instanceof Error?u.message:String(u)};return this.submitFlagCheckEvent(s,f,i),n}}debug(t,...n){this.debugEnabled&&console.log(`[Schematic] ${t}`,...n)}isOffline(){return this.offlineEnabled}submitFlagCheckEvent(t,n,s){let i={flagKey:t,value:n.value,reason:n.reason,flagId:n.flagId,ruleId:n.ruleId,companyId:n.companyId,userId:n.userId,error:n.error,reqCompany:s.company,reqUser:s.user};return this.debug("submitting flag check event:",i),this.handleEvent("flag_check",B(i))}async fallbackToRest(t,n,s){if(this.isOffline())return this.debug(`fallbackToRest offline result: ${t}`,{value:s,offlineMode:!0}),s;try{let i=`${this.apiUrl}/flags/${t}/check`,c=await fetch(i,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(n)});if(!c.ok)throw new Error("Network response was not ok");let u=await c.json(),f=T(u);this.debug(`fallbackToRest result: ${t}`,f);let p=O(f.data);return this.submitFlagCheckEvent(t,p,n),p.value}catch(i){console.error("REST API call failed, using fallback value:",i);let c={flag:t,value:s,reason:"API request failed (fallback)",error:i instanceof Error?i.message:String(i)};return this.submitFlagCheckEvent(t,c,n),s}}checkFlags=async t=>{if(t=t||this.context,this.debug("checkFlags",{context:t}),this.isOffline())return this.debug("checkFlags offline result: returning empty object"),{};let n=`${this.apiUrl}/flags/check`,s=JSON.stringify(t);return fetch(n,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:s}).then(i=>{if(!i.ok)throw new Error("Network response was not ok");return i.json()}).then(i=>{let c=N(i);return this.debug("checkFlags result:",c),(c?.data?.flags??[]).reduce((u,f)=>(u[f.flag]=f.value,u),{})}).catch(i=>(console.error("There was a problem with the fetch operation:",i),{}))};identify=t=>{this.debug("identify:",t);try{this.setContext({company:t.company?.keys,user:t.keys})}catch(n){console.error("Error setting context:",n)}return this.handleEvent("identify",t)};setContext=async t=>{if(this.isOffline())return this.context=t,this.setIsPending(!1),Promise.resolve();if(!this.useWebSocket)return Promise.resolve();try{this.setIsPending(!0),this.conn||(this.conn=this.wsConnect());let n=await this.conn;await this.wsSendMessage(n,t)}catch(n){throw console.error("Failed to establish WebSocket connection:",n),n}};track=t=>{let{company:n,user:s,event:i,traits:c}=t,u={company:n??this.context.company,event:i,traits:c??{},user:s??this.context.user};return this.debug("track:",u),this.handleEvent("track",u)};flushEventQueue=()=>{for(;this.eventQueue.length>0;){let t=this.eventQueue.shift();t&&this.sendEvent(t)}};getAnonymousId=()=>{if(!this.storage)return _();let t=this.storage.getItem(G);if(typeof t<"u")return t;let n=_();return this.storage.setItem(G,n),n};handleEvent=(t,n)=>{let s={api_key:this.apiKey,body:n,sent_at:new Date().toISOString(),tracker_event_id:_(),tracker_user_id:this.getAnonymousId(),type:t};return document?.hidden?this.storeEvent(s):this.sendEvent(s)};sendEvent=async t=>{let n=`${this.eventUrl}/e`,s=JSON.stringify(t);if(this.debug("sending event:",{url:n,event:t}),this.isOffline())return this.debug("event not sent (offline mode):",{event:t}),Promise.resolve();try{let i=await fetch(n,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8"},body:s});this.debug("event sent:",{status:i.status,statusText:i.statusText})}catch(i){console.error("Error sending Schematic event: ",i)}return Promise.resolve()};storeEvent=t=>(this.eventQueue.push(t),Promise.resolve());cleanup=async()=>{if(this.isOffline())return this.debug("cleanup: skipped (offline mode)"),Promise.resolve();if(this.conn)try{(await this.conn).close()}catch(t){console.error("Error during cleanup:",t)}finally{this.conn=null}};wsConnect=()=>this.isOffline()?(this.debug("wsConnect: skipped (offline mode)"),Promise.reject(new Error("WebSocket connection skipped in offline mode"))):new Promise((t,n)=>{let s=`${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;this.debug("connecting to WebSocket:",s);let i=new WebSocket(s);i.onopen=()=>{this.debug("WebSocket connection opened"),t(i)},i.onerror=c=>{this.debug("WebSocket connection error:",c),n(c)},i.onclose=()=>{this.debug("WebSocket connection closed"),this.conn=null}});wsSendMessage=(t,n)=>this.isOffline()?(this.debug("wsSendMessage: skipped (offline mode)"),this.setIsPending(!1),Promise.resolve()):new Promise((s,i)=>{if(S(n)==S(this.context))return this.debug("WebSocket context unchanged, skipping update"),s(this.setIsPending(!1));this.debug("WebSocket context updated:",n),this.context=n;let c=()=>{let u=!1,f=E=>{let d=JSON.parse(E.data);this.debug("WebSocket message received:",d),S(n)in this.checks||(this.checks[S(n)]={}),(d.flags??[]).forEach(F=>{let b=O(F);this.checks[S(n)][b.flag]=b,this.debug("WebSocket flag update:",{flag:b.flag,value:b.value,flagCheck:b}),(this.flagCheckListeners[F.flag]?.size>0||this.flagValueListeners[F.flag]?.size>0)&&this.submitFlagCheckEvent(b.flag,b,n),this.notifyFlagCheckListeners(F.flag,b),this.notifyFlagValueListeners(F.flag,b.value)}),this.setIsPending(!1),u||(u=!0,s())};t.addEventListener("message",f);let p=this.additionalHeaders["X-Schematic-Client-Version"]??`schematic-js@${J}`,k={apiKey:this.apiKey,clientVersion:p,data:n};this.debug("WebSocket sending message:",k),t.send(JSON.stringify(k))};t.readyState===WebSocket.OPEN?(this.debug("WebSocket already open, sending message"),c()):t.readyState===WebSocket.CONNECTING?(this.debug("WebSocket connecting, waiting for open to send message"),t.addEventListener("open",c)):(this.debug("WebSocket is closed, cannot send message"),i("WebSocket is not open or connecting"))});getIsPending=()=>this.isPending;addIsPendingListener=t=>(this.isPendingListeners.add(t),()=>{this.isPendingListeners.delete(t)});setIsPending=t=>{this.isPending=t,this.isPendingListeners.forEach(n=>ve(n,t))};getFlagCheck=t=>{let n=S(this.context);return(this.checks[n]??{})[t]};getFlagValue=t=>this.getFlagCheck(t)?.value;addFlagValueListener=(t,n)=>(t in this.flagValueListeners||(this.flagValueListeners[t]=new Set),this.flagValueListeners[t].add(n),()=>{this.flagValueListeners[t].delete(n)});addFlagCheckListener=(t,n)=>(t in this.flagCheckListeners||(this.flagCheckListeners[t]=new Set),this.flagCheckListeners[t].add(n),()=>{this.flagCheckListeners[t].delete(n)});notifyFlagCheckListeners=(t,n)=>{let s=this.flagCheckListeners?.[t]??[];s.size>0&&this.debug(`Notifying ${s.size} flag check listeners for ${t}`,n),s.forEach(i=>Ee(i,n))};notifyFlagValueListeners=(t,n)=>{let s=this.flagValueListeners?.[t]??[];s.size>0&&this.debug(`Notifying ${s.size} flag value listeners for ${t}`,{value:n}),s.forEach(i=>Ce(i,n))}},ve=(r,t)=>{r.length>0?r(t):r()},Ee=(r,t)=>{r.length>0?r(t):r()},Ce=(r,t)=>{r.length>0?r(t):r()};window.Schematic=D;})();
1
+ "use strict";(()=>{var ne=Object.create;var $=Object.defineProperty;var se=Object.getOwnPropertyDescriptor;var ae=Object.getOwnPropertyNames;var ie=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var le=(n,e)=>()=>(e||n((e={exports:{}}).exports,e),e.exports);var ue=(n,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of ae(e))!oe.call(n,a)&&a!==r&&$(n,a,{get:()=>e[a],enumerable:!(s=se(e,a))||s.enumerable});return n};var ce=(n,e,r)=>(r=n!=null?ne(ie(n)):{},ue(e||!n||!n.__esModule?$(r,"default",{value:n,enumerable:!0}):r,n));var X=le(z=>{(function(n){var e=function(r){var s=typeof globalThis<"u"&&globalThis||typeof n<"u"&&n||typeof global<"u"&&global||{},a={searchParams:"URLSearchParams"in s,iterable:"Symbol"in s&&"iterator"in Symbol,blob:"FileReader"in s&&"Blob"in s&&function(){try{return new Blob,!0}catch{return!1}}(),formData:"FormData"in s,arrayBuffer:"ArrayBuffer"in s};function f(t){return t&&DataView.prototype.isPrototypeOf(t)}if(a.arrayBuffer)var o=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],u=ArrayBuffer.isView||function(t){return t&&o.indexOf(Object.prototype.toString.call(t))>-1};function d(t){if(typeof t!="string"&&(t=String(t)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(t)||t==="")throw new TypeError('Invalid character in header field name: "'+t+'"');return t.toLowerCase()}function y(t){return typeof t!="string"&&(t=String(t)),t}function F(t){var i={next:function(){var l=t.shift();return{done:l===void 0,value:l}}};return a.iterable&&(i[Symbol.iterator]=function(){return i}),i}function g(t){this.map={},t instanceof g?t.forEach(function(i,l){this.append(l,i)},this):Array.isArray(t)?t.forEach(function(i){if(i.length!=2)throw new TypeError("Headers constructor: expected name/value pair to be length 2, found"+i.length);this.append(i[0],i[1])},this):t&&Object.getOwnPropertyNames(t).forEach(function(i){this.append(i,t[i])},this)}g.prototype.append=function(t,i){t=d(t),i=y(i);var l=this.map[t];this.map[t]=l?l+", "+i:i},g.prototype.delete=function(t){delete this.map[d(t)]},g.prototype.get=function(t){return t=d(t),this.has(t)?this.map[t]:null},g.prototype.has=function(t){return this.map.hasOwnProperty(d(t))},g.prototype.set=function(t,i){this.map[d(t)]=y(i)},g.prototype.forEach=function(t,i){for(var l in this.map)this.map.hasOwnProperty(l)&&t.call(i,this.map[l],l,this)},g.prototype.keys=function(){var t=[];return this.forEach(function(i,l){t.push(l)}),F(t)},g.prototype.values=function(){var t=[];return this.forEach(function(i){t.push(i)}),F(t)},g.prototype.entries=function(){var t=[];return this.forEach(function(i,l){t.push([l,i])}),F(t)},a.iterable&&(g.prototype[Symbol.iterator]=g.prototype.entries);function v(t){if(!t._noBody){if(t.bodyUsed)return Promise.reject(new TypeError("Already read"));t.bodyUsed=!0}}function m(t){return new Promise(function(i,l){t.onload=function(){i(t.result)},t.onerror=function(){l(t.error)}})}function S(t){var i=new FileReader,l=m(i);return i.readAsArrayBuffer(t),l}function V(t){var i=new FileReader,l=m(i),h=/charset=([A-Za-z0-9_-]+)/.exec(t.type),p=h?h[1]:"utf-8";return i.readAsText(t,p),l}function Y(t){for(var i=new Uint8Array(t),l=new Array(i.length),h=0;h<i.length;h++)l[h]=String.fromCharCode(i[h]);return l.join("")}function q(t){if(t.slice)return t.slice(0);var i=new Uint8Array(t.byteLength);return i.set(new Uint8Array(t)),i.buffer}function H(){return this.bodyUsed=!1,this._initBody=function(t){this.bodyUsed=this.bodyUsed,this._bodyInit=t,t?typeof t=="string"?this._bodyText=t:a.blob&&Blob.prototype.isPrototypeOf(t)?this._bodyBlob=t:a.formData&&FormData.prototype.isPrototypeOf(t)?this._bodyFormData=t:a.searchParams&&URLSearchParams.prototype.isPrototypeOf(t)?this._bodyText=t.toString():a.arrayBuffer&&a.blob&&f(t)?(this._bodyArrayBuffer=q(t.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):a.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(t)||u(t))?this._bodyArrayBuffer=q(t):this._bodyText=t=Object.prototype.toString.call(t):(this._noBody=!0,this._bodyText=""),this.headers.get("content-type")||(typeof t=="string"?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):a.searchParams&&URLSearchParams.prototype.isPrototypeOf(t)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},a.blob&&(this.blob=function(){var t=v(this);if(t)return t;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))}),this.arrayBuffer=function(){if(this._bodyArrayBuffer){var t=v(this);return t||(ArrayBuffer.isView(this._bodyArrayBuffer)?Promise.resolve(this._bodyArrayBuffer.buffer.slice(this._bodyArrayBuffer.byteOffset,this._bodyArrayBuffer.byteOffset+this._bodyArrayBuffer.byteLength)):Promise.resolve(this._bodyArrayBuffer))}else{if(a.blob)return this.blob().then(S);throw new Error("could not read as ArrayBuffer")}},this.text=function(){var t=v(this);if(t)return t;if(this._bodyBlob)return V(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(Y(this._bodyArrayBuffer));if(this._bodyFormData)throw new Error("could not read FormData body as text");return Promise.resolve(this._bodyText)},a.formData&&(this.formData=function(){return this.text().then(ee)}),this.json=function(){return this.text().then(JSON.parse)},this}var Z=["CONNECT","DELETE","GET","HEAD","OPTIONS","PATCH","POST","PUT","TRACE"];function j(t){var i=t.toUpperCase();return Z.indexOf(i)>-1?i:t}function R(t,i){if(!(this instanceof R))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');i=i||{};var l=i.body;if(t instanceof R){if(t.bodyUsed)throw new TypeError("Already read");this.url=t.url,this.credentials=t.credentials,i.headers||(this.headers=new g(t.headers)),this.method=t.method,this.mode=t.mode,this.signal=t.signal,!l&&t._bodyInit!=null&&(l=t._bodyInit,t.bodyUsed=!0)}else this.url=String(t);if(this.credentials=i.credentials||this.credentials||"same-origin",(i.headers||!this.headers)&&(this.headers=new g(i.headers)),this.method=j(i.method||this.method||"GET"),this.mode=i.mode||this.mode||null,this.signal=i.signal||this.signal||function(){if("AbortController"in s){var c=new AbortController;return c.signal}}(),this.referrer=null,(this.method==="GET"||this.method==="HEAD")&&l)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(l),(this.method==="GET"||this.method==="HEAD")&&(i.cache==="no-store"||i.cache==="no-cache")){var h=/([?&])_=[^&]*/;if(h.test(this.url))this.url=this.url.replace(h,"$1_="+new Date().getTime());else{var p=/\?/;this.url+=(p.test(this.url)?"&":"?")+"_="+new Date().getTime()}}}R.prototype.clone=function(){return new R(this,{body:this._bodyInit})};function ee(t){var i=new FormData;return t.trim().split("&").forEach(function(l){if(l){var h=l.split("="),p=h.shift().replace(/\+/g," "),c=h.join("=").replace(/\+/g," ");i.append(decodeURIComponent(p),decodeURIComponent(c))}}),i}function te(t){var i=new g,l=t.replace(/\r?\n[\t ]+/g," ");return l.split("\r").map(function(h){return h.indexOf(`
2
+ `)===0?h.substr(1,h.length):h}).forEach(function(h){var p=h.split(":"),c=p.shift().trim();if(c){var U=p.join(":").trim();try{i.append(c,U)}catch(A){console.warn("Response "+A.message)}}}),i}H.call(R.prototype);function E(t,i){if(!(this instanceof E))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');if(i||(i={}),this.type="default",this.status=i.status===void 0?200:i.status,this.status<200||this.status>599)throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].");this.ok=this.status>=200&&this.status<300,this.statusText=i.statusText===void 0?"":""+i.statusText,this.headers=new g(i.headers),this.url=i.url||"",this._initBody(t)}H.call(E.prototype),E.prototype.clone=function(){return new E(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new g(this.headers),url:this.url})},E.error=function(){var t=new E(null,{status:200,statusText:""});return t.ok=!1,t.status=0,t.type="error",t};var re=[301,302,303,307,308];E.redirect=function(t,i){if(re.indexOf(i)===-1)throw new RangeError("Invalid status code");return new E(null,{status:i,headers:{location:t}})},r.DOMException=s.DOMException;try{new r.DOMException}catch{r.DOMException=function(i,l){this.message=i,this.name=l;var h=Error(i);this.stack=h.stack},r.DOMException.prototype=Object.create(Error.prototype),r.DOMException.prototype.constructor=r.DOMException}function I(t,i){return new Promise(function(l,h){var p=new R(t,i);if(p.signal&&p.signal.aborted)return h(new r.DOMException("Aborted","AbortError"));var c=new XMLHttpRequest;function U(){c.abort()}c.onload=function(){var b={statusText:c.statusText,headers:te(c.getAllResponseHeaders()||"")};p.url.indexOf("file://")===0&&(c.status<200||c.status>599)?b.status=200:b.status=c.status,b.url="responseURL"in c?c.responseURL:b.headers.get("X-Request-URL");var w="response"in c?c.response:c.responseText;setTimeout(function(){l(new E(w,b))},0)},c.onerror=function(){setTimeout(function(){h(new TypeError("Network request failed"))},0)},c.ontimeout=function(){setTimeout(function(){h(new TypeError("Network request timed out"))},0)},c.onabort=function(){setTimeout(function(){h(new r.DOMException("Aborted","AbortError"))},0)};function A(b){try{return b===""&&s.location.href?s.location.href:b}catch{return b}}if(c.open(p.method,A(p.url),!0),p.credentials==="include"?c.withCredentials=!0:p.credentials==="omit"&&(c.withCredentials=!1),"responseType"in c&&(a.blob?c.responseType="blob":a.arrayBuffer&&(c.responseType="arraybuffer")),i&&typeof i.headers=="object"&&!(i.headers instanceof g||s.Headers&&i.headers instanceof s.Headers)){var W=[];Object.getOwnPropertyNames(i.headers).forEach(function(b){W.push(d(b)),c.setRequestHeader(b,y(i.headers[b]))}),p.headers.forEach(function(b,w){W.indexOf(w)===-1&&c.setRequestHeader(w,b)})}else p.headers.forEach(function(b,w){c.setRequestHeader(w,b)});p.signal&&(p.signal.addEventListener("abort",U),c.onreadystatechange=function(){c.readyState===4&&p.signal.removeEventListener("abort",U)}),c.send(typeof p._bodyInit>"u"?null:p._bodyInit)})}return I.polyfill=!0,s.fetch||(s.fetch=I,s.Headers=g,s.Request=R,s.Response=E),r.Headers=g,r.Request=R,r.Response=E,r.fetch=I,r}({})})(typeof self<"u"?self:z)});var k=[];for(let n=0;n<256;++n)k.push((n+256).toString(16).slice(1));function K(n,e=0){return(k[n[e+0]]+k[n[e+1]]+k[n[e+2]]+k[n[e+3]]+"-"+k[n[e+4]]+k[n[e+5]]+"-"+k[n[e+6]]+k[n[e+7]]+"-"+k[n[e+8]]+k[n[e+9]]+"-"+k[n[e+10]]+k[n[e+11]]+k[n[e+12]]+k[n[e+13]]+k[n[e+14]]+k[n[e+15]]).toLowerCase()}var P,fe=new Uint8Array(16);function L(){if(!P){if(typeof crypto>"u"||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");P=crypto.getRandomValues.bind(crypto)}return P(fe)}var de=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),B={randomUUID:de};function he(n,e,r){if(B.randomUUID&&!e&&!n)return B.randomUUID();n=n||{};let s=n.random??n.rng?.()??L();if(s.length<16)throw new Error("Random bytes length must be >= 16");if(s[6]=s[6]&15|64,s[8]=s[8]&63|128,e){if(r=r||0,r<0||r+16>e.length)throw new RangeError(`UUID byte range ${r}:${r+15} is out of buffer bounds`);for(let a=0;a<16;++a)e[r+a]=s[a];return e}return K(s)}var _=he;var rt=ce(X());function x(n){return pe(n,!1)}function pe(n,e){return n==null?n:{companyId:n.company_id==null?void 0:n.company_id,error:n.error==null?void 0:n.error,featureAllocation:n.feature_allocation==null?void 0:n.feature_allocation,featureUsage:n.feature_usage==null?void 0:n.feature_usage,featureUsageEvent:n.feature_usage_event==null?void 0:n.feature_usage_event,featureUsagePeriod:n.feature_usage_period==null?void 0:n.feature_usage_period,featureUsageResetAt:n.feature_usage_reset_at==null?void 0:new Date(n.feature_usage_reset_at),flag:n.flag,flagId:n.flag_id==null?void 0:n.flag_id,reason:n.reason,ruleId:n.rule_id==null?void 0:n.rule_id,ruleType:n.rule_type==null?void 0:n.rule_type,userId:n.user_id==null?void 0:n.user_id,value:n.value}}function N(n){return ye(n,!1)}function ye(n,e=!1){return n==null?n:{company_id:n.companyId,error:n.error,flag_id:n.flagId,flag_key:n.flagKey,reason:n.reason,req_company:n.reqCompany,req_user:n.reqUser,rule_id:n.ruleId,user_id:n.userId,value:n.value}}function T(n){return be(n,!1)}function be(n,e){return n==null?n:{data:x(n.data),params:n.params}}function G(n){return ke(n,!1)}function ke(n,e){return n==null?n:{flags:n.flags.map(x)}}function J(n){return ve(n,!1)}function ve(n,e){return n==null?n:{data:G(n.data),params:n.params}}var O=n=>{let{companyId:e,error:r,featureAllocation:s,featureUsage:a,featureUsageEvent:f,featureUsagePeriod:o,featureUsageResetAt:u,flag:d,flagId:y,reason:F,ruleId:g,ruleType:v,userId:m,value:S}=x(n);return{featureUsageExceeded:!S&&(v=="company_override_usage_exceeded"||v=="plan_entitlement_usage_exceeded"),companyId:e??void 0,error:r??void 0,featureAllocation:s??void 0,featureUsage:a??void 0,featureUsageEvent:f===null?void 0:f,featureUsagePeriod:o??void 0,featureUsageResetAt:u??void 0,flag:d,flagId:y??void 0,reason:F,ruleId:g??void 0,ruleType:v??void 0,userId:m??void 0,value:S}};function C(n){let e=Object.keys(n).reduce((r,s)=>{let f=Object.keys(n[s]||{}).sort().reduce((o,u)=>(o[u]=n[s][u],o),{});return r[s]=f,r},{});return JSON.stringify(e)}var M="1.2.3";var Q="schematicId";var D=class{additionalHeaders={};apiKey;apiUrl="https://api.schematichq.com";conn=null;context={};debugEnabled=!1;offlineEnabled=!1;eventQueue;eventUrl="https://c.schematichq.com";flagCheckListeners={};flagValueListeners={};isPending=!0;isPendingListeners=new Set;storage;useWebSocket=!1;checks={};featureUsageEventMap={};webSocketUrl="wss://api.schematichq.com";constructor(e,r){if(this.apiKey=e,this.eventQueue=[],this.useWebSocket=r?.useWebSocket??!1,this.debugEnabled=r?.debug??!1,this.offlineEnabled=r?.offline??!1,typeof window<"u"&&typeof window.location<"u"){let s=new URLSearchParams(window.location.search),a=s.get("schematic_debug");a!==null&&(a===""||a==="true"||a==="1")&&(this.debugEnabled=!0);let f=s.get("schematic_offline");f!==null&&(f===""||f==="true"||f==="1")&&(this.offlineEnabled=!0,this.debugEnabled=!0)}this.offlineEnabled&&r?.debug!==!1&&(this.debugEnabled=!0),this.offlineEnabled&&this.setIsPending(!1),this.additionalHeaders={"X-Schematic-Client-Version":`schematic-js@${M}`,...r?.additionalHeaders??{}},r?.storage?this.storage=r.storage:typeof localStorage<"u"&&(this.storage=localStorage),r?.apiUrl!==void 0&&(this.apiUrl=r.apiUrl),r?.eventUrl!==void 0&&(this.eventUrl=r.eventUrl),r?.webSocketUrl!==void 0&&(this.webSocketUrl=r.webSocketUrl),typeof window<"u"&&window?.addEventListener&&window.addEventListener("beforeunload",()=>{this.flushEventQueue()}),this.offlineEnabled?this.debug("Initialized with offline mode enabled - no network requests will be made"):this.debugEnabled&&this.debug("Initialized with debug mode enabled")}async checkFlag(e){let{fallback:r=!1,key:s}=e,a=e.context||this.context,f=C(a);if(this.debug(`checkFlag: ${s}`,{context:a,fallback:r}),this.isOffline())return this.debug(`checkFlag offline result: ${s}`,{value:r,offlineMode:!0}),r;if(!this.useWebSocket){let o=`${this.apiUrl}/flags/${s}/check`;return fetch(o,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(a)}).then(u=>{if(!u.ok)throw new Error("Network response was not ok");return u.json()}).then(u=>{let d=T(u);this.debug(`checkFlag result: ${s}`,d);let y=O(d.data);return typeof y.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(y),this.submitFlagCheckEvent(s,y,a),y.value}).catch(u=>{console.error("There was a problem with the fetch operation:",u);let d={flag:s,value:r,reason:"API request failed",error:u instanceof Error?u.message:String(u)};return this.submitFlagCheckEvent(s,d,a),r})}try{let o=this.checks[f];if(this.conn!==null&&typeof o<"u"&&typeof o[s]<"u")return this.debug(`checkFlag cached result: ${s}`,o[s]),o[s].value;if(this.isOffline())return r;try{await this.setContext(a)}catch(F){return console.error("WebSocket connection failed, falling back to REST:",F),this.fallbackToRest(s,a,r)}let d=(this.checks[f]??{})[s],y=d?.value??r;return this.debug(`checkFlag WebSocket result: ${s}`,typeof d<"u"?d:{value:r,fallbackUsed:!0}),typeof d<"u"&&this.submitFlagCheckEvent(s,d,a),y}catch(o){console.error("Unexpected error in checkFlag:",o);let u={flag:s,value:r,reason:"Unexpected error in flag check",error:o instanceof Error?o.message:String(o)};return this.submitFlagCheckEvent(s,u,a),r}}debug(e,...r){this.debugEnabled&&console.log(`[Schematic] ${e}`,...r)}isOffline(){return this.offlineEnabled}submitFlagCheckEvent(e,r,s){let a={flagKey:e,value:r.value,reason:r.reason,flagId:r.flagId,ruleId:r.ruleId,companyId:r.companyId,userId:r.userId,error:r.error,reqCompany:s.company,reqUser:s.user};return this.debug("submitting flag check event:",a),this.handleEvent("flag_check",N(a))}async fallbackToRest(e,r,s){if(this.isOffline())return this.debug(`fallbackToRest offline result: ${e}`,{value:s,offlineMode:!0}),s;try{let a=`${this.apiUrl}/flags/${e}/check`,f=await fetch(a,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(r)});if(!f.ok)throw new Error("Network response was not ok");let o=await f.json(),u=T(o);this.debug(`fallbackToRest result: ${e}`,u);let d=O(u.data);return typeof d.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(d),this.submitFlagCheckEvent(e,d,r),d.value}catch(a){console.error("REST API call failed, using fallback value:",a);let f={flag:e,value:s,reason:"API request failed (fallback)",error:a instanceof Error?a.message:String(a)};return this.submitFlagCheckEvent(e,f,r),s}}checkFlags=async e=>{if(e=e||this.context,this.debug("checkFlags",{context:e}),this.isOffline())return this.debug("checkFlags offline result: returning empty object"),{};let r=`${this.apiUrl}/flags/check`,s=JSON.stringify(e);return fetch(r,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:s}).then(a=>{if(!a.ok)throw new Error("Network response was not ok");return a.json()}).then(a=>{let f=J(a);return this.debug("checkFlags result:",f),(f?.data?.flags??[]).reduce((o,u)=>(o[u.flag]=u.value,o),{})}).catch(a=>(console.error("There was a problem with the fetch operation:",a),{}))};identify=e=>{this.debug("identify:",e);try{this.setContext({company:e.company?.keys,user:e.keys})}catch(r){console.error("Error setting context:",r)}return this.handleEvent("identify",e)};setContext=async e=>{if(this.isOffline()||!this.useWebSocket)return this.context=e,this.setIsPending(!1),Promise.resolve();try{this.setIsPending(!0),this.conn||(this.conn=this.wsConnect());let r=await this.conn;await this.wsSendMessage(r,e)}catch(r){throw console.error("Failed to establish WebSocket connection:",r),r}};track=e=>{let{company:r,user:s,event:a,traits:f,quantity:o=1}=e,u={company:r??this.context.company,event:a,traits:f??{},user:s??this.context.user,quantity:o};return this.debug("track:",u),a in this.featureUsageEventMap&&this.optimisticallyUpdateFeatureUsage(a,o),this.handleEvent("track",u)};optimisticallyUpdateFeatureUsage=(e,r=1)=>{let s=this.featureUsageEventMap[e];s!=null&&(this.debug(`Optimistically updating feature usage for event: ${e}`,{quantity:r}),Object.entries(s).forEach(([a,f])=>{if(f===void 0)return;let o={...f};if(typeof o.featureUsage=="number"){if(o.featureUsage+=r,typeof o.featureAllocation=="number"){let d=o.featureUsageExceeded===!0,y=o.featureUsage>=o.featureAllocation;y!==d&&(o.featureUsageExceeded=y,y&&(o.value=!1),this.debug(`Usage limit status changed for flag: ${a}`,{was:d?"exceeded":"within limits",now:y?"exceeded":"within limits",featureUsage:o.featureUsage,featureAllocation:o.featureAllocation,value:o.value}))}this.featureUsageEventMap[e]!==void 0&&(this.featureUsageEventMap[e][a]=o);let u=C(this.context);this.checks[u]!==void 0&&this.checks[u]!==null&&(this.checks[u][a]=o),this.notifyFlagCheckListeners(a,o),this.notifyFlagValueListeners(a,o.value)}}))};flushEventQueue=()=>{for(;this.eventQueue.length>0;){let e=this.eventQueue.shift();e&&this.sendEvent(e)}};getAnonymousId=()=>{if(!this.storage)return _();let e=this.storage.getItem(Q);if(typeof e<"u")return e;let r=_();return this.storage.setItem(Q,r),r};handleEvent=(e,r)=>{let s={api_key:this.apiKey,body:r,sent_at:new Date().toISOString(),tracker_event_id:_(),tracker_user_id:this.getAnonymousId(),type:e};return document?.hidden?this.storeEvent(s):this.sendEvent(s)};sendEvent=async e=>{let r=`${this.eventUrl}/e`,s=JSON.stringify(e);if(this.debug("sending event:",{url:r,event:e}),this.isOffline())return this.debug("event not sent (offline mode):",{event:e}),Promise.resolve();try{let a=await fetch(r,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8"},body:s});this.debug("event sent:",{status:a.status,statusText:a.statusText})}catch(a){console.error("Error sending Schematic event: ",a)}return Promise.resolve()};storeEvent=e=>(this.eventQueue.push(e),Promise.resolve());cleanup=async()=>{if(this.isOffline())return this.debug("cleanup: skipped (offline mode)"),Promise.resolve();if(this.conn)try{(await this.conn).close()}catch(e){console.error("Error during cleanup:",e)}finally{this.conn=null}};wsConnect=()=>this.isOffline()?(this.debug("wsConnect: skipped (offline mode)"),Promise.reject(new Error("WebSocket connection skipped in offline mode"))):new Promise((e,r)=>{let s=`${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;this.debug("connecting to WebSocket:",s);let a=new WebSocket(s);a.onopen=()=>{this.debug("WebSocket connection opened"),e(a)},a.onerror=f=>{this.debug("WebSocket connection error:",f),r(f)},a.onclose=()=>{this.debug("WebSocket connection closed"),this.conn=null}});wsSendMessage=(e,r)=>this.isOffline()?(this.debug("wsSendMessage: skipped (offline mode)"),this.setIsPending(!1),Promise.resolve()):new Promise((s,a)=>{if(C(r)==C(this.context))return this.debug("WebSocket context unchanged, skipping update"),s(this.setIsPending(!1));this.debug("WebSocket context updated:",r),this.context=r;let f=()=>{let o=!1,u=F=>{let g=JSON.parse(F.data);this.debug("WebSocket message received:",g),C(r)in this.checks||(this.checks[C(r)]={}),(g.flags??[]).forEach(v=>{let m=O(v),S=C(r);this.checks[S]===void 0&&(this.checks[S]={}),this.checks[S][m.flag]=m,this.debug("WebSocket flag update:",{flag:m.flag,value:m.value,flagCheck:m}),typeof m.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(m),(this.flagCheckListeners[v.flag]?.size>0||this.flagValueListeners[v.flag]?.size>0)&&this.submitFlagCheckEvent(m.flag,m,r),this.notifyFlagCheckListeners(v.flag,m),this.notifyFlagValueListeners(v.flag,m.value)}),this.setIsPending(!1),o||(o=!0,s())};e.addEventListener("message",u);let d=this.additionalHeaders["X-Schematic-Client-Version"]??`schematic-js@${M}`,y={apiKey:this.apiKey,clientVersion:d,data:r};this.debug("WebSocket sending message:",y),e.send(JSON.stringify(y))};e.readyState===WebSocket.OPEN?(this.debug("WebSocket already open, sending message"),f()):e.readyState===WebSocket.CONNECTING?(this.debug("WebSocket connecting, waiting for open to send message"),e.addEventListener("open",f)):(this.debug("WebSocket is closed, cannot send message"),a("WebSocket is not open or connecting"))});getIsPending=()=>this.isPending;addIsPendingListener=e=>(this.isPendingListeners.add(e),()=>{this.isPendingListeners.delete(e)});setIsPending=e=>{this.isPending=e,this.isPendingListeners.forEach(r=>Ee(r,e))};getFlagCheck=e=>{let r=C(this.context);return(this.checks[r]??{})[e]};getFlagValue=e=>this.getFlagCheck(e)?.value;addFlagValueListener=(e,r)=>(e in this.flagValueListeners||(this.flagValueListeners[e]=new Set),this.flagValueListeners[e].add(r),()=>{this.flagValueListeners[e].delete(r)});addFlagCheckListener=(e,r)=>(e in this.flagCheckListeners||(this.flagCheckListeners[e]=new Set),this.flagCheckListeners[e].add(r),()=>{this.flagCheckListeners[e].delete(r)});notifyFlagCheckListeners=(e,r)=>{let s=this.flagCheckListeners?.[e]??[];s.size>0&&this.debug(`Notifying ${s.size} flag check listeners for ${e}`,r),typeof r.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(r),s.forEach(a=>Fe(a,r))};updateFeatureUsageEventMap=e=>{if(typeof e.featureUsageEvent!="string")return;let r=e.featureUsageEvent;(this.featureUsageEventMap[r]===void 0||this.featureUsageEventMap[r]===null)&&(this.featureUsageEventMap[r]={}),this.featureUsageEventMap[r]!==void 0&&(this.featureUsageEventMap[r][e.flag]=e),this.debug(`Updated featureUsageEventMap for event: ${r}, flag: ${e.flag}`,e)};notifyFlagValueListeners=(e,r)=>{let s=this.flagValueListeners?.[e]??[];s.size>0&&this.debug(`Notifying ${s.size} flag value listeners for ${e}`,{value:r}),s.forEach(a=>Ce(a,r))}},Ee=(n,e)=>{n.length>0?n(e):n()},Fe=(n,e)=>{n.length>0?n(e):n()},Ce=(n,e)=>{n.length>0?n(e):n()};window.Schematic=D;})();
3
3
  /* @preserve */
@@ -647,6 +647,7 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
647
647
  error: json["error"] == null ? void 0 : json["error"],
648
648
  featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
649
649
  featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
650
+ featureUsageEvent: json["feature_usage_event"] == null ? void 0 : json["feature_usage_event"],
650
651
  featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
651
652
  featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
652
653
  flag: json["flag"],
@@ -746,6 +747,7 @@ var CheckFlagReturnFromJSON = (json) => {
746
747
  error,
747
748
  featureAllocation,
748
749
  featureUsage,
750
+ featureUsageEvent,
749
751
  featureUsagePeriod,
750
752
  featureUsageResetAt,
751
753
  flag,
@@ -765,6 +767,7 @@ var CheckFlagReturnFromJSON = (json) => {
765
767
  error: error == null ? void 0 : error,
766
768
  featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
767
769
  featureUsage: featureUsage == null ? void 0 : featureUsage,
770
+ featureUsageEvent: featureUsageEvent === null ? void 0 : featureUsageEvent,
768
771
  featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
769
772
  featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
770
773
  flag,
@@ -794,7 +797,7 @@ function contextString(context) {
794
797
  }
795
798
 
796
799
  // src/version.ts
797
- var version = "1.2.2";
800
+ var version = "1.2.3";
798
801
 
799
802
  // src/index.ts
800
803
  var anonymousIdKey = "schematicId";
@@ -815,6 +818,7 @@ var Schematic = class {
815
818
  storage;
816
819
  useWebSocket = false;
817
820
  checks = {};
821
+ featureUsageEventMap = {};
818
822
  webSocketUrl = "wss://api.schematichq.com";
819
823
  constructor(apiKey, options) {
820
824
  this.apiKey = apiKey;
@@ -909,6 +913,9 @@ var Schematic = class {
909
913
  const parsedResponse = CheckFlagResponseFromJSON(response);
910
914
  this.debug(`checkFlag result: ${key}`, parsedResponse);
911
915
  const result = CheckFlagReturnFromJSON(parsedResponse.data);
916
+ if (typeof result.featureUsageEvent === "string") {
917
+ this.updateFeatureUsageEventMap(result);
918
+ }
912
919
  this.submitFlagCheckEvent(key, result, context);
913
920
  return result.value;
914
921
  }).catch((error) => {
@@ -1028,6 +1035,9 @@ var Schematic = class {
1028
1035
  const data = CheckFlagResponseFromJSON(responseJson);
1029
1036
  this.debug(`fallbackToRest result: ${key}`, data);
1030
1037
  const result = CheckFlagReturnFromJSON(data.data);
1038
+ if (typeof result.featureUsageEvent === "string") {
1039
+ this.updateFeatureUsageEventMap(result);
1040
+ }
1031
1041
  this.submitFlagCheckEvent(key, result, context);
1032
1042
  return result.value;
1033
1043
  } catch (error) {
@@ -1111,14 +1121,11 @@ var Schematic = class {
1111
1121
  * In offline mode, this will just set the context locally without connecting.
1112
1122
  */
1113
1123
  setContext = async (context) => {
1114
- if (this.isOffline()) {
1124
+ if (this.isOffline() || !this.useWebSocket) {
1115
1125
  this.context = context;
1116
1126
  this.setIsPending(false);
1117
1127
  return Promise.resolve();
1118
1128
  }
1119
- if (!this.useWebSocket) {
1120
- return Promise.resolve();
1121
- }
1122
1129
  try {
1123
1130
  this.setIsPending(true);
1124
1131
  if (!this.conn) {
@@ -1134,18 +1141,69 @@ var Schematic = class {
1134
1141
  /**
1135
1142
  * Send a track event
1136
1143
  * Track usage for a company and/or user.
1144
+ * Optimistically updates feature usage flags if tracking a featureUsageEvent.
1137
1145
  */
1138
1146
  track = (body) => {
1139
- const { company, user, event, traits } = body;
1147
+ const { company, user, event, traits, quantity = 1 } = body;
1140
1148
  const trackData = {
1141
1149
  company: company ?? this.context.company,
1142
1150
  event,
1143
1151
  traits: traits ?? {},
1144
- user: user ?? this.context.user
1152
+ user: user ?? this.context.user,
1153
+ quantity
1145
1154
  };
1146
1155
  this.debug(`track:`, trackData);
1156
+ if (event in this.featureUsageEventMap) {
1157
+ this.optimisticallyUpdateFeatureUsage(event, quantity);
1158
+ }
1147
1159
  return this.handleEvent("track", trackData);
1148
1160
  };
1161
+ /**
1162
+ * Optimistically update feature usage flags associated with a tracked event
1163
+ * This updates flags in memory with updated usage counts and value/featureUsageExceeded flags
1164
+ * before the network request completes
1165
+ */
1166
+ optimisticallyUpdateFeatureUsage = (eventName, quantity = 1) => {
1167
+ const flagsForEvent = this.featureUsageEventMap[eventName];
1168
+ if (flagsForEvent === void 0 || flagsForEvent === null) return;
1169
+ this.debug(
1170
+ `Optimistically updating feature usage for event: ${eventName}`,
1171
+ { quantity }
1172
+ );
1173
+ Object.entries(flagsForEvent).forEach(([flagKey, check]) => {
1174
+ if (check === void 0) return;
1175
+ const updatedCheck = { ...check };
1176
+ if (typeof updatedCheck.featureUsage === "number") {
1177
+ updatedCheck.featureUsage += quantity;
1178
+ if (typeof updatedCheck.featureAllocation === "number") {
1179
+ const wasExceeded = updatedCheck.featureUsageExceeded === true;
1180
+ const nowExceeded = updatedCheck.featureUsage >= updatedCheck.featureAllocation;
1181
+ if (nowExceeded !== wasExceeded) {
1182
+ updatedCheck.featureUsageExceeded = nowExceeded;
1183
+ if (nowExceeded) {
1184
+ updatedCheck.value = false;
1185
+ }
1186
+ this.debug(`Usage limit status changed for flag: ${flagKey}`, {
1187
+ was: wasExceeded ? "exceeded" : "within limits",
1188
+ now: nowExceeded ? "exceeded" : "within limits",
1189
+ featureUsage: updatedCheck.featureUsage,
1190
+ featureAllocation: updatedCheck.featureAllocation,
1191
+ value: updatedCheck.value
1192
+ });
1193
+ }
1194
+ }
1195
+ if (this.featureUsageEventMap[eventName] !== void 0) {
1196
+ this.featureUsageEventMap[eventName][flagKey] = updatedCheck;
1197
+ }
1198
+ const contextStr = contextString(this.context);
1199
+ if (this.checks[contextStr] !== void 0 && this.checks[contextStr] !== null) {
1200
+ this.checks[contextStr][flagKey] = updatedCheck;
1201
+ }
1202
+ this.notifyFlagCheckListeners(flagKey, updatedCheck);
1203
+ this.notifyFlagValueListeners(flagKey, updatedCheck.value);
1204
+ }
1205
+ });
1206
+ };
1149
1207
  /**
1150
1208
  * Event processing
1151
1209
  */
@@ -1288,12 +1346,19 @@ var Schematic = class {
1288
1346
  }
1289
1347
  (message.flags ?? []).forEach((flag) => {
1290
1348
  const flagCheck = CheckFlagReturnFromJSON(flag);
1291
- this.checks[contextString(context)][flagCheck.flag] = flagCheck;
1349
+ const contextStr = contextString(context);
1350
+ if (this.checks[contextStr] === void 0) {
1351
+ this.checks[contextStr] = {};
1352
+ }
1353
+ this.checks[contextStr][flagCheck.flag] = flagCheck;
1292
1354
  this.debug(`WebSocket flag update:`, {
1293
1355
  flag: flagCheck.flag,
1294
1356
  value: flagCheck.value,
1295
1357
  flagCheck
1296
1358
  });
1359
+ if (typeof flagCheck.featureUsageEvent === "string") {
1360
+ this.updateFeatureUsageEventMap(flagCheck);
1361
+ }
1297
1362
  if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
1298
1363
  this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
1299
1364
  }
@@ -1386,8 +1451,26 @@ var Schematic = class {
1386
1451
  check
1387
1452
  );
1388
1453
  }
1454
+ if (typeof check.featureUsageEvent === "string") {
1455
+ this.updateFeatureUsageEventMap(check);
1456
+ }
1389
1457
  listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
1390
1458
  };
1459
+ /** Add or update a CheckFlagReturn in the featureUsageEventMap */
1460
+ updateFeatureUsageEventMap = (check) => {
1461
+ if (typeof check.featureUsageEvent !== "string") return;
1462
+ const eventName = check.featureUsageEvent;
1463
+ if (this.featureUsageEventMap[eventName] === void 0 || this.featureUsageEventMap[eventName] === null) {
1464
+ this.featureUsageEventMap[eventName] = {};
1465
+ }
1466
+ if (this.featureUsageEventMap[eventName] !== void 0) {
1467
+ this.featureUsageEventMap[eventName][check.flag] = check;
1468
+ }
1469
+ this.debug(
1470
+ `Updated featureUsageEventMap for event: ${eventName}, flag: ${check.flag}`,
1471
+ check
1472
+ );
1473
+ };
1391
1474
  notifyFlagValueListeners = (flagKey, value) => {
1392
1475
  const listeners = this.flagValueListeners?.[flagKey] ?? [];
1393
1476
  if (listeners.size > 0) {
@@ -61,6 +61,12 @@ export declare interface CheckFlagResponseData {
61
61
  * @memberof CheckFlagResponseData
62
62
  */
63
63
  featureUsage?: number | null;
64
+ /**
65
+ * If an event-based numeric feature entitlement rule was matched, the event used to track its usage
66
+ * @type {string}
67
+ * @memberof CheckFlagResponseData
68
+ */
69
+ featureUsageEvent?: string | null;
64
70
  /**
65
71
  * For event-based feature entitlement rules, the period over which usage is tracked (current_month, current_day, current_week, all_time)
66
72
  * @type {string}
@@ -130,6 +136,8 @@ export declare type CheckFlagReturn = {
130
136
  featureAllocation?: number;
131
137
  /** If a numeric feature entitlement rule was matched, the company's usage */
132
138
  featureUsage?: number;
139
+ /** Event representing the feature usage */
140
+ featureUsageEvent?: string;
133
141
  /** For event-based feature entitlement rules, the period over which usage is tracked (current_month, current_day, current_week, all_time) */
134
142
  featureUsagePeriod?: UsagePeriod;
135
143
  /** For event-based feature entitlement rules, when the usage period will reset */
@@ -357,6 +365,7 @@ export declare class Schematic {
357
365
  private storage;
358
366
  private useWebSocket;
359
367
  private checks;
368
+ private featureUsageEventMap;
360
369
  private webSocketUrl;
361
370
  constructor(apiKey: string, options?: SchematicOptions);
362
371
  /**
@@ -410,8 +419,15 @@ export declare class Schematic {
410
419
  /**
411
420
  * Send a track event
412
421
  * Track usage for a company and/or user.
422
+ * Optimistically updates feature usage flags if tracking a featureUsageEvent.
413
423
  */
414
424
  track: (body: EventBodyTrack) => Promise<void>;
425
+ /**
426
+ * Optimistically update feature usage flags associated with a tracked event
427
+ * This updates flags in memory with updated usage counts and value/featureUsageExceeded flags
428
+ * before the network request completes
429
+ */
430
+ private optimisticallyUpdateFeatureUsage;
415
431
  /**
416
432
  * Event processing
417
433
  */
@@ -436,13 +452,15 @@ export declare class Schematic {
436
452
  getIsPending: () => boolean;
437
453
  addIsPendingListener: (listener: PendingListenerFn) => () => void;
438
454
  private setIsPending;
439
- getFlagCheck: (flagKey: string) => CheckFlagReturn;
440
- getFlagValue: (flagKey: string) => boolean;
455
+ getFlagCheck: (flagKey: string) => CheckFlagReturn | undefined;
456
+ getFlagValue: (flagKey: string) => boolean | undefined;
441
457
  /** Register an event listener that will be notified with the boolean value for a given flag when this value changes */
442
458
  addFlagValueListener: (flagKey: string, listener: FlagValueListenerFn) => () => void;
443
459
  /** Register an event listener that will be notified with the full flag check response for a given flag whenever this value changes */
444
460
  addFlagCheckListener: (flagKey: string, listener: FlagCheckListenerFn) => () => void;
445
461
  private notifyFlagCheckListeners;
462
+ /** Add or update a CheckFlagReturn in the featureUsageEventMap */
463
+ private updateFeatureUsageEventMap;
446
464
  private notifyFlagValueListeners;
447
465
  }
448
466
 
@@ -628,6 +628,7 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
628
628
  error: json["error"] == null ? void 0 : json["error"],
629
629
  featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
630
630
  featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
631
+ featureUsageEvent: json["feature_usage_event"] == null ? void 0 : json["feature_usage_event"],
631
632
  featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
632
633
  featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
633
634
  flag: json["flag"],
@@ -727,6 +728,7 @@ var CheckFlagReturnFromJSON = (json) => {
727
728
  error,
728
729
  featureAllocation,
729
730
  featureUsage,
731
+ featureUsageEvent,
730
732
  featureUsagePeriod,
731
733
  featureUsageResetAt,
732
734
  flag,
@@ -746,6 +748,7 @@ var CheckFlagReturnFromJSON = (json) => {
746
748
  error: error == null ? void 0 : error,
747
749
  featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
748
750
  featureUsage: featureUsage == null ? void 0 : featureUsage,
751
+ featureUsageEvent: featureUsageEvent === null ? void 0 : featureUsageEvent,
749
752
  featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
750
753
  featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
751
754
  flag,
@@ -775,7 +778,7 @@ function contextString(context) {
775
778
  }
776
779
 
777
780
  // src/version.ts
778
- var version = "1.2.2";
781
+ var version = "1.2.3";
779
782
 
780
783
  // src/index.ts
781
784
  var anonymousIdKey = "schematicId";
@@ -796,6 +799,7 @@ var Schematic = class {
796
799
  storage;
797
800
  useWebSocket = false;
798
801
  checks = {};
802
+ featureUsageEventMap = {};
799
803
  webSocketUrl = "wss://api.schematichq.com";
800
804
  constructor(apiKey, options) {
801
805
  this.apiKey = apiKey;
@@ -890,6 +894,9 @@ var Schematic = class {
890
894
  const parsedResponse = CheckFlagResponseFromJSON(response);
891
895
  this.debug(`checkFlag result: ${key}`, parsedResponse);
892
896
  const result = CheckFlagReturnFromJSON(parsedResponse.data);
897
+ if (typeof result.featureUsageEvent === "string") {
898
+ this.updateFeatureUsageEventMap(result);
899
+ }
893
900
  this.submitFlagCheckEvent(key, result, context);
894
901
  return result.value;
895
902
  }).catch((error) => {
@@ -1009,6 +1016,9 @@ var Schematic = class {
1009
1016
  const data = CheckFlagResponseFromJSON(responseJson);
1010
1017
  this.debug(`fallbackToRest result: ${key}`, data);
1011
1018
  const result = CheckFlagReturnFromJSON(data.data);
1019
+ if (typeof result.featureUsageEvent === "string") {
1020
+ this.updateFeatureUsageEventMap(result);
1021
+ }
1012
1022
  this.submitFlagCheckEvent(key, result, context);
1013
1023
  return result.value;
1014
1024
  } catch (error) {
@@ -1092,14 +1102,11 @@ var Schematic = class {
1092
1102
  * In offline mode, this will just set the context locally without connecting.
1093
1103
  */
1094
1104
  setContext = async (context) => {
1095
- if (this.isOffline()) {
1105
+ if (this.isOffline() || !this.useWebSocket) {
1096
1106
  this.context = context;
1097
1107
  this.setIsPending(false);
1098
1108
  return Promise.resolve();
1099
1109
  }
1100
- if (!this.useWebSocket) {
1101
- return Promise.resolve();
1102
- }
1103
1110
  try {
1104
1111
  this.setIsPending(true);
1105
1112
  if (!this.conn) {
@@ -1115,18 +1122,69 @@ var Schematic = class {
1115
1122
  /**
1116
1123
  * Send a track event
1117
1124
  * Track usage for a company and/or user.
1125
+ * Optimistically updates feature usage flags if tracking a featureUsageEvent.
1118
1126
  */
1119
1127
  track = (body) => {
1120
- const { company, user, event, traits } = body;
1128
+ const { company, user, event, traits, quantity = 1 } = body;
1121
1129
  const trackData = {
1122
1130
  company: company ?? this.context.company,
1123
1131
  event,
1124
1132
  traits: traits ?? {},
1125
- user: user ?? this.context.user
1133
+ user: user ?? this.context.user,
1134
+ quantity
1126
1135
  };
1127
1136
  this.debug(`track:`, trackData);
1137
+ if (event in this.featureUsageEventMap) {
1138
+ this.optimisticallyUpdateFeatureUsage(event, quantity);
1139
+ }
1128
1140
  return this.handleEvent("track", trackData);
1129
1141
  };
1142
+ /**
1143
+ * Optimistically update feature usage flags associated with a tracked event
1144
+ * This updates flags in memory with updated usage counts and value/featureUsageExceeded flags
1145
+ * before the network request completes
1146
+ */
1147
+ optimisticallyUpdateFeatureUsage = (eventName, quantity = 1) => {
1148
+ const flagsForEvent = this.featureUsageEventMap[eventName];
1149
+ if (flagsForEvent === void 0 || flagsForEvent === null) return;
1150
+ this.debug(
1151
+ `Optimistically updating feature usage for event: ${eventName}`,
1152
+ { quantity }
1153
+ );
1154
+ Object.entries(flagsForEvent).forEach(([flagKey, check]) => {
1155
+ if (check === void 0) return;
1156
+ const updatedCheck = { ...check };
1157
+ if (typeof updatedCheck.featureUsage === "number") {
1158
+ updatedCheck.featureUsage += quantity;
1159
+ if (typeof updatedCheck.featureAllocation === "number") {
1160
+ const wasExceeded = updatedCheck.featureUsageExceeded === true;
1161
+ const nowExceeded = updatedCheck.featureUsage >= updatedCheck.featureAllocation;
1162
+ if (nowExceeded !== wasExceeded) {
1163
+ updatedCheck.featureUsageExceeded = nowExceeded;
1164
+ if (nowExceeded) {
1165
+ updatedCheck.value = false;
1166
+ }
1167
+ this.debug(`Usage limit status changed for flag: ${flagKey}`, {
1168
+ was: wasExceeded ? "exceeded" : "within limits",
1169
+ now: nowExceeded ? "exceeded" : "within limits",
1170
+ featureUsage: updatedCheck.featureUsage,
1171
+ featureAllocation: updatedCheck.featureAllocation,
1172
+ value: updatedCheck.value
1173
+ });
1174
+ }
1175
+ }
1176
+ if (this.featureUsageEventMap[eventName] !== void 0) {
1177
+ this.featureUsageEventMap[eventName][flagKey] = updatedCheck;
1178
+ }
1179
+ const contextStr = contextString(this.context);
1180
+ if (this.checks[contextStr] !== void 0 && this.checks[contextStr] !== null) {
1181
+ this.checks[contextStr][flagKey] = updatedCheck;
1182
+ }
1183
+ this.notifyFlagCheckListeners(flagKey, updatedCheck);
1184
+ this.notifyFlagValueListeners(flagKey, updatedCheck.value);
1185
+ }
1186
+ });
1187
+ };
1130
1188
  /**
1131
1189
  * Event processing
1132
1190
  */
@@ -1269,12 +1327,19 @@ var Schematic = class {
1269
1327
  }
1270
1328
  (message.flags ?? []).forEach((flag) => {
1271
1329
  const flagCheck = CheckFlagReturnFromJSON(flag);
1272
- this.checks[contextString(context)][flagCheck.flag] = flagCheck;
1330
+ const contextStr = contextString(context);
1331
+ if (this.checks[contextStr] === void 0) {
1332
+ this.checks[contextStr] = {};
1333
+ }
1334
+ this.checks[contextStr][flagCheck.flag] = flagCheck;
1273
1335
  this.debug(`WebSocket flag update:`, {
1274
1336
  flag: flagCheck.flag,
1275
1337
  value: flagCheck.value,
1276
1338
  flagCheck
1277
1339
  });
1340
+ if (typeof flagCheck.featureUsageEvent === "string") {
1341
+ this.updateFeatureUsageEventMap(flagCheck);
1342
+ }
1278
1343
  if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
1279
1344
  this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
1280
1345
  }
@@ -1367,8 +1432,26 @@ var Schematic = class {
1367
1432
  check
1368
1433
  );
1369
1434
  }
1435
+ if (typeof check.featureUsageEvent === "string") {
1436
+ this.updateFeatureUsageEventMap(check);
1437
+ }
1370
1438
  listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
1371
1439
  };
1440
+ /** Add or update a CheckFlagReturn in the featureUsageEventMap */
1441
+ updateFeatureUsageEventMap = (check) => {
1442
+ if (typeof check.featureUsageEvent !== "string") return;
1443
+ const eventName = check.featureUsageEvent;
1444
+ if (this.featureUsageEventMap[eventName] === void 0 || this.featureUsageEventMap[eventName] === null) {
1445
+ this.featureUsageEventMap[eventName] = {};
1446
+ }
1447
+ if (this.featureUsageEventMap[eventName] !== void 0) {
1448
+ this.featureUsageEventMap[eventName][check.flag] = check;
1449
+ }
1450
+ this.debug(
1451
+ `Updated featureUsageEventMap for event: ${eventName}, flag: ${check.flag}`,
1452
+ check
1453
+ );
1454
+ };
1372
1455
  notifyFlagValueListeners = (flagKey, value) => {
1373
1456
  const listeners = this.flagValueListeners?.[flagKey] ?? [];
1374
1457
  if (listeners.size > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schematichq/schematic-js",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "main": "dist/schematic.cjs.js",
5
5
  "module": "dist/schematic.esm.js",
6
6
  "types": "dist/schematic.d.ts",
@@ -33,23 +33,24 @@
33
33
  "uuid": "^11.0.5"
34
34
  },
35
35
  "devDependencies": {
36
- "@microsoft/api-extractor": "^7.49.2",
37
- "@openapitools/openapi-generator-cli": "^2.16.3",
36
+ "@eslint/js": "^9.24.0",
37
+ "@microsoft/api-extractor": "^7.52.2",
38
+ "@openapitools/openapi-generator-cli": "^2.18.4",
38
39
  "@types/jest": "^29.5.14",
39
40
  "@types/uuid": "^10.0.0",
40
- "@typescript-eslint/eslint-plugin": "^8.23.0",
41
- "@typescript-eslint/parser": "^8.23.0",
42
- "esbuild": "^0.25.0",
41
+ "esbuild": "^0.25.2",
43
42
  "esbuild-jest": "^0.5.0",
44
- "eslint": "^8.57.1",
43
+ "eslint": "^9.24.0",
44
+ "globals": "^16.0.0",
45
45
  "jest": "^29.7.0",
46
46
  "jest-environment-jsdom": "^29.7.0",
47
47
  "jest-esbuild": "^0.3.0",
48
48
  "jest-fetch-mock": "^3.0.3",
49
49
  "mock-socket": "^9.3.1",
50
50
  "prettier": "^3.4.2",
51
- "ts-jest": "^29.2.5",
52
- "typescript": "^5.7.3"
51
+ "ts-jest": "^29.3.0",
52
+ "typescript": "^5.7.3",
53
+ "typescript-eslint": "^8.29.1"
53
54
  },
54
55
  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
55
56
  }