@kelviq/react-sdk 0.0.1-beta → 2.0.1-beta

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.
@@ -1 +1 @@
1
- (function(c,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],l):(c=typeof globalThis<"u"?globalThis:c||self,l(c.KelviqReactSDK={},c.jsxRuntime,c.React))})(this,function(c,l,q){"use strict";const P="GET";function I(e){const{method:i=P,headers:t={},body:r,timeout:y=5e3,maxRetries:o=3,backoffBaseDelay:L=1e3,queryParams:v,accessToken:K}=e;let{url:E}=e,f=0;return new Promise((b,g)=>{if(v){const s=new URLSearchParams;for(const A in v)v[A]!==void 0&&s.append(A,String(v[A]));s.toString()&&(E+=(E.includes("?")?"&":"?")+s.toString())}function a(){const s=new XMLHttpRequest;s.open(i,E,!0),s.timeout=y;const A={Accept:"application/json","X-Requested-With":"XMLHttpRequest",...t};i!=="GET"&&r&&typeof r=="object"&&!(r instanceof FormData)&&(A["Content-Type"]||(A["Content-Type"]="application/json")),K&&(A.Authorization=`Bearer ${K}`);for(const[u,h]of Object.entries(A))s.setRequestHeader(u,h);s.onload=function(){const{status:u,statusText:h,responseText:T}=s;if(u>=200&&u<300)try{const w=T?JSON.parse(T):{};b({status:u,statusText:h,data:w})}catch(w){const C=w instanceof Error?w:new Error(String(w));console.error(`Kelviq SDK (apiRequest): Invalid JSON response for URL ${E}. Error: ${C.message}. Response: ${T}`),g(new Error(`Invalid JSON response: ${C.message}`))}else S(`Request to ${E} failed with status ${u} ${h}. Response: ${T}`)},s.onerror=()=>S(`Network error for URL ${E}. The request could not be completed.`),s.ontimeout=()=>S(`Request to ${E} timed out.`);function S(u){if(f<o){f++;const h=Math.min(L*Math.pow(2,f-1),3e4);console.warn(`Kelviq SDK (apiRequest): Retrying request to ${E}. Attempt ${f}/${o}. Error: ${u}. Retrying in ${h}ms.`),setTimeout(a,h)}else g(new Error(`${u} (Max retries ${o} reached)`))}try{let u=r;r&&typeof r=="object"&&!(r instanceof FormData)&&A["Content-Type"]==="application/json"&&(u=JSON.stringify(r)),s.send(u)}catch(u){const h=u instanceof Error?u:new Error(String(u));console.error(`Kelviq SDK (apiRequest): Error sending request to ${E}.`,h),g(new Error(`Failed to send request: ${h.message}`))}}a()})}function _(e){const i={};return!e||!Array.isArray(e.entitlements)?(console.warn("Kelviq SDK: Invalid or empty entitlements array in API response.",e),i):(e.entitlements.forEach(t=>{if(!t||typeof t.featureId!="string"||typeof t.featureType!="string"){console.warn("Kelviq SDK: Skipping invalid raw entitlement object:",t);return}let r=null;const y={featureKey:t.featureId,hasAccess:t.hasAccess};switch(t.featureType){case"BOOLEAN":r={...y,type:"boolean"};break;case"CUSTOMIZABLE":{const o=t;r={...y,type:"customizable",configuration:typeof o.value=="number"?o.value:null};break}case"METER":{const o=t,L=typeof o.usageLimit=="number"?o.usageLimit:null,v=typeof o.currentUsage=="number"?o.currentUsage:0;r={...y,type:"metered",limit:L,used:v,remaining:L!==null?L-v:null,resetAt:o.resetAt,hardLimit:o.hardLimit};break}default:{const o=t;console.warn(`Kelviq SDK: Encountered unknown or unhandled entitlement featureType: '${String(o.featureType)}' for feature '${String(o.featureId)}'. This entitlement will be ignored.`);break}}r&&(i[r.featureKey]=r)}),i)}const D={allEntitlements:{data:null,isLoading:!0,error:null},refreshAllEntitlements:()=>(console.warn("refreshAllEntitlements called on default KelviqContext. Ensure KelviqProvider is properly set up."),Promise.resolve()),getEntitlement:()=>({data:null,isLoading:!1,error:new Error("KelviqContext not fully initialized or entitlements not fetched.")}),hasAccess:()=>{},isLoading:!0,error:null,apiUrl:"",environment:"production",entitlementsPath:"",customerId:""},U=q.createContext(D),R="https://edge.api.kelviq.com/api/v1/",O="https://edge.sandboxapi.kelviq.com/api/v1/",B="entitlements/",x=({children:e,apiUrl:i,entitlementsPath:t=B,customerId:r,environment:y="production",accessToken:o,config:L={}})=>{const{onError:v,maxRetries:K,timeout:E,backoffBaseDelay:f,fetchEntitlementsOnMount:b=!0}=L,g=i||(y==="sandbox"?O:R),[a,s]=q.useState({data:null,isLoading:!!g&&b,error:null}),[A,S]=q.useState(!!g&&b),[u,h]=q.useState(null),T=q.useCallback((p,m,n)=>{const d=new Error(`Kelviq SDK (${m}): ${p}`);console.error(d.message,n||""),h(k=>k||d),v&&v(d)},[v]),w=q.useCallback(()=>{if(!g){const n="API URL not configured. Cannot fetch entitlements.";return s({data:null,isLoading:!1,error:new Error(n)}),T(n,"fetchAllEntitlements"),S(!1),Promise.reject(new Error(n))}if(!r){const n="CustomerId must be provided as props to KelviqProvider.";return s(()=>({data:null,isLoading:!1,error:new Error(n)})),T(n,"fetchAllEntitlements"),S(!1),Promise.reject(new Error(n))}s(n=>({...n,isLoading:!0,error:null})),S(!0),h(null);const p=`${g.replace(/\/$/,"")}/${t.replace(/^\//,"")}`;return I({url:p,method:"GET",timeout:E,maxRetries:K,backoffBaseDelay:f,accessToken:o,queryParams:{customer_id:r}}).then(n=>{if(n&&n.data&&Array.isArray(n.data.entitlements)){const d=_(n.data);s({data:d,isLoading:!1,error:null})}else{const d=new Error("Received empty, malformed, or invalid data structure from entitlements API.");throw s({data:null,isLoading:!1,error:d}),T(d.message,"fetchAllEntitlements",n),d}}).catch(n=>{const d=n instanceof Error?n:new Error(String(n));return s(k=>({...k,isLoading:!1,error:d})),T(d.message,"fetchAllEntitlements",n),Promise.reject(d)}).finally(()=>{S(!1)})},[g,r,t,E,K,f,o,T]);q.useEffect(()=>{let p=!0;if(!g){if(p){const m=new Error("KelviqProvider: `apiBaseUrl` must be provided in config.");h(m),S(!1),s(n=>({...n,isLoading:!1,error:m})),L.onError&&L.onError(m)}return}return b?w().catch(m=>{p&&A&&S(!1),console.error("Kelviq SDK: Initial entitlement fetch failed.",m)}):p&&(s(m=>({...m,isLoading:!1,data:null,error:null})),S(!1)),()=>{p=!1}},[g,b,L.onError]);const C=q.useCallback((p,m)=>{if(a.isLoading)return{data:null,isLoading:!0,error:null};if(a.error&&!a.data)return{data:null,isLoading:!1,error:a.error};if(a.data){const d=a.data[p];return d?d.type===m?{data:d,isLoading:!1,error:null}:{data:null,isLoading:!1,error:new Error(`Entitlement type mismatch for ${p}. Expected ${m}, got ${d.type}`)}:{data:null,isLoading:!1,error:null}}const n=g?`Entitlements not yet fetched for ${p}. Call refreshAllEntitlements or ensure fetchEntitlementsOnMount is true.`:"API URL not configured. Check KelviqProvider configuration.";return{data:null,isLoading:!1,error:new Error(n)}},[a,g]),F=q.useCallback(p=>{if(a.isLoading&&!a.data||a.error&&!a.data||!a.data)return;const m=a.data[p];return m?m.hasAccess:!1},[a]),J=q.useMemo(()=>({allEntitlements:a,refreshAllEntitlements:w,getEntitlement:C,hasAccess:F,isLoading:A,error:u,environment:y,apiUrl:g,entitlementsPath:t,customerId:r}),[a,w,C,F,A,u,y,g,t,r]);return l.jsx(U.Provider,{value:J,children:e})},$=()=>{const e=q.useContext(U);if(e===void 0||e===D)throw new Error("useKelviq must be used within an initialized KelviqProvider.");return e},M=({featureKey:e,type:i,children:t,fallback:r=null,loadingComponent:y,condition:o})=>{const{getEntitlement:L,isLoading:v,error:K,allEntitlements:E}=$(),f=L(e,i);return f.isLoading||v&&!f.data&&!f.error&&!E.data&&!E.error?l.jsx(l.Fragment,{children:y!==void 0?y:r}):f.error||K&&!f.data?l.jsx(l.Fragment,{children:r}):!f.data||!f.data.hasAccess||o&&!o(f.data)?l.jsx(l.Fragment,{children:r}):typeof t=="function"?l.jsx(l.Fragment,{children:t(f.data)}):l.jsx(l.Fragment,{children:t})},N=({children:e,...i})=>l.jsx(M,{...i,type:"customizable",children:t=>t.hasAccess&&t.configuration!==null&&typeof t.configuration=="number"?e(t.configuration):l.jsx(l.Fragment,{children:i.fallback||null})}),j=e=>{const i=t=>t.hasAccess&&(t.remaining===null||t.remaining>0);return l.jsx(M,{...e,type:"metered",condition:e.condition||i})},W=e=>l.jsx(M,{...e,type:"boolean"}),G=()=>{const{allEntitlements:e}=$();return e},z=e=>{const{getEntitlement:i}=$();return i(e,"boolean")},H=e=>{const{getEntitlement:i}=$();return i(e,"customizable")},X=e=>{const{getEntitlement:i}=$();return i(e,"metered")};c.KelviqContext=U,c.KelviqProvider=x,c.ShowWhenBooleanEntitled=W,c.ShowWhenConfigEntitled=N,c.ShowWhenMeteredEntitled=j,c.defaultKelviqContextValue=D,c.useAllEntitlements=G,c.useBooleanEntitlement=z,c.useConfigEntitlement=H,c.useKelviq=$,c.useMeteredEntitlement=X,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})});
1
+ (function(d,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],g):(d=typeof globalThis<"u"?globalThis:d||self,g(d.KelviqReactSDK={},d.jsxRuntime,d.React))})(this,function(d,g,f){"use strict";const V="GET";function I(n){const{method:o=V,headers:i={},body:t,timeout:L=5e3,maxRetries:h=3,backoffBaseDelay:w=1e3,queryParams:S,accessToken:a}=n;let{url:E}=n,v=0;return new Promise((T,K)=>{if(S){const s=new URLSearchParams;for(const u in S)S[u]!==void 0&&s.append(u,String(S[u]));s.toString()&&(E+=(E.includes("?")?"&":"?")+s.toString())}function M(){const s=new XMLHttpRequest;s.open(o,E,!0),s.timeout=L;const u={Accept:"application/json","X-Requested-With":"XMLHttpRequest",...i};o!=="GET"&&t&&typeof t=="object"&&!(t instanceof FormData)&&(u["Content-Type"]||(u["Content-Type"]="application/json")),a&&(u.Authorization=`Bearer ${a}`);for(const[l,q]of Object.entries(u))s.setRequestHeader(l,q);s.onload=function(){const{status:l,statusText:q,responseText:P}=s;if(l>=200&&l<300)try{const y=P?JSON.parse(P):{};T({status:l,statusText:q,data:y})}catch(y){const D=y instanceof Error?y:new Error(String(y));console.error(`Kelviq SDK (apiRequest): Invalid JSON response for URL ${E}. Error: ${D.message}. Response: ${P}`),K(new Error(`Invalid JSON response: ${D.message}`))}else A(`Request to ${E} failed with status ${l} ${q}. Response: ${P}`)},s.onerror=()=>A(`Network error for URL ${E}. The request could not be completed.`),s.ontimeout=()=>A(`Request to ${E} timed out.`);function A(l){if(v<h){v++;const q=Math.min(w*Math.pow(2,v-1),3e4);console.warn(`Kelviq SDK (apiRequest): Retrying request to ${E}. Attempt ${v}/${h}. Error: ${l}. Retrying in ${q}ms.`),setTimeout(M,q)}else K(new Error(`${l} (Max retries ${h} reached)`))}try{let l=t;t&&typeof t=="object"&&!(t instanceof FormData)&&u["Content-Type"]==="application/json"&&(l=JSON.stringify(t)),s.send(l)}catch(l){const q=l instanceof Error?l:new Error(String(l));console.error(`Kelviq SDK (apiRequest): Error sending request to ${E}.`,q),K(new Error(`Failed to send request: ${q.message}`))}}M()})}function Y(n,o){const i=o[0].featureType;if(i==="METER"){let t=0,L=0,h=!1;for(const S of o){const a=S,E=typeof a.usageLimit=="number"?a.usageLimit:null,v=typeof a.currentUsage=="number"?a.currentUsage:0;E===null&&(t=null),t!==null&&(t+=E),L+=v,a.hardLimit&&(h=!0)}const w=t!==null?t-L:null;return{featureId:n,featureType:i,hasAccess:w===null||w>0,currentUsage:L,usageLimit:t,remaining:w,hardLimit:h,items:o}}if(i==="CUSTOMIZABLE"){const t=o[0];return{featureId:n,featureType:i,hasAccess:o.some(L=>L.hasAccess),currentUsage:typeof t.currentUsage=="number"?t.currentUsage:0,usageLimit:typeof t.usageLimit=="number"?t.usageLimit:null,remaining:typeof t.remaining=="number"?t.remaining:null,hardLimit:t.hardLimit===!0,items:o}}return{featureId:n,featureType:i,hasAccess:o.some(t=>t.hasAccess),currentUsage:0,usageLimit:null,remaining:null,hardLimit:!1,items:o}}function Z(n){const o={};if(!n||!Array.isArray(n.entitlements))return console.warn("Kelviq SDK: Invalid or empty entitlements array in API response.",n),o;const i={};for(const t of n.entitlements){if(!t||typeof t.featureId!="string"||typeof t.featureType!="string"){console.warn("Kelviq SDK: Skipping invalid raw entitlement object:",t);continue}i[t.featureId]||(i[t.featureId]=[]),i[t.featureId].push(t)}for(const t in i)o[t]=Y(t,i[t]);return o}const R={allEntitlements:{data:null,isLoading:!0,error:null},refreshAllEntitlements:()=>(console.warn("refreshAllEntitlements called on default KelviqContext. Ensure KelviqProvider is properly set up."),Promise.resolve()),getEntitlements:()=>({}),getEntitlement:()=>null,getRawEntitlements:()=>null,getRawEntitlement:()=>null,hasAccess:()=>!1,updateEntitlement:()=>{console.warn("updateEntitlement called on default KelviqContext. Ensure KelviqProvider is properly set up.")},subscriptions:{data:null,isLoading:!1,error:null},refreshSubscriptions:()=>(console.warn("refreshSubscriptions called on default KelviqContext. Ensure KelviqProvider is properly set up."),Promise.resolve()),customer:{data:null,isLoading:!1,error:null},refreshCustomer:()=>(console.warn("refreshCustomer called on default KelviqContext. Ensure KelviqProvider is properly set up."),Promise.resolve()),isLoading:!0,error:null,apiUrl:"",environment:"production",entitlementsPath:"",customerId:""},F=f.createContext(R),Q="https://edge.api.kelviq.com/api/v1/",ee="https://edge.sandboxapi.kelviq.com/api/v1/",te="entitlements/",re=({children:n,apiUrl:o,entitlementsPath:i=te,customerId:t,environment:L="production",accessToken:h,config:w={}})=>{const{onError:S,maxRetries:a,timeout:E,backoffBaseDelay:v,fetchEntitlementsOnMount:T=!0,fetchSubscriptionsOnMount:K=!1,fetchCustomerOnMount:M=!1}=w,s=o||(L==="sandbox"?ee:Q),[u,A]=f.useState({data:null,isLoading:!!s&&T,error:null}),[l,q]=f.useState(null),[P,y]=f.useState({data:null,isLoading:!!s&&K,error:null}),[D,$]=f.useState({data:null,isLoading:!!s&&M,error:null}),[k,U]=f.useState(!!s&&T),[N,O]=f.useState(null),p=f.useCallback((c,e,r)=>{const m=new Error(`Kelviq SDK (${e}): ${c}`);console.error(m.message,r||""),O(b=>b||m),S&&S(m)},[S]),j=f.useCallback(()=>{if(!s){const r="API URL not configured. Cannot fetch entitlements.";return A({data:null,isLoading:!1,error:new Error(r)}),p(r,"fetchAllEntitlements"),U(!1),Promise.reject(new Error(r))}if(!t){const r="CustomerId must be provided as props to KelviqProvider.";return A(()=>({data:null,isLoading:!1,error:new Error(r)})),p(r,"fetchAllEntitlements"),U(!1),Promise.reject(new Error(r))}A(r=>({...r,isLoading:!0,error:null})),U(!0),O(null);const c=`${s.replace(/\/$/,"")}/${i.replace(/^\//,"")}`;return I({url:c,method:"GET",timeout:E,maxRetries:a,backoffBaseDelay:v,accessToken:h,queryParams:{customer_id:t}}).then(r=>{if(r&&r.data&&Array.isArray(r.data.entitlements)){q(r.data);const m=Z(r.data);A({data:m,isLoading:!1,error:null})}else{const m=new Error("Received empty, malformed, or invalid data structure from entitlements API.");throw A({data:null,isLoading:!1,error:m}),p(m.message,"fetchAllEntitlements",r),m}}).catch(r=>{const m=r instanceof Error?r:new Error(String(r));return A(b=>({...b,isLoading:!1,error:m})),p(m.message,"fetchAllEntitlements",r),Promise.reject(m)}).finally(()=>{U(!1)})},[s,t,i,E,a,v,h,p]),B=f.useCallback(()=>{if(!s){const e="API URL not configured. Cannot fetch subscriptions.";return y({data:null,isLoading:!1,error:new Error(e)}),p(e,"fetchSubscriptions"),Promise.reject(new Error(e))}if(!t){const e="CustomerId must be provided as props to KelviqProvider.";return y({data:null,isLoading:!1,error:new Error(e)}),p(e,"fetchSubscriptions"),Promise.reject(new Error(e))}y(e=>({...e,isLoading:!0,error:null}));const c=`${s.replace(/\/$/,"")}/subscriptions/`;return I({url:c,method:"GET",timeout:E,maxRetries:a,backoffBaseDelay:v,accessToken:h,queryParams:{customer_id:t}}).then(e=>{if(e&&e.data&&Array.isArray(e.data.results))y({data:e.data.results,isLoading:!1,error:null});else{const r=new Error("Received empty, malformed, or invalid data structure from subscriptions API.");throw y({data:null,isLoading:!1,error:r}),p(r.message,"fetchSubscriptions",e),r}}).catch(e=>{const r=e instanceof Error?e:new Error(String(e));return y(m=>({...m,isLoading:!1,error:r})),p(r.message,"fetchSubscriptions",e),Promise.reject(r)})},[s,t,E,a,v,h,p]),x=f.useCallback(()=>{if(!s){const e="API URL not configured. Cannot fetch customer.";return $({data:null,isLoading:!1,error:new Error(e)}),p(e,"fetchCustomer"),Promise.reject(new Error(e))}if(!t){const e="CustomerId must be provided as props to KelviqProvider.";return $({data:null,isLoading:!1,error:new Error(e)}),p(e,"fetchCustomer"),Promise.reject(new Error(e))}$(e=>({...e,isLoading:!0,error:null}));const c=`${s.replace(/\/$/,"")}/customers/${t}/`;return I({url:c,method:"GET",timeout:E,maxRetries:a,backoffBaseDelay:v,accessToken:h}).then(e=>{if(e&&e.data)$({data:e.data,isLoading:!1,error:null});else{const r=new Error("Received empty or invalid data from customer API.");throw $({data:null,isLoading:!1,error:r}),p(r.message,"fetchCustomer",e),r}}).catch(e=>{const r=e instanceof Error?e:new Error(String(e));return $(m=>({...m,isLoading:!1,error:r})),p(r.message,"fetchCustomer",e),Promise.reject(r)})},[s,t,E,a,v,h,p]);f.useEffect(()=>{let c=!0;if(!s){if(c){const e=new Error("KelviqProvider: `apiBaseUrl` must be provided in config.");O(e),U(!1),A(r=>({...r,isLoading:!1,error:e})),w.onError&&w.onError(e)}return}return T?j().catch(e=>{c&&k&&U(!1),console.error("Kelviq SDK: Initial entitlement fetch failed.",e)}):c&&(A(e=>({...e,isLoading:!1,data:null,error:null})),U(!1)),K&&B().catch(e=>{console.error("Kelviq SDK: Initial subscriptions fetch failed.",e)}),M&&x().catch(e=>{console.error("Kelviq SDK: Initial customer fetch failed.",e)}),()=>{c=!1}},[s,T,K,M,w.onError]);const W=f.useCallback(()=>u.data?u.data:{},[u.data]),G=f.useCallback(()=>l?{customerId:l.customerId,entitlements:l.entitlements}:null,[l]),H=f.useCallback(c=>l?{customerId:l.customerId,entitlements:l.entitlements.filter(e=>e.featureId===c)}:null,[l]),z=f.useCallback(c=>u.data?u.data[c]??null:null,[u.data]),X=f.useCallback((c,e)=>{A(r=>{if(!r.data)return r;const m=r.data[c];if(!m)return r;const b={...m,...e};return("usageLimit"in e||"currentUsage"in e)&&(b.remaining=b.usageLimit!==null?b.usageLimit-b.currentUsage:null),{...r,data:{...r.data,[c]:b}}})},[]),J=f.useCallback(c=>{if(!u.data)return!1;const e=u.data[c];return e?e.hasAccess:!1},[u.data]),fe=f.useMemo(()=>({allEntitlements:u,refreshAllEntitlements:j,getEntitlements:W,getEntitlement:z,getRawEntitlements:G,getRawEntitlement:H,hasAccess:J,updateEntitlement:X,subscriptions:P,refreshSubscriptions:B,customer:D,refreshCustomer:x,isLoading:k,error:N,environment:L,apiUrl:s,entitlementsPath:i,customerId:t}),[u,j,W,z,G,H,J,X,P,B,D,x,k,N,L,s,i,t]);return g.jsx(F.Provider,{value:fe,children:n})},C=()=>{const n=f.useContext(F);if(n===void 0||n===R)throw new Error("useKelviq must be used within an initialized KelviqProvider.");return n},_=({featureId:n,children:o,fallback:i=null,loadingComponent:t,condition:L})=>{const{getEntitlement:h,isLoading:w,error:S}=C(),a=h(n);return w&&!a?g.jsx(g.Fragment,{children:t!==void 0?t:i}):S&&!a?g.jsx(g.Fragment,{children:i}):!a||!a.hasAccess||L&&!L(a)?g.jsx(g.Fragment,{children:i}):typeof o=="function"?g.jsx(g.Fragment,{children:o(a)}):g.jsx(g.Fragment,{children:o})},ne=n=>g.jsx(_,{...n}),se=n=>{const o=i=>i.hasAccess&&(i.remaining===null||i.remaining>0);return g.jsx(_,{...n,condition:n.condition||o})},oe=n=>g.jsx(_,{...n}),ie=()=>{const{allEntitlements:n}=C();return n},le=n=>{const{getEntitlement:o}=C();return o(n)},ae=n=>{const{getEntitlement:o}=C();return o(n)},ue=n=>{const{getEntitlement:o}=C();return o(n)},ce=()=>{const{subscriptions:n}=C();return n},de=()=>{const{customer:n}=C();return n};d.KelviqContext=F,d.KelviqProvider=re,d.ShowWhenBooleanEntitled=oe,d.ShowWhenCustomizableEntitled=ne,d.ShowWhenMeteredEntitled=se,d.defaultKelviqContextValue=R,d.useAllEntitlements=ie,d.useBooleanEntitlement=le,d.useCustomer=de,d.useCustomizableEntitlement=ae,d.useKelviq=C,d.useMeteredEntitlement=ue,d.useSubscriptions=ce,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})});
@@ -2,7 +2,7 @@
2
2
  * Configuration for an API request.
3
3
  * @template ReqBody The type of the request body.
4
4
  */
5
- export interface ApiRequestConfig<ReqBody = any> {
5
+ export interface ApiRequestConfig<ReqBody = unknown> {
6
6
  /** The URL for the request. */
7
7
  url: string;
8
8
  /** HTTP method (GET, POST, PUT, DELETE, etc.). Defaults to GET. */
@@ -41,4 +41,4 @@ export interface ApiResponse<ResData> {
41
41
  * @param config Configuration object for the API request.
42
42
  * @returns A Promise that resolves to an ApiResponse<ResData> or rejects with an Error.
43
43
  */
44
- export declare function apiRequest<ResData = any, ReqBody = any>(config: ApiRequestConfig<ReqBody>): Promise<ApiResponse<ResData>>;
44
+ export declare function apiRequest<ResData = unknown, ReqBody = unknown>(config: ApiRequestConfig<ReqBody>): Promise<ApiResponse<ResData>>;
@@ -10,10 +10,18 @@ interface RawEntitlement {
10
10
  export interface RawBooleanEntitlementData extends RawEntitlement {
11
11
  featureType: 'BOOLEAN';
12
12
  }
13
- export interface RawConfigEntitlementData extends RawEntitlement {
13
+ export interface RawCustomizableEntitlementData extends RawEntitlement {
14
14
  featureType: 'CUSTOMIZABLE';
15
- /** The numeric configuration value associated with the feature, if applicable. */
16
- value?: number;
15
+ /** ISO 8601 string indicating when the usage counter resets, if applicable. */
16
+ resetAt?: string | null;
17
+ /** If true, the limit is a hard cap. If false, it might be a soft limit. */
18
+ hardLimit?: boolean;
19
+ /** The configured usage limit (represents the customizable value, e.g. retention days). */
20
+ usageLimit: number | null;
21
+ /** The current usage count. */
22
+ currentUsage: number;
23
+ /** The remaining usage (usageLimit - currentUsage). */
24
+ remaining: number | null;
17
25
  }
18
26
  export interface RawMeteredEntitlementData extends RawEntitlement {
19
27
  featureType: 'METER';
@@ -25,15 +33,78 @@ export interface RawMeteredEntitlementData extends RawEntitlement {
25
33
  usageLimit: number | null;
26
34
  /** The current usage count. */
27
35
  currentUsage: number;
36
+ /** The remaining usage (usageLimit - currentUsage). */
37
+ remaining: number | null;
28
38
  }
29
39
  /** Union type for any raw entitlement object received from the API. */
30
- export type AnyRawEntitlementData = RawBooleanEntitlementData | RawConfigEntitlementData | RawMeteredEntitlementData;
40
+ export type AnyRawEntitlementData = RawBooleanEntitlementData | RawCustomizableEntitlementData | RawMeteredEntitlementData;
31
41
  /**
32
42
  * Represents the expected raw API response structure when fetching all entitlements.
33
- * Example: { "customer_id": "128", "entitlements": [...] }
43
+ * Example: { "customerId": "128", "entitlements": [...] }
34
44
  */
35
45
  export interface RawEntitlementsApiResponse {
36
- customer_id: string;
46
+ customerId: string;
37
47
  entitlements: AnyRawEntitlementData[];
38
48
  }
49
+ export interface RawSubscriptionFeature {
50
+ featureId: string;
51
+ featureType: string;
52
+ hasAccess: boolean;
53
+ usageLimit: number | null;
54
+ currentUsage: number;
55
+ remaining: number | null;
56
+ }
57
+ export interface RawSubscriptionPlan {
58
+ name: string;
59
+ identifier: string;
60
+ }
61
+ export interface RawSubscriptionProduct {
62
+ name: string;
63
+ identifier: string;
64
+ }
65
+ /** A single subscription object as returned by the subscriptions API. */
66
+ export interface RawSubscriptionData {
67
+ id: string;
68
+ externalSubscriptionId: string;
69
+ startDate: string;
70
+ endDate: string | null;
71
+ billingPeriodStartTime: string;
72
+ billingPeriodEndTime: string;
73
+ amount: string;
74
+ recurrence: string;
75
+ currency: string;
76
+ status: string;
77
+ product: RawSubscriptionProduct;
78
+ plan: RawSubscriptionPlan;
79
+ features: RawSubscriptionFeature[];
80
+ trialDaysRemaining: number | null;
81
+ customerId: string;
82
+ }
83
+ /** Paginated response from the subscriptions list endpoint. */
84
+ export interface RawSubscriptionsApiResponse {
85
+ count: number;
86
+ next: string | null;
87
+ previous: string | null;
88
+ results: RawSubscriptionData[];
89
+ }
90
+ export interface RawBillingAddress {
91
+ country: string;
92
+ line1: string;
93
+ line2: string;
94
+ postalCode: string;
95
+ city: string;
96
+ state: string;
97
+ }
98
+ /** Raw customer data as returned by the customer API. */
99
+ export interface RawCustomerApiResponse {
100
+ id: string;
101
+ customerId: string;
102
+ name: string;
103
+ email: string;
104
+ details: Record<string, unknown>;
105
+ metadata: Record<string, unknown>;
106
+ billingAddress: RawBillingAddress | null;
107
+ createdOn: string;
108
+ modifiedOn: string;
109
+ }
39
110
  export {};
@@ -1,3 +1,4 @@
1
+ import { AnyRawEntitlementData } from './api.types';
1
2
  /**
2
3
  * Represents the state of an asynchronous data fetching operation.
3
4
  * @template T The type of the data being fetched.
@@ -7,47 +8,44 @@ export interface AsyncState<T> {
7
8
  isLoading: boolean;
8
9
  error: Error | null;
9
10
  }
10
- /** Base interface for all processed entitlements within the SDK. */
11
+ /**
12
+ * A single aggregated entitlement for a feature.
13
+ * When the API returns multiple entries for the same featureId (e.g. from
14
+ * different subscriptions), the SDK groups them and exposes aggregated
15
+ * totals at the top level alongside the raw `items` array.
16
+ */
11
17
  export interface Entitlement {
12
- /** The unique key identifying the feature (mapped from `feature`). */
13
- featureKey: string;
14
- /** True if the user has access to this feature, false otherwise (mapped from `enabled`). */
18
+ /** The feature identifier (matches `featureId` from the API). */
19
+ featureId: string;
20
+ /** The type of the feature as returned by the API. */
21
+ featureType: 'METER' | 'BOOLEAN' | 'CUSTOMIZABLE';
22
+ /** True if remaining > 0 (for METER) or any boolean item is true. */
15
23
  hasAccess: boolean;
16
- /** The standardized type of the entitlement for SDK logic. */
17
- type: 'boolean' | 'customizable' | 'metered';
18
- }
19
- /** Represents a processed boolean entitlement. */
20
- export interface BooleanEntitlement extends Entitlement {
21
- type: 'boolean';
22
- }
23
- /** Represents a processed configuration entitlement. */
24
- export interface ConfigEntitlement extends Entitlement {
25
- type: 'customizable';
26
- /** The numeric configuration value. Null if not applicable or not provided. */
27
- configuration: number | null;
28
- }
29
- /** Represents a processed metered entitlement. */
30
- export interface MeteredEntitlement extends Entitlement {
31
- type: 'metered';
32
- /** The usage limit for the feature. Null if unlimited. */
33
- limit: number | null;
34
- /** The current usage count. */
35
- used: number;
36
- /** The remaining usage (limit - used). Null if limit is null. */
24
+ /** SUM of all items' currentUsage. 0 for non-metered features. */
25
+ currentUsage: number;
26
+ /** SUM of all items' usageLimit. Null for non-metered or unlimited features. */
27
+ usageLimit: number | null;
28
+ /** usageLimit - currentUsage. Null when usageLimit is null. */
37
29
  remaining: number | null;
38
- /** ISO 8601 string for when the meter resets. */
39
- resetAt?: string | null;
40
- /** Indicates if the limit is a hard cap. */
41
- hardLimit?: boolean;
30
+ /** True if at least one item has a hard limit. */
31
+ hardLimit: boolean;
32
+ /** The raw array of original API objects for this featureId. */
33
+ items: AnyRawEntitlementData[];
42
34
  }
43
- /** Union type for any processed entitlement object used within the SDK. */
44
- export type AnyEntitlement = BooleanEntitlement | ConfigEntitlement | MeteredEntitlement;
45
35
  /**
46
36
  * Type for the cached and processed entitlements data stored by the provider.
47
- * It's a map where keys are featureKeys and values are the processed entitlement objects,
48
- * allowing for efficient O(1) lookups.
37
+ * Keys are featureIds and values are the aggregated entitlement objects.
38
+ */
39
+ export type EntitlementMap = Record<string, Entitlement>;
40
+ /**
41
+ * The raw entitlements response mirroring the backend API structure.
42
+ * Contains the customerId and the un-aggregated array of entitlement items,
43
+ * keeping duplicate featureId buckets intact.
49
44
  */
50
- export type EntitlementMap = Record<string, AnyEntitlement>;
45
+ export interface EntitlementsResponse {
46
+ customerId: string;
47
+ entitlements: AnyRawEntitlementData[];
48
+ }
51
49
  /** Optional behavioral configuration options for the KelviqProvider. */
52
50
  export interface KelviqApiBehaviorOptions {
53
51
  /** Optional callback for handling errors that occur within the SDK during entitlement fetching. */
@@ -65,4 +63,14 @@ export interface KelviqApiBehaviorOptions {
65
63
  * If not provided, the `apiRequest` service's default will be used.
66
64
  */
67
65
  backoffBaseDelay?: number;
66
+ /**
67
+ * If true, fetches the customer's subscriptions when the provider mounts.
68
+ * Defaults to false. When enabled, data is available via `useSubscriptions()`.
69
+ */
70
+ fetchSubscriptionsOnMount?: boolean;
71
+ /**
72
+ * If true, fetches customer data when the provider mounts.
73
+ * Defaults to false. When enabled, data is available via `useCustomer()`.
74
+ */
75
+ fetchCustomerOnMount?: boolean;
68
76
  }
@@ -1,10 +1,10 @@
1
1
  import { RawEntitlementsApiResponse } from '../types/api.types';
2
2
  import { EntitlementMap } from '../types/sdk.types';
3
3
  /**
4
- * Transforms the raw API response for entitlements into a more usable
5
- * and structured format (EntitlementMap) for the SDK.
6
- * This function maps API field names to SDK-standard field names and calculates derived values.
4
+ * Transforms the raw API response for entitlements into an EntitlementMap.
5
+ * Groups entitlements by featureId, aggregates values across subscriptions,
6
+ * and exposes the raw API objects via the `items` array.
7
7
  * @param apiResponse The raw response from the entitlements API.
8
- * @returns An EntitlementMap: a map of feature keys to their processed entitlement objects.
8
+ * @returns An EntitlementMap keyed by featureId.
9
9
  */
10
10
  export declare function transformApiEntitlements(apiResponse: RawEntitlementsApiResponse): EntitlementMap;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kelviq/react-sdk",
3
3
  "private": false,
4
- "version": "0.0.1-beta",
4
+ "version": "2.0.1-beta",
5
5
  "type": "module",
6
6
  "main": "dist/kelviq-react-sdk.js",
7
7
  "types": "dist/index.d.ts",
@@ -22,7 +22,9 @@
22
22
  "precommit": "lint-staged",
23
23
  "commitlint": "commitlint --edit",
24
24
  "commit": "git-cz",
25
- "prepare": "husky install"
25
+ "prepare": "husky install",
26
+ "publish:beta": "bash scripts/publish.sh beta",
27
+ "publish:stable": "bash scripts/publish.sh stable"
26
28
  },
27
29
  "lint-staged": {
28
30
  "{src,test}/**/*.{ts,tsx}": [
@@ -1,14 +0,0 @@
1
- import { ReactNode } from 'react';
2
- import { ConfigEntitlement } from '../types/sdk.types';
3
- import { ShowWhenEntitledProps } from './ShowWhenEntitledInternal';
4
- /**
5
- * Conditionally renders children based on a configuration entitlement.
6
- * The children prop is a render function that receives the numeric configuration value.
7
- * @param featureKey The key of the configuration feature.
8
- * @param children A function that receives the numeric configuration and returns a ReactNode.
9
- * @param fallback Content to render if not entitled, loading (and no loadingComponent), error, or config is not a number.
10
- * @param loadingComponent Specific content to render while the entitlement is loading.
11
- */
12
- export declare const ShowWhenConfigEntitled: React.FC<Omit<ShowWhenEntitledProps<ConfigEntitlement>, 'condition' | 'type' | 'children'> & {
13
- children: (config: number) => ReactNode;
14
- }>;
@@ -1,8 +0,0 @@
1
- import { AsyncState } from '../types';
2
- import { ConfigEntitlement } from '../types/sdk.types';
3
- /**
4
- * Hook to get a specific configuration entitlement's state.
5
- * @param featureKey The key of the configuration feature.
6
- * @returns AsyncState containing the ConfigEntitlement (with numeric configuration) or null.
7
- */
8
- export declare const useConfigEntitlement: (featureKey: string) => AsyncState<ConfigEntitlement | null>;