@schematichq/schematic-js 1.2.2 → 1.2.5

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 re=Object.create;var W=Object.defineProperty;var se=Object.getOwnPropertyDescriptor;var ae=Object.getOwnPropertyNames;var ie=Object.getPrototypeOf,oe=Object.prototype.hasOwnProperty;var le=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports);var ue=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of ae(e))!oe.call(r,a)&&a!==t&&W(r,a,{get:()=>e[a],enumerable:!(s=se(e,a))||s.enumerable});return r};var ce=(r,e,t)=>(t=r!=null?re(ie(r)):{},ue(e||!r||!r.__esModule?W(t,"default",{value:r,enumerable:!0}):t,r));var z=le(Q=>{(function(r){var e=(function(t){var s=typeof globalThis<"u"&&globalThis||typeof r<"u"&&r||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 c(n){return n&&DataView.prototype.isPrototypeOf(n)}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(n){return n&&o.indexOf(Object.prototype.toString.call(n))>-1};function f(n){if(typeof n!="string"&&(n=String(n)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(n)||n==="")throw new TypeError('Invalid character in header field name: "'+n+'"');return n.toLowerCase()}function y(n){return typeof n!="string"&&(n=String(n)),n}function F(n){var i={next:function(){var l=n.shift();return{done:l===void 0,value:l}}};return a.iterable&&(i[Symbol.iterator]=function(){return i}),i}function g(n){this.map={},n instanceof g?n.forEach(function(i,l){this.append(l,i)},this):Array.isArray(n)?n.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):n&&Object.getOwnPropertyNames(n).forEach(function(i){this.append(i,n[i])},this)}g.prototype.append=function(n,i){n=f(n),i=y(i);var l=this.map[n];this.map[n]=l?l+", "+i:i},g.prototype.delete=function(n){delete this.map[f(n)]},g.prototype.get=function(n){return n=f(n),this.has(n)?this.map[n]:null},g.prototype.has=function(n){return this.map.hasOwnProperty(f(n))},g.prototype.set=function(n,i){this.map[f(n)]=y(i)},g.prototype.forEach=function(n,i){for(var l in this.map)this.map.hasOwnProperty(l)&&n.call(i,this.map[l],l,this)},g.prototype.keys=function(){var n=[];return this.forEach(function(i,l){n.push(l)}),F(n)},g.prototype.values=function(){var n=[];return this.forEach(function(i){n.push(i)}),F(n)},g.prototype.entries=function(){var n=[];return this.forEach(function(i,l){n.push([l,i])}),F(n)},a.iterable&&(g.prototype[Symbol.iterator]=g.prototype.entries);function k(n){if(!n._noBody){if(n.bodyUsed)return Promise.reject(new TypeError("Already read"));n.bodyUsed=!0}}function m(n){return new Promise(function(i,l){n.onload=function(){i(n.result)},n.onerror=function(){l(n.error)}})}function x(n){var i=new FileReader,l=m(i);return i.readAsArrayBuffer(n),l}function V(n){var i=new FileReader,l=m(i),h=/charset=([A-Za-z0-9_-]+)/.exec(n.type),p=h?h[1]:"utf-8";return i.readAsText(n,p),l}function Y(n){for(var i=new Uint8Array(n),l=new Array(i.length),h=0;h<i.length;h++)l[h]=String.fromCharCode(i[h]);return l.join("")}function q(n){if(n.slice)return n.slice(0);var i=new Uint8Array(n.byteLength);return i.set(new Uint8Array(n)),i.buffer}function $(){return this.bodyUsed=!1,this._initBody=function(n){this.bodyUsed=this.bodyUsed,this._bodyInit=n,n?typeof n=="string"?this._bodyText=n:a.blob&&Blob.prototype.isPrototypeOf(n)?this._bodyBlob=n:a.formData&&FormData.prototype.isPrototypeOf(n)?this._bodyFormData=n:a.searchParams&&URLSearchParams.prototype.isPrototypeOf(n)?this._bodyText=n.toString():a.arrayBuffer&&a.blob&&c(n)?(this._bodyArrayBuffer=q(n.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):a.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(n)||u(n))?this._bodyArrayBuffer=q(n):this._bodyText=n=Object.prototype.toString.call(n):(this._noBody=!0,this._bodyText=""),this.headers.get("content-type")||(typeof n=="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(n)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},a.blob&&(this.blob=function(){var n=k(this);if(n)return n;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 n=k(this);return n||(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(x);throw new Error("could not read as ArrayBuffer")}},this.text=function(){var n=k(this);if(n)return n;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(n){var i=n.toUpperCase();return Z.indexOf(i)>-1?i:n}function S(n,i){if(!(this instanceof S))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(n instanceof S){if(n.bodyUsed)throw new TypeError("Already read");this.url=n.url,this.credentials=n.credentials,i.headers||(this.headers=new g(n.headers)),this.method=n.method,this.mode=n.mode,this.signal=n.signal,!l&&n._bodyInit!=null&&(l=n._bodyInit,n.bodyUsed=!0)}else this.url=String(n);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 d=new AbortController;return d.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()}}}S.prototype.clone=function(){return new S(this,{body:this._bodyInit})};function ee(n){var i=new FormData;return n.trim().split("&").forEach(function(l){if(l){var h=l.split("="),p=h.shift().replace(/\+/g," "),d=h.join("=").replace(/\+/g," ");i.append(decodeURIComponent(p),decodeURIComponent(d))}}),i}function te(n){var i=new g,l=n.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(":"),d=p.shift().trim();if(d){var U=p.join(":").trim();try{i.append(d,U)}catch(A){console.warn("Response "+A.message)}}}),i}$.call(S.prototype);function E(n,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(n)}$.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 n=new E(null,{status:200,statusText:""});return n.ok=!1,n.status=0,n.type="error",n};var ne=[301,302,303,307,308];E.redirect=function(n,i){if(ne.indexOf(i)===-1)throw new RangeError("Invalid status code");return new E(null,{status:i,headers:{location:n}})},t.DOMException=s.DOMException;try{new t.DOMException}catch{t.DOMException=function(i,l){this.message=i,this.name=l;var h=Error(i);this.stack=h.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}function I(n,i){return new Promise(function(l,h){var p=new S(n,i);if(p.signal&&p.signal.aborted)return h(new t.DOMException("Aborted","AbortError"));var d=new XMLHttpRequest;function U(){d.abort()}d.onload=function(){var b={statusText:d.statusText,headers:te(d.getAllResponseHeaders()||"")};p.url.indexOf("file://")===0&&(d.status<200||d.status>599)?b.status=200:b.status=d.status,b.url="responseURL"in d?d.responseURL:b.headers.get("X-Request-URL");var R="response"in d?d.response:d.responseText;setTimeout(function(){l(new E(R,b))},0)},d.onerror=function(){setTimeout(function(){h(new TypeError("Network request failed"))},0)},d.ontimeout=function(){setTimeout(function(){h(new TypeError("Network request timed out"))},0)},d.onabort=function(){setTimeout(function(){h(new t.DOMException("Aborted","AbortError"))},0)};function A(b){try{return b===""&&s.location.href?s.location.href:b}catch{return b}}if(d.open(p.method,A(p.url),!0),p.credentials==="include"?d.withCredentials=!0:p.credentials==="omit"&&(d.withCredentials=!1),"responseType"in d&&(a.blob?d.responseType="blob":a.arrayBuffer&&(d.responseType="arraybuffer")),i&&typeof i.headers=="object"&&!(i.headers instanceof g||s.Headers&&i.headers instanceof s.Headers)){var H=[];Object.getOwnPropertyNames(i.headers).forEach(function(b){H.push(f(b)),d.setRequestHeader(b,y(i.headers[b]))}),p.headers.forEach(function(b,R){H.indexOf(R)===-1&&d.setRequestHeader(R,b)})}else p.headers.forEach(function(b,R){d.setRequestHeader(R,b)});p.signal&&(p.signal.addEventListener("abort",U),d.onreadystatechange=function(){d.readyState===4&&p.signal.removeEventListener("abort",U)}),d.send(typeof p._bodyInit>"u"?null:p._bodyInit)})}return I.polyfill=!0,s.fetch||(s.fetch=I,s.Headers=g,s.Request=S,s.Response=E),t.Headers=g,t.Request=S,t.Response=E,t.fetch=I,t})({})})(typeof self<"u"?self:Q)});var v=[];for(let r=0;r<256;++r)v.push((r+256).toString(16).slice(1));function K(r,e=0){return(v[r[e+0]]+v[r[e+1]]+v[r[e+2]]+v[r[e+3]]+"-"+v[r[e+4]]+v[r[e+5]]+"-"+v[r[e+6]]+v[r[e+7]]+"-"+v[r[e+8]]+v[r[e+9]]+"-"+v[r[e+10]]+v[r[e+11]]+v[r[e+12]]+v[r[e+13]]+v[r[e+14]]+v[r[e+15]]).toLowerCase()}var P,de=new Uint8Array(16);function B(){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,e,t){r=r||{};let s=r.random??r.rng?.()??B();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(t=t||0,t<0||t+16>e.length)throw new RangeError(`UUID byte range ${t}:${t+15} is out of buffer bounds`);for(let a=0;a<16;++a)e[t+a]=s[a];return e}return K(s)}function ge(r,e,t){return L.randomUUID&&!e&&!r?L.randomUUID():he(r,e,t)}var _=ge;var st=ce(z());function w(r){return pe(r,!1)}function pe(r,e){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,featureUsageEvent:r.feature_usage_event==null?void 0:r.feature_usage_event,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 N(r){return ye(r,!1)}function ye(r,e=!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,e){return r==null?r:{data:w(r.data),params:r.params}}function X(r){return ve(r,!1)}function ve(r,e){return r==null?r:{flags:r.flags.map(w)}}function J(r){return ke(r,!1)}function ke(r,e){return r==null?r:{data:X(r.data),params:r.params}}var O=r=>{let{companyId:e,error:t,featureAllocation:s,featureUsage:a,featureUsageEvent:c,featureUsagePeriod:o,featureUsageResetAt:u,flag:f,flagId:y,reason:F,ruleId:g,ruleType:k,userId:m,value:x}=w(r);return{featureUsageExceeded:!x&&(k=="company_override_usage_exceeded"||k=="plan_entitlement_usage_exceeded"),companyId:e??void 0,error:t??void 0,featureAllocation:s??void 0,featureUsage:a??void 0,featureUsageEvent:c===null?void 0:c,featureUsagePeriod:o??void 0,featureUsageResetAt:u??void 0,flag:f,flagId:y??void 0,reason:F,ruleId:g??void 0,ruleType:k??void 0,userId:m??void 0,value:x}};function C(r){let e=Object.keys(r).reduce((t,s)=>{let c=Object.keys(r[s]||{}).sort().reduce((o,u)=>(o[u]=r[s][u],o),{});return t[s]=c,t},{});return JSON.stringify(e)}var M="1.2.5";var G="schematicId";var D=class{additionalHeaders={};apiKey;apiUrl="https://api.schematichq.com";conn=null;context={};debugEnabled=!1;offlineEnabled=!1;eventQueue;contextDependentEventQueue;eventUrl="https://c.schematichq.com";flagCheckListeners={};flagValueListeners={};isPending=!0;isPendingListeners=new Set;storage;useWebSocket=!1;checks={};featureUsageEventMap={};webSocketUrl="wss://api.schematichq.com";constructor(e,t){if(this.apiKey=e,this.eventQueue=[],this.contextDependentEventQueue=[],this.useWebSocket=t?.useWebSocket??!1,this.debugEnabled=t?.debug??!1,this.offlineEnabled=t?.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 c=s.get("schematic_offline");c!==null&&(c===""||c==="true"||c==="1")&&(this.offlineEnabled=!0,this.debugEnabled=!0)}this.offlineEnabled&&t?.debug!==!1&&(this.debugEnabled=!0),this.offlineEnabled&&this.setIsPending(!1),this.additionalHeaders={"X-Schematic-Client-Version":`schematic-js@${M}`,...t?.additionalHeaders??{}},t?.storage?this.storage=t.storage:typeof localStorage<"u"&&(this.storage=localStorage),t?.apiUrl!==void 0&&(this.apiUrl=t.apiUrl),t?.eventUrl!==void 0&&(this.eventUrl=t.eventUrl),t?.webSocketUrl!==void 0&&(this.webSocketUrl=t.webSocketUrl),typeof window<"u"&&window?.addEventListener&&window.addEventListener("beforeunload",()=>{this.flushEventQueue(),this.flushContextDependentEventQueue()}),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:t=!1,key:s}=e,a=e.context||this.context,c=C(a);if(this.debug(`checkFlag: ${s}`,{context:a,fallback:t}),this.isOffline())return this.debug(`checkFlag offline result: ${s}`,{value:t,offlineMode:!0}),t;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 f=T(u);this.debug(`checkFlag result: ${s}`,f);let y=O(f.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 f={flag:s,value:t,reason:"API request failed",error:u instanceof Error?u.message:String(u)};return this.submitFlagCheckEvent(s,f,a),t})}try{let o=this.checks[c];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 t;try{await this.setContext(a)}catch(F){return console.error("WebSocket connection failed, falling back to REST:",F),this.fallbackToRest(s,a,t)}let f=(this.checks[c]??{})[s],y=f?.value??t;return this.debug(`checkFlag WebSocket result: ${s}`,typeof f<"u"?f:{value:t,fallbackUsed:!0}),typeof f<"u"&&this.submitFlagCheckEvent(s,f,a),y}catch(o){console.error("Unexpected error in checkFlag:",o);let u={flag:s,value:t,reason:"Unexpected error in flag check",error:o instanceof Error?o.message:String(o)};return this.submitFlagCheckEvent(s,u,a),t}}debug(e,...t){this.debugEnabled&&console.log(`[Schematic] ${e}`,...t)}isOffline(){return this.offlineEnabled}submitFlagCheckEvent(e,t,s){let a={flagKey:e,value:t.value,reason:t.reason,flagId:t.flagId,ruleId:t.ruleId,companyId:t.companyId,userId:t.userId,error:t.error,reqCompany:s.company,reqUser:s.user};return this.debug("submitting flag check event:",a),this.handleEvent("flag_check",N(a))}async fallbackToRest(e,t,s){if(this.isOffline())return this.debug(`fallbackToRest offline result: ${e}`,{value:s,offlineMode:!0}),s;try{let a=`${this.apiUrl}/flags/${e}/check`,c=await fetch(a,{method:"POST",headers:{...this.additionalHeaders??{},"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:JSON.stringify(t)});if(!c.ok)throw new Error("Network response was not ok");let o=await c.json(),u=T(o);this.debug(`fallbackToRest result: ${e}`,u);let f=O(u.data);return typeof f.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(f),this.submitFlagCheckEvent(e,f,t),f.value}catch(a){console.error("REST API call failed, using fallback value:",a);let c={flag:e,value:s,reason:"API request failed (fallback)",error:a instanceof Error?a.message:String(a)};return this.submitFlagCheckEvent(e,c,t),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 t=`${this.apiUrl}/flags/check`,s=JSON.stringify(e);return fetch(t,{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 c=J(a);return this.debug("checkFlags result:",c),(c?.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(t){console.error("Error setting context:",t)}return this.handleEvent("identify",e)};setContext=async e=>{if(this.isOffline()||!this.useWebSocket)return this.context=e,this.flushContextDependentEventQueue(),this.setIsPending(!1),Promise.resolve();try{this.setIsPending(!0),this.conn||(this.conn=this.wsConnect());let t=await this.conn;await this.wsSendMessage(t,e)}catch(t){throw console.error("Failed to establish WebSocket connection:",t),t}};track=e=>{let{company:t,user:s,event:a,traits:c,quantity:o=1}=e;if(!this.hasContext(t,s)){this.debug(`track: queuing event "${a}" until context is available`);let f={api_key:this.apiKey,body:{company:t,event:a,traits:c??{},user:s,quantity:o},sent_at:new Date().toISOString(),tracker_event_id:_(),tracker_user_id:this.getAnonymousId(),type:"track"};return this.contextDependentEventQueue.push(f),Promise.resolve()}let u={company:t??this.context.company,event:a,traits:c??{},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,t=1)=>{let s=this.featureUsageEventMap[e];s!=null&&(this.debug(`Optimistically updating feature usage for event: ${e}`,{quantity:t}),Object.entries(s).forEach(([a,c])=>{if(c===void 0)return;let o={...c};if(typeof o.featureUsage=="number"){if(o.featureUsage+=t,typeof o.featureAllocation=="number"){let f=o.featureUsageExceeded===!0,y=o.featureUsage>=o.featureAllocation;y!==f&&(o.featureUsageExceeded=y,y&&(o.value=!1),this.debug(`Usage limit status changed for flag: ${a}`,{was:f?"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)}}))};hasContext=(e,t)=>{let s=e!=null&&Object.keys(e).length>0||t!=null&&Object.keys(t).length>0,a=this.context.company!==void 0&&this.context.company!==null&&Object.keys(this.context.company).length>0||this.context.user!==void 0&&this.context.user!==null&&Object.keys(this.context.user).length>0;return s||a};flushContextDependentEventQueue=()=>{for(this.debug(`flushing ${this.contextDependentEventQueue.length} context-dependent events`);this.contextDependentEventQueue.length>0;){let e=this.contextDependentEventQueue.shift();if(e)if(e.type==="track"&&typeof e.body=="object"&&e.body!==null){let t=e.body,s={...t,company:t.company??this.context.company,user:t.user??this.context.user},a={...e,body:s,sent_at:new Date().toISOString()};this.sendEvent(a)}else this.sendEvent(e)}};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(G);if(typeof e<"u")return e;let t=_();return this.storage.setItem(G,t),t};handleEvent=(e,t)=>{let s={api_key:this.apiKey,body:t,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 t=`${this.eventUrl}/e`,s=JSON.stringify(e);if(this.debug("sending event:",{url:t,event:e}),this.isOffline())return this.debug("event not sent (offline mode):",{event:e}),Promise.resolve();try{let a=await fetch(t,{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,t)=>{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=c=>{this.debug("WebSocket connection error:",c),t(c)},a.onclose=()=>{this.debug("WebSocket connection closed"),this.conn=null}});wsSendMessage=(e,t)=>this.isOffline()?(this.debug("wsSendMessage: skipped (offline mode)"),this.setIsPending(!1),Promise.resolve()):new Promise((s,a)=>{if(C(t)==C(this.context))return this.debug("WebSocket context unchanged, skipping update"),s(this.setIsPending(!1));this.debug("WebSocket context updated:",t),this.context=t;let c=()=>{let o=!1,u=F=>{let g=JSON.parse(F.data);this.debug("WebSocket message received:",g),C(t)in this.checks||(this.checks[C(t)]={}),(g.flags??[]).forEach(k=>{let m=O(k),x=C(t);this.checks[x]===void 0&&(this.checks[x]={}),this.checks[x][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[k.flag]?.size>0||this.flagValueListeners[k.flag]?.size>0)&&this.submitFlagCheckEvent(m.flag,m,t),this.notifyFlagCheckListeners(k.flag,m),this.notifyFlagValueListeners(k.flag,m.value)}),this.flushContextDependentEventQueue(),this.setIsPending(!1),o||(o=!0,s())};e.addEventListener("message",u);let f=this.additionalHeaders["X-Schematic-Client-Version"]??`schematic-js@${M}`,y={apiKey:this.apiKey,clientVersion:f,data:t};this.debug("WebSocket sending message:",y),e.send(JSON.stringify(y))};e.readyState===WebSocket.OPEN?(this.debug("WebSocket already open, sending message"),c()):e.readyState===WebSocket.CONNECTING?(this.debug("WebSocket connecting, waiting for open to send message"),e.addEventListener("open",c)):(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(t=>Ee(t,e))};getFlagCheck=e=>{let t=C(this.context);return(this.checks[t]??{})[e]};getFlagValue=e=>this.getFlagCheck(e)?.value;addFlagValueListener=(e,t)=>(e in this.flagValueListeners||(this.flagValueListeners[e]=new Set),this.flagValueListeners[e].add(t),()=>{this.flagValueListeners[e].delete(t)});addFlagCheckListener=(e,t)=>(e in this.flagCheckListeners||(this.flagCheckListeners[e]=new Set),this.flagCheckListeners[e].add(t),()=>{this.flagCheckListeners[e].delete(t)});notifyFlagCheckListeners=(e,t)=>{let s=this.flagCheckListeners?.[e]??[];s.size>0&&this.debug(`Notifying ${s.size} flag check listeners for ${e}`,t),typeof t.featureUsageEvent=="string"&&this.updateFeatureUsageEventMap(t),s.forEach(a=>Fe(a,t))};updateFeatureUsageEventMap=e=>{if(typeof e.featureUsageEvent!="string")return;let t=e.featureUsageEvent;(this.featureUsageEventMap[t]===void 0||this.featureUsageEventMap[t]===null)&&(this.featureUsageEventMap[t]={}),this.featureUsageEventMap[t]!==void 0&&(this.featureUsageEventMap[t][e.flag]=e),this.debug(`Updated featureUsageEventMap for event: ${t}, flag: ${e.flag}`,e)};notifyFlagValueListeners=(e,t)=>{let s=this.flagValueListeners?.[e]??[];s.size>0&&this.debug(`Notifying ${s.size} flag value listeners for ${e}`,{value:t}),s.forEach(a=>Ce(a,t))}},Ee=(r,e)=>{r.length>0?r(e):r()},Fe=(r,e)=>{r.length>0?r(e):r()},Ce=(r,e)=>{r.length>0?r(e):r()};window.Schematic=D;})();
3
3
  /* @preserve */
@@ -34,20 +34,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
34
34
  var require_browser_polyfill = __commonJS({
35
35
  "node_modules/cross-fetch/dist/browser-polyfill.js"(exports) {
36
36
  (function(self2) {
37
- var irrelevant = function(exports2) {
37
+ var irrelevant = (function(exports2) {
38
38
  var g = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || // eslint-disable-next-line no-undef
39
39
  typeof global !== "undefined" && global || {};
40
40
  var support = {
41
41
  searchParams: "URLSearchParams" in g,
42
42
  iterable: "Symbol" in g && "iterator" in Symbol,
43
- blob: "FileReader" in g && "Blob" in g && function() {
43
+ blob: "FileReader" in g && "Blob" in g && (function() {
44
44
  try {
45
45
  new Blob();
46
46
  return true;
47
47
  } catch (e) {
48
48
  return false;
49
49
  }
50
- }(),
50
+ })(),
51
51
  formData: "FormData" in g,
52
52
  arrayBuffer: "ArrayBuffer" in g
53
53
  };
@@ -349,12 +349,12 @@ var require_browser_polyfill = __commonJS({
349
349
  }
350
350
  this.method = normalizeMethod(options.method || this.method || "GET");
351
351
  this.mode = options.mode || this.mode || null;
352
- this.signal = options.signal || this.signal || function() {
352
+ this.signal = options.signal || this.signal || (function() {
353
353
  if ("AbortController" in g) {
354
354
  var ctrl = new AbortController();
355
355
  return ctrl.signal;
356
356
  }
357
- }();
357
+ })();
358
358
  this.referrer = null;
359
359
  if ((this.method === "GET" || this.method === "HEAD") && body) {
360
360
  throw new TypeError("Body not allowed for GET or HEAD requests");
@@ -561,7 +561,7 @@ var require_browser_polyfill = __commonJS({
561
561
  exports2.Response = Response;
562
562
  exports2.fetch = fetch2;
563
563
  return exports2;
564
- }({});
564
+ })({});
565
565
  })(typeof self !== "undefined" ? self : exports);
566
566
  }
567
567
  });
@@ -579,7 +579,7 @@ __export(index_exports, {
579
579
  });
580
580
  module.exports = __toCommonJS(index_exports);
581
581
 
582
- // node_modules/uuid/dist/esm-browser/stringify.js
582
+ // node_modules/uuid/dist/stringify.js
583
583
  var byteToHex = [];
584
584
  for (let i = 0; i < 256; ++i) {
585
585
  byteToHex.push((i + 256).toString(16).slice(1));
@@ -588,7 +588,7 @@ function unsafeStringify(arr, offset = 0) {
588
588
  return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
589
589
  }
590
590
 
591
- // node_modules/uuid/dist/esm-browser/rng.js
591
+ // node_modules/uuid/dist/rng.js
592
592
  var getRandomValues;
593
593
  var rnds8 = new Uint8Array(16);
594
594
  function rng() {
@@ -601,15 +601,12 @@ function rng() {
601
601
  return getRandomValues(rnds8);
602
602
  }
603
603
 
604
- // node_modules/uuid/dist/esm-browser/native.js
604
+ // node_modules/uuid/dist/native.js
605
605
  var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
606
606
  var native_default = { randomUUID };
607
607
 
608
- // node_modules/uuid/dist/esm-browser/v4.js
609
- function v4(options, buf, offset) {
610
- if (native_default.randomUUID && !buf && !options) {
611
- return native_default.randomUUID();
612
- }
608
+ // node_modules/uuid/dist/v4.js
609
+ function _v4(options, buf, offset) {
613
610
  options = options || {};
614
611
  const rnds = options.random ?? options.rng?.() ?? rng();
615
612
  if (rnds.length < 16) {
@@ -629,6 +626,12 @@ function v4(options, buf, offset) {
629
626
  }
630
627
  return unsafeStringify(rnds);
631
628
  }
629
+ function v4(options, buf, offset) {
630
+ if (native_default.randomUUID && !buf && !options) {
631
+ return native_default.randomUUID();
632
+ }
633
+ return _v4(options, buf, offset);
634
+ }
632
635
  var v4_default = v4;
633
636
 
634
637
  // src/index.ts
@@ -647,6 +650,7 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
647
650
  error: json["error"] == null ? void 0 : json["error"],
648
651
  featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
649
652
  featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
653
+ featureUsageEvent: json["feature_usage_event"] == null ? void 0 : json["feature_usage_event"],
650
654
  featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
651
655
  featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
652
656
  flag: json["flag"],
@@ -746,6 +750,7 @@ var CheckFlagReturnFromJSON = (json) => {
746
750
  error,
747
751
  featureAllocation,
748
752
  featureUsage,
753
+ featureUsageEvent,
749
754
  featureUsagePeriod,
750
755
  featureUsageResetAt,
751
756
  flag,
@@ -765,6 +770,7 @@ var CheckFlagReturnFromJSON = (json) => {
765
770
  error: error == null ? void 0 : error,
766
771
  featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
767
772
  featureUsage: featureUsage == null ? void 0 : featureUsage,
773
+ featureUsageEvent: featureUsageEvent === null ? void 0 : featureUsageEvent,
768
774
  featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
769
775
  featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
770
776
  flag,
@@ -794,7 +800,7 @@ function contextString(context) {
794
800
  }
795
801
 
796
802
  // src/version.ts
797
- var version = "1.2.2";
803
+ var version = "1.2.5";
798
804
 
799
805
  // src/index.ts
800
806
  var anonymousIdKey = "schematicId";
@@ -807,6 +813,7 @@ var Schematic = class {
807
813
  debugEnabled = false;
808
814
  offlineEnabled = false;
809
815
  eventQueue;
816
+ contextDependentEventQueue;
810
817
  eventUrl = "https://c.schematichq.com";
811
818
  flagCheckListeners = {};
812
819
  flagValueListeners = {};
@@ -815,10 +822,12 @@ var Schematic = class {
815
822
  storage;
816
823
  useWebSocket = false;
817
824
  checks = {};
825
+ featureUsageEventMap = {};
818
826
  webSocketUrl = "wss://api.schematichq.com";
819
827
  constructor(apiKey, options) {
820
828
  this.apiKey = apiKey;
821
829
  this.eventQueue = [];
830
+ this.contextDependentEventQueue = [];
822
831
  this.useWebSocket = options?.useWebSocket ?? false;
823
832
  this.debugEnabled = options?.debug ?? false;
824
833
  this.offlineEnabled = options?.offline ?? false;
@@ -861,6 +870,7 @@ var Schematic = class {
861
870
  if (typeof window !== "undefined" && window?.addEventListener) {
862
871
  window.addEventListener("beforeunload", () => {
863
872
  this.flushEventQueue();
873
+ this.flushContextDependentEventQueue();
864
874
  });
865
875
  }
866
876
  if (this.offlineEnabled) {
@@ -909,6 +919,9 @@ var Schematic = class {
909
919
  const parsedResponse = CheckFlagResponseFromJSON(response);
910
920
  this.debug(`checkFlag result: ${key}`, parsedResponse);
911
921
  const result = CheckFlagReturnFromJSON(parsedResponse.data);
922
+ if (typeof result.featureUsageEvent === "string") {
923
+ this.updateFeatureUsageEventMap(result);
924
+ }
912
925
  this.submitFlagCheckEvent(key, result, context);
913
926
  return result.value;
914
927
  }).catch((error) => {
@@ -1028,6 +1041,9 @@ var Schematic = class {
1028
1041
  const data = CheckFlagResponseFromJSON(responseJson);
1029
1042
  this.debug(`fallbackToRest result: ${key}`, data);
1030
1043
  const result = CheckFlagReturnFromJSON(data.data);
1044
+ if (typeof result.featureUsageEvent === "string") {
1045
+ this.updateFeatureUsageEventMap(result);
1046
+ }
1031
1047
  this.submitFlagCheckEvent(key, result, context);
1032
1048
  return result.value;
1033
1049
  } catch (error) {
@@ -1111,14 +1127,12 @@ var Schematic = class {
1111
1127
  * In offline mode, this will just set the context locally without connecting.
1112
1128
  */
1113
1129
  setContext = async (context) => {
1114
- if (this.isOffline()) {
1130
+ if (this.isOffline() || !this.useWebSocket) {
1115
1131
  this.context = context;
1132
+ this.flushContextDependentEventQueue();
1116
1133
  this.setIsPending(false);
1117
1134
  return Promise.resolve();
1118
1135
  }
1119
- if (!this.useWebSocket) {
1120
- return Promise.resolve();
1121
- }
1122
1136
  try {
1123
1137
  this.setIsPending(true);
1124
1138
  if (!this.conn) {
@@ -1134,21 +1148,123 @@ var Schematic = class {
1134
1148
  /**
1135
1149
  * Send a track event
1136
1150
  * Track usage for a company and/or user.
1151
+ * Optimistically updates feature usage flags if tracking a featureUsageEvent.
1137
1152
  */
1138
1153
  track = (body) => {
1139
- const { company, user, event, traits } = body;
1154
+ const { company, user, event, traits, quantity = 1 } = body;
1155
+ if (!this.hasContext(company, user)) {
1156
+ this.debug(`track: queuing event "${event}" until context is available`);
1157
+ const queuedEvent = {
1158
+ api_key: this.apiKey,
1159
+ body: {
1160
+ company,
1161
+ event,
1162
+ traits: traits ?? {},
1163
+ user,
1164
+ quantity
1165
+ },
1166
+ sent_at: (/* @__PURE__ */ new Date()).toISOString(),
1167
+ tracker_event_id: v4_default(),
1168
+ tracker_user_id: this.getAnonymousId(),
1169
+ type: "track"
1170
+ };
1171
+ this.contextDependentEventQueue.push(queuedEvent);
1172
+ return Promise.resolve();
1173
+ }
1140
1174
  const trackData = {
1141
1175
  company: company ?? this.context.company,
1142
1176
  event,
1143
1177
  traits: traits ?? {},
1144
- user: user ?? this.context.user
1178
+ user: user ?? this.context.user,
1179
+ quantity
1145
1180
  };
1146
1181
  this.debug(`track:`, trackData);
1182
+ if (event in this.featureUsageEventMap) {
1183
+ this.optimisticallyUpdateFeatureUsage(event, quantity);
1184
+ }
1147
1185
  return this.handleEvent("track", trackData);
1148
1186
  };
1187
+ /**
1188
+ * Optimistically update feature usage flags associated with a tracked event
1189
+ * This updates flags in memory with updated usage counts and value/featureUsageExceeded flags
1190
+ * before the network request completes
1191
+ */
1192
+ optimisticallyUpdateFeatureUsage = (eventName, quantity = 1) => {
1193
+ const flagsForEvent = this.featureUsageEventMap[eventName];
1194
+ if (flagsForEvent === void 0 || flagsForEvent === null) return;
1195
+ this.debug(
1196
+ `Optimistically updating feature usage for event: ${eventName}`,
1197
+ { quantity }
1198
+ );
1199
+ Object.entries(flagsForEvent).forEach(([flagKey, check]) => {
1200
+ if (check === void 0) return;
1201
+ const updatedCheck = { ...check };
1202
+ if (typeof updatedCheck.featureUsage === "number") {
1203
+ updatedCheck.featureUsage += quantity;
1204
+ if (typeof updatedCheck.featureAllocation === "number") {
1205
+ const wasExceeded = updatedCheck.featureUsageExceeded === true;
1206
+ const nowExceeded = updatedCheck.featureUsage >= updatedCheck.featureAllocation;
1207
+ if (nowExceeded !== wasExceeded) {
1208
+ updatedCheck.featureUsageExceeded = nowExceeded;
1209
+ if (nowExceeded) {
1210
+ updatedCheck.value = false;
1211
+ }
1212
+ this.debug(`Usage limit status changed for flag: ${flagKey}`, {
1213
+ was: wasExceeded ? "exceeded" : "within limits",
1214
+ now: nowExceeded ? "exceeded" : "within limits",
1215
+ featureUsage: updatedCheck.featureUsage,
1216
+ featureAllocation: updatedCheck.featureAllocation,
1217
+ value: updatedCheck.value
1218
+ });
1219
+ }
1220
+ }
1221
+ if (this.featureUsageEventMap[eventName] !== void 0) {
1222
+ this.featureUsageEventMap[eventName][flagKey] = updatedCheck;
1223
+ }
1224
+ const contextStr = contextString(this.context);
1225
+ if (this.checks[contextStr] !== void 0 && this.checks[contextStr] !== null) {
1226
+ this.checks[contextStr][flagKey] = updatedCheck;
1227
+ }
1228
+ this.notifyFlagCheckListeners(flagKey, updatedCheck);
1229
+ this.notifyFlagValueListeners(flagKey, updatedCheck.value);
1230
+ }
1231
+ });
1232
+ };
1149
1233
  /**
1150
1234
  * Event processing
1151
1235
  */
1236
+ hasContext = (company, user) => {
1237
+ const hasProvidedContext = company !== void 0 && company !== null && Object.keys(company).length > 0 || user !== void 0 && user !== null && Object.keys(user).length > 0;
1238
+ const hasInstanceContext = this.context.company !== void 0 && this.context.company !== null && Object.keys(this.context.company).length > 0 || this.context.user !== void 0 && this.context.user !== null && Object.keys(this.context.user).length > 0;
1239
+ return hasProvidedContext || hasInstanceContext;
1240
+ };
1241
+ flushContextDependentEventQueue = () => {
1242
+ this.debug(
1243
+ `flushing ${this.contextDependentEventQueue.length} context-dependent events`
1244
+ );
1245
+ while (this.contextDependentEventQueue.length > 0) {
1246
+ const event = this.contextDependentEventQueue.shift();
1247
+ if (event) {
1248
+ if (event.type === "track" && typeof event.body === "object" && event.body !== null) {
1249
+ const trackBody = event.body;
1250
+ const updatedBody = {
1251
+ ...trackBody,
1252
+ company: trackBody.company ?? this.context.company,
1253
+ user: trackBody.user ?? this.context.user
1254
+ };
1255
+ const updatedEvent = {
1256
+ ...event,
1257
+ body: updatedBody,
1258
+ sent_at: (/* @__PURE__ */ new Date()).toISOString()
1259
+ // Update timestamp to actual send time
1260
+ };
1261
+ this.sendEvent(updatedEvent);
1262
+ } else {
1263
+ this.sendEvent(event);
1264
+ }
1265
+ }
1266
+ }
1267
+ };
1152
1268
  flushEventQueue = () => {
1153
1269
  while (this.eventQueue.length > 0) {
1154
1270
  const event = this.eventQueue.shift();
@@ -1288,18 +1404,26 @@ var Schematic = class {
1288
1404
  }
1289
1405
  (message.flags ?? []).forEach((flag) => {
1290
1406
  const flagCheck = CheckFlagReturnFromJSON(flag);
1291
- this.checks[contextString(context)][flagCheck.flag] = flagCheck;
1407
+ const contextStr = contextString(context);
1408
+ if (this.checks[contextStr] === void 0) {
1409
+ this.checks[contextStr] = {};
1410
+ }
1411
+ this.checks[contextStr][flagCheck.flag] = flagCheck;
1292
1412
  this.debug(`WebSocket flag update:`, {
1293
1413
  flag: flagCheck.flag,
1294
1414
  value: flagCheck.value,
1295
1415
  flagCheck
1296
1416
  });
1417
+ if (typeof flagCheck.featureUsageEvent === "string") {
1418
+ this.updateFeatureUsageEventMap(flagCheck);
1419
+ }
1297
1420
  if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
1298
1421
  this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
1299
1422
  }
1300
1423
  this.notifyFlagCheckListeners(flag.flag, flagCheck);
1301
1424
  this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1302
1425
  });
1426
+ this.flushContextDependentEventQueue();
1303
1427
  this.setIsPending(false);
1304
1428
  if (!resolved) {
1305
1429
  resolved = true;
@@ -1386,8 +1510,26 @@ var Schematic = class {
1386
1510
  check
1387
1511
  );
1388
1512
  }
1513
+ if (typeof check.featureUsageEvent === "string") {
1514
+ this.updateFeatureUsageEventMap(check);
1515
+ }
1389
1516
  listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
1390
1517
  };
1518
+ /** Add or update a CheckFlagReturn in the featureUsageEventMap */
1519
+ updateFeatureUsageEventMap = (check) => {
1520
+ if (typeof check.featureUsageEvent !== "string") return;
1521
+ const eventName = check.featureUsageEvent;
1522
+ if (this.featureUsageEventMap[eventName] === void 0 || this.featureUsageEventMap[eventName] === null) {
1523
+ this.featureUsageEventMap[eventName] = {};
1524
+ }
1525
+ if (this.featureUsageEventMap[eventName] !== void 0) {
1526
+ this.featureUsageEventMap[eventName][check.flag] = check;
1527
+ }
1528
+ this.debug(
1529
+ `Updated featureUsageEventMap for event: ${eventName}, flag: ${check.flag}`,
1530
+ check
1531
+ );
1532
+ };
1391
1533
  notifyFlagValueListeners = (flagKey, value) => {
1392
1534
  const listeners = this.flagValueListeners?.[flagKey] ?? [];
1393
1535
  if (listeners.size > 0) {
@@ -32,7 +32,7 @@ declare interface CheckFlagResponse {
32
32
  * Do not edit the class manually.
33
33
  */
34
34
  /**
35
- * The returned resource
35
+ *
36
36
  * @export
37
37
  * @interface CheckFlagResponseData
38
38
  */
@@ -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 */
@@ -175,7 +183,7 @@ declare interface CheckFlagsResponse {
175
183
  }
176
184
 
177
185
  /**
178
- * The created resource
186
+ *
179
187
  * @export
180
188
  * @interface CheckFlagsResponseData
181
189
  */
@@ -349,6 +357,7 @@ export declare class Schematic {
349
357
  private debugEnabled;
350
358
  private offlineEnabled;
351
359
  private eventQueue;
360
+ private contextDependentEventQueue;
352
361
  private eventUrl;
353
362
  private flagCheckListeners;
354
363
  private flagValueListeners;
@@ -357,6 +366,7 @@ export declare class Schematic {
357
366
  private storage;
358
367
  private useWebSocket;
359
368
  private checks;
369
+ private featureUsageEventMap;
360
370
  private webSocketUrl;
361
371
  constructor(apiKey: string, options?: SchematicOptions);
362
372
  /**
@@ -410,11 +420,20 @@ export declare class Schematic {
410
420
  /**
411
421
  * Send a track event
412
422
  * Track usage for a company and/or user.
423
+ * Optimistically updates feature usage flags if tracking a featureUsageEvent.
413
424
  */
414
425
  track: (body: EventBodyTrack) => Promise<void>;
426
+ /**
427
+ * Optimistically update feature usage flags associated with a tracked event
428
+ * This updates flags in memory with updated usage counts and value/featureUsageExceeded flags
429
+ * before the network request completes
430
+ */
431
+ private optimisticallyUpdateFeatureUsage;
415
432
  /**
416
433
  * Event processing
417
434
  */
435
+ private hasContext;
436
+ private flushContextDependentEventQueue;
418
437
  private flushEventQueue;
419
438
  private getAnonymousId;
420
439
  private handleEvent;
@@ -436,13 +455,15 @@ export declare class Schematic {
436
455
  getIsPending: () => boolean;
437
456
  addIsPendingListener: (listener: PendingListenerFn) => () => void;
438
457
  private setIsPending;
439
- getFlagCheck: (flagKey: string) => CheckFlagReturn;
440
- getFlagValue: (flagKey: string) => boolean;
458
+ getFlagCheck: (flagKey: string) => CheckFlagReturn | undefined;
459
+ getFlagValue: (flagKey: string) => boolean | undefined;
441
460
  /** Register an event listener that will be notified with the boolean value for a given flag when this value changes */
442
461
  addFlagValueListener: (flagKey: string, listener: FlagValueListenerFn) => () => void;
443
462
  /** Register an event listener that will be notified with the full flag check response for a given flag whenever this value changes */
444
463
  addFlagCheckListener: (flagKey: string, listener: FlagCheckListenerFn) => () => void;
445
464
  private notifyFlagCheckListeners;
465
+ /** Add or update a CheckFlagReturn in the featureUsageEventMap */
466
+ private updateFeatureUsageEventMap;
446
467
  private notifyFlagValueListeners;
447
468
  }
448
469
 
@@ -28,20 +28,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var require_browser_polyfill = __commonJS({
29
29
  "node_modules/cross-fetch/dist/browser-polyfill.js"(exports) {
30
30
  (function(self2) {
31
- var irrelevant = function(exports2) {
31
+ var irrelevant = (function(exports2) {
32
32
  var g = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || // eslint-disable-next-line no-undef
33
33
  typeof global !== "undefined" && global || {};
34
34
  var support = {
35
35
  searchParams: "URLSearchParams" in g,
36
36
  iterable: "Symbol" in g && "iterator" in Symbol,
37
- blob: "FileReader" in g && "Blob" in g && function() {
37
+ blob: "FileReader" in g && "Blob" in g && (function() {
38
38
  try {
39
39
  new Blob();
40
40
  return true;
41
41
  } catch (e) {
42
42
  return false;
43
43
  }
44
- }(),
44
+ })(),
45
45
  formData: "FormData" in g,
46
46
  arrayBuffer: "ArrayBuffer" in g
47
47
  };
@@ -343,12 +343,12 @@ var require_browser_polyfill = __commonJS({
343
343
  }
344
344
  this.method = normalizeMethod(options.method || this.method || "GET");
345
345
  this.mode = options.mode || this.mode || null;
346
- this.signal = options.signal || this.signal || function() {
346
+ this.signal = options.signal || this.signal || (function() {
347
347
  if ("AbortController" in g) {
348
348
  var ctrl = new AbortController();
349
349
  return ctrl.signal;
350
350
  }
351
- }();
351
+ })();
352
352
  this.referrer = null;
353
353
  if ((this.method === "GET" || this.method === "HEAD") && body) {
354
354
  throw new TypeError("Body not allowed for GET or HEAD requests");
@@ -555,12 +555,12 @@ var require_browser_polyfill = __commonJS({
555
555
  exports2.Response = Response;
556
556
  exports2.fetch = fetch2;
557
557
  return exports2;
558
- }({});
558
+ })({});
559
559
  })(typeof self !== "undefined" ? self : exports);
560
560
  }
561
561
  });
562
562
 
563
- // node_modules/uuid/dist/esm-browser/stringify.js
563
+ // node_modules/uuid/dist/stringify.js
564
564
  var byteToHex = [];
565
565
  for (let i = 0; i < 256; ++i) {
566
566
  byteToHex.push((i + 256).toString(16).slice(1));
@@ -569,7 +569,7 @@ function unsafeStringify(arr, offset = 0) {
569
569
  return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
570
570
  }
571
571
 
572
- // node_modules/uuid/dist/esm-browser/rng.js
572
+ // node_modules/uuid/dist/rng.js
573
573
  var getRandomValues;
574
574
  var rnds8 = new Uint8Array(16);
575
575
  function rng() {
@@ -582,15 +582,12 @@ function rng() {
582
582
  return getRandomValues(rnds8);
583
583
  }
584
584
 
585
- // node_modules/uuid/dist/esm-browser/native.js
585
+ // node_modules/uuid/dist/native.js
586
586
  var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
587
587
  var native_default = { randomUUID };
588
588
 
589
- // node_modules/uuid/dist/esm-browser/v4.js
590
- function v4(options, buf, offset) {
591
- if (native_default.randomUUID && !buf && !options) {
592
- return native_default.randomUUID();
593
- }
589
+ // node_modules/uuid/dist/v4.js
590
+ function _v4(options, buf, offset) {
594
591
  options = options || {};
595
592
  const rnds = options.random ?? options.rng?.() ?? rng();
596
593
  if (rnds.length < 16) {
@@ -610,6 +607,12 @@ function v4(options, buf, offset) {
610
607
  }
611
608
  return unsafeStringify(rnds);
612
609
  }
610
+ function v4(options, buf, offset) {
611
+ if (native_default.randomUUID && !buf && !options) {
612
+ return native_default.randomUUID();
613
+ }
614
+ return _v4(options, buf, offset);
615
+ }
613
616
  var v4_default = v4;
614
617
 
615
618
  // src/index.ts
@@ -628,6 +631,7 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
628
631
  error: json["error"] == null ? void 0 : json["error"],
629
632
  featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
630
633
  featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
634
+ featureUsageEvent: json["feature_usage_event"] == null ? void 0 : json["feature_usage_event"],
631
635
  featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
632
636
  featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
633
637
  flag: json["flag"],
@@ -727,6 +731,7 @@ var CheckFlagReturnFromJSON = (json) => {
727
731
  error,
728
732
  featureAllocation,
729
733
  featureUsage,
734
+ featureUsageEvent,
730
735
  featureUsagePeriod,
731
736
  featureUsageResetAt,
732
737
  flag,
@@ -746,6 +751,7 @@ var CheckFlagReturnFromJSON = (json) => {
746
751
  error: error == null ? void 0 : error,
747
752
  featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
748
753
  featureUsage: featureUsage == null ? void 0 : featureUsage,
754
+ featureUsageEvent: featureUsageEvent === null ? void 0 : featureUsageEvent,
749
755
  featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
750
756
  featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
751
757
  flag,
@@ -775,7 +781,7 @@ function contextString(context) {
775
781
  }
776
782
 
777
783
  // src/version.ts
778
- var version = "1.2.2";
784
+ var version = "1.2.5";
779
785
 
780
786
  // src/index.ts
781
787
  var anonymousIdKey = "schematicId";
@@ -788,6 +794,7 @@ var Schematic = class {
788
794
  debugEnabled = false;
789
795
  offlineEnabled = false;
790
796
  eventQueue;
797
+ contextDependentEventQueue;
791
798
  eventUrl = "https://c.schematichq.com";
792
799
  flagCheckListeners = {};
793
800
  flagValueListeners = {};
@@ -796,10 +803,12 @@ var Schematic = class {
796
803
  storage;
797
804
  useWebSocket = false;
798
805
  checks = {};
806
+ featureUsageEventMap = {};
799
807
  webSocketUrl = "wss://api.schematichq.com";
800
808
  constructor(apiKey, options) {
801
809
  this.apiKey = apiKey;
802
810
  this.eventQueue = [];
811
+ this.contextDependentEventQueue = [];
803
812
  this.useWebSocket = options?.useWebSocket ?? false;
804
813
  this.debugEnabled = options?.debug ?? false;
805
814
  this.offlineEnabled = options?.offline ?? false;
@@ -842,6 +851,7 @@ var Schematic = class {
842
851
  if (typeof window !== "undefined" && window?.addEventListener) {
843
852
  window.addEventListener("beforeunload", () => {
844
853
  this.flushEventQueue();
854
+ this.flushContextDependentEventQueue();
845
855
  });
846
856
  }
847
857
  if (this.offlineEnabled) {
@@ -890,6 +900,9 @@ var Schematic = class {
890
900
  const parsedResponse = CheckFlagResponseFromJSON(response);
891
901
  this.debug(`checkFlag result: ${key}`, parsedResponse);
892
902
  const result = CheckFlagReturnFromJSON(parsedResponse.data);
903
+ if (typeof result.featureUsageEvent === "string") {
904
+ this.updateFeatureUsageEventMap(result);
905
+ }
893
906
  this.submitFlagCheckEvent(key, result, context);
894
907
  return result.value;
895
908
  }).catch((error) => {
@@ -1009,6 +1022,9 @@ var Schematic = class {
1009
1022
  const data = CheckFlagResponseFromJSON(responseJson);
1010
1023
  this.debug(`fallbackToRest result: ${key}`, data);
1011
1024
  const result = CheckFlagReturnFromJSON(data.data);
1025
+ if (typeof result.featureUsageEvent === "string") {
1026
+ this.updateFeatureUsageEventMap(result);
1027
+ }
1012
1028
  this.submitFlagCheckEvent(key, result, context);
1013
1029
  return result.value;
1014
1030
  } catch (error) {
@@ -1092,14 +1108,12 @@ var Schematic = class {
1092
1108
  * In offline mode, this will just set the context locally without connecting.
1093
1109
  */
1094
1110
  setContext = async (context) => {
1095
- if (this.isOffline()) {
1111
+ if (this.isOffline() || !this.useWebSocket) {
1096
1112
  this.context = context;
1113
+ this.flushContextDependentEventQueue();
1097
1114
  this.setIsPending(false);
1098
1115
  return Promise.resolve();
1099
1116
  }
1100
- if (!this.useWebSocket) {
1101
- return Promise.resolve();
1102
- }
1103
1117
  try {
1104
1118
  this.setIsPending(true);
1105
1119
  if (!this.conn) {
@@ -1115,21 +1129,123 @@ var Schematic = class {
1115
1129
  /**
1116
1130
  * Send a track event
1117
1131
  * Track usage for a company and/or user.
1132
+ * Optimistically updates feature usage flags if tracking a featureUsageEvent.
1118
1133
  */
1119
1134
  track = (body) => {
1120
- const { company, user, event, traits } = body;
1135
+ const { company, user, event, traits, quantity = 1 } = body;
1136
+ if (!this.hasContext(company, user)) {
1137
+ this.debug(`track: queuing event "${event}" until context is available`);
1138
+ const queuedEvent = {
1139
+ api_key: this.apiKey,
1140
+ body: {
1141
+ company,
1142
+ event,
1143
+ traits: traits ?? {},
1144
+ user,
1145
+ quantity
1146
+ },
1147
+ sent_at: (/* @__PURE__ */ new Date()).toISOString(),
1148
+ tracker_event_id: v4_default(),
1149
+ tracker_user_id: this.getAnonymousId(),
1150
+ type: "track"
1151
+ };
1152
+ this.contextDependentEventQueue.push(queuedEvent);
1153
+ return Promise.resolve();
1154
+ }
1121
1155
  const trackData = {
1122
1156
  company: company ?? this.context.company,
1123
1157
  event,
1124
1158
  traits: traits ?? {},
1125
- user: user ?? this.context.user
1159
+ user: user ?? this.context.user,
1160
+ quantity
1126
1161
  };
1127
1162
  this.debug(`track:`, trackData);
1163
+ if (event in this.featureUsageEventMap) {
1164
+ this.optimisticallyUpdateFeatureUsage(event, quantity);
1165
+ }
1128
1166
  return this.handleEvent("track", trackData);
1129
1167
  };
1168
+ /**
1169
+ * Optimistically update feature usage flags associated with a tracked event
1170
+ * This updates flags in memory with updated usage counts and value/featureUsageExceeded flags
1171
+ * before the network request completes
1172
+ */
1173
+ optimisticallyUpdateFeatureUsage = (eventName, quantity = 1) => {
1174
+ const flagsForEvent = this.featureUsageEventMap[eventName];
1175
+ if (flagsForEvent === void 0 || flagsForEvent === null) return;
1176
+ this.debug(
1177
+ `Optimistically updating feature usage for event: ${eventName}`,
1178
+ { quantity }
1179
+ );
1180
+ Object.entries(flagsForEvent).forEach(([flagKey, check]) => {
1181
+ if (check === void 0) return;
1182
+ const updatedCheck = { ...check };
1183
+ if (typeof updatedCheck.featureUsage === "number") {
1184
+ updatedCheck.featureUsage += quantity;
1185
+ if (typeof updatedCheck.featureAllocation === "number") {
1186
+ const wasExceeded = updatedCheck.featureUsageExceeded === true;
1187
+ const nowExceeded = updatedCheck.featureUsage >= updatedCheck.featureAllocation;
1188
+ if (nowExceeded !== wasExceeded) {
1189
+ updatedCheck.featureUsageExceeded = nowExceeded;
1190
+ if (nowExceeded) {
1191
+ updatedCheck.value = false;
1192
+ }
1193
+ this.debug(`Usage limit status changed for flag: ${flagKey}`, {
1194
+ was: wasExceeded ? "exceeded" : "within limits",
1195
+ now: nowExceeded ? "exceeded" : "within limits",
1196
+ featureUsage: updatedCheck.featureUsage,
1197
+ featureAllocation: updatedCheck.featureAllocation,
1198
+ value: updatedCheck.value
1199
+ });
1200
+ }
1201
+ }
1202
+ if (this.featureUsageEventMap[eventName] !== void 0) {
1203
+ this.featureUsageEventMap[eventName][flagKey] = updatedCheck;
1204
+ }
1205
+ const contextStr = contextString(this.context);
1206
+ if (this.checks[contextStr] !== void 0 && this.checks[contextStr] !== null) {
1207
+ this.checks[contextStr][flagKey] = updatedCheck;
1208
+ }
1209
+ this.notifyFlagCheckListeners(flagKey, updatedCheck);
1210
+ this.notifyFlagValueListeners(flagKey, updatedCheck.value);
1211
+ }
1212
+ });
1213
+ };
1130
1214
  /**
1131
1215
  * Event processing
1132
1216
  */
1217
+ hasContext = (company, user) => {
1218
+ const hasProvidedContext = company !== void 0 && company !== null && Object.keys(company).length > 0 || user !== void 0 && user !== null && Object.keys(user).length > 0;
1219
+ const hasInstanceContext = this.context.company !== void 0 && this.context.company !== null && Object.keys(this.context.company).length > 0 || this.context.user !== void 0 && this.context.user !== null && Object.keys(this.context.user).length > 0;
1220
+ return hasProvidedContext || hasInstanceContext;
1221
+ };
1222
+ flushContextDependentEventQueue = () => {
1223
+ this.debug(
1224
+ `flushing ${this.contextDependentEventQueue.length} context-dependent events`
1225
+ );
1226
+ while (this.contextDependentEventQueue.length > 0) {
1227
+ const event = this.contextDependentEventQueue.shift();
1228
+ if (event) {
1229
+ if (event.type === "track" && typeof event.body === "object" && event.body !== null) {
1230
+ const trackBody = event.body;
1231
+ const updatedBody = {
1232
+ ...trackBody,
1233
+ company: trackBody.company ?? this.context.company,
1234
+ user: trackBody.user ?? this.context.user
1235
+ };
1236
+ const updatedEvent = {
1237
+ ...event,
1238
+ body: updatedBody,
1239
+ sent_at: (/* @__PURE__ */ new Date()).toISOString()
1240
+ // Update timestamp to actual send time
1241
+ };
1242
+ this.sendEvent(updatedEvent);
1243
+ } else {
1244
+ this.sendEvent(event);
1245
+ }
1246
+ }
1247
+ }
1248
+ };
1133
1249
  flushEventQueue = () => {
1134
1250
  while (this.eventQueue.length > 0) {
1135
1251
  const event = this.eventQueue.shift();
@@ -1269,18 +1385,26 @@ var Schematic = class {
1269
1385
  }
1270
1386
  (message.flags ?? []).forEach((flag) => {
1271
1387
  const flagCheck = CheckFlagReturnFromJSON(flag);
1272
- this.checks[contextString(context)][flagCheck.flag] = flagCheck;
1388
+ const contextStr = contextString(context);
1389
+ if (this.checks[contextStr] === void 0) {
1390
+ this.checks[contextStr] = {};
1391
+ }
1392
+ this.checks[contextStr][flagCheck.flag] = flagCheck;
1273
1393
  this.debug(`WebSocket flag update:`, {
1274
1394
  flag: flagCheck.flag,
1275
1395
  value: flagCheck.value,
1276
1396
  flagCheck
1277
1397
  });
1398
+ if (typeof flagCheck.featureUsageEvent === "string") {
1399
+ this.updateFeatureUsageEventMap(flagCheck);
1400
+ }
1278
1401
  if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
1279
1402
  this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
1280
1403
  }
1281
1404
  this.notifyFlagCheckListeners(flag.flag, flagCheck);
1282
1405
  this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1283
1406
  });
1407
+ this.flushContextDependentEventQueue();
1284
1408
  this.setIsPending(false);
1285
1409
  if (!resolved) {
1286
1410
  resolved = true;
@@ -1367,8 +1491,26 @@ var Schematic = class {
1367
1491
  check
1368
1492
  );
1369
1493
  }
1494
+ if (typeof check.featureUsageEvent === "string") {
1495
+ this.updateFeatureUsageEventMap(check);
1496
+ }
1370
1497
  listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
1371
1498
  };
1499
+ /** Add or update a CheckFlagReturn in the featureUsageEventMap */
1500
+ updateFeatureUsageEventMap = (check) => {
1501
+ if (typeof check.featureUsageEvent !== "string") return;
1502
+ const eventName = check.featureUsageEvent;
1503
+ if (this.featureUsageEventMap[eventName] === void 0 || this.featureUsageEventMap[eventName] === null) {
1504
+ this.featureUsageEventMap[eventName] = {};
1505
+ }
1506
+ if (this.featureUsageEventMap[eventName] !== void 0) {
1507
+ this.featureUsageEventMap[eventName][check.flag] = check;
1508
+ }
1509
+ this.debug(
1510
+ `Updated featureUsageEventMap for event: ${eventName}, flag: ${check.flag}`,
1511
+ check
1512
+ );
1513
+ };
1372
1514
  notifyFlagValueListeners = (flagKey, value) => {
1373
1515
  const listeners = this.flagValueListeners?.[flagKey] ?? [];
1374
1516
  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.5",
4
4
  "main": "dist/schematic.cjs.js",
5
5
  "module": "dist/schematic.esm.js",
6
6
  "types": "dist/schematic.d.ts",
@@ -26,30 +26,34 @@
26
26
  "format": "prettier --write src/*.ts",
27
27
  "lint": "eslint src --report-unused-disable-directives --fix",
28
28
  "openapi": "rm -rf src/types/api/ && npx openapi-generator-cli generate -c openapi-config.yaml --global-property models=\"EventBody:EventBodyFlagCheck:EventBodyIdentify:EventBodyIdentifyCompany:EventBodyTrack:CheckFlagResponse:CheckFlagResponseData:CheckFlagsResponse:CheckFlagsResponseData\",supportingFiles=runtime.ts && prettier --write \"src/types/api/**/*.{ts,tsx}\"",
29
- "test": "jest --config jest.config.js"
29
+ "prepare": "husky",
30
+ "test": "jest --config jest.config.js",
31
+ "tsc": "npx tsc"
30
32
  },
31
33
  "dependencies": {
32
34
  "cross-fetch": "^4.1.0",
33
- "uuid": "^11.0.5"
35
+ "uuid": "^13.0.0"
34
36
  },
35
37
  "devDependencies": {
36
- "@microsoft/api-extractor": "^7.49.2",
37
- "@openapitools/openapi-generator-cli": "^2.16.3",
38
- "@types/jest": "^29.5.14",
38
+ "@eslint/js": "^9.24.0",
39
+ "@microsoft/api-extractor": "^7.52.2",
40
+ "@openapitools/openapi-generator-cli": "^2.18.4",
41
+ "@types/jest": "^30.0.0",
39
42
  "@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",
43
+ "esbuild": "^0.25.2",
43
44
  "esbuild-jest": "^0.5.0",
44
- "eslint": "^8.57.1",
45
- "jest": "^29.7.0",
46
- "jest-environment-jsdom": "^29.7.0",
47
- "jest-esbuild": "^0.3.0",
45
+ "eslint": "^9.24.0",
46
+ "globals": "^16.0.0",
47
+ "husky": "^9.1.7",
48
+ "jest": "^30.0.0",
49
+ "jest-environment-jsdom": "^30.0.0",
50
+ "jest-esbuild": "^0.4.0",
48
51
  "jest-fetch-mock": "^3.0.3",
49
52
  "mock-socket": "^9.3.1",
50
- "prettier": "^3.4.2",
51
- "ts-jest": "^29.2.5",
52
- "typescript": "^5.7.3"
53
+ "prettier": "^3.6.2",
54
+ "ts-jest": "^29.3.0",
55
+ "typescript": "^5.9.2",
56
+ "typescript-eslint": "^8.29.1"
53
57
  },
54
58
  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
55
59
  }