@kelviq/js-sdk 1.0.0 → 2.0.0-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.
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
- ---
2
- title: "JavaScript SDK"
3
- description: "Documentation for the Kelviq JavaScript SDK"
4
- icon: "js"
5
- ---
1
+ # @kelviq/js-sdk
6
2
 
7
- The `@kelviq/js-sdk` is a lightweight, plain TypeScript library designed for interacting with the Kelviq entitlement service. It allows you to:
3
+ A lightweight TypeScript SDK for integrating with the Kelviq entitlement service. Easily check feature access, retrieve entitlement details, and manage client-side caching for your application.
8
4
 
9
- - Fetch all feature entitlements for a specific organization and customer.
10
- - Cache these entitlements client-side for efficient and repeated access.
11
- - Check if a user has access to a particular feature.
12
- - Retrieve specific entitlement details, including boolean flags, numeric configurations, and metered usage data.
13
- - Manually refresh entitlement data from the server.
5
+ ## Installation
14
6
 
15
- ### Docs - https://docs.kelviq.com/frontend-integration/js-sdk
7
+ ```bash
8
+ npm install @kelviq/js-sdk
9
+ ```
10
+
11
+ ## Documentation
12
+
13
+ For full usage instructions, API reference, and examples, visit the official docs:
14
+
15
+ **[https://docs.kelviq.com/frontend-integration/js-sdk](https://docs.kelviq.com/frontend-integration/js-sdk)**
package/dist/index.d.ts CHANGED
@@ -1,27 +1,30 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
- export interface Entitlement {
3
+ export interface RawEntitlement {
4
4
  featureId: string;
5
+ featureType: "BOOLEAN" | "CUSTOMIZABLE" | "METER";
5
6
  hasAccess: boolean;
6
- type: "boolean" | "customizable" | "metered";
7
- }
8
- export interface BooleanEntitlement extends Entitlement {
9
- type: "boolean";
7
+ resetAt?: string | null;
8
+ hardLimit?: boolean;
9
+ usageLimit?: number | null;
10
+ currentUsage?: number;
11
+ remaining?: number | null;
10
12
  }
11
- export interface ConfigEntitlement extends Entitlement {
12
- type: "customizable";
13
- configuration: number | null;
13
+ export interface RawEntitlementsApiResponse {
14
+ customer_id: string;
15
+ entitlements: RawEntitlement[];
14
16
  }
15
- export interface MeteredEntitlement extends Entitlement {
16
- type: "metered";
17
- limit: number | null;
18
- used: number;
17
+ export interface Entitlement {
18
+ featureId: string;
19
+ featureType: "METER" | "BOOLEAN" | "CUSTOMIZABLE";
20
+ hasAccess: boolean;
21
+ hardLimit: boolean;
22
+ currentUsage: number;
23
+ usageLimit: number | null;
19
24
  remaining: number | null;
20
- resetAt?: string | null;
21
- hardLimit?: boolean;
25
+ items: RawEntitlement[];
22
26
  }
23
- export type AnyEntitlement = BooleanEntitlement | ConfigEntitlement | MeteredEntitlement;
24
- export type EntitlementMap = Record<string, AnyEntitlement>;
27
+ export type EntitlementMap = Record<string, Entitlement>;
25
28
  /**
26
29
  * Configuration for an API request.
27
30
  * @template ReqBody The type of the request body.
@@ -95,53 +98,63 @@ declare function apiRequest<ResData = any, ReqBody = any>(config: ApiRequestConf
95
98
  declare class KelviqClient {
96
99
  private readonly options;
97
100
  private entitlementsCache;
101
+ private rawApiResponse;
98
102
  private isFetching;
103
+ private inFlightPromise;
104
+ private readyPromise;
99
105
  private lastFetchError;
100
106
  private readonly apiRequestService;
101
107
  /**
102
108
  * Creates an instance of KelviqClient.
103
- * Prefer using the `createKelviqClient` factory function for instantiation.
109
+ * Prefer using the `kelviqSDK` factory function for instantiation.
104
110
  * @internal
105
111
  * @param options Configuration options for the client.
106
112
  * @param customApiRequest Optional: A custom function to handle API requests, conforming to ApiRequestConfig and ApiResponse.
107
113
  */
108
114
  constructor(options: KelviqClientOptions, customApiRequest?: typeof apiRequest);
109
115
  /**
110
- * Fetches all entitlements from the API for the configured orgId and customerId,
116
+ * Fetches all entitlements from the API for the configured customerId,
111
117
  * processes them, and caches the result.
112
118
  * Subsequent calls will return cached data unless `forceRefresh` is true.
113
119
  *
114
120
  * @param forceRefresh - If true, fetches from the API even if data is already cached. Defaults to false.
115
- * @returns A promise that resolves to the map of entitlements (EntitlementMap).
121
+ * @returns A promise that resolves to the map of aggregated entitlements.
116
122
  * @throws Will throw an error if the API request fails after all retries.
117
123
  */
118
124
  fetchAllEntitlements(forceRefresh?: boolean): Promise<EntitlementMap>;
119
125
  /**
120
- * Retrieves a specific entitlement from the local cache.
121
- * Note: `fetchAllEntitlements()` must be called successfully at least once before using this method,
122
- * or the cache will be empty.
126
+ * Retrieves a specific aggregated entitlement from the local cache.
123
127
  *
124
- * @param featureId The unique key of the feature.
125
- * @param type The expected type of the entitlement ('boolean', 'config', 'metered').
126
- * @returns The processed entitlement object if found in the cache and the type matches, otherwise `null`.
127
- * Logs a warning if entitlements haven't been fetched or if a type mismatch occurs.
128
+ * @param featureId The unique identifier of the feature.
129
+ * @returns The aggregated Entitlement object if found, otherwise `null`.
128
130
  */
129
- getEntitlement<T extends AnyEntitlement>(featureId: string, type: T["type"]): T | null;
131
+ getEntitlement(featureId: string): Entitlement | null;
130
132
  /**
131
- * Checks if the user has access to a specific feature based on cached entitlements.
132
- * Note: `fetchAllEntitlements()` should be called successfully at least once before using this method.
133
+ * Returns all aggregated entitlements as a map keyed by featureId.
133
134
  *
134
- * @param featureId The key of the feature.
135
- * @returns `true` if access is granted, `false` if denied or the feature is not found in the cache.
136
- * Logs a warning and returns `false` if entitlements haven't been fetched.
135
+ * @returns Record of featureId to aggregated Entitlement, or `null` if not fetched.
137
136
  */
138
- hasAccess(featureId: string): boolean;
137
+ getEntitlements(): EntitlementMap | null;
138
+ /**
139
+ * Returns the raw API items for a specific featureId (un-aggregated).
140
+ *
141
+ * @param featureId The unique identifier of the feature.
142
+ * @returns Array of raw entitlement items for the given featureId, or `null` if not fetched.
143
+ */
144
+ getRawEntitlement(featureId: string): RawEntitlement[] | null;
145
+ /**
146
+ * Returns the full raw API response including the customer_id wrapper.
147
+ *
148
+ * @returns The raw API response `{ customer_id, entitlements }`, or `null` if not fetched.
149
+ */
150
+ getRawEntitlements(): RawEntitlementsApiResponse | null;
139
151
  /**
140
- * Returns the currently cached map of all entitlements.
141
- * `fetchAllEntitlements()` should be called successfully at least once.
142
- * @returns The EntitlementMap (a record of featureKey to entitlement data) or `null` if not fetched or an error occurred during the last fetch.
152
+ * Checks if the user has access to a specific feature based on cached entitlements.
153
+ *
154
+ * @param featureId The identifier of the feature.
155
+ * @returns `true` if access is granted, `false` otherwise.
143
156
  */
144
- getAllEntitlements(): EntitlementMap | null;
157
+ hasAccess(featureId: string): boolean;
145
158
  /**
146
159
  * Indicates if an entitlement fetch operation is currently in progress.
147
160
  * @returns `true` if a fetch is in progress, `false` otherwise.
@@ -152,9 +165,17 @@ declare class KelviqClient {
152
165
  * @returns An `Error` object if the last fetch failed, otherwise `null`.
153
166
  */
154
167
  getLastError(): Error | null;
168
+ /**
169
+ * Returns a promise that resolves when the initial fetch (triggered by `initializeAndFetch: true`) completes.
170
+ * If no initial fetch was triggered, resolves immediately.
171
+ */
172
+ ready(): Promise<void>;
173
+ /**
174
+ * @internal Sets the ready promise. Called by the factory when initializeAndFetch is true.
175
+ */
176
+ _setReadyPromise(promise: Promise<void>): void;
155
177
  /**
156
178
  * Clears the local entitlement cache and resets loading and error states.
157
- * After calling this, `fetchAllEntitlements()` will need to be called again to populate the cache.
158
179
  */
159
180
  clearCache(): void;
160
181
  }
@@ -165,8 +186,7 @@ declare class KelviqClient {
165
186
  * @param customApiRequest Optional: A custom function to handle API requests, useful for testing or custom HTTP clients.
166
187
  * @returns An instance of KelviqClient.
167
188
  */
168
- declare function kelviqSDK(// Renamed from createKelviqClient
169
- options: KelviqClientOptions, customApiRequest?: typeof apiRequest): KelviqClient;
189
+ declare function kelviqSDK(options: KelviqClientOptions, customApiRequest?: typeof apiRequest): KelviqClient;
170
190
 
171
191
  export {
172
192
  kelviqSDK as default,
@@ -1 +1 @@
1
- var kelviqSDK=function(){"use strict";var P=Object.defineProperty;var b=(d,m,y)=>m in d?P(d,m,{enumerable:!0,configurable:!0,writable:!0,value:y}):d[m]=y;var A=(d,m,y)=>b(d,typeof m!="symbol"?m+"":m,y);function d(s){const n={};return!s||!Array.isArray(s.entitlements)?(console.warn("[Kelviq SDK] transformApiEntitlements: Invalid or empty entitlements array in API response.",s),n):(s.entitlements.forEach(e=>{if(!e||typeof e.featureId!="string"||typeof e.featureType!="string"){console.warn("[Kelviq SDK] transformApiEntitlements: Skipping invalid raw entitlement object:",e);return}let t=null;const f={featureId:e.featureId,hasAccess:e.hasAccess};if(e.featureType==="BOOLEAN")t={...f,type:"boolean"};else if(e.featureType==="CUSTOMIZABLE"){const r=e;t={...f,type:"customizable",configuration:typeof r.value=="number"?r.value:null}}else if(e.featureType==="METER"){const r=e,E=typeof r.usageLimit=="number"?r.usageLimit:null,o=typeof r.currentUsage=="number"?r.currentUsage:0;t={...f,type:"metered",limit:E,used:o,remaining:E!==null?E-o:null,resetAt:r.resetAt,hardLimit:r.hardLimit}}else{const r=e;console.warn(`[Kelviq SDK] transformApiEntitlements: Encountered unknown or unhandled entitlement featureType: '${String(r.featureType)}' for feature '${String(r.feature)}'. This entitlement will be ignored.`)}t&&(n[t.featureId]=t)}),n)}const m="GET",y=5e3,R=3,$=1e3;function F(s){const{method:n=m,headers:e={},body:t,timeout:f=y,maxRetries:r=R,backoffBaseDelay:E=$,queryParams:o,accessToken:v}=s;let a=s.url,g=0;return new Promise((T,l)=>{if(o){const c=new URLSearchParams;for(const p in o)o[p]!==void 0&&c.append(p,String(o[p]));c.toString()&&(a=a+(a.includes("?")?"&":"?")+c.toString())}function u(){const c=new XMLHttpRequest;c.open(n,a,!0),c.timeout=f;const p={Accept:"application/json","X-Requested-With":"XMLHttpRequest",...e};n!=="GET"&&t&&typeof t=="object"&&!(t instanceof FormData)&&(p["Content-Type"]||(p["Content-Type"]="application/json")),v&&(p.Authorization=`Bearer ${v}`);for(const[i,h]of Object.entries(p))c.setRequestHeader(i,h);c.onload=function(){const{status:i,statusText:h,responseText:S}=c;if(i>=200&&i<300)try{const q=S?JSON.parse(S):{};T({status:i,statusText:h,data:q})}catch(q){const D=q instanceof Error?q:new Error(String(q));console.error(`Kelviq SDK (apiRequest): Invalid JSON response for URL ${a}. Error: ${D.message}. Response: ${S}`),l(new Error(`Invalid JSON response: ${D.message}`))}else K(`Request to ${a} failed with status ${i} ${h}. Response: ${S}`)},c.onerror=()=>K(`Network error for URL ${a}. The request could not be completed.`),c.ontimeout=()=>K(`Request to ${a} timed out.`);function K(i){if(g<r){g++;const h=Math.min(E*Math.pow(2,g-1),3e4);console.warn(`Kelviq SDK (apiRequest): Retrying request to ${a}. Attempt ${g}/${r}. Error: ${i}. Retrying in ${h}ms.`),setTimeout(u,h)}else l(new Error(`${i} (Max retries ${r} reached)`))}try{let i=t;t&&typeof t=="object"&&!(t instanceof FormData)&&p["Content-Type"]==="application/json"&&(i=JSON.stringify(t)),c.send(i)}catch(i){const h=i instanceof Error?i:new Error(String(i));console.error(`Kelviq SDK (apiRequest): Error sending request to ${a}.`,h),l(new Error(`Failed to send request: ${h.message}`))}}u()})}const L="https://edge.api.kelviq.com/api/v1/",w="https://edge.sandboxapi.kelviq.com/api/v1/",C="entitlements/";class I{constructor(n,e){A(this,"options");A(this,"entitlementsCache",null);A(this,"isFetching",!1);A(this,"lastFetchError",null);A(this,"apiRequestService");if(!n||!n.customerId)throw new Error("[Kelviq SDK] KelviqClient: CustomerId is required in options.");this.options={...n,apiConfig:n.apiConfig||{},entitlementsPath:n.entitlementsPath||C},this.apiRequestService=e||F}async fetchAllEntitlements(n=!1){if(this.isFetching)return Promise.reject(new Error("[Kelviq SDK] An entitlement fetch is already in progress."));if(this.entitlementsCache&&!n)return Promise.resolve(this.entitlementsCache);this.isFetching=!0,this.lastFetchError=null;const{apiUrl:e=this.options.environment==="sandbox"?w:L,entitlementsPath:t,customerId:f,accessToken:r,apiConfig:E,onError:o}=this.options,v=e.replace(/\/$/,""),a=t.replace(/^\//,""),g=`${v}/${a}`,T={customer_id:f};try{const l=await this.apiRequestService({url:g,method:"GET",queryParams:T,accessToken:r,...E||{}});if(l&&l.data&&Array.isArray(l.data.entitlements))return this.entitlementsCache=d(l.data),this.isFetching=!1,this.entitlementsCache;{const u=new Error("[Kelviq SDK] Received empty, malformed, or invalid data structure from entitlements API.");throw this.lastFetchError=u,this.isFetching=!1,this.entitlementsCache=null,o&&o(u),u}}catch(l){const u=l instanceof Error?l:new Error(String(l));throw this.lastFetchError=u,this.isFetching=!1,this.entitlementsCache=null,o&&o(u),u}}getEntitlement(n,e){if(!this.entitlementsCache)return console.warn(`[Kelviq SDK] getEntitlement: Entitlements not fetched or cache is empty for featureId '${n}'. Call fetchAllEntitlements() first.`),null;const t=this.entitlementsCache[n];return t?t.type===e?t:(console.warn(`[Kelviq SDK] getEntitlement: Type mismatch for featureId '${n}'. Expected type '${e}', but cached type is '${t.type}'.`),null):null}hasAccess(n){if(!this.entitlementsCache)return console.warn(`[Kelviq SDK] hasAccess: Entitlements not fetched yet for featureId '${n}'. Call fetchAllEntitlements() first. Returning false.`),!1;const e=this.entitlementsCache[n];return e?e.hasAccess:!1}getAllEntitlements(){return this.entitlementsCache}isLoading(){return this.isFetching}getLastError(){return this.lastFetchError}clearCache(){this.entitlementsCache=null,this.isFetching=!1,this.lastFetchError=null,console.log("[Kelviq SDK] Entitlement cache cleared.")}}function U(s,n){const e={...s,initializeAndFetch:s.initializeAndFetch||!0,apiConfig:s.apiConfig||{},environment:s.environment||"production",entitlementsPath:s.entitlementsPath||C},t=new I(e,n);return e.initializeAndFetch===!0&&t.fetchAllEntitlements().catch(f=>{console.error("[Kelviq SDK] Initial fetch on client creation failed:",f.message)}),t}return U}();
1
+ var kelviqSDK=function(){"use strict";var I=Object.defineProperty;var _=(y,d,q)=>d in y?I(y,d,{enumerable:!0,configurable:!0,writable:!0,value:q}):y[d]=q;var E=(y,d,q)=>_(y,typeof d!="symbol"?d+"":d,q);function y(a){const e={};if(!a||!Array.isArray(a.entitlements))return console.warn("[Kelviq SDK] transformApiEntitlements: Invalid or empty entitlements array in API response.",a),e;const i={};a.entitlements.forEach(t=>{if(!t||typeof t.featureId!="string"||typeof t.featureType!="string"){console.warn("[Kelviq SDK] transformApiEntitlements: Skipping invalid raw entitlement object:",t);return}i[t.featureId]||(i[t.featureId]=[]),i[t.featureId].push(t)});for(const t of Object.keys(i)){const c=i[t],u=c[0].featureType;let A=0,r=null,f=null,l=!1,m=!1;if(u==="METER"){let n=!0;for(const s of c)A+=typeof s.currentUsage=="number"?s.currentUsage:0,typeof s.usageLimit=="number"&&(r=(r??0)+s.usageLimit,n=!1);n&&(r=null),f=r!==null?r-A:null,l=c.some(s=>s.hardLimit===!0),m=f===null||f>0}else if(u==="CUSTOMIZABLE"){const n=c[0];r=typeof n.usageLimit=="number"?n.usageLimit:null,A=typeof n.currentUsage=="number"?n.currentUsage:0,f=typeof n.remaining=="number"?n.remaining:null,l=n.hardLimit===!0,m=c.some(s=>s.hasAccess)}else if(u==="BOOLEAN")m=c.some(n=>n.hasAccess);else{console.warn(`[Kelviq SDK] transformApiEntitlements: Encountered unknown featureType: '${u}' for feature '${t}'. This entitlement will be ignored.`);continue}const R={featureId:t,featureType:u,hasAccess:m,hardLimit:l,currentUsage:A,usageLimit:r,remaining:f,items:c};e[t]=R}return e}const d="GET",q=5e3,K=3,P=1e3;function C(a){const{method:e=d,headers:i={},body:t,timeout:c=q,maxRetries:u=K,backoffBaseDelay:A=P,queryParams:r,accessToken:f}=a;let l=a.url,m=0;return new Promise((R,n)=>{if(r){const h=new URLSearchParams;for(const g in r)r[g]!==void 0&&h.append(g,String(r[g]));h.toString()&&(l=l+(l.includes("?")?"&":"?")+h.toString())}function s(){const h=new XMLHttpRequest;h.open(e,l,!0),h.timeout=c;const g={Accept:"application/json","X-Requested-With":"XMLHttpRequest",...i};e!=="GET"&&t&&typeof t=="object"&&!(t instanceof FormData)&&(g["Content-Type"]||(g["Content-Type"]="application/json")),f&&(g.Authorization=`Bearer ${f}`);for(const[o,p]of Object.entries(g))h.setRequestHeader(o,p);h.onload=function(){const{status:o,statusText:p,responseText:F}=h;if(o>=200&&o<300)try{const v=F?JSON.parse(F):{};R({status:o,statusText:p,data:v})}catch(v){const w=v instanceof Error?v:new Error(String(v));console.error(`Kelviq SDK (apiRequest): Invalid JSON response for URL ${l}. Error: ${w.message}. Response: ${F}`),n(new Error(`Invalid JSON response: ${w.message}`))}else S(`Request to ${l} failed with status ${o} ${p}. Response: ${F}`)},h.onerror=()=>S(`Network error for URL ${l}. The request could not be completed.`),h.ontimeout=()=>S(`Request to ${l} timed out.`);function S(o){if(m<u){m++;const p=Math.min(A*Math.pow(2,m-1),3e4);console.warn(`Kelviq SDK (apiRequest): Retrying request to ${l}. Attempt ${m}/${u}. Error: ${o}. Retrying in ${p}ms.`),setTimeout(s,p)}else n(new Error(`${o} (Max retries ${u} reached)`))}try{let o=t;t&&typeof t=="object"&&!(t instanceof FormData)&&g["Content-Type"]==="application/json"&&(o=JSON.stringify(t)),h.send(o)}catch(o){const p=o instanceof Error?o:new Error(String(o));console.error(`Kelviq SDK (apiRequest): Error sending request to ${l}.`,p),n(new Error(`Failed to send request: ${p.message}`))}}s()})}const L="https://edge.api.kelviq.com/api/v1/",D="https://edge.sandboxapi.kelviq.com/api/v1/",T="entitlements/";class ${constructor(e,i){E(this,"options");E(this,"entitlementsCache",null);E(this,"rawApiResponse",null);E(this,"isFetching",!1);E(this,"inFlightPromise",null);E(this,"readyPromise",null);E(this,"lastFetchError",null);E(this,"apiRequestService");if(!e||!e.customerId)throw new Error("[Kelviq SDK] KelviqClient: CustomerId is required in options.");this.options={...e,apiConfig:e.apiConfig||{},entitlementsPath:e.entitlementsPath||T},this.apiRequestService=i||C}async fetchAllEntitlements(e=!1){if(this.inFlightPromise&&!e)return this.inFlightPromise;if(this.entitlementsCache&&!e)return Promise.resolve(this.entitlementsCache);this.isFetching=!0,this.lastFetchError=null;const{apiUrl:i=this.options.environment==="sandbox"?D:L,entitlementsPath:t,customerId:c,accessToken:u,apiConfig:A,onError:r}=this.options,f=i.replace(/\/$/,""),l=t.replace(/^\//,""),m=`${f}/${l}`,R={customer_id:c};return this.inFlightPromise=this.apiRequestService({url:m,method:"GET",queryParams:R,accessToken:u,...A||{}}).then(n=>{if(n&&n.data&&Array.isArray(n.data.entitlements))return this.rawApiResponse=n.data,this.entitlementsCache=y(n.data),this.isFetching=!1,this.inFlightPromise=null,this.entitlementsCache;{const s=new Error("[Kelviq SDK] Received empty, malformed, or invalid data structure from entitlements API.");throw this.lastFetchError=s,this.isFetching=!1,this.inFlightPromise=null,this.entitlementsCache=null,this.rawApiResponse=null,r&&r(s),s}}).catch(n=>{const s=n instanceof Error?n:new Error(String(n));throw this.lastFetchError=s,this.isFetching=!1,this.inFlightPromise=null,this.entitlementsCache=null,this.rawApiResponse=null,r&&r(s),s}),this.inFlightPromise}getEntitlement(e){return this.entitlementsCache?this.entitlementsCache[e]??null:(console.warn(`[Kelviq SDK] getEntitlement: Entitlements not fetched or cache is empty for featureId '${e}'. Call fetchAllEntitlements() first.`),null)}getEntitlements(){return this.entitlementsCache}getRawEntitlement(e){return this.rawApiResponse?this.rawApiResponse.entitlements.filter(i=>i.featureId===e):(console.warn(`[Kelviq SDK] getRawEntitlement: Entitlements not fetched for featureId '${e}'. Call fetchAllEntitlements() first.`),null)}getRawEntitlements(){return this.rawApiResponse}hasAccess(e){if(!this.entitlementsCache)return console.warn(`[Kelviq SDK] hasAccess: Entitlements not fetched yet for featureId '${e}'. Call fetchAllEntitlements() first. Returning false.`),!1;const i=this.entitlementsCache[e];return i?i.hasAccess:!1}isLoading(){return this.isFetching}getLastError(){return this.lastFetchError}ready(){return this.readyPromise??Promise.resolve()}_setReadyPromise(e){this.readyPromise=e}clearCache(){this.entitlementsCache=null,this.rawApiResponse=null,this.isFetching=!1,this.lastFetchError=null,console.log("[Kelviq SDK] Entitlement cache cleared.")}}function U(a,e){const i={...a,initializeAndFetch:a.initializeAndFetch??!0,apiConfig:a.apiConfig||{},environment:a.environment||"production",entitlementsPath:a.entitlementsPath||T},t=new $(i,e);if(i.initializeAndFetch===!0){const c=t.fetchAllEntitlements().catch(u=>{console.error("[Kelviq SDK] Initial fetch on client creation failed:",u.message)});t._setReadyPromise(c.then(()=>{}))}return t}return U}();
@@ -1,257 +1,266 @@
1
- var K = Object.defineProperty;
2
- var C = (r, t, e) => t in r ? K(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
3
- var E = (r, t, e) => C(r, typeof t != "symbol" ? t + "" : t, e);
4
- function R(r) {
5
- const t = {};
6
- return !r || !Array.isArray(r.entitlements) ? (console.warn(
7
- "[Kelviq SDK] transformApiEntitlements: Invalid or empty entitlements array in API response.",
8
- r
9
- ), t) : (r.entitlements.forEach((e) => {
10
- if (!e || typeof e.featureId != "string" || typeof e.featureType != "string") {
1
+ var S = Object.defineProperty;
2
+ var T = (s, e, i) => e in s ? S(s, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : s[e] = i;
3
+ var g = (s, e, i) => T(s, typeof e != "symbol" ? e + "" : e, i);
4
+ function w(s) {
5
+ const e = {};
6
+ if (!s || !Array.isArray(s.entitlements))
7
+ return console.warn(
8
+ "[Kelviq SDK] transformApiEntitlements: Invalid or empty entitlements array in API response.",
9
+ s
10
+ ), e;
11
+ const i = {};
12
+ s.entitlements.forEach((t) => {
13
+ if (!t || typeof t.featureId != "string" || typeof t.featureType != "string") {
11
14
  console.warn(
12
15
  "[Kelviq SDK] transformApiEntitlements: Skipping invalid raw entitlement object:",
13
- e
16
+ t
14
17
  );
15
18
  return;
16
19
  }
17
- let n = null;
18
- const m = {
19
- featureId: e.featureId,
20
- hasAccess: e.hasAccess
21
- };
22
- if (e.featureType === "BOOLEAN")
23
- n = {
24
- ...m,
25
- type: "boolean"
26
- };
27
- else if (e.featureType === "CUSTOMIZABLE") {
28
- const i = e;
29
- n = {
30
- ...m,
31
- type: "customizable",
32
- configuration: typeof i.value == "number" ? i.value : null
33
- };
34
- } else if (e.featureType === "METER") {
35
- const i = e, p = typeof i.usageLimit == "number" ? i.usageLimit : null, o = typeof i.currentUsage == "number" ? i.currentUsage : 0;
36
- n = {
37
- ...m,
38
- type: "metered",
39
- limit: p,
40
- used: o,
41
- remaining: p !== null ? p - o : null,
42
- resetAt: i.resetAt,
43
- hardLimit: i.hardLimit
44
- };
45
- } else {
46
- const i = e;
20
+ i[t.featureId] || (i[t.featureId] = []), i[t.featureId].push(t);
21
+ });
22
+ for (const t of Object.keys(i)) {
23
+ const c = i[t], u = c[0].featureType;
24
+ let E = 0, l = null, f = null, o = !1, m = !1;
25
+ if (u === "METER") {
26
+ let n = !0;
27
+ for (const r of c)
28
+ E += typeof r.currentUsage == "number" ? r.currentUsage : 0, typeof r.usageLimit == "number" && (l = (l ?? 0) + r.usageLimit, n = !1);
29
+ n && (l = null), f = l !== null ? l - E : null, o = c.some((r) => r.hardLimit === !0), m = f === null || f > 0;
30
+ } else if (u === "CUSTOMIZABLE") {
31
+ const n = c[0];
32
+ l = typeof n.usageLimit == "number" ? n.usageLimit : null, E = typeof n.currentUsage == "number" ? n.currentUsage : 0, f = typeof n.remaining == "number" ? n.remaining : null, o = n.hardLimit === !0, m = c.some((r) => r.hasAccess);
33
+ } else if (u === "BOOLEAN")
34
+ m = c.some((n) => n.hasAccess);
35
+ else {
47
36
  console.warn(
48
- `[Kelviq SDK] transformApiEntitlements: Encountered unknown or unhandled entitlement featureType: '${String(i.featureType)}' for feature '${String(i.feature)}'. This entitlement will be ignored.`
37
+ `[Kelviq SDK] transformApiEntitlements: Encountered unknown featureType: '${u}' for feature '${t}'. This entitlement will be ignored.`
49
38
  );
39
+ continue;
50
40
  }
51
- n && (t[n.featureId] = n);
52
- }), t);
41
+ const A = {
42
+ featureId: t,
43
+ featureType: u,
44
+ hasAccess: m,
45
+ hardLimit: o,
46
+ currentUsage: E,
47
+ usageLimit: l,
48
+ remaining: f,
49
+ items: c
50
+ };
51
+ e[t] = A;
52
+ }
53
+ return e;
53
54
  }
54
- const $ = "GET", D = 5e3, F = 3, L = 1e3;
55
- function w(r) {
55
+ const P = "GET", C = 5e3, K = 3, L = 1e3;
56
+ function $(s) {
56
57
  const {
57
- method: t = $,
58
- headers: e = {},
58
+ method: e = P,
59
+ headers: i = {},
59
60
  // Renamed to avoid conflict with internal 'headers' variable
60
- body: n,
61
- timeout: m = D,
62
- maxRetries: i = F,
63
- backoffBaseDelay: p = L,
64
- queryParams: o,
65
- accessToken: g
61
+ body: t,
62
+ timeout: c = C,
63
+ maxRetries: u = K,
64
+ backoffBaseDelay: E = L,
65
+ queryParams: l,
66
+ accessToken: f
66
67
  // Use the direct accessToken
67
- } = r;
68
- let a = r.url, d = 0;
69
- return new Promise((q, l) => {
70
- if (o) {
71
- const c = new URLSearchParams();
72
- for (const f in o)
73
- o[f] !== void 0 && c.append(f, String(o[f]));
74
- c.toString() && (a = a + (a.includes("?") ? "&" : "?") + c.toString());
68
+ } = s;
69
+ let o = s.url, m = 0;
70
+ return new Promise((A, n) => {
71
+ if (l) {
72
+ const h = new URLSearchParams();
73
+ for (const d in l)
74
+ l[d] !== void 0 && h.append(d, String(l[d]));
75
+ h.toString() && (o = o + (o.includes("?") ? "&" : "?") + h.toString());
75
76
  }
76
- function u() {
77
- const c = new XMLHttpRequest();
78
- c.open(t, a, !0), c.timeout = m;
79
- const f = {
77
+ function r() {
78
+ const h = new XMLHttpRequest();
79
+ h.open(e, o, !0), h.timeout = c;
80
+ const d = {
80
81
  Accept: "application/json",
81
82
  "X-Requested-With": "XMLHttpRequest",
82
- ...e
83
+ ...i
83
84
  // Apply custom headers, allowing them to override defaults
84
85
  };
85
- t !== "GET" && n && typeof n == "object" && !(n instanceof FormData) && (f["Content-Type"] || (f["Content-Type"] = "application/json")), g && (f.Authorization = `Bearer ${g}`);
86
- for (const [s, h] of Object.entries(f))
87
- c.setRequestHeader(s, h);
88
- c.onload = function() {
89
- const { status: s, statusText: h, responseText: A } = c;
90
- if (s >= 200 && s < 300)
86
+ e !== "GET" && t && typeof t == "object" && !(t instanceof FormData) && (d["Content-Type"] || (d["Content-Type"] = "application/json")), f && (d.Authorization = `Bearer ${f}`);
87
+ for (const [a, p] of Object.entries(d))
88
+ h.setRequestHeader(a, p);
89
+ h.onload = function() {
90
+ const { status: a, statusText: p, responseText: R } = h;
91
+ if (a >= 200 && a < 300)
91
92
  try {
92
- const y = A ? JSON.parse(A) : {};
93
- q({ status: s, statusText: h, data: y });
93
+ const y = R ? JSON.parse(R) : {};
94
+ A({ status: a, statusText: p, data: y });
94
95
  } catch (y) {
95
- const S = y instanceof Error ? y : new Error(String(y));
96
+ const v = y instanceof Error ? y : new Error(String(y));
96
97
  console.error(
97
- `Kelviq SDK (apiRequest): Invalid JSON response for URL ${a}. Error: ${S.message}. Response: ${A}`
98
- ), l(new Error(`Invalid JSON response: ${S.message}`));
98
+ `Kelviq SDK (apiRequest): Invalid JSON response for URL ${o}. Error: ${v.message}. Response: ${R}`
99
+ ), n(new Error(`Invalid JSON response: ${v.message}`));
99
100
  }
100
101
  else
101
- v(
102
- `Request to ${a} failed with status ${s} ${h}. Response: ${A}`
102
+ q(
103
+ `Request to ${o} failed with status ${a} ${p}. Response: ${R}`
103
104
  );
104
- }, c.onerror = () => v(
105
- `Network error for URL ${a}. The request could not be completed.`
106
- ), c.ontimeout = () => v(`Request to ${a} timed out.`);
107
- function v(s) {
108
- if (d < i) {
109
- d++;
110
- const h = Math.min(
111
- p * Math.pow(2, d - 1),
105
+ }, h.onerror = () => q(
106
+ `Network error for URL ${o}. The request could not be completed.`
107
+ ), h.ontimeout = () => q(`Request to ${o} timed out.`);
108
+ function q(a) {
109
+ if (m < u) {
110
+ m++;
111
+ const p = Math.min(
112
+ E * Math.pow(2, m - 1),
112
113
  3e4
113
114
  // Cap retry delay at 30 seconds
114
115
  );
115
116
  console.warn(
116
- `Kelviq SDK (apiRequest): Retrying request to ${a}. Attempt ${d}/${i}. Error: ${s}. Retrying in ${h}ms.`
117
- ), setTimeout(u, h);
117
+ `Kelviq SDK (apiRequest): Retrying request to ${o}. Attempt ${m}/${u}. Error: ${a}. Retrying in ${p}ms.`
118
+ ), setTimeout(r, p);
118
119
  } else
119
- l(
120
- new Error(`${s} (Max retries ${i} reached)`)
120
+ n(
121
+ new Error(`${a} (Max retries ${u} reached)`)
121
122
  );
122
123
  }
123
124
  try {
124
- let s = n;
125
- n && typeof n == "object" && !(n instanceof FormData) && f["Content-Type"] === "application/json" && (s = JSON.stringify(n)), c.send(s);
126
- } catch (s) {
127
- const h = s instanceof Error ? s : new Error(String(s));
125
+ let a = t;
126
+ t && typeof t == "object" && !(t instanceof FormData) && d["Content-Type"] === "application/json" && (a = JSON.stringify(t)), h.send(a);
127
+ } catch (a) {
128
+ const p = a instanceof Error ? a : new Error(String(a));
128
129
  console.error(
129
- `Kelviq SDK (apiRequest): Error sending request to ${a}.`,
130
- h
131
- ), l(new Error(`Failed to send request: ${h.message}`));
130
+ `Kelviq SDK (apiRequest): Error sending request to ${o}.`,
131
+ p
132
+ ), n(new Error(`Failed to send request: ${p.message}`));
132
133
  }
133
134
  }
134
- u();
135
+ r();
135
136
  });
136
137
  }
137
- const I = "https://edge.api.kelviq.com/api/v1/", U = "https://edge.sandboxapi.kelviq.com/api/v1/", T = "entitlements/";
138
- class P {
138
+ const D = "https://edge.api.kelviq.com/api/v1/", U = "https://edge.sandboxapi.kelviq.com/api/v1/", F = "entitlements/";
139
+ class I {
139
140
  /**
140
141
  * Creates an instance of KelviqClient.
141
- * Prefer using the `createKelviqClient` factory function for instantiation.
142
+ * Prefer using the `kelviqSDK` factory function for instantiation.
142
143
  * @internal
143
144
  * @param options Configuration options for the client.
144
145
  * @param customApiRequest Optional: A custom function to handle API requests, conforming to ApiRequestConfig and ApiResponse.
145
146
  */
146
- constructor(t, e) {
147
- E(this, "options");
148
- E(this, "entitlementsCache", null);
149
- E(this, "isFetching", !1);
150
- E(this, "lastFetchError", null);
151
- E(this, "apiRequestService");
152
- if (!t || !t.customerId)
147
+ constructor(e, i) {
148
+ g(this, "options");
149
+ g(this, "entitlementsCache", null);
150
+ g(this, "rawApiResponse", null);
151
+ g(this, "isFetching", !1);
152
+ g(this, "inFlightPromise", null);
153
+ g(this, "readyPromise", null);
154
+ g(this, "lastFetchError", null);
155
+ g(this, "apiRequestService");
156
+ if (!e || !e.customerId)
153
157
  throw new Error(
154
158
  "[Kelviq SDK] KelviqClient: CustomerId is required in options."
155
159
  );
156
160
  this.options = {
157
- ...t,
158
- apiConfig: t.apiConfig || {},
159
- entitlementsPath: t.entitlementsPath || T
160
- }, this.apiRequestService = e || w;
161
+ ...e,
162
+ apiConfig: e.apiConfig || {},
163
+ entitlementsPath: e.entitlementsPath || F
164
+ }, this.apiRequestService = i || $;
161
165
  }
162
166
  /**
163
- * Fetches all entitlements from the API for the configured orgId and customerId,
167
+ * Fetches all entitlements from the API for the configured customerId,
164
168
  * processes them, and caches the result.
165
169
  * Subsequent calls will return cached data unless `forceRefresh` is true.
166
170
  *
167
171
  * @param forceRefresh - If true, fetches from the API even if data is already cached. Defaults to false.
168
- * @returns A promise that resolves to the map of entitlements (EntitlementMap).
172
+ * @returns A promise that resolves to the map of aggregated entitlements.
169
173
  * @throws Will throw an error if the API request fails after all retries.
170
174
  */
171
- async fetchAllEntitlements(t = !1) {
172
- if (this.isFetching)
173
- return Promise.reject(
174
- new Error("[Kelviq SDK] An entitlement fetch is already in progress.")
175
- );
176
- if (this.entitlementsCache && !t)
175
+ async fetchAllEntitlements(e = !1) {
176
+ if (this.inFlightPromise && !e)
177
+ return this.inFlightPromise;
178
+ if (this.entitlementsCache && !e)
177
179
  return Promise.resolve(this.entitlementsCache);
178
180
  this.isFetching = !0, this.lastFetchError = null;
179
181
  const {
180
- apiUrl: e = this.options.environment === "sandbox" ? U : I,
181
- entitlementsPath: n,
182
- customerId: m,
183
- accessToken: i,
184
- apiConfig: p,
185
- onError: o
186
- } = this.options, g = e.replace(/\/$/, ""), a = n.replace(/^\//, ""), d = `${g}/${a}`, q = {
187
- customer_id: m
182
+ apiUrl: i = this.options.environment === "sandbox" ? U : D,
183
+ entitlementsPath: t,
184
+ customerId: c,
185
+ accessToken: u,
186
+ apiConfig: E,
187
+ onError: l
188
+ } = this.options, f = i.replace(/\/$/, ""), o = t.replace(/^\//, ""), m = `${f}/${o}`, A = {
189
+ customer_id: c
188
190
  };
189
- try {
190
- const l = await this.apiRequestService(
191
- {
192
- url: d,
193
- method: "GET",
194
- queryParams: q,
195
- accessToken: i,
196
- ...p || {}
197
- }
198
- );
199
- if (l && l.data && Array.isArray(l.data.entitlements))
200
- return this.entitlementsCache = R(l.data), this.isFetching = !1, this.entitlementsCache;
191
+ return this.inFlightPromise = this.apiRequestService({
192
+ url: m,
193
+ method: "GET",
194
+ queryParams: A,
195
+ accessToken: u,
196
+ ...E || {}
197
+ }).then((n) => {
198
+ if (n && n.data && Array.isArray(n.data.entitlements))
199
+ return this.rawApiResponse = n.data, this.entitlementsCache = w(n.data), this.isFetching = !1, this.inFlightPromise = null, this.entitlementsCache;
201
200
  {
202
- const u = new Error(
201
+ const r = new Error(
203
202
  "[Kelviq SDK] Received empty, malformed, or invalid data structure from entitlements API."
204
203
  );
205
- throw this.lastFetchError = u, this.isFetching = !1, this.entitlementsCache = null, o && o(u), u;
204
+ throw this.lastFetchError = r, this.isFetching = !1, this.inFlightPromise = null, this.entitlementsCache = null, this.rawApiResponse = null, l && l(r), r;
206
205
  }
207
- } catch (l) {
208
- const u = l instanceof Error ? l : new Error(String(l));
209
- throw this.lastFetchError = u, this.isFetching = !1, this.entitlementsCache = null, o && o(u), u;
210
- }
206
+ }).catch((n) => {
207
+ const r = n instanceof Error ? n : new Error(String(n));
208
+ throw this.lastFetchError = r, this.isFetching = !1, this.inFlightPromise = null, this.entitlementsCache = null, this.rawApiResponse = null, l && l(r), r;
209
+ }), this.inFlightPromise;
211
210
  }
212
211
  /**
213
- * Retrieves a specific entitlement from the local cache.
214
- * Note: `fetchAllEntitlements()` must be called successfully at least once before using this method,
215
- * or the cache will be empty.
212
+ * Retrieves a specific aggregated entitlement from the local cache.
216
213
  *
217
- * @param featureId The unique key of the feature.
218
- * @param type The expected type of the entitlement ('boolean', 'config', 'metered').
219
- * @returns The processed entitlement object if found in the cache and the type matches, otherwise `null`.
220
- * Logs a warning if entitlements haven't been fetched or if a type mismatch occurs.
214
+ * @param featureId The unique identifier of the feature.
215
+ * @returns The aggregated Entitlement object if found, otherwise `null`.
221
216
  */
222
- getEntitlement(t, e) {
223
- if (!this.entitlementsCache)
224
- return console.warn(
225
- `[Kelviq SDK] getEntitlement: Entitlements not fetched or cache is empty for featureId '${t}'. Call fetchAllEntitlements() first.`
226
- ), null;
227
- const n = this.entitlementsCache[t];
228
- return n ? n.type === e ? n : (console.warn(
229
- `[Kelviq SDK] getEntitlement: Type mismatch for featureId '${t}'. Expected type '${e}', but cached type is '${n.type}'.`
230
- ), null) : null;
217
+ getEntitlement(e) {
218
+ return this.entitlementsCache ? this.entitlementsCache[e] ?? null : (console.warn(
219
+ `[Kelviq SDK] getEntitlement: Entitlements not fetched or cache is empty for featureId '${e}'. Call fetchAllEntitlements() first.`
220
+ ), null);
221
+ }
222
+ /**
223
+ * Returns all aggregated entitlements as a map keyed by featureId.
224
+ *
225
+ * @returns Record of featureId to aggregated Entitlement, or `null` if not fetched.
226
+ */
227
+ getEntitlements() {
228
+ return this.entitlementsCache;
229
+ }
230
+ /**
231
+ * Returns the raw API items for a specific featureId (un-aggregated).
232
+ *
233
+ * @param featureId The unique identifier of the feature.
234
+ * @returns Array of raw entitlement items for the given featureId, or `null` if not fetched.
235
+ */
236
+ getRawEntitlement(e) {
237
+ return this.rawApiResponse ? this.rawApiResponse.entitlements.filter(
238
+ (i) => i.featureId === e
239
+ ) : (console.warn(
240
+ `[Kelviq SDK] getRawEntitlement: Entitlements not fetched for featureId '${e}'. Call fetchAllEntitlements() first.`
241
+ ), null);
242
+ }
243
+ /**
244
+ * Returns the full raw API response including the customer_id wrapper.
245
+ *
246
+ * @returns The raw API response `{ customer_id, entitlements }`, or `null` if not fetched.
247
+ */
248
+ getRawEntitlements() {
249
+ return this.rawApiResponse;
231
250
  }
232
251
  /**
233
252
  * Checks if the user has access to a specific feature based on cached entitlements.
234
- * Note: `fetchAllEntitlements()` should be called successfully at least once before using this method.
235
253
  *
236
- * @param featureId The key of the feature.
237
- * @returns `true` if access is granted, `false` if denied or the feature is not found in the cache.
238
- * Logs a warning and returns `false` if entitlements haven't been fetched.
254
+ * @param featureId The identifier of the feature.
255
+ * @returns `true` if access is granted, `false` otherwise.
239
256
  */
240
- hasAccess(t) {
257
+ hasAccess(e) {
241
258
  if (!this.entitlementsCache)
242
259
  return console.warn(
243
- `[Kelviq SDK] hasAccess: Entitlements not fetched yet for featureId '${t}'. Call fetchAllEntitlements() first. Returning false.`
260
+ `[Kelviq SDK] hasAccess: Entitlements not fetched yet for featureId '${e}'. Call fetchAllEntitlements() first. Returning false.`
244
261
  ), !1;
245
- const e = this.entitlementsCache[t];
246
- return e ? e.hasAccess : !1;
247
- }
248
- /**
249
- * Returns the currently cached map of all entitlements.
250
- * `fetchAllEntitlements()` should be called successfully at least once.
251
- * @returns The EntitlementMap (a record of featureKey to entitlement data) or `null` if not fetched or an error occurred during the last fetch.
252
- */
253
- getAllEntitlements() {
254
- return this.entitlementsCache;
262
+ const i = this.entitlementsCache[e];
263
+ return i ? i.hasAccess : !1;
255
264
  }
256
265
  /**
257
266
  * Indicates if an entitlement fetch operation is currently in progress.
@@ -267,29 +276,46 @@ class P {
267
276
  getLastError() {
268
277
  return this.lastFetchError;
269
278
  }
279
+ /**
280
+ * Returns a promise that resolves when the initial fetch (triggered by `initializeAndFetch: true`) completes.
281
+ * If no initial fetch was triggered, resolves immediately.
282
+ */
283
+ ready() {
284
+ return this.readyPromise ?? Promise.resolve();
285
+ }
286
+ /**
287
+ * @internal Sets the ready promise. Called by the factory when initializeAndFetch is true.
288
+ */
289
+ _setReadyPromise(e) {
290
+ this.readyPromise = e;
291
+ }
270
292
  /**
271
293
  * Clears the local entitlement cache and resets loading and error states.
272
- * After calling this, `fetchAllEntitlements()` will need to be called again to populate the cache.
273
294
  */
274
295
  clearCache() {
275
- this.entitlementsCache = null, this.isFetching = !1, this.lastFetchError = null, console.log("[Kelviq SDK] Entitlement cache cleared.");
296
+ this.entitlementsCache = null, this.rawApiResponse = null, this.isFetching = !1, this.lastFetchError = null, console.log("[Kelviq SDK] Entitlement cache cleared.");
276
297
  }
277
298
  }
278
- function _(r, t) {
279
- const e = {
280
- ...r,
281
- initializeAndFetch: r.initializeAndFetch || !0,
282
- apiConfig: r.apiConfig || {},
283
- environment: r.environment || "production",
284
- entitlementsPath: r.entitlementsPath || T
285
- }, n = new P(e, t);
286
- return e.initializeAndFetch === !0 && n.fetchAllEntitlements().catch((m) => {
287
- console.error(
288
- "[Kelviq SDK] Initial fetch on client creation failed:",
289
- m.message
290
- );
291
- }), n;
299
+ function b(s, e) {
300
+ const i = {
301
+ ...s,
302
+ initializeAndFetch: s.initializeAndFetch ?? !0,
303
+ apiConfig: s.apiConfig || {},
304
+ environment: s.environment || "production",
305
+ entitlementsPath: s.entitlementsPath || F
306
+ }, t = new I(i, e);
307
+ if (i.initializeAndFetch === !0) {
308
+ const c = t.fetchAllEntitlements().catch((u) => {
309
+ console.error(
310
+ "[Kelviq SDK] Initial fetch on client creation failed:",
311
+ u.message
312
+ );
313
+ });
314
+ t._setReadyPromise(c.then(() => {
315
+ }));
316
+ }
317
+ return t;
292
318
  }
293
319
  export {
294
- _ as default
320
+ b as default
295
321
  };
@@ -1 +1 @@
1
- (function(f,o){typeof exports=="object"&&typeof module<"u"?module.exports=o():typeof define=="function"&&define.amd?define(o):(f=typeof globalThis<"u"?globalThis:f||self,f.kelviqSDK=o())})(this,function(){"use strict";var b=Object.defineProperty;var P=(f,o,y)=>o in f?b(f,o,{enumerable:!0,configurable:!0,writable:!0,value:y}):f[o]=y;var A=(f,o,y)=>P(f,typeof o!="symbol"?o+"":o,y);function f(s){const n={};return!s||!Array.isArray(s.entitlements)?(console.warn("[Kelviq SDK] transformApiEntitlements: Invalid or empty entitlements array in API response.",s),n):(s.entitlements.forEach(e=>{if(!e||typeof e.featureId!="string"||typeof e.featureType!="string"){console.warn("[Kelviq SDK] transformApiEntitlements: Skipping invalid raw entitlement object:",e);return}let t=null;const d={featureId:e.featureId,hasAccess:e.hasAccess};if(e.featureType==="BOOLEAN")t={...d,type:"boolean"};else if(e.featureType==="CUSTOMIZABLE"){const i=e;t={...d,type:"customizable",configuration:typeof i.value=="number"?i.value:null}}else if(e.featureType==="METER"){const i=e,E=typeof i.usageLimit=="number"?i.usageLimit:null,l=typeof i.currentUsage=="number"?i.currentUsage:0;t={...d,type:"metered",limit:E,used:l,remaining:E!==null?E-l:null,resetAt:i.resetAt,hardLimit:i.hardLimit}}else{const i=e;console.warn(`[Kelviq SDK] transformApiEntitlements: Encountered unknown or unhandled entitlement featureType: '${String(i.featureType)}' for feature '${String(i.feature)}'. This entitlement will be ignored.`)}t&&(n[t.featureId]=t)}),n)}const o="GET",y=5e3,R=3,$=1e3;function F(s){const{method:n=o,headers:e={},body:t,timeout:d=y,maxRetries:i=R,backoffBaseDelay:E=$,queryParams:l,accessToken:T}=s;let a=s.url,g=0;return new Promise((S,c)=>{if(l){const u=new URLSearchParams;for(const p in l)l[p]!==void 0&&u.append(p,String(l[p]));u.toString()&&(a=a+(a.includes("?")?"&":"?")+u.toString())}function h(){const u=new XMLHttpRequest;u.open(n,a,!0),u.timeout=d;const p={Accept:"application/json","X-Requested-With":"XMLHttpRequest",...e};n!=="GET"&&t&&typeof t=="object"&&!(t instanceof FormData)&&(p["Content-Type"]||(p["Content-Type"]="application/json")),T&&(p.Authorization=`Bearer ${T}`);for(const[r,m]of Object.entries(p))u.setRequestHeader(r,m);u.onload=function(){const{status:r,statusText:m,responseText:v}=u;if(r>=200&&r<300)try{const q=v?JSON.parse(v):{};S({status:r,statusText:m,data:q})}catch(q){const D=q instanceof Error?q:new Error(String(q));console.error(`Kelviq SDK (apiRequest): Invalid JSON response for URL ${a}. Error: ${D.message}. Response: ${v}`),c(new Error(`Invalid JSON response: ${D.message}`))}else K(`Request to ${a} failed with status ${r} ${m}. Response: ${v}`)},u.onerror=()=>K(`Network error for URL ${a}. The request could not be completed.`),u.ontimeout=()=>K(`Request to ${a} timed out.`);function K(r){if(g<i){g++;const m=Math.min(E*Math.pow(2,g-1),3e4);console.warn(`Kelviq SDK (apiRequest): Retrying request to ${a}. Attempt ${g}/${i}. Error: ${r}. Retrying in ${m}ms.`),setTimeout(h,m)}else c(new Error(`${r} (Max retries ${i} reached)`))}try{let r=t;t&&typeof t=="object"&&!(t instanceof FormData)&&p["Content-Type"]==="application/json"&&(r=JSON.stringify(t)),u.send(r)}catch(r){const m=r instanceof Error?r:new Error(String(r));console.error(`Kelviq SDK (apiRequest): Error sending request to ${a}.`,m),c(new Error(`Failed to send request: ${m.message}`))}}h()})}const L="https://edge.api.kelviq.com/api/v1/",w="https://edge.sandboxapi.kelviq.com/api/v1/",C="entitlements/";class I{constructor(n,e){A(this,"options");A(this,"entitlementsCache",null);A(this,"isFetching",!1);A(this,"lastFetchError",null);A(this,"apiRequestService");if(!n||!n.customerId)throw new Error("[Kelviq SDK] KelviqClient: CustomerId is required in options.");this.options={...n,apiConfig:n.apiConfig||{},entitlementsPath:n.entitlementsPath||C},this.apiRequestService=e||F}async fetchAllEntitlements(n=!1){if(this.isFetching)return Promise.reject(new Error("[Kelviq SDK] An entitlement fetch is already in progress."));if(this.entitlementsCache&&!n)return Promise.resolve(this.entitlementsCache);this.isFetching=!0,this.lastFetchError=null;const{apiUrl:e=this.options.environment==="sandbox"?w:L,entitlementsPath:t,customerId:d,accessToken:i,apiConfig:E,onError:l}=this.options,T=e.replace(/\/$/,""),a=t.replace(/^\//,""),g=`${T}/${a}`,S={customer_id:d};try{const c=await this.apiRequestService({url:g,method:"GET",queryParams:S,accessToken:i,...E||{}});if(c&&c.data&&Array.isArray(c.data.entitlements))return this.entitlementsCache=f(c.data),this.isFetching=!1,this.entitlementsCache;{const h=new Error("[Kelviq SDK] Received empty, malformed, or invalid data structure from entitlements API.");throw this.lastFetchError=h,this.isFetching=!1,this.entitlementsCache=null,l&&l(h),h}}catch(c){const h=c instanceof Error?c:new Error(String(c));throw this.lastFetchError=h,this.isFetching=!1,this.entitlementsCache=null,l&&l(h),h}}getEntitlement(n,e){if(!this.entitlementsCache)return console.warn(`[Kelviq SDK] getEntitlement: Entitlements not fetched or cache is empty for featureId '${n}'. Call fetchAllEntitlements() first.`),null;const t=this.entitlementsCache[n];return t?t.type===e?t:(console.warn(`[Kelviq SDK] getEntitlement: Type mismatch for featureId '${n}'. Expected type '${e}', but cached type is '${t.type}'.`),null):null}hasAccess(n){if(!this.entitlementsCache)return console.warn(`[Kelviq SDK] hasAccess: Entitlements not fetched yet for featureId '${n}'. Call fetchAllEntitlements() first. Returning false.`),!1;const e=this.entitlementsCache[n];return e?e.hasAccess:!1}getAllEntitlements(){return this.entitlementsCache}isLoading(){return this.isFetching}getLastError(){return this.lastFetchError}clearCache(){this.entitlementsCache=null,this.isFetching=!1,this.lastFetchError=null,console.log("[Kelviq SDK] Entitlement cache cleared.")}}function U(s,n){const e={...s,initializeAndFetch:s.initializeAndFetch||!0,apiConfig:s.apiConfig||{},environment:s.environment||"production",entitlementsPath:s.entitlementsPath||C},t=new I(e,n);return e.initializeAndFetch===!0&&t.fetchAllEntitlements().catch(d=>{console.error("[Kelviq SDK] Initial fetch on client creation failed:",d.message)}),t}return U});
1
+ (function(f,c){typeof exports=="object"&&typeof module<"u"?module.exports=c():typeof define=="function"&&define.amd?define(c):(f=typeof globalThis<"u"?globalThis:f||self,f.kelviqSDK=c())})(this,function(){"use strict";var I=Object.defineProperty;var b=(f,c,q)=>c in f?I(f,c,{enumerable:!0,configurable:!0,writable:!0,value:q}):f[c]=q;var A=(f,c,q)=>b(f,typeof c!="symbol"?c+"":c,q);function f(a){const e={};if(!a||!Array.isArray(a.entitlements))return console.warn("[Kelviq SDK] transformApiEntitlements: Invalid or empty entitlements array in API response.",a),e;const i={};a.entitlements.forEach(t=>{if(!t||typeof t.featureId!="string"||typeof t.featureType!="string"){console.warn("[Kelviq SDK] transformApiEntitlements: Skipping invalid raw entitlement object:",t);return}i[t.featureId]||(i[t.featureId]=[]),i[t.featureId].push(t)});for(const t of Object.keys(i)){const u=i[t],h=u[0].featureType;let y=0,r=null,p=null,o=!1,d=!1;if(h==="METER"){let n=!0;for(const s of u)y+=typeof s.currentUsage=="number"?s.currentUsage:0,typeof s.usageLimit=="number"&&(r=(r??0)+s.usageLimit,n=!1);n&&(r=null),p=r!==null?r-y:null,o=u.some(s=>s.hardLimit===!0),d=p===null||p>0}else if(h==="CUSTOMIZABLE"){const n=u[0];r=typeof n.usageLimit=="number"?n.usageLimit:null,y=typeof n.currentUsage=="number"?n.currentUsage:0,p=typeof n.remaining=="number"?n.remaining:null,o=n.hardLimit===!0,d=u.some(s=>s.hasAccess)}else if(h==="BOOLEAN")d=u.some(n=>n.hasAccess);else{console.warn(`[Kelviq SDK] transformApiEntitlements: Encountered unknown featureType: '${h}' for feature '${t}'. This entitlement will be ignored.`);continue}const R={featureId:t,featureType:h,hasAccess:d,hardLimit:o,currentUsage:y,usageLimit:r,remaining:p,items:u};e[t]=R}return e}const c="GET",q=5e3,K=3,P=1e3;function C(a){const{method:e=c,headers:i={},body:t,timeout:u=q,maxRetries:h=K,backoffBaseDelay:y=P,queryParams:r,accessToken:p}=a;let o=a.url,d=0;return new Promise((R,n)=>{if(r){const m=new URLSearchParams;for(const E in r)r[E]!==void 0&&m.append(E,String(r[E]));m.toString()&&(o=o+(o.includes("?")?"&":"?")+m.toString())}function s(){const m=new XMLHttpRequest;m.open(e,o,!0),m.timeout=u;const E={Accept:"application/json","X-Requested-With":"XMLHttpRequest",...i};e!=="GET"&&t&&typeof t=="object"&&!(t instanceof FormData)&&(E["Content-Type"]||(E["Content-Type"]="application/json")),p&&(E.Authorization=`Bearer ${p}`);for(const[l,g]of Object.entries(E))m.setRequestHeader(l,g);m.onload=function(){const{status:l,statusText:g,responseText:T}=m;if(l>=200&&l<300)try{const v=T?JSON.parse(T):{};R({status:l,statusText:g,data:v})}catch(v){const w=v instanceof Error?v:new Error(String(v));console.error(`Kelviq SDK (apiRequest): Invalid JSON response for URL ${o}. Error: ${w.message}. Response: ${T}`),n(new Error(`Invalid JSON response: ${w.message}`))}else F(`Request to ${o} failed with status ${l} ${g}. Response: ${T}`)},m.onerror=()=>F(`Network error for URL ${o}. The request could not be completed.`),m.ontimeout=()=>F(`Request to ${o} timed out.`);function F(l){if(d<h){d++;const g=Math.min(y*Math.pow(2,d-1),3e4);console.warn(`Kelviq SDK (apiRequest): Retrying request to ${o}. Attempt ${d}/${h}. Error: ${l}. Retrying in ${g}ms.`),setTimeout(s,g)}else n(new Error(`${l} (Max retries ${h} reached)`))}try{let l=t;t&&typeof t=="object"&&!(t instanceof FormData)&&E["Content-Type"]==="application/json"&&(l=JSON.stringify(t)),m.send(l)}catch(l){const g=l instanceof Error?l:new Error(String(l));console.error(`Kelviq SDK (apiRequest): Error sending request to ${o}.`,g),n(new Error(`Failed to send request: ${g.message}`))}}s()})}const L="https://edge.api.kelviq.com/api/v1/",D="https://edge.sandboxapi.kelviq.com/api/v1/",S="entitlements/";class ${constructor(e,i){A(this,"options");A(this,"entitlementsCache",null);A(this,"rawApiResponse",null);A(this,"isFetching",!1);A(this,"inFlightPromise",null);A(this,"readyPromise",null);A(this,"lastFetchError",null);A(this,"apiRequestService");if(!e||!e.customerId)throw new Error("[Kelviq SDK] KelviqClient: CustomerId is required in options.");this.options={...e,apiConfig:e.apiConfig||{},entitlementsPath:e.entitlementsPath||S},this.apiRequestService=i||C}async fetchAllEntitlements(e=!1){if(this.inFlightPromise&&!e)return this.inFlightPromise;if(this.entitlementsCache&&!e)return Promise.resolve(this.entitlementsCache);this.isFetching=!0,this.lastFetchError=null;const{apiUrl:i=this.options.environment==="sandbox"?D:L,entitlementsPath:t,customerId:u,accessToken:h,apiConfig:y,onError:r}=this.options,p=i.replace(/\/$/,""),o=t.replace(/^\//,""),d=`${p}/${o}`,R={customer_id:u};return this.inFlightPromise=this.apiRequestService({url:d,method:"GET",queryParams:R,accessToken:h,...y||{}}).then(n=>{if(n&&n.data&&Array.isArray(n.data.entitlements))return this.rawApiResponse=n.data,this.entitlementsCache=f(n.data),this.isFetching=!1,this.inFlightPromise=null,this.entitlementsCache;{const s=new Error("[Kelviq SDK] Received empty, malformed, or invalid data structure from entitlements API.");throw this.lastFetchError=s,this.isFetching=!1,this.inFlightPromise=null,this.entitlementsCache=null,this.rawApiResponse=null,r&&r(s),s}}).catch(n=>{const s=n instanceof Error?n:new Error(String(n));throw this.lastFetchError=s,this.isFetching=!1,this.inFlightPromise=null,this.entitlementsCache=null,this.rawApiResponse=null,r&&r(s),s}),this.inFlightPromise}getEntitlement(e){return this.entitlementsCache?this.entitlementsCache[e]??null:(console.warn(`[Kelviq SDK] getEntitlement: Entitlements not fetched or cache is empty for featureId '${e}'. Call fetchAllEntitlements() first.`),null)}getEntitlements(){return this.entitlementsCache}getRawEntitlement(e){return this.rawApiResponse?this.rawApiResponse.entitlements.filter(i=>i.featureId===e):(console.warn(`[Kelviq SDK] getRawEntitlement: Entitlements not fetched for featureId '${e}'. Call fetchAllEntitlements() first.`),null)}getRawEntitlements(){return this.rawApiResponse}hasAccess(e){if(!this.entitlementsCache)return console.warn(`[Kelviq SDK] hasAccess: Entitlements not fetched yet for featureId '${e}'. Call fetchAllEntitlements() first. Returning false.`),!1;const i=this.entitlementsCache[e];return i?i.hasAccess:!1}isLoading(){return this.isFetching}getLastError(){return this.lastFetchError}ready(){return this.readyPromise??Promise.resolve()}_setReadyPromise(e){this.readyPromise=e}clearCache(){this.entitlementsCache=null,this.rawApiResponse=null,this.isFetching=!1,this.lastFetchError=null,console.log("[Kelviq SDK] Entitlement cache cleared.")}}function U(a,e){const i={...a,initializeAndFetch:a.initializeAndFetch??!0,apiConfig:a.apiConfig||{},environment:a.environment||"production",entitlementsPath:a.entitlementsPath||S},t=new $(i,e);if(i.initializeAndFetch===!0){const u=t.fetchAllEntitlements().catch(h=>{console.error("[Kelviq SDK] Initial fetch on client creation failed:",h.message)});t._setReadyPromise(u.then(()=>{}))}return t}return U});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kelviq/js-sdk",
3
- "version": "1.0.0",
3
+ "version": "2.0.0-beta",
4
4
  "module": "./dist/kelviq-js-sdk.js",
5
5
  "type": "module",
6
6
  "files": [
@@ -30,7 +30,9 @@
30
30
  "format:styles": "stylelint ./**/*.{css,scss} --fix",
31
31
  "format": "npm run format:scripts && npm run format:styles",
32
32
  "prepare": "husky && echo 'npx lint-staged' > .husky/pre-commit && git add .husky/pre-commit",
33
- "uninstall-husky": "npm uninstall husky --no-save && git config --unset core.hooksPath && npx rimraf .husky"
33
+ "uninstall-husky": "npm uninstall husky --no-save && git config --unset core.hooksPath && npx rimraf .husky",
34
+ "publish:stable": "./publish.sh",
35
+ "publish:beta": "./publish.sh beta"
34
36
  },
35
37
  "devDependencies": {
36
38
  "@types/jsdom": "^21.1.7",