@entitlehub/sdk 0.2.0 → 0.3.0

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/CHANGELOG.md CHANGED
@@ -2,30 +2,42 @@
2
2
 
3
3
  All notable changes to `@entitlehub/sdk`. Adheres to [Semantic Versioning](https://semver.org/).
4
4
 
5
- ## 0.2.0 2026-07-18
5
+ ## 0.3.0 (2026-09-19)
6
6
 
7
7
  ### Added
8
- - Client `EntitleHub.reportPurchase(...)` report a **validated** purchase (Apple JWS / Google
8
+ - Catalog API on `EntitleHubServer`: `eh.entitlements`, `eh.products`, `eh.offerings`. Create,
9
+ update, archive and delete entitlements, products and offerings; attach/detach mappings from
10
+ either side; manage paywall packages. Refs take ids or the key / SKU / identifier you already
11
+ have. See [the Catalog API docs](https://entitlehub.com/docs/catalog-api).
12
+ - `ensure(...)` on entitlements and products: create, or return the existing row, so a catalog
13
+ setup script can run on every deploy.
14
+ - `EntitleHubError.apiCode` and `.body`: the API's machine-readable error code (e.g.
15
+ `product_exists`, `ambiguous_product`, `rate_limited`) and the full error payload.
16
+
17
+ ## 0.2.0 (2026-07-18)
18
+
19
+ ### Added
20
+ - Client `EntitleHub.reportPurchase(...)`: report a **validated** purchase (Apple JWS / Google
9
21
  token / Stripe subscription) directly from the app with your publishable key; updates the cache
10
22
  and notifies listeners. Validation makes a forged receipt impossible, so no secret key is needed
11
23
  on the client. (The trusted server-report mode still requires a secret key.)
12
24
  - Powers the new [`@entitlehub/react-native`](https://www.npmjs.com/package/@entitlehub/react-native)
13
25
  purchase SDK.
14
26
 
15
- ## 0.1.3 2026-07-18
27
+ ## 0.1.3 (2026-07-18)
16
28
 
17
29
  ### Added
18
- - `reportPurchase` accepts `stripeSubscriptionId` EntitleHub validates the subscription against
30
+ - `reportPurchase` accepts `stripeSubscriptionId`, EntitleHub validates the subscription against
19
31
  your Stripe secret key (active/trialing → granted, current period end as the expiry).
20
32
 
21
- ## 0.1.2 2026-07-18
33
+ ## 0.1.2 (2026-07-18)
22
34
 
23
35
  ### Changed
24
36
  - The SDK now lives in its own public repository:
25
37
  [github.com/DanCue44/entitlehub-sdk](https://github.com/DanCue44/entitlehub-sdk) (MIT). Open-source
26
38
  SDK, closed backend. `repository` / `bugs` metadata point there.
27
39
 
28
- ## 0.1.1 2026-07-18
40
+ ## 0.1.1 (2026-07-18)
29
41
 
30
42
  ### Added
31
43
  - README badges (npm version, types, zero-deps, license) and docs links.
@@ -36,14 +48,14 @@ All notable changes to `@entitlehub/sdk`. Adheres to [Semantic Versioning](https
36
48
  + `isSubscription`), Apple StoreKit 2 (validated via `signedTransaction`), and trusted
37
49
  server-report.
38
50
 
39
- ## 0.1.0 2026-07-18
51
+ ## 0.1.0 (2026-07-18)
40
52
 
41
53
  Initial release.
42
54
 
43
- - **`EntitleHub`** (client, publishable key) `getCustomerInfo`, `isEntitled`,
55
+ - **`EntitleHub`** (client, publishable key): `getCustomerInfo`, `isEntitled`,
44
56
  `checkEntitlement`, `getOfferings`, `logIn` / `logOut`, `addCustomerInfoUpdateListener`.
45
57
  Cached CustomerInfo; safe for browsers, React Native, and Expo.
46
- - **`EntitleHubServer`** (server, secret key) `reportPurchase` (Apple JWS / Google Play
58
+ - **`EntitleHubServer`** (server, secret key): `reportPurchase` (Apple JWS / Google Play
47
59
  token / trusted), `grantEntitlement`, `getCustomerInfo`, `check`.
48
60
  - **`CustomerInfo`** ergonomic wrapper; **`EntitleHubError`** with `.status` / `.code`.
49
61
  - Zero dependencies; ESM + CJS + bundled type declarations.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](https://www.npmjs.com/package/@entitlehub/sdk)
6
6
  [![license MIT](https://img.shields.io/npm/l/@entitlehub/sdk)](./LICENSE)
7
7
 
8
- One entitlement API across **App Store, Google Play, Stripe, and web** for browsers, React Native, Expo, and Node. Stop checking raw product IDs; ask *"what is this user entitled to right now?"*
8
+ One entitlement API across **App Store, Google Play, Stripe, and web**, for browsers, React Native, Expo, and Node. Stop checking raw product IDs; ask *"what is this user entitled to right now?"*
9
9
 
10
10
  > Docs: **[entitlehub.com/docs](https://entitlehub.com/docs)** · [REST API](https://entitlehub.com/docs/api) · [Migrating from RevenueCat](https://entitlehub.com/docs/migrating-from-revenuecat)
11
11
 
@@ -15,7 +15,7 @@ npm install @entitlehub/sdk
15
15
 
16
16
  Zero dependencies. Uses the global `fetch` (Node 18+, all modern browsers, React Native, Expo).
17
17
 
18
- ## Client (in your app) publishable key
18
+ ## Client (in your app) (publishable key)
19
19
 
20
20
  Reads only. Safe to ship in your app. **Never put a secret (`sk_`) key here.**
21
21
 
@@ -66,7 +66,7 @@ eh.logOut(); // forget cached info
66
66
  | `getOfferings()` | `Offerings` | Entitlements + products, for building a paywall. |
67
67
  | `addCustomerInfoUpdateListener(fn)` | `() => void` | Returns an unsubscribe. |
68
68
 
69
- ## Server (in your backend) secret key
69
+ ## Server (in your backend) (secret key)
70
70
 
71
71
  Report purchases and grant entitlements. **Server only.**
72
72
 
@@ -76,17 +76,17 @@ import { EntitleHubServer } from "@entitlehub/sdk";
76
76
  const eh = new EntitleHubServer({ apiKey: process.env.ENTITLEHUB_SECRET_KEY! });
77
77
 
78
78
  // After the store purchase is confirmed on-device, report it from your server.
79
- // Google Play validated (EntitleHub verifies the token with your Play service account):
79
+ // Google Play: validated (EntitleHub verifies the token with your Play service account):
80
80
  const info = await eh.reportPurchase(userId, {
81
81
  store: "play", storeProductId: "pro_monthly",
82
82
  purchaseToken: playToken, isSubscription: true,
83
83
  });
84
84
  info.isActive("pro"); // → true
85
85
 
86
- // Apple StoreKit 2 validated (product / environment read from the signed JWS):
86
+ // Apple StoreKit 2: validated (product / environment read from the signed JWS):
87
87
  await eh.reportPurchase(userId, { store: "app_store", storeProductId: "", signedTransaction: jws });
88
88
 
89
- // Trusted server-report (no store validation only if you validated the receipt elsewhere):
89
+ // Trusted server-report (no store validation, only if you validated the receipt elsewhere):
90
90
  await eh.reportPurchase(userId, { store: "app_store", storeProductId: "pro_monthly" });
91
91
 
92
92
  // Grant directly (promo / comp / support), no purchase:
@@ -99,6 +99,41 @@ await eh.grantEntitlement(userId, "pro", { durationDays: 30 });
99
99
  | `grantEntitlement(userId, id, { durationDays?, isSandbox? })` | `CustomerInfo` |
100
100
  | `getCustomerInfo(userId)` / `check(userId, id)` | `CustomerInfo` / `CheckResult` |
101
101
 
102
+ ### Catalog: entitlements, products, offerings
103
+
104
+ Set up and change your catalog from code. Refs take the id **or** the key / SKU / identifier you
105
+ already have. Every create has an `ensure` twin that returns the existing row instead of failing,
106
+ so a setup script can run on every deploy.
107
+
108
+ ```ts
109
+ await eh.entitlements.ensure({ key: "pro", name: "Pro" });
110
+
111
+ await eh.products.ensure({
112
+ store: "stripe", storeProductId: "price_1Q...", type: "subscription", duration: "P1M",
113
+ priceMicros: 9_990_000, entitlements: ["pro"],
114
+ });
115
+
116
+ // Remap (future purchases only; access already granted is untouched):
117
+ await eh.products.update({ store: "play", storeProductId: "pro_monthly" }, { entitlements: ["pro", "team"] });
118
+ await eh.entitlements.attachProducts("team", ["price_1Q..."]);
119
+
120
+ // Retire a SKU you no longer sell. Existing subscribers keep access and renewals:
121
+ await eh.products.archive("price_old");
122
+
123
+ await eh.offerings.create({
124
+ identifier: "default", isCurrent: true, metadata: { headline: "Go Pro" },
125
+ packages: [{ identifier: "monthly", product: "price_1Q..." }],
126
+ });
127
+ ```
128
+
129
+ | Namespace | Methods |
130
+ |---|---|
131
+ | `eh.entitlements` | `list` `get` `create` `ensure` `update` `delete` `products` `attachProducts` `detachProducts` `archive` `unarchive` |
132
+ | `eh.products` | `list` `get` `create` `ensure` `update` `delete` `attachEntitlements` `detachEntitlements` `archive` `unarchive` |
133
+ | `eh.offerings` | `list` `get` `create` `update` `delete` `archive` `unarchive` `packages` `addPackage` `updatePackage` `deletePackage` |
134
+
135
+ Full reference: [entitlehub.com/docs/catalog-api](https://entitlehub.com/docs/catalog-api).
136
+
102
137
  ## Already have an IAP setup?
103
138
 
104
139
  Keep making the actual store purchase with your existing IAP flow (StoreKit / Play Billing /
@@ -108,6 +143,8 @@ with `getCustomerInfo()`. See the full guide at [entitlehub.com/docs](https://en
108
143
  ## Errors
109
144
 
110
145
  Every failure throws an `EntitleHubError` with `.status` and `.code` (`auth` | `http` | `network` | `config`).
146
+ When the API sends its own machine-readable code it is on `.apiCode` (e.g. `product_exists`,
147
+ `ambiguous_product`, `rate_limited`), with the full payload on `.body`.
111
148
 
112
149
  ```ts
113
150
  import { EntitleHubError } from "@entitlehub/sdk";
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var d=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var x=(n,t)=>{for(var e in t)d(n,e,{get:t[e],enumerable:!0})},P=(n,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of _(t))!k.call(n,r)&&r!==e&&d(n,r,{get:()=>t[r],enumerable:!(s=C(t,r))||s.enumerable});return n};var E=n=>P(d({},"__esModule",{value:!0}),n);var S={};x(S,{CustomerInfo:()=>o,EntitleHub:()=>l,EntitleHubError:()=>i,EntitleHubServer:()=>f});module.exports=E(S);var o=class{constructor(t){this.appUserId=t.app_user_id,this.entitlements=t.active_entitlements??[];let e={};for(let s of this.entitlements)e[s.entitlement]=s;this.active=e}isActive(t){return!!this.active[t]}get activeEntitlementIds(){return Object.keys(this.active)}expirationDate(t){let e=this.active[t];if(e)return e.expires_at?new Date(e.expires_at):null}};var i=class extends Error{constructor(t,e,s="error"){super(t),this.name="EntitleHubError",this.status=e,this.code=s}},T=n=>new Promise(t=>setTimeout(t,n));async function c(n,t,e,s){let r=n.fetchImpl??globalThis.fetch;if(!r)throw new i("No fetch implementation available. On Node <18, pass fetchImpl.",0,"no_fetch");let h=`${n.baseUrl.replace(/\/$/,"")}${e}`,b=t==="GET"?n.retries??2:0,m;for(let u=0;u<=b;u++){let y=new AbortController,g=n.timeoutMs?setTimeout(()=>y.abort(),n.timeoutMs):void 0;try{let a=await r(h,{method:t,headers:{Authorization:`Bearer ${n.apiKey}`,...s?{"Content-Type":"application/json"}:{}},body:s?JSON.stringify(s):void 0,signal:y.signal}),I=await a.text(),p=I?v(I):{};if(!a.ok){let w=`HTTP ${a.status}`;throw p&&typeof p=="object"&&"error"in p&&(w=String(p.error)),new i(w,a.status,a.status===401||a.status===403?"auth":"http")}return p}catch(a){if(m=a,a instanceof i)throw a;u<b&&await T(250*(u+1))}finally{g&&clearTimeout(g)}}throw new i(`Network request failed: ${String(m?.message??m)}`,0,"network")}function v(n){try{return JSON.parse(n)}catch{return{}}}var l=class{constructor(t){this.listeners=new Set;if(!t.apiKey)throw new i("apiKey is required.",0,"config");if(!t.appUserId)throw new i("appUserId is required.",0,"config");if(t.apiKey.startsWith("sk_"))throw new i("Never use a secret (sk_) key in the client SDK \u2014 use your publishable (pk_) key.",0,"config");this.appUserId=t.appUserId,this.cacheTtlMs=t.cacheTtlMs??5*6e4,this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3}}get currentAppUserId(){return this.appUserId}async logIn(t){return this.appUserId=t,this.cached=void 0,this.getCustomerInfo({fetchPolicy:"network-only"})}logOut(){this.cached=void 0}async getCustomerInfo(t={}){if((t.fetchPolicy??"cache-first")==="cache-first"&&this.cached&&Date.now()-this.cached.at<this.cacheTtlMs)return this.cached.info;let s=await c(this.http,"GET",`/subscribers/${encodeURIComponent(this.appUserId)}`),r=new o(s);this.cached={info:r,at:Date.now()};for(let h of this.listeners)try{h(r)}catch{}return r}async isEntitled(t){return(await this.getCustomerInfo()).isActive(t)}async checkEntitlement(t,e={}){return c(this.http,"POST","/check",{app_user_id:this.appUserId,entitlement:t,sandbox:!!e.sandbox})}async getOfferings(){return c(this.http,"GET","/offerings")}async reportPurchase(t){let e=await c(this.http,"POST",`/subscribers/${encodeURIComponent(this.appUserId)}/purchases`,{store:t.store??"",store_product_id:t.storeProductId??"",is_sandbox:!!t.isSandbox,purchase_token:t.purchaseToken??"",is_subscription:!!t.isSubscription,signed_transaction:t.signedTransaction??"",stripe_subscription_id:t.stripeSubscriptionId??""}),s=new o(e);this.cached={info:s,at:Date.now()};for(let r of this.listeners)try{r(s)}catch{}return s}addCustomerInfoUpdateListener(t){return this.listeners.add(t),()=>this.listeners.delete(t)}};var f=class{constructor(t){if(!t.apiKey)throw new i("apiKey is required.",0,"config");if(!t.apiKey.startsWith("sk_"))throw new i("EntitleHubServer needs a secret (sk_) key. For client reads, use the EntitleHub class with a pk_ key.",0,"config");this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3}}async reportPurchase(t,e){let s=await c(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/purchases`,{store:e.store,store_product_id:e.storeProductId,is_sandbox:!!e.isSandbox,transaction_id:e.transactionId??"",purchase_token:e.purchaseToken??"",is_subscription:!!e.isSubscription,signed_transaction:e.signedTransaction??"",stripe_subscription_id:e.stripeSubscriptionId??""});return new o(s)}async grantEntitlement(t,e,s={}){let r=await c(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/grant`,{entitlement:e,duration_days:s.durationDays??0,is_sandbox:!!s.isSandbox});return new o(r)}async getCustomerInfo(t){let e=await c(this.http,"GET",`/subscribers/${encodeURIComponent(t)}`);return new o(e)}async check(t,e,s={}){return c(this.http,"POST","/check",{app_user_id:t,entitlement:e,sandbox:!!s.sandbox})}};0&&(module.exports={CustomerInfo,EntitleHub,EntitleHubError,EntitleHubServer});
1
+ "use strict";var I=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var A=(r,t)=>{for(var e in t)I(r,e,{get:t[e],enumerable:!0})},D=(r,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of H(t))!U.call(r,s)&&s!==e&&I(r,s,{get:()=>t[s],enumerable:!(n=R(t,s))||n.enumerable});return r};var q=r=>D(I({},"__esModule",{value:!0}),r);var j={};A(j,{CustomerInfo:()=>c,EntitleHub:()=>y,EntitleHubError:()=>a,EntitleHubServer:()=>b,EntitlementsApi:()=>l,OfferingsApi:()=>m,ProductsApi:()=>g});module.exports=q(j);var c=class{constructor(t){this.appUserId=t.app_user_id,this.entitlements=t.active_entitlements??[];let e={};for(let n of this.entitlements)e[n.entitlement]=n;this.active=e}isActive(t){return!!this.active[t]}get activeEntitlementIds(){return Object.keys(this.active)}expirationDate(t){let e=this.active[t];if(e)return e.expires_at?new Date(e.expires_at):null}};var S="0.3.0";var a=class extends Error{constructor(t,e,n="error",s){super(t),this.name="EntitleHubError",this.status=e,this.code=n,this.body=s,this.apiCode=typeof s?.code=="string"?s.code:void 0}},M=r=>new Promise(t=>setTimeout(t,r));async function i(r,t,e,n){let s=r.fetchImpl??globalThis.fetch;if(!s)throw new a("No fetch implementation available. On Node <18, pass fetchImpl.",0,"no_fetch");let C=`${r.baseUrl.replace(/\/$/,"")}${e}`,k=t==="GET"?r.retries??2:0,_;for(let f=0;f<=k;f++){let T=new AbortController,w=r.timeoutMs?setTimeout(()=>T.abort(),r.timeoutMs):void 0;try{let u=await s(C,{method:t,headers:{Authorization:`Bearer ${r.apiKey}`,"X-EntitleHub-Client":r.client??`${"js"}/${S}`,...n?{"Content-Type":"application/json"}:{}},body:n?JSON.stringify(n):void 0,signal:T.signal}),O=await u.text(),h=O?K(O):{};if(!u.ok){let v=`HTTP ${u.status}`,P=h&&typeof h=="object"?h:void 0;throw P&&"error"in P&&(v=String(P.error)),new a(v,u.status,u.status===401||u.status===403?"auth":"http",P)}return h}catch(u){if(_=u,u instanceof a)throw u;f<k&&await M(250*(f+1))}finally{w&&clearTimeout(w)}}throw new a(`Network request failed: ${String(_?.message??_)}`,0,"network")}function K(r){try{return JSON.parse(r)}catch{return{}}}var y=class{constructor(t){this.listeners=new Set;if(!t.apiKey)throw new a("apiKey is required.",0,"config");if(!t.appUserId)throw new a("appUserId is required.",0,"config");if(t.apiKey.startsWith("sk_"))throw new a("Never use a secret (sk_) key in the client SDK. Use your publishable (pk_) key.",0,"config");this.appUserId=t.appUserId,this.cacheTtlMs=t.cacheTtlMs??5*6e4,this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3,client:t.client}}get currentAppUserId(){return this.appUserId}async logIn(t){return this.appUserId=t,this.cached=void 0,this.getCustomerInfo({fetchPolicy:"network-only"})}logOut(){this.cached=void 0}async getCustomerInfo(t={}){if((t.fetchPolicy??"cache-first")==="cache-first"&&this.cached&&Date.now()-this.cached.at<this.cacheTtlMs)return this.cached.info;let n=await i(this.http,"GET",`/subscribers/${encodeURIComponent(this.appUserId)}`),s=new c(n);this.cached={info:s,at:Date.now()};for(let C of this.listeners)try{C(s)}catch{}return s}async isEntitled(t){return(await this.getCustomerInfo()).isActive(t)}async checkEntitlement(t,e={}){return i(this.http,"POST","/check",{app_user_id:this.appUserId,entitlement:t,sandbox:!!e.sandbox})}async getOfferings(){return i(this.http,"GET","/offerings")}async reportPurchase(t){let e=await i(this.http,"POST",`/subscribers/${encodeURIComponent(this.appUserId)}/purchases`,{store:t.store??"",store_product_id:t.storeProductId??"",is_sandbox:!!t.isSandbox,purchase_token:t.purchaseToken??"",is_subscription:!!t.isSubscription,signed_transaction:t.signedTransaction??"",stripe_subscription_id:t.stripeSubscriptionId??""}),n=new c(e);this.cached={info:n,at:Date.now()};for(let s of this.listeners)try{s(n)}catch{}return n}addCustomerInfoUpdateListener(t){return this.listeners.add(t),()=>this.listeners.delete(t)}};var o=encodeURIComponent;function d(r){return typeof r=="string"?r:"id"in r?{id:r.id}:{store:r.store,store_product_id:r.storeProductId}}function p(r){return typeof r=="string"?{seg:o(r),qs:""}:"id"in r?{seg:o(r.id),qs:""}:{seg:o(r.storeProductId),qs:`?store=${o(r.store)}`}}var E=r=>r?.includeArchived?"include_archived=true":"";async function $(r,t){try{return await r()}catch(e){if(e instanceof a&&e.apiCode===t&&e.body?.existing)return e.body.existing;throw e}}var l=class{constructor(t){this.http=t}async list(t){let e=E(t);return(await i(this.http,"GET",`/entitlements${e?`?${e}`:""}`)).entitlements}get(t){return i(this.http,"GET",`/entitlements/${o(t)}`)}create(t){return i(this.http,"POST","/entitlements",{key:t.key,name:t.name,description:t.description,products:t.products?.map(d)})}ensure(t){return $(()=>this.create(t),"entitlement_exists")}update(t,e){return i(this.http,"PATCH",`/entitlements/${o(t)}`,{name:e.name,description:e.description,products:e.products?.map(d)})}delete(t){return i(this.http,"DELETE",`/entitlements/${o(t)}`)}async products(t,e){let n=E(e);return(await i(this.http,"GET",`/entitlements/${o(t)}/products${n?`?${n}`:""}`)).products}attachProducts(t,e){return i(this.http,"POST",`/entitlements/${o(t)}/attach_products`,{products:e.map(d)})}detachProducts(t,e){return i(this.http,"POST",`/entitlements/${o(t)}/detach_products`,{products:e.map(d)})}archive(t){return i(this.http,"POST",`/entitlements/${o(t)}/archive`)}unarchive(t){return i(this.http,"POST",`/entitlements/${o(t)}/unarchive`)}},g=class{constructor(t){this.http=t}async list(t){let e=new URLSearchParams;t?.store&&e.set("store",t.store),t?.entitlement&&e.set("entitlement",t.entitlement),t?.includeArchived&&e.set("include_archived","true");let n=e.toString();return(await i(this.http,"GET",`/products${n?`?${n}`:""}`)).products}get(t){let{seg:e,qs:n}=p(t);return i(this.http,"GET",`/products/${e}${n}`)}create(t){return i(this.http,"POST","/products",{store:t.store,store_product_id:t.storeProductId,type:t.type,duration:t.duration,display_name:t.displayName,price_micros:t.priceMicros,currency:t.currency,entitlements:t.entitlements})}ensure(t){return $(()=>this.create(t),"product_exists")}update(t,e){let{seg:n,qs:s}=p(t);return i(this.http,"PATCH",`/products/${n}${s}`,{display_name:e.displayName,price_micros:e.priceMicros,currency:e.currency,entitlements:e.entitlements})}delete(t){let{seg:e,qs:n}=p(t);return i(this.http,"DELETE",`/products/${e}${n}`)}attachEntitlements(t,e){let{seg:n,qs:s}=p(t);return i(this.http,"POST",`/products/${n}/attach_entitlements${s}`,{entitlements:e})}detachEntitlements(t,e){let{seg:n,qs:s}=p(t);return i(this.http,"POST",`/products/${n}/detach_entitlements${s}`,{entitlements:e})}archive(t){let{seg:e,qs:n}=p(t);return i(this.http,"POST",`/products/${e}/archive${n}`)}unarchive(t){let{seg:e,qs:n}=p(t);return i(this.http,"POST",`/products/${e}/unarchive${n}`)}},m=class{constructor(t){this.http=t}async list(t){let e=E(t);return(await i(this.http,"GET",`/offerings/all${e?`?${e}`:""}`)).offerings}get(t){return i(this.http,"GET",`/offerings/${o(t)}`)}create(t){return i(this.http,"POST","/offerings",{identifier:t.identifier,display_name:t.displayName,is_current:t.isCurrent,metadata:t.metadata,packages:t.packages?.map(x)})}update(t,e){return i(this.http,"PATCH",`/offerings/${o(t)}`,{display_name:e.displayName,is_current:e.isCurrent,metadata:e.metadata})}delete(t){return i(this.http,"DELETE",`/offerings/${o(t)}`)}archive(t){return i(this.http,"POST",`/offerings/${o(t)}/archive`)}unarchive(t){return i(this.http,"POST",`/offerings/${o(t)}/unarchive`)}async packages(t){return(await i(this.http,"GET",`/offerings/${o(t)}/packages`)).packages}addPackage(t,e){return i(this.http,"POST",`/offerings/${o(t)}/packages`,x(e))}updatePackage(t,e,n){return i(this.http,"PATCH",`/offerings/${o(t)}/packages/${o(e)}`,{display_name:n.displayName,position:n.position,product:n.product===void 0?void 0:d(n.product)})}deletePackage(t,e){return i(this.http,"DELETE",`/offerings/${o(t)}/packages/${o(e)}`)}};function x(r){return{identifier:r.identifier,product:d(r.product),position:r.position,display_name:r.displayName}}var b=class{constructor(t){if(!t.apiKey)throw new a("apiKey is required.",0,"config");if(!t.apiKey.startsWith("sk_"))throw new a("EntitleHubServer needs a secret (sk_) key. For client reads, use the EntitleHub class with a pk_ key.",0,"config");this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3},this.entitlements=new l(this.http),this.products=new g(this.http),this.offerings=new m(this.http)}async reportPurchase(t,e){let n=await i(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/purchases`,{store:e.store,store_product_id:e.storeProductId,is_sandbox:!!e.isSandbox,transaction_id:e.transactionId??"",purchase_token:e.purchaseToken??"",is_subscription:!!e.isSubscription,signed_transaction:e.signedTransaction??"",stripe_subscription_id:e.stripeSubscriptionId??""});return new c(n)}async grantEntitlement(t,e,n={}){let s=await i(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/grant`,{entitlement:e,duration_days:n.durationDays??0,is_sandbox:!!n.isSandbox});return new c(s)}async getCustomerInfo(t){let e=await i(this.http,"GET",`/subscribers/${encodeURIComponent(t)}`);return new c(e)}async check(t,e,n={}){return i(this.http,"POST","/check",{app_user_id:t,entitlement:e,sandbox:!!n.sandbox})}};0&&(module.exports={CustomerInfo,EntitleHub,EntitleHubError,EntitleHubServer,EntitlementsApi,OfferingsApi,ProductsApi});
package/dist/index.d.cts CHANGED
@@ -80,7 +80,7 @@ interface PurchaseInput {
80
80
  }
81
81
 
82
82
  /**
83
- * Ergonomic wrapper around a subscriber's active entitlements the object your UI reads.
83
+ * Ergonomic wrapper around a subscriber's active entitlements, the object your UI reads.
84
84
  *
85
85
  * const info = await eh.getCustomerInfo();
86
86
  * if (info.isActive("pro")) showProFeatures();
@@ -112,11 +112,16 @@ interface EntitleHubOptions {
112
112
  fetchImpl?: typeof fetch;
113
113
  /** How long a cached CustomerInfo is considered fresh, ms (default 5 min). */
114
114
  cacheTtlMs?: number;
115
+ /**
116
+ * Identifies the calling SDK as `name/version`, e.g. "react-native/0.1.5". Wrapper SDKs set this
117
+ * so the dashboard can flag an out-of-date SDK. Defaults to this package's own name/version.
118
+ */
119
+ client?: string;
115
120
  }
116
121
  type CustomerInfoListener = (info: CustomerInfo) => void;
117
122
  type FetchPolicy = "cache-first" | "network-only";
118
123
  /**
119
- * The client-side EntitleHub SDK configure once, then ask "what is this user entitled to?".
124
+ * The client-side EntitleHub SDK: configure once, then ask "what is this user entitled to?".
120
125
  * Safe for browsers, React Native, and Expo (publishable key only; never ship a secret key).
121
126
  *
122
127
  * const eh = new EntitleHub({ apiKey: "pk_live_…", appUserId: user.id });
@@ -146,7 +151,7 @@ declare class EntitleHub {
146
151
  checkEntitlement(entitlementId: string, opts?: {
147
152
  sandbox?: boolean;
148
153
  }): Promise<CheckResult>;
149
- /** The project's entitlements + products for building a paywall. */
154
+ /** The project's entitlements + products: for building a paywall. */
150
155
  getOfferings(): Promise<Offerings>;
151
156
  /**
152
157
  * Report a VALIDATED store purchase for the current user and return the updated entitlements.
@@ -169,30 +174,273 @@ declare class EntitleHub {
169
174
  addCustomerInfoUpdateListener(listener: CustomerInfoListener): () => void;
170
175
  }
171
176
 
177
+ declare class EntitleHubError extends Error {
178
+ readonly status: number;
179
+ /** SDK-level category: config | auth | http | network | no_fetch. */
180
+ readonly code: string;
181
+ /**
182
+ * The API's own machine-readable code, when it sent one (e.g. "entitlement_exists",
183
+ * "ambiguous_product", "rate_limited"). Branch on this, not on the message text.
184
+ */
185
+ readonly apiCode?: string;
186
+ /** The full error body, for context the API attaches: `existing`, `candidates`, `unknown`, `grants`… */
187
+ readonly body?: Record<string, unknown>;
188
+ constructor(message: string, status: number, code?: string, body?: Record<string, unknown>);
189
+ }
190
+ interface HttpOptions {
191
+ baseUrl: string;
192
+ apiKey: string;
193
+ /** Network-error retries for idempotent reads (default 2). */
194
+ retries?: number;
195
+ timeoutMs?: number;
196
+ fetchImpl?: typeof fetch;
197
+ /** Identifies the calling SDK, e.g. "react-native/0.1.5". Reported to the dashboard. */
198
+ client?: string;
199
+ }
200
+
201
+ type ProductType = "subscription" | "non_consumable" | "consumable";
202
+ type Duration = "P1W" | "P1M" | "P3M" | "P6M" | "P1Y";
203
+ /** A product by id, by SKU (when it is on one store), or by explicit store + SKU. */
204
+ type ProductRef = string | {
205
+ id: string;
206
+ } | {
207
+ store: Store;
208
+ storeProductId: string;
209
+ };
210
+ interface CatalogEntitlement {
211
+ object: "entitlement";
212
+ id: string;
213
+ key: string;
214
+ name: string;
215
+ description: string;
216
+ products: {
217
+ id: string;
218
+ store: Store;
219
+ store_product_id: string;
220
+ }[];
221
+ active_grants: number;
222
+ created_at: string | null;
223
+ archived_at: string | null;
224
+ }
225
+ interface CatalogProduct {
226
+ object: "product";
227
+ id: string;
228
+ store: Store;
229
+ store_product_id: string;
230
+ type: ProductType;
231
+ duration: Duration | null;
232
+ display_name: string;
233
+ price_micros: number;
234
+ currency: string;
235
+ entitlements: string[];
236
+ active_grants: number;
237
+ archived_at: string | null;
238
+ }
239
+ interface CatalogPackage {
240
+ object: "package";
241
+ id: string;
242
+ identifier: string;
243
+ display_name: string;
244
+ position: number;
245
+ store: Store;
246
+ store_product_id: string;
247
+ product: CatalogProduct | null;
248
+ }
249
+ interface CatalogOffering {
250
+ object: "offering";
251
+ id: string;
252
+ identifier: string;
253
+ display_name: string;
254
+ is_current: boolean;
255
+ metadata: Record<string, unknown> | null;
256
+ packages: CatalogPackage[];
257
+ created_at: string;
258
+ archived_at: string | null;
259
+ }
260
+ /** An offering as the SDK-facing /v1/offerings/all returns it: packages joined with their product. */
261
+ interface ListedOffering {
262
+ id: string;
263
+ identifier: string;
264
+ display_name: string;
265
+ is_current: boolean;
266
+ metadata: Record<string, unknown> | null;
267
+ archived_at: string | null;
268
+ packages: {
269
+ id: string;
270
+ identifier: string;
271
+ display_name: string;
272
+ position: number;
273
+ store: Store;
274
+ store_product_id: string;
275
+ product: {
276
+ display_name: string;
277
+ type: string;
278
+ price_micros: number;
279
+ currency: string;
280
+ entitlements: string[];
281
+ } | null;
282
+ }[];
283
+ }
284
+ interface Deleted {
285
+ object: string;
286
+ id: string;
287
+ deleted: true;
288
+ }
289
+ interface CreateEntitlementInput {
290
+ /** The exact string your app checks with isEntitled(). Can't change later. */
291
+ key: string;
292
+ name?: string;
293
+ description?: string;
294
+ /** Products that grant it, attached in the same call. */
295
+ products?: ProductRef[];
296
+ }
297
+ interface CreateProductInput {
298
+ store: Store;
299
+ storeProductId: string;
300
+ type: ProductType;
301
+ /** Required for subscriptions. */
302
+ duration?: Duration;
303
+ displayName?: string;
304
+ /** Integer micros (4.99 → 4_990_000). */
305
+ priceMicros?: number;
306
+ currency?: string;
307
+ /** Entitlement keys this product grants. */
308
+ entitlements?: string[];
309
+ }
310
+ interface PackageInput {
311
+ identifier: string;
312
+ product: ProductRef;
313
+ position?: number;
314
+ displayName?: string;
315
+ }
316
+ declare class EntitlementsApi {
317
+ private http;
318
+ constructor(http: HttpOptions);
319
+ list(opts?: {
320
+ includeArchived?: boolean;
321
+ }): Promise<CatalogEntitlement[]>;
322
+ /** By id or key. */
323
+ get(ref: string): Promise<CatalogEntitlement>;
324
+ create(input: CreateEntitlementInput): Promise<CatalogEntitlement>;
325
+ /** Create, or return the entitlement that already has this key. */
326
+ ensure(input: CreateEntitlementInput): Promise<CatalogEntitlement>;
327
+ /** `products`, when given, REPLACES the set of products that grant it. */
328
+ update(ref: string, patch: {
329
+ name?: string;
330
+ description?: string;
331
+ products?: ProductRef[];
332
+ }): Promise<CatalogEntitlement>;
333
+ /** Refused (409 `entitlement_in_use`) once anyone has been granted it. Archive instead. */
334
+ delete(ref: string): Promise<Deleted>;
335
+ products(ref: string, opts?: {
336
+ includeArchived?: boolean;
337
+ }): Promise<CatalogProduct[]>;
338
+ attachProducts(ref: string, products: ProductRef[]): Promise<CatalogEntitlement>;
339
+ detachProducts(ref: string, products: ProductRef[]): Promise<CatalogEntitlement>;
340
+ /** Hide it from listings. Never takes access away from anyone who holds it. */
341
+ archive(ref: string): Promise<CatalogEntitlement>;
342
+ unarchive(ref: string): Promise<CatalogEntitlement>;
343
+ }
344
+ declare class ProductsApi {
345
+ private http;
346
+ constructor(http: HttpOptions);
347
+ list(opts?: {
348
+ store?: Store;
349
+ entitlement?: string;
350
+ includeArchived?: boolean;
351
+ }): Promise<CatalogProduct[]>;
352
+ get(ref: ProductRef): Promise<CatalogProduct>;
353
+ create(input: CreateProductInput): Promise<CatalogProduct>;
354
+ /** Create, or return the product that already has this store + SKU. */
355
+ ensure(input: CreateProductInput): Promise<CatalogProduct>;
356
+ /** `entitlements`, when given, REPLACES what it grants (future purchases only). */
357
+ update(ref: ProductRef, patch: {
358
+ displayName?: string;
359
+ priceMicros?: number;
360
+ currency?: string;
361
+ entitlements?: string[];
362
+ }): Promise<CatalogProduct>;
363
+ /** Refused (409 `product_in_use`) once anyone has bought it. Archive instead. */
364
+ delete(ref: ProductRef): Promise<Deleted>;
365
+ attachEntitlements(ref: ProductRef, entitlements: string[]): Promise<CatalogProduct>;
366
+ detachEntitlements(ref: ProductRef, entitlements: string[]): Promise<CatalogProduct>;
367
+ /** Retire a SKU: gone from listings and paywalls; existing subscribers keep access and renewals. */
368
+ archive(ref: ProductRef): Promise<CatalogProduct>;
369
+ unarchive(ref: ProductRef): Promise<CatalogProduct>;
370
+ }
371
+ declare class OfferingsApi {
372
+ private http;
373
+ constructor(http: HttpOptions);
374
+ /** Every offering. Archived ones only with includeArchived. */
375
+ list(opts?: {
376
+ includeArchived?: boolean;
377
+ }): Promise<ListedOffering[]>;
378
+ /** By id or identifier. */
379
+ get(ref: string): Promise<CatalogOffering>;
380
+ create(input: {
381
+ identifier: string;
382
+ displayName?: string;
383
+ isCurrent?: boolean;
384
+ metadata?: Record<string, unknown>;
385
+ packages?: PackageInput[];
386
+ }): Promise<CatalogOffering>;
387
+ /** `isCurrent: true` makes it the offering /current serves. `metadata: null` clears it. */
388
+ update(ref: string, patch: {
389
+ displayName?: string;
390
+ isCurrent?: true;
391
+ metadata?: Record<string, unknown> | null;
392
+ }): Promise<CatalogOffering>;
393
+ delete(ref: string): Promise<Deleted>;
394
+ archive(ref: string): Promise<CatalogOffering>;
395
+ unarchive(ref: string): Promise<CatalogOffering>;
396
+ packages(ref: string): Promise<CatalogPackage[]>;
397
+ addPackage(ref: string, input: PackageInput): Promise<CatalogPackage>;
398
+ /** `pkg` is the package id, or its identifier when that is used on one store only. */
399
+ updatePackage(ref: string, pkg: string, patch: {
400
+ displayName?: string;
401
+ position?: number;
402
+ product?: ProductRef;
403
+ }): Promise<CatalogPackage>;
404
+ deletePackage(ref: string, pkg: string): Promise<Deleted>;
405
+ }
406
+
172
407
  interface EntitleHubServerOptions {
173
- /** Your secret key (sk_live_… / sk_test_…). SERVER ONLY never ship this to a client. */
408
+ /** Your secret key (sk_live_… / sk_test_…). SERVER ONLY, never ship this to a client. */
174
409
  apiKey: string;
175
410
  baseUrl?: string;
176
411
  fetchImpl?: typeof fetch;
177
412
  }
178
413
  /**
179
- * The server-side EntitleHub SDK report purchases and grant entitlements from your backend.
414
+ * The server-side EntitleHub SDK: report purchases and grant entitlements from your backend.
180
415
  * Keep this on your server; it uses a secret key.
181
416
  *
182
417
  * const eh = new EntitleHubServer({ apiKey: process.env.ENTITLEHUB_SECRET_KEY! });
183
418
  * await eh.reportPurchase(userId, { store: "app_store", storeProductId: "pro_monthly" });
419
+ *
420
+ * Manage the catalog from code too (every create has an `ensure` twin that returns the existing
421
+ * row instead of failing, so a setup script can run on every deploy):
422
+ *
423
+ * await eh.entitlements.ensure({ key: "pro", name: "Pro" });
424
+ * await eh.products.ensure({ store: "stripe", storeProductId: "price_1Q…", type: "subscription",
425
+ * duration: "P1M", priceMicros: 9_990_000, entitlements: ["pro"] });
184
426
  */
185
427
  declare class EntitleHubServer {
186
428
  private http;
429
+ /** Catalog API: entitlements. */
430
+ readonly entitlements: EntitlementsApi;
431
+ /** Catalog API: products and their entitlement mappings. */
432
+ readonly products: ProductsApi;
433
+ /** Catalog API: paywall offerings and packages. */
434
+ readonly offerings: OfferingsApi;
187
435
  constructor(opts: EntitleHubServerOptions);
188
436
  /**
189
437
  * Report a completed store purchase → writes the mapped entitlement grant(s). Returns updated info.
190
438
  *
191
439
  * Three modes (pick one):
192
440
  * • Google (validated): { store:"play", storeProductId, purchaseToken, isSubscription }
193
- * • Apple (validated): { signedTransaction } store/product are read from the JWS
194
- * • Stripe (validated): { stripeSubscriptionId } validated via your Stripe secret key
195
- * • Trusted server-report: { store, storeProductId } no store validation; only when you've
441
+ * • Apple (validated): { signedTransaction } , store/product are read from the JWS
442
+ * • Stripe (validated): { stripeSubscriptionId }, validated via your Stripe secret key
443
+ * • Trusted server-report: { store, storeProductId }, no store validation; only when you've
196
444
  * already validated the receipt elsewhere.
197
445
  */
198
446
  reportPurchase(appUserId: string, purchase: PurchaseInput): Promise<CustomerInfo>;
@@ -209,10 +457,4 @@ declare class EntitleHubServer {
209
457
  }): Promise<CheckResult>;
210
458
  }
211
459
 
212
- declare class EntitleHubError extends Error {
213
- readonly status: number;
214
- readonly code: string;
215
- constructor(message: string, status: number, code?: string);
216
- }
217
-
218
- export { type ActiveEntitlement, type CheckResult, CustomerInfo, type CustomerInfoListener, type CustomerInfoResponse, EntitleHub, EntitleHubError, type EntitleHubOptions, EntitleHubServer, type EntitleHubServerOptions, type EntitlementStatus, type FetchPolicy, type OfferingEntitlement, type OfferingProduct, type Offerings, type PurchaseInput, type Store };
460
+ export { type ActiveEntitlement, type CatalogEntitlement, type CatalogOffering, type CatalogPackage, type CatalogProduct, type CheckResult, type CreateEntitlementInput, type CreateProductInput, CustomerInfo, type CustomerInfoListener, type CustomerInfoResponse, type Deleted, type Duration, EntitleHub, EntitleHubError, type EntitleHubOptions, EntitleHubServer, type EntitleHubServerOptions, type EntitlementStatus, EntitlementsApi, type FetchPolicy, type ListedOffering, type OfferingEntitlement, type OfferingProduct, type Offerings, OfferingsApi, type PackageInput, type ProductRef, type ProductType, ProductsApi, type PurchaseInput, type Store };
package/dist/index.d.ts CHANGED
@@ -80,7 +80,7 @@ interface PurchaseInput {
80
80
  }
81
81
 
82
82
  /**
83
- * Ergonomic wrapper around a subscriber's active entitlements the object your UI reads.
83
+ * Ergonomic wrapper around a subscriber's active entitlements, the object your UI reads.
84
84
  *
85
85
  * const info = await eh.getCustomerInfo();
86
86
  * if (info.isActive("pro")) showProFeatures();
@@ -112,11 +112,16 @@ interface EntitleHubOptions {
112
112
  fetchImpl?: typeof fetch;
113
113
  /** How long a cached CustomerInfo is considered fresh, ms (default 5 min). */
114
114
  cacheTtlMs?: number;
115
+ /**
116
+ * Identifies the calling SDK as `name/version`, e.g. "react-native/0.1.5". Wrapper SDKs set this
117
+ * so the dashboard can flag an out-of-date SDK. Defaults to this package's own name/version.
118
+ */
119
+ client?: string;
115
120
  }
116
121
  type CustomerInfoListener = (info: CustomerInfo) => void;
117
122
  type FetchPolicy = "cache-first" | "network-only";
118
123
  /**
119
- * The client-side EntitleHub SDK configure once, then ask "what is this user entitled to?".
124
+ * The client-side EntitleHub SDK: configure once, then ask "what is this user entitled to?".
120
125
  * Safe for browsers, React Native, and Expo (publishable key only; never ship a secret key).
121
126
  *
122
127
  * const eh = new EntitleHub({ apiKey: "pk_live_…", appUserId: user.id });
@@ -146,7 +151,7 @@ declare class EntitleHub {
146
151
  checkEntitlement(entitlementId: string, opts?: {
147
152
  sandbox?: boolean;
148
153
  }): Promise<CheckResult>;
149
- /** The project's entitlements + products for building a paywall. */
154
+ /** The project's entitlements + products: for building a paywall. */
150
155
  getOfferings(): Promise<Offerings>;
151
156
  /**
152
157
  * Report a VALIDATED store purchase for the current user and return the updated entitlements.
@@ -169,30 +174,273 @@ declare class EntitleHub {
169
174
  addCustomerInfoUpdateListener(listener: CustomerInfoListener): () => void;
170
175
  }
171
176
 
177
+ declare class EntitleHubError extends Error {
178
+ readonly status: number;
179
+ /** SDK-level category: config | auth | http | network | no_fetch. */
180
+ readonly code: string;
181
+ /**
182
+ * The API's own machine-readable code, when it sent one (e.g. "entitlement_exists",
183
+ * "ambiguous_product", "rate_limited"). Branch on this, not on the message text.
184
+ */
185
+ readonly apiCode?: string;
186
+ /** The full error body, for context the API attaches: `existing`, `candidates`, `unknown`, `grants`… */
187
+ readonly body?: Record<string, unknown>;
188
+ constructor(message: string, status: number, code?: string, body?: Record<string, unknown>);
189
+ }
190
+ interface HttpOptions {
191
+ baseUrl: string;
192
+ apiKey: string;
193
+ /** Network-error retries for idempotent reads (default 2). */
194
+ retries?: number;
195
+ timeoutMs?: number;
196
+ fetchImpl?: typeof fetch;
197
+ /** Identifies the calling SDK, e.g. "react-native/0.1.5". Reported to the dashboard. */
198
+ client?: string;
199
+ }
200
+
201
+ type ProductType = "subscription" | "non_consumable" | "consumable";
202
+ type Duration = "P1W" | "P1M" | "P3M" | "P6M" | "P1Y";
203
+ /** A product by id, by SKU (when it is on one store), or by explicit store + SKU. */
204
+ type ProductRef = string | {
205
+ id: string;
206
+ } | {
207
+ store: Store;
208
+ storeProductId: string;
209
+ };
210
+ interface CatalogEntitlement {
211
+ object: "entitlement";
212
+ id: string;
213
+ key: string;
214
+ name: string;
215
+ description: string;
216
+ products: {
217
+ id: string;
218
+ store: Store;
219
+ store_product_id: string;
220
+ }[];
221
+ active_grants: number;
222
+ created_at: string | null;
223
+ archived_at: string | null;
224
+ }
225
+ interface CatalogProduct {
226
+ object: "product";
227
+ id: string;
228
+ store: Store;
229
+ store_product_id: string;
230
+ type: ProductType;
231
+ duration: Duration | null;
232
+ display_name: string;
233
+ price_micros: number;
234
+ currency: string;
235
+ entitlements: string[];
236
+ active_grants: number;
237
+ archived_at: string | null;
238
+ }
239
+ interface CatalogPackage {
240
+ object: "package";
241
+ id: string;
242
+ identifier: string;
243
+ display_name: string;
244
+ position: number;
245
+ store: Store;
246
+ store_product_id: string;
247
+ product: CatalogProduct | null;
248
+ }
249
+ interface CatalogOffering {
250
+ object: "offering";
251
+ id: string;
252
+ identifier: string;
253
+ display_name: string;
254
+ is_current: boolean;
255
+ metadata: Record<string, unknown> | null;
256
+ packages: CatalogPackage[];
257
+ created_at: string;
258
+ archived_at: string | null;
259
+ }
260
+ /** An offering as the SDK-facing /v1/offerings/all returns it: packages joined with their product. */
261
+ interface ListedOffering {
262
+ id: string;
263
+ identifier: string;
264
+ display_name: string;
265
+ is_current: boolean;
266
+ metadata: Record<string, unknown> | null;
267
+ archived_at: string | null;
268
+ packages: {
269
+ id: string;
270
+ identifier: string;
271
+ display_name: string;
272
+ position: number;
273
+ store: Store;
274
+ store_product_id: string;
275
+ product: {
276
+ display_name: string;
277
+ type: string;
278
+ price_micros: number;
279
+ currency: string;
280
+ entitlements: string[];
281
+ } | null;
282
+ }[];
283
+ }
284
+ interface Deleted {
285
+ object: string;
286
+ id: string;
287
+ deleted: true;
288
+ }
289
+ interface CreateEntitlementInput {
290
+ /** The exact string your app checks with isEntitled(). Can't change later. */
291
+ key: string;
292
+ name?: string;
293
+ description?: string;
294
+ /** Products that grant it, attached in the same call. */
295
+ products?: ProductRef[];
296
+ }
297
+ interface CreateProductInput {
298
+ store: Store;
299
+ storeProductId: string;
300
+ type: ProductType;
301
+ /** Required for subscriptions. */
302
+ duration?: Duration;
303
+ displayName?: string;
304
+ /** Integer micros (4.99 → 4_990_000). */
305
+ priceMicros?: number;
306
+ currency?: string;
307
+ /** Entitlement keys this product grants. */
308
+ entitlements?: string[];
309
+ }
310
+ interface PackageInput {
311
+ identifier: string;
312
+ product: ProductRef;
313
+ position?: number;
314
+ displayName?: string;
315
+ }
316
+ declare class EntitlementsApi {
317
+ private http;
318
+ constructor(http: HttpOptions);
319
+ list(opts?: {
320
+ includeArchived?: boolean;
321
+ }): Promise<CatalogEntitlement[]>;
322
+ /** By id or key. */
323
+ get(ref: string): Promise<CatalogEntitlement>;
324
+ create(input: CreateEntitlementInput): Promise<CatalogEntitlement>;
325
+ /** Create, or return the entitlement that already has this key. */
326
+ ensure(input: CreateEntitlementInput): Promise<CatalogEntitlement>;
327
+ /** `products`, when given, REPLACES the set of products that grant it. */
328
+ update(ref: string, patch: {
329
+ name?: string;
330
+ description?: string;
331
+ products?: ProductRef[];
332
+ }): Promise<CatalogEntitlement>;
333
+ /** Refused (409 `entitlement_in_use`) once anyone has been granted it. Archive instead. */
334
+ delete(ref: string): Promise<Deleted>;
335
+ products(ref: string, opts?: {
336
+ includeArchived?: boolean;
337
+ }): Promise<CatalogProduct[]>;
338
+ attachProducts(ref: string, products: ProductRef[]): Promise<CatalogEntitlement>;
339
+ detachProducts(ref: string, products: ProductRef[]): Promise<CatalogEntitlement>;
340
+ /** Hide it from listings. Never takes access away from anyone who holds it. */
341
+ archive(ref: string): Promise<CatalogEntitlement>;
342
+ unarchive(ref: string): Promise<CatalogEntitlement>;
343
+ }
344
+ declare class ProductsApi {
345
+ private http;
346
+ constructor(http: HttpOptions);
347
+ list(opts?: {
348
+ store?: Store;
349
+ entitlement?: string;
350
+ includeArchived?: boolean;
351
+ }): Promise<CatalogProduct[]>;
352
+ get(ref: ProductRef): Promise<CatalogProduct>;
353
+ create(input: CreateProductInput): Promise<CatalogProduct>;
354
+ /** Create, or return the product that already has this store + SKU. */
355
+ ensure(input: CreateProductInput): Promise<CatalogProduct>;
356
+ /** `entitlements`, when given, REPLACES what it grants (future purchases only). */
357
+ update(ref: ProductRef, patch: {
358
+ displayName?: string;
359
+ priceMicros?: number;
360
+ currency?: string;
361
+ entitlements?: string[];
362
+ }): Promise<CatalogProduct>;
363
+ /** Refused (409 `product_in_use`) once anyone has bought it. Archive instead. */
364
+ delete(ref: ProductRef): Promise<Deleted>;
365
+ attachEntitlements(ref: ProductRef, entitlements: string[]): Promise<CatalogProduct>;
366
+ detachEntitlements(ref: ProductRef, entitlements: string[]): Promise<CatalogProduct>;
367
+ /** Retire a SKU: gone from listings and paywalls; existing subscribers keep access and renewals. */
368
+ archive(ref: ProductRef): Promise<CatalogProduct>;
369
+ unarchive(ref: ProductRef): Promise<CatalogProduct>;
370
+ }
371
+ declare class OfferingsApi {
372
+ private http;
373
+ constructor(http: HttpOptions);
374
+ /** Every offering. Archived ones only with includeArchived. */
375
+ list(opts?: {
376
+ includeArchived?: boolean;
377
+ }): Promise<ListedOffering[]>;
378
+ /** By id or identifier. */
379
+ get(ref: string): Promise<CatalogOffering>;
380
+ create(input: {
381
+ identifier: string;
382
+ displayName?: string;
383
+ isCurrent?: boolean;
384
+ metadata?: Record<string, unknown>;
385
+ packages?: PackageInput[];
386
+ }): Promise<CatalogOffering>;
387
+ /** `isCurrent: true` makes it the offering /current serves. `metadata: null` clears it. */
388
+ update(ref: string, patch: {
389
+ displayName?: string;
390
+ isCurrent?: true;
391
+ metadata?: Record<string, unknown> | null;
392
+ }): Promise<CatalogOffering>;
393
+ delete(ref: string): Promise<Deleted>;
394
+ archive(ref: string): Promise<CatalogOffering>;
395
+ unarchive(ref: string): Promise<CatalogOffering>;
396
+ packages(ref: string): Promise<CatalogPackage[]>;
397
+ addPackage(ref: string, input: PackageInput): Promise<CatalogPackage>;
398
+ /** `pkg` is the package id, or its identifier when that is used on one store only. */
399
+ updatePackage(ref: string, pkg: string, patch: {
400
+ displayName?: string;
401
+ position?: number;
402
+ product?: ProductRef;
403
+ }): Promise<CatalogPackage>;
404
+ deletePackage(ref: string, pkg: string): Promise<Deleted>;
405
+ }
406
+
172
407
  interface EntitleHubServerOptions {
173
- /** Your secret key (sk_live_… / sk_test_…). SERVER ONLY never ship this to a client. */
408
+ /** Your secret key (sk_live_… / sk_test_…). SERVER ONLY, never ship this to a client. */
174
409
  apiKey: string;
175
410
  baseUrl?: string;
176
411
  fetchImpl?: typeof fetch;
177
412
  }
178
413
  /**
179
- * The server-side EntitleHub SDK report purchases and grant entitlements from your backend.
414
+ * The server-side EntitleHub SDK: report purchases and grant entitlements from your backend.
180
415
  * Keep this on your server; it uses a secret key.
181
416
  *
182
417
  * const eh = new EntitleHubServer({ apiKey: process.env.ENTITLEHUB_SECRET_KEY! });
183
418
  * await eh.reportPurchase(userId, { store: "app_store", storeProductId: "pro_monthly" });
419
+ *
420
+ * Manage the catalog from code too (every create has an `ensure` twin that returns the existing
421
+ * row instead of failing, so a setup script can run on every deploy):
422
+ *
423
+ * await eh.entitlements.ensure({ key: "pro", name: "Pro" });
424
+ * await eh.products.ensure({ store: "stripe", storeProductId: "price_1Q…", type: "subscription",
425
+ * duration: "P1M", priceMicros: 9_990_000, entitlements: ["pro"] });
184
426
  */
185
427
  declare class EntitleHubServer {
186
428
  private http;
429
+ /** Catalog API: entitlements. */
430
+ readonly entitlements: EntitlementsApi;
431
+ /** Catalog API: products and their entitlement mappings. */
432
+ readonly products: ProductsApi;
433
+ /** Catalog API: paywall offerings and packages. */
434
+ readonly offerings: OfferingsApi;
187
435
  constructor(opts: EntitleHubServerOptions);
188
436
  /**
189
437
  * Report a completed store purchase → writes the mapped entitlement grant(s). Returns updated info.
190
438
  *
191
439
  * Three modes (pick one):
192
440
  * • Google (validated): { store:"play", storeProductId, purchaseToken, isSubscription }
193
- * • Apple (validated): { signedTransaction } store/product are read from the JWS
194
- * • Stripe (validated): { stripeSubscriptionId } validated via your Stripe secret key
195
- * • Trusted server-report: { store, storeProductId } no store validation; only when you've
441
+ * • Apple (validated): { signedTransaction } , store/product are read from the JWS
442
+ * • Stripe (validated): { stripeSubscriptionId }, validated via your Stripe secret key
443
+ * • Trusted server-report: { store, storeProductId }, no store validation; only when you've
196
444
  * already validated the receipt elsewhere.
197
445
  */
198
446
  reportPurchase(appUserId: string, purchase: PurchaseInput): Promise<CustomerInfo>;
@@ -209,10 +457,4 @@ declare class EntitleHubServer {
209
457
  }): Promise<CheckResult>;
210
458
  }
211
459
 
212
- declare class EntitleHubError extends Error {
213
- readonly status: number;
214
- readonly code: string;
215
- constructor(message: string, status: number, code?: string);
216
- }
217
-
218
- export { type ActiveEntitlement, type CheckResult, CustomerInfo, type CustomerInfoListener, type CustomerInfoResponse, EntitleHub, EntitleHubError, type EntitleHubOptions, EntitleHubServer, type EntitleHubServerOptions, type EntitlementStatus, type FetchPolicy, type OfferingEntitlement, type OfferingProduct, type Offerings, type PurchaseInput, type Store };
460
+ export { type ActiveEntitlement, type CatalogEntitlement, type CatalogOffering, type CatalogPackage, type CatalogProduct, type CheckResult, type CreateEntitlementInput, type CreateProductInput, CustomerInfo, type CustomerInfoListener, type CustomerInfoResponse, type Deleted, type Duration, EntitleHub, EntitleHubError, type EntitleHubOptions, EntitleHubServer, type EntitleHubServerOptions, type EntitlementStatus, EntitlementsApi, type FetchPolicy, type ListedOffering, type OfferingEntitlement, type OfferingProduct, type Offerings, OfferingsApi, type PackageInput, type ProductRef, type ProductType, ProductsApi, type PurchaseInput, type Store };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var a=class{constructor(t){this.appUserId=t.app_user_id,this.entitlements=t.active_entitlements??[];let e={};for(let s of this.entitlements)e[s.entitlement]=s;this.active=e}isActive(t){return!!this.active[t]}get activeEntitlementIds(){return Object.keys(this.active)}expirationDate(t){let e=this.active[t];if(e)return e.expires_at?new Date(e.expires_at):null}};var r=class extends Error{constructor(t,e,s="error"){super(t),this.name="EntitleHubError",this.status=e,this.code=s}},w=n=>new Promise(t=>setTimeout(t,n));async function c(n,t,e,s){let i=n.fetchImpl??globalThis.fetch;if(!i)throw new r("No fetch implementation available. On Node <18, pass fetchImpl.",0,"no_fetch");let l=`${n.baseUrl.replace(/\/$/,"")}${e}`,d=t==="GET"?n.retries??2:0,f;for(let u=0;u<=d;u++){let b=new AbortController,y=n.timeoutMs?setTimeout(()=>b.abort(),n.timeoutMs):void 0;try{let o=await i(l,{method:t,headers:{Authorization:`Bearer ${n.apiKey}`,...s?{"Content-Type":"application/json"}:{}},body:s?JSON.stringify(s):void 0,signal:b.signal}),g=await o.text(),p=g?C(g):{};if(!o.ok){let I=`HTTP ${o.status}`;throw p&&typeof p=="object"&&"error"in p&&(I=String(p.error)),new r(I,o.status,o.status===401||o.status===403?"auth":"http")}return p}catch(o){if(f=o,o instanceof r)throw o;u<d&&await w(250*(u+1))}finally{y&&clearTimeout(y)}}throw new r(`Network request failed: ${String(f?.message??f)}`,0,"network")}function C(n){try{return JSON.parse(n)}catch{return{}}}var h=class{constructor(t){this.listeners=new Set;if(!t.apiKey)throw new r("apiKey is required.",0,"config");if(!t.appUserId)throw new r("appUserId is required.",0,"config");if(t.apiKey.startsWith("sk_"))throw new r("Never use a secret (sk_) key in the client SDK \u2014 use your publishable (pk_) key.",0,"config");this.appUserId=t.appUserId,this.cacheTtlMs=t.cacheTtlMs??5*6e4,this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3}}get currentAppUserId(){return this.appUserId}async logIn(t){return this.appUserId=t,this.cached=void 0,this.getCustomerInfo({fetchPolicy:"network-only"})}logOut(){this.cached=void 0}async getCustomerInfo(t={}){if((t.fetchPolicy??"cache-first")==="cache-first"&&this.cached&&Date.now()-this.cached.at<this.cacheTtlMs)return this.cached.info;let s=await c(this.http,"GET",`/subscribers/${encodeURIComponent(this.appUserId)}`),i=new a(s);this.cached={info:i,at:Date.now()};for(let l of this.listeners)try{l(i)}catch{}return i}async isEntitled(t){return(await this.getCustomerInfo()).isActive(t)}async checkEntitlement(t,e={}){return c(this.http,"POST","/check",{app_user_id:this.appUserId,entitlement:t,sandbox:!!e.sandbox})}async getOfferings(){return c(this.http,"GET","/offerings")}async reportPurchase(t){let e=await c(this.http,"POST",`/subscribers/${encodeURIComponent(this.appUserId)}/purchases`,{store:t.store??"",store_product_id:t.storeProductId??"",is_sandbox:!!t.isSandbox,purchase_token:t.purchaseToken??"",is_subscription:!!t.isSubscription,signed_transaction:t.signedTransaction??"",stripe_subscription_id:t.stripeSubscriptionId??""}),s=new a(e);this.cached={info:s,at:Date.now()};for(let i of this.listeners)try{i(s)}catch{}return s}addCustomerInfoUpdateListener(t){return this.listeners.add(t),()=>this.listeners.delete(t)}};var m=class{constructor(t){if(!t.apiKey)throw new r("apiKey is required.",0,"config");if(!t.apiKey.startsWith("sk_"))throw new r("EntitleHubServer needs a secret (sk_) key. For client reads, use the EntitleHub class with a pk_ key.",0,"config");this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3}}async reportPurchase(t,e){let s=await c(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/purchases`,{store:e.store,store_product_id:e.storeProductId,is_sandbox:!!e.isSandbox,transaction_id:e.transactionId??"",purchase_token:e.purchaseToken??"",is_subscription:!!e.isSubscription,signed_transaction:e.signedTransaction??"",stripe_subscription_id:e.stripeSubscriptionId??""});return new a(s)}async grantEntitlement(t,e,s={}){let i=await c(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/grant`,{entitlement:e,duration_days:s.durationDays??0,is_sandbox:!!s.isSandbox});return new a(i)}async getCustomerInfo(t){let e=await c(this.http,"GET",`/subscribers/${encodeURIComponent(t)}`);return new a(e)}async check(t,e,s={}){return c(this.http,"POST","/check",{app_user_id:t,entitlement:e,sandbox:!!s.sandbox})}};export{a as CustomerInfo,h as EntitleHub,r as EntitleHubError,m as EntitleHubServer};
1
+ var u=class{constructor(t){this.appUserId=t.app_user_id,this.entitlements=t.active_entitlements??[];let e={};for(let r of this.entitlements)e[r.entitlement]=r;this.active=e}isActive(t){return!!this.active[t]}get activeEntitlementIds(){return Object.keys(this.active)}expirationDate(t){let e=this.active[t];if(e)return e.expires_at?new Date(e.expires_at):null}};var v="0.3.0";var a=class extends Error{constructor(t,e,r="error",o){super(t),this.name="EntitleHubError",this.status=e,this.code=r,this.body=o,this.apiCode=typeof o?.code=="string"?o.code:void 0}},R=n=>new Promise(t=>setTimeout(t,n));async function i(n,t,e,r){let o=n.fetchImpl??globalThis.fetch;if(!o)throw new a("No fetch implementation available. On Node <18, pass fetchImpl.",0,"no_fetch");let y=`${n.baseUrl.replace(/\/$/,"")}${e}`,E=t==="GET"?n.retries??2:0,b;for(let f=0;f<=E;f++){let k=new AbortController,T=n.timeoutMs?setTimeout(()=>k.abort(),n.timeoutMs):void 0;try{let c=await o(y,{method:t,headers:{Authorization:`Bearer ${n.apiKey}`,"X-EntitleHub-Client":n.client??`${"js"}/${v}`,...r?{"Content-Type":"application/json"}:{}},body:r?JSON.stringify(r):void 0,signal:k.signal}),w=await c.text(),h=w?H(w):{};if(!c.ok){let O=`HTTP ${c.status}`,P=h&&typeof h=="object"?h:void 0;throw P&&"error"in P&&(O=String(P.error)),new a(O,c.status,c.status===401||c.status===403?"auth":"http",P)}return h}catch(c){if(b=c,c instanceof a)throw c;f<E&&await R(250*(f+1))}finally{T&&clearTimeout(T)}}throw new a(`Network request failed: ${String(b?.message??b)}`,0,"network")}function H(n){try{return JSON.parse(n)}catch{return{}}}var C=class{constructor(t){this.listeners=new Set;if(!t.apiKey)throw new a("apiKey is required.",0,"config");if(!t.appUserId)throw new a("appUserId is required.",0,"config");if(t.apiKey.startsWith("sk_"))throw new a("Never use a secret (sk_) key in the client SDK. Use your publishable (pk_) key.",0,"config");this.appUserId=t.appUserId,this.cacheTtlMs=t.cacheTtlMs??5*6e4,this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3,client:t.client}}get currentAppUserId(){return this.appUserId}async logIn(t){return this.appUserId=t,this.cached=void 0,this.getCustomerInfo({fetchPolicy:"network-only"})}logOut(){this.cached=void 0}async getCustomerInfo(t={}){if((t.fetchPolicy??"cache-first")==="cache-first"&&this.cached&&Date.now()-this.cached.at<this.cacheTtlMs)return this.cached.info;let r=await i(this.http,"GET",`/subscribers/${encodeURIComponent(this.appUserId)}`),o=new u(r);this.cached={info:o,at:Date.now()};for(let y of this.listeners)try{y(o)}catch{}return o}async isEntitled(t){return(await this.getCustomerInfo()).isActive(t)}async checkEntitlement(t,e={}){return i(this.http,"POST","/check",{app_user_id:this.appUserId,entitlement:t,sandbox:!!e.sandbox})}async getOfferings(){return i(this.http,"GET","/offerings")}async reportPurchase(t){let e=await i(this.http,"POST",`/subscribers/${encodeURIComponent(this.appUserId)}/purchases`,{store:t.store??"",store_product_id:t.storeProductId??"",is_sandbox:!!t.isSandbox,purchase_token:t.purchaseToken??"",is_subscription:!!t.isSubscription,signed_transaction:t.signedTransaction??"",stripe_subscription_id:t.stripeSubscriptionId??""}),r=new u(e);this.cached={info:r,at:Date.now()};for(let o of this.listeners)try{o(r)}catch{}return r}addCustomerInfoUpdateListener(t){return this.listeners.add(t),()=>this.listeners.delete(t)}};var s=encodeURIComponent;function d(n){return typeof n=="string"?n:"id"in n?{id:n.id}:{store:n.store,store_product_id:n.storeProductId}}function p(n){return typeof n=="string"?{seg:s(n),qs:""}:"id"in n?{seg:s(n.id),qs:""}:{seg:s(n.storeProductId),qs:`?store=${s(n.store)}`}}var _=n=>n?.includeArchived?"include_archived=true":"";async function x(n,t){try{return await n()}catch(e){if(e instanceof a&&e.apiCode===t&&e.body?.existing)return e.body.existing;throw e}}var l=class{constructor(t){this.http=t}async list(t){let e=_(t);return(await i(this.http,"GET",`/entitlements${e?`?${e}`:""}`)).entitlements}get(t){return i(this.http,"GET",`/entitlements/${s(t)}`)}create(t){return i(this.http,"POST","/entitlements",{key:t.key,name:t.name,description:t.description,products:t.products?.map(d)})}ensure(t){return x(()=>this.create(t),"entitlement_exists")}update(t,e){return i(this.http,"PATCH",`/entitlements/${s(t)}`,{name:e.name,description:e.description,products:e.products?.map(d)})}delete(t){return i(this.http,"DELETE",`/entitlements/${s(t)}`)}async products(t,e){let r=_(e);return(await i(this.http,"GET",`/entitlements/${s(t)}/products${r?`?${r}`:""}`)).products}attachProducts(t,e){return i(this.http,"POST",`/entitlements/${s(t)}/attach_products`,{products:e.map(d)})}detachProducts(t,e){return i(this.http,"POST",`/entitlements/${s(t)}/detach_products`,{products:e.map(d)})}archive(t){return i(this.http,"POST",`/entitlements/${s(t)}/archive`)}unarchive(t){return i(this.http,"POST",`/entitlements/${s(t)}/unarchive`)}},g=class{constructor(t){this.http=t}async list(t){let e=new URLSearchParams;t?.store&&e.set("store",t.store),t?.entitlement&&e.set("entitlement",t.entitlement),t?.includeArchived&&e.set("include_archived","true");let r=e.toString();return(await i(this.http,"GET",`/products${r?`?${r}`:""}`)).products}get(t){let{seg:e,qs:r}=p(t);return i(this.http,"GET",`/products/${e}${r}`)}create(t){return i(this.http,"POST","/products",{store:t.store,store_product_id:t.storeProductId,type:t.type,duration:t.duration,display_name:t.displayName,price_micros:t.priceMicros,currency:t.currency,entitlements:t.entitlements})}ensure(t){return x(()=>this.create(t),"product_exists")}update(t,e){let{seg:r,qs:o}=p(t);return i(this.http,"PATCH",`/products/${r}${o}`,{display_name:e.displayName,price_micros:e.priceMicros,currency:e.currency,entitlements:e.entitlements})}delete(t){let{seg:e,qs:r}=p(t);return i(this.http,"DELETE",`/products/${e}${r}`)}attachEntitlements(t,e){let{seg:r,qs:o}=p(t);return i(this.http,"POST",`/products/${r}/attach_entitlements${o}`,{entitlements:e})}detachEntitlements(t,e){let{seg:r,qs:o}=p(t);return i(this.http,"POST",`/products/${r}/detach_entitlements${o}`,{entitlements:e})}archive(t){let{seg:e,qs:r}=p(t);return i(this.http,"POST",`/products/${e}/archive${r}`)}unarchive(t){let{seg:e,qs:r}=p(t);return i(this.http,"POST",`/products/${e}/unarchive${r}`)}},m=class{constructor(t){this.http=t}async list(t){let e=_(t);return(await i(this.http,"GET",`/offerings/all${e?`?${e}`:""}`)).offerings}get(t){return i(this.http,"GET",`/offerings/${s(t)}`)}create(t){return i(this.http,"POST","/offerings",{identifier:t.identifier,display_name:t.displayName,is_current:t.isCurrent,metadata:t.metadata,packages:t.packages?.map(S)})}update(t,e){return i(this.http,"PATCH",`/offerings/${s(t)}`,{display_name:e.displayName,is_current:e.isCurrent,metadata:e.metadata})}delete(t){return i(this.http,"DELETE",`/offerings/${s(t)}`)}archive(t){return i(this.http,"POST",`/offerings/${s(t)}/archive`)}unarchive(t){return i(this.http,"POST",`/offerings/${s(t)}/unarchive`)}async packages(t){return(await i(this.http,"GET",`/offerings/${s(t)}/packages`)).packages}addPackage(t,e){return i(this.http,"POST",`/offerings/${s(t)}/packages`,S(e))}updatePackage(t,e,r){return i(this.http,"PATCH",`/offerings/${s(t)}/packages/${s(e)}`,{display_name:r.displayName,position:r.position,product:r.product===void 0?void 0:d(r.product)})}deletePackage(t,e){return i(this.http,"DELETE",`/offerings/${s(t)}/packages/${s(e)}`)}};function S(n){return{identifier:n.identifier,product:d(n.product),position:n.position,display_name:n.displayName}}var I=class{constructor(t){if(!t.apiKey)throw new a("apiKey is required.",0,"config");if(!t.apiKey.startsWith("sk_"))throw new a("EntitleHubServer needs a secret (sk_) key. For client reads, use the EntitleHub class with a pk_ key.",0,"config");this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3},this.entitlements=new l(this.http),this.products=new g(this.http),this.offerings=new m(this.http)}async reportPurchase(t,e){let r=await i(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/purchases`,{store:e.store,store_product_id:e.storeProductId,is_sandbox:!!e.isSandbox,transaction_id:e.transactionId??"",purchase_token:e.purchaseToken??"",is_subscription:!!e.isSubscription,signed_transaction:e.signedTransaction??"",stripe_subscription_id:e.stripeSubscriptionId??""});return new u(r)}async grantEntitlement(t,e,r={}){let o=await i(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/grant`,{entitlement:e,duration_days:r.durationDays??0,is_sandbox:!!r.isSandbox});return new u(o)}async getCustomerInfo(t){let e=await i(this.http,"GET",`/subscribers/${encodeURIComponent(t)}`);return new u(e)}async check(t,e,r={}){return i(this.http,"POST","/check",{app_user_id:t,entitlement:e,sandbox:!!r.sandbox})}};export{u as CustomerInfo,C as EntitleHub,a as EntitleHubError,I as EntitleHubServer,l as EntitlementsApi,m as OfferingsApi,g as ProductsApi};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@entitlehub/sdk",
3
- "version": "0.2.0",
4
- "description": "EntitleHub SDK one entitlement API across App Store, Google Play, Stripe, and web. Works in browsers, React Native, Expo, and Node.",
3
+ "version": "0.3.0",
4
+ "description": "EntitleHub SDK: one entitlement API across App Store, Google Play, Stripe, and web. Works in browsers, React Native, Expo, and Node.",
5
5
  "keywords": [
6
6
  "entitlehub",
7
7
  "entitlements",
@@ -37,7 +37,7 @@
37
37
  "scripts": {
38
38
  "build": "tsup src/index.ts --format esm,cjs --dts --clean --minify",
39
39
  "typecheck": "tsc --noEmit",
40
- "test": "node --test",
40
+ "test": "node --test \"test/*.test.mjs\"",
41
41
  "prepublishOnly": "npm run build"
42
42
  },
43
43
  "engines": {