Skip to content

Commit 3dc5ea8

Browse files
authored
Align OpenIAP GQL unions (#210)
Align OpenIAP literal unions across generated types and runtime helpers to match the updated schema. Refresh docs, examples, and tests so consumers see the lowercase literals while preserving native compatibility. Complies with https://github.com/hyodotdev/openiap-gql/releases/tag/1.0.1 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Public APIs now normalize purchase types to "in-app" (backward-compatible with "inapp"), standardize platform values to "ios"/"android", and introduce updated purchase/subscription request typing and an auto-sync option. - **Documentation** - Guides, migration notes, examples, and API docs updated to use "in-app" and the new string-union terminology for product types, platforms, events, and states. - **Tests** - Unit and example tests updated to reflect "in-app" and standardized platform/purchase string values. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 8473e23 commit 3dc5ea8

35 files changed

+340
-276
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module.exports = {
22
root: true,
33
extends: ['expo', 'prettier'],
4+
ignorePatterns: ['src/types.ts'],
45
plugins: ['prettier'],
56
rules: {
67
'eslint-comments/no-unlimited-disable': 0,

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ For complete type definitions and documentation, see: <https://www.openiap.dev/d
6262

6363
The library follows the OpenIAP type specifications with platform-specific extensions using iOS/Android suffixes.
6464

65-
> **Note:** `src/types.ts` is generated from the OpenIAP schema. Do **not** edit this file manually—run `npm run generate` after updating any `*.graphql` schema instead.
65+
> **Important:** `src/types.ts` is generated from the OpenIAP schema. Never edit this file manually or commit hand-written changes. After updating any `*.graphql` schema, run `bun run generate:types` (or the equivalent script in your package manager) to refresh the file.
6666
6767
### React/JSX Conventions
6868

docs/CONVENTIONS.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export interface Platform {
8181
// ✅ Good - Clear parameter and return types
8282
export const requestProducts = async (params: {
8383
skus: string[];
84-
type?: 'inapp' | 'subs';
84+
type?: 'in-app' | 'subs';
8585
}): Promise<Product[]> => {
8686
// implementation
8787
};
@@ -141,7 +141,7 @@ export const deepLinkToSubscriptionsAndroid = async ({
141141
// These functions handle platform differences internally
142142
export const requestProducts = async (params: {
143143
skus: string[];
144-
type?: 'inapp' | 'subs';
144+
type?: 'in-app' | 'subs';
145145
}): Promise<Product[]> => {
146146
return (
147147
Platform.select({
@@ -274,7 +274,7 @@ Always use async/await over promises:
274274
// ✅ Good
275275
export const requestProducts = async (params: {
276276
skus: string[];
277-
type?: 'inapp' | 'subs';
277+
type?: 'in-app' | 'subs';
278278
}): Promise<Product[]> => {
279279
try {
280280
const products = await ExpoIapModule.requestProducts(params);
@@ -312,14 +312,14 @@ All public APIs must have JSDoc comments:
312312
*
313313
* @example
314314
* ```typescript
315-
* const products = await requestProducts({ skus: ['com.example.premium'], type: 'inapp' });
315+
* const products = await requestProducts({ skus: ['com.example.premium'], type: 'in-app' });
316316
* ```
317317
*
318318
* @platform iOS
319319
*/
320320
export const requestProductsIOS = async (params: {
321321
skus: string[];
322-
type?: 'inapp' | 'subs';
322+
type?: 'in-app' | 'subs';
323323
}): Promise<ProductIOS[]> => {
324324
// implementation
325325
};
@@ -358,7 +358,7 @@ describe('PurchaseManager', () => {
358358
it('should get products on iOS', async () => {
359359
const products = await requestProductsIOS({
360360
skus: ['com.example.product'],
361-
type: 'inapp',
361+
type: 'in-app',
362362
});
363363
expect(products).toHaveLength(1);
364364
});
@@ -371,7 +371,7 @@ describe('PurchaseManager', () => {
371371

372372
it('should throw error on Android', async () => {
373373
await expect(
374-
requestProductsIOS({skus: ['com.example.product'], type: 'inapp'}),
374+
requestProductsIOS({skus: ['com.example.product'], type: 'in-app'}),
375375
).rejects.toThrow('This method is only available on iOS');
376376
});
377377
});
@@ -487,7 +487,7 @@ await requestPurchase({
487487
obfuscatedAccountIdAndroid: 'user-123',
488488
},
489489
},
490-
type: 'inapp',
490+
type: 'in-app',
491491
});
492492

493493
// ❌ Bad - Old API with Platform.OS checks

docs/blog/2025-08-18-v2.8.0-migration-guide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ const purchase = await requestPurchase({
161161
ios: {sku: 'product-id'},
162162
android: {skus: ['product-id']},
163163
},
164-
type: 'inapp',
164+
type: 'in-app',
165165
});
166166
if (purchase.expirationDateIOS) {
167167
console.log('Expires:', purchase.expirationDateIOS);

docs/blog/2025-09-03-v2.8.7.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@ import AdFitTopFixed from "@site/src/uis/AdFitTopFixed";
2323

2424
```typescript
2525
// Old (deprecated)
26-
const products = await requestProducts({skus: ['product1'], type: 'inapp'});
26+
const products = await requestProducts({skus: ['product1'], type: 'in-app'});
2727

2828
// New (recommended)
29-
const products = await fetchProducts({skus: ['product1'], type: 'inapp'});
29+
const products = await fetchProducts({skus: ['product1'], type: 'in-app'});
3030
```
3131

3232
### useIAP Hook
3333

3434
```typescript
3535
const {fetchProducts, requestProducts} = useIAP(); // requestProducts still works but deprecated
3636

37-
await fetchProducts({skus: ['product1'], type: 'inapp'});
37+
await fetchProducts({skus: ['product1'], type: 'in-app'});
3838
```
3939

4040
## Deprecations

docs/blog/2025-09-13-v3.0.0.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ import AdFitTopFixed from "@site/src/uis/AdFitTopFixed";
5454
```ts
5555
import {fetchProducts, type ProductSubscription} from 'expo-iap';
5656

57-
const inapps = await fetchProducts({skus: ['prod1', 'prod2'], type: 'inapp'});
57+
const inapps = await fetchProducts({skus: ['prod1', 'prod2'], type: 'in-app'});
5858

5959
const subs = (await fetchProducts({
6060
skus: ['sub_monthly'],
@@ -70,7 +70,7 @@ import {requestPurchase} from 'expo-iap';
7070
// In‑app
7171
await requestPurchase({
7272
request: {ios: {sku: 'prod1'}, android: {skus: ['prod1']}},
73-
type: 'inapp',
73+
type: 'in-app',
7474
});
7575

7676
// Subscriptions (Android supply offer tokens)

docs/docs/api/methods/core-methods.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const loadProducts = async () => {
8484
try {
8585
const products = await fetchProducts({
8686
skus: ['com.example.product1', 'com.example.product2'],
87-
type: 'inapp',
87+
type: 'in-app',
8888
});
8989

9090
console.log('Products:', products);
@@ -114,7 +114,7 @@ const loadSubscriptions = async () => {
114114

115115
- `params` (object):
116116
- `skus` (string[]): Array of product or subscription IDs to fetch
117-
- `type` ('inapp' | 'subs'): Product type - 'inapp' for products, 'subs' for subscriptions
117+
- `type` ('in-app' | 'subs'): Product type - 'in-app' for products, 'subs' for subscriptions
118118

119119
**Returns:** `Promise<Product[]>`
120120

@@ -149,7 +149,7 @@ const buyProduct = async (productId: string) => {
149149
skus: [productId],
150150
},
151151
},
152-
type: 'inapp',
152+
type: 'in-app',
153153
});
154154
} catch (error) {
155155
console.error('Purchase failed:', error);
@@ -193,7 +193,7 @@ await requestPurchase({
193193
quantity: 1,
194194
appAccountToken: 'user-account-token',
195195
},
196-
type: 'inapp',
196+
type: 'in-app',
197197
});
198198
```
199199

@@ -206,7 +206,7 @@ await requestPurchase({
206206
obfuscatedAccountIdAndroid: 'user-account-id',
207207
obfuscatedProfileIdAndroid: 'user-profile-id',
208208
},
209-
type: 'inapp',
209+
type: 'in-app',
210210
});
211211
```
212212

@@ -222,7 +222,7 @@ await requestPurchase({
222222
- `obfuscatedAccountIdAndroid?` (string, Android only): Obfuscated account ID
223223
- `obfuscatedProfileIdAndroid?` (string, Android only): Obfuscated profile ID
224224
- `isOfferPersonalized?` (boolean, Android only): Whether offer is personalized
225-
- `type?` ('inapp' | 'subs'): Purchase type, defaults to 'inapp'
225+
- `type?` ('in-app' | 'subs'): Purchase type, defaults to 'in-app'
226226

227227
**Returns:** `Promise<Purchase | Purchase[] | void>`
228228

@@ -776,6 +776,6 @@ This ensures pending transactions are surfaced and properly resolved without a s
776776
## Removed APIs
777777

778778
- `requestProducts()` — Removed in v3.0.0. Use `fetchProducts({ skus, type })` instead.
779-
- `getProducts()` — Removed in v3.0.0. Use `fetchProducts({ skus, type: 'inapp' })` instead.
779+
- `getProducts()` — Removed in v3.0.0. Use `fetchProducts({ skus, type: 'in-app' })` instead.
780780
- `getSubscriptions()` — Removed in v3.0.0. Use `fetchProducts({ skus, type: 'subs' })` instead.
781781
- `requestSubscription()` — Removed in v3.0.0. Use `requestPurchase({ ..., type: 'subs' })` and supply Android `subscriptionOffers`.

docs/docs/api/types.md

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import AdFitTopFixed from "@site/src/uis/AdFitTopFixed";
44

55
<AdFitTopFixed />
66

7-
The expo-iap type surface is now generated in one place: `src/types.ts`. The file is produced by our GraphQL schema and represents the canonical source for all product, purchase, subscription, and request shapes. After updating any schema definitions, run `npm run generate` to refresh the file.
7+
The expo-iap type surface is now generated in one place: `src/types.ts`. The file is produced by our GraphQL schema and represents the canonical source for all product, purchase, subscription, and request shapes. After updating any schema definitions, run `bun run generate:types` to refresh the file.
88

99
Key runtime helpers that build on these types live alongside them:
1010

@@ -14,27 +14,20 @@ Key runtime helpers that build on these types live alongside them:
1414

1515
Below is a curated overview of the most commonly used types. Consult `src/types.ts` for the full schema.
1616

17-
## Core Enumerations
17+
## Core Type Aliases
1818

1919
```ts
20-
export enum Platform {
21-
Android = 'ANDROID',
22-
Ios = 'IOS',
23-
}
20+
export type IapPlatform = 'android' | 'ios';
2421

25-
export enum ProductType {
26-
InApp = 'IN_APP',
27-
Subs = 'SUBS',
28-
}
22+
export type ProductType = 'in-app' | 'subs';
2923

30-
export enum PurchaseState {
31-
Deferred = 'DEFERRED',
32-
Failed = 'FAILED',
33-
Pending = 'PENDING',
34-
Purchased = 'PURCHASED',
35-
Restored = 'RESTORED',
36-
Unknown = 'UNKNOWN',
37-
}
24+
export type PurchaseState =
25+
| 'deferred'
26+
| 'failed'
27+
| 'pending'
28+
| 'purchased'
29+
| 'restored'
30+
| 'unknown';
3831
```
3932

4033
The `ErrorCode` enum now mirrors the OpenIAP schema without the legacy `E_` prefix:
@@ -54,7 +47,7 @@ Use `PurchaseError` from `src/purchase-error.ts` to work with typed errors and p
5447

5548
## Product Types
5649

57-
All products share the generated `ProductCommon` interface. Platform extensions discriminate on the `platform` field via the `Platform` enum.
50+
All products share the generated `ProductCommon` interface. Platform extensions discriminate on the `platform` field via the `IapPlatform` string union.
5851

5952
```ts
6053
export interface ProductCommon {
@@ -66,7 +59,7 @@ export interface ProductCommon {
6659
displayPrice: string;
6760
currency: string;
6861
price?: number | null;
69-
platform: Platform;
62+
platform: IapPlatform;
7063
}
7164

7265
export interface ProductAndroid extends ProductCommon {
@@ -77,29 +70,29 @@ export interface ProductAndroid extends ProductCommon {
7770
| null;
7871
}
7972

80-
export interface ProductIos extends ProductCommon {
73+
export interface ProductIOS extends ProductCommon {
8174
displayNameIOS: string;
8275
isFamilyShareableIOS: boolean;
8376
jsonRepresentationIOS: string;
84-
typeIOS: ProductTypeIos;
85-
subscriptionInfoIOS?: SubscriptionInfoIos | null;
77+
typeIOS: ProductTypeIOS;
78+
subscriptionInfoIOS?: SubscriptionInfoIOS | null;
8679
}
8780

88-
export type Product = ProductAndroid | ProductIos;
81+
export type Product = ProductAndroid | ProductIOS;
8982
export type ProductSubscription =
9083
| ProductSubscriptionAndroid
91-
| ProductSubscriptionIos;
84+
| ProductSubscriptionIOS;
9285
```
9386

9487
## Purchase Types
9588

96-
Purchases share the `PurchaseCommon` shape and discriminate on the same `platform` enum. Both variants expose the unified `purchaseToken` field for server validation.
89+
Purchases share the `PurchaseCommon` shape and discriminate on the same `platform` union. Both variants expose the unified `purchaseToken` field for server validation.
9790

9891
```ts
9992
export interface PurchaseCommon {
10093
id: string;
10194
productId: string;
102-
platform: Platform;
95+
platform: IapPlatform;
10396
purchaseState: PurchaseState;
10497
transactionDate: number;
10598
quantity: number;
@@ -115,15 +108,15 @@ export interface PurchaseAndroid extends PurchaseCommon {
115108
dataAndroid?: string | null;
116109
}
117110

118-
export interface PurchaseIos extends PurchaseCommon {
111+
export interface PurchaseIOS extends PurchaseCommon {
119112
appAccountToken?: string | null;
120113
environmentIOS?: string | null;
121114
expirationDateIOS?: number | null;
122115
originalTransactionIdentifierIOS?: string | null;
123-
offerIOS?: PurchaseOfferIos | null;
116+
offerIOS?: PurchaseOfferIOS | null;
124117
}
125118

126-
export type Purchase = PurchaseAndroid | PurchaseIos;
119+
export type Purchase = PurchaseAndroid | PurchaseIOS;
127120
```
128121

129122
## Active Subscriptions

docs/docs/api/use-iap.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ interface UseIAPOptions {
134134
```tsx
135135
if (connected) {
136136
// Safe to make IAP calls
137-
fetchProducts({skus: ['product.id'], type: 'inapp'});
137+
fetchProducts({skus: ['product.id'], type: 'in-app'});
138138
}
139139
```
140140

@@ -219,7 +219,7 @@ interface UseIAPOptions {
219219

220220
#### fetchProducts
221221

222-
- **Type**: `(params: { skus: string[]; type?: 'inapp' | 'subs' }) => Promise<void>`
222+
- **Type**: `(params: { skus: string[]; type?: 'in-app' | 'subs' }) => Promise<void>`
223223
- **Description**: Fetch products or subscriptions and update `products` / `subscriptions` state. In the hook this returns `void` (no data result), by design.
224224
- **Do not await for data**: Call it, then consume `products` / `subscriptions` state from the hook.
225225
- **Example**:
@@ -230,7 +230,7 @@ interface UseIAPOptions {
230230
// In hook: returns void, updates state
231231
fetchProducts({
232232
skus: ['com.app.premium', 'com.app.coins_100'],
233-
type: 'inapp',
233+
type: 'in-app',
234234
});
235235
fetchProducts({skus: ['com.app.premium_monthly'], type: 'subs'});
236236
}, [connected, fetchProducts]);
@@ -507,7 +507,7 @@ const {requestPurchase} = useIAP({
507507
```tsx
508508
useEffect(() => {
509509
if (connected) {
510-
fetchProducts({skus: productIds, type: 'inapp'});
510+
fetchProducts({skus: productIds, type: 'in-app'});
511511
}
512512
}, [connected, fetchProducts]);
513513
```

docs/docs/examples/purchase-flow.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ View the full example source:
2222

2323
- Load products:
2424

25-
`fetchProducts({ skus, type: 'inapp' })`
25+
`fetchProducts({ skus, type: 'in-app' })`
2626

2727
- Start purchase:
2828

29-
`requestPurchase({ request: { ios: { sku }, android: { skus: [sku] } }, type: 'inapp' })`
29+
`requestPurchase({ request: { ios: { sku }, android: { skus: [sku] } }, type: 'in-app' })`
3030

3131
- Receive callbacks: `onPurchaseSuccess` / `onPurchaseError` (from `useIAP`)
3232

@@ -80,7 +80,7 @@ await requestPurchase({
8080
ios: {sku: productId, quantity: 1},
8181
android: {skus: [productId]},
8282
},
83-
type: 'inapp',
83+
type: 'in-app',
8484
});
8585
```
8686

0 commit comments

Comments
 (0)