This document captures the consistency rules for OpenIAP documentation, code
comments, and generated types. PR #107 (and earlier rounds) repeatedly
surfaced the same class of drift — the docs claimed one field/default/type,
but the SDK code actually used another. These rules + the companion audit
script (scripts/audit-docs.ts) catch those before review.
When two places disagree, the upstream wins:
GraphQL schema → generated Types → hand-written wrapper SDK → docs page
(packages/gql (libraries/*/src (Swift / Kotlin / (packages/docs/
/src/*.graphql) /types.{ts,kt,...}) Dart / TS / GDScript) src/pages/...)
packages/gql/src/*.graphql— schema descriptions ARE the canonical doc string. Edits propagate viabun run generateto every generatedTypes.{ts,kt,swift,dart,gd}.libraries/*/src/types.ts(or equivalent) — generated; never hand-edit. When a docs page mentions a field name, that field MUST exist in the generated TS type. The audit script enforces this.- Wrapper SDK source (e.g.
libraries/expo-iap/src/index.ts) — JSDoc parameter names MUST match the actual function-signature parameter names. ESLint ruletsdoc/syntax+ the audit script catch drift. - Doc pages — the surface visible to users. Must reflect what each upstream layer actually exposes.
If the function declares (args) =>, the JSDoc tag is @param args ….
If it declares (request) =>, the tag is @param request …. Don't carry
over the schema field name (props, params) when the wrapper destructured
or renamed.
// ✅ wrapper destructures from `args`
/** @param args Purchase request. … */
export const requestPurchase = async (args) => { … };
// ❌ JSDoc says `props`; signature says `args`
/** @param props … */
export const requestPurchase = async (args) => { … };If fetchProducts.type defaults to 'in-app' in Flutter / expo-iap /
react-native-iap / godot-iap, then the Apple wrapper must also default to
.inApp — and the Apple doc comment must say .inApp. The schema
description is the canonical statement.
When changing a default, update:
- The GraphQL schema description.
- Re-run
bun run generate. - Every wrapper SDK's
?? <default>expression and JSDoc / KDoc / etc. - Every API doc page (
packages/docs/src/pages/docs/apis/<symbol>.tsx).
When a Type doc page lists fields in a <table> or <ul>, every field name
MUST exist in the generated libraries/expo-iap/src/types.ts (or
libraries/react-native-iap/src/types.ts — they're identical in shape).
The audit script greps for fields that don't appear in the type definition
and flags them.
Example failure modes already encountered:
BillingProgramAvailabilityResultAndroiddoc listedresponseCode+debugMessage— neither field exists; the type hasbillingProgram+isAvailable.LaunchExternalLinkParamsAndroiddoc listedprogram+url— neither exists; the type hasbillingProgram+launchMode+linkType+linkUri.ExternalPurchaseCustomLinkNoticeResultIOSdoc listedresult+noticeType— neither exists; the type hascontinued+error.
When a doc page mentions enum values (e.g.
'continue' | 'cancelled', .acquisition, .services), they must
appear in the generated enum definition. The audit script extracts string
literals from <code>'…'</code> blocks in doc pages and checks them
against the generated TypeScript union types.
ExternalPurchaseCustomLinkNoticeTypeIOS is the canonical recent miss —
the union is 'browser' only, but the doc claimed
'continue' | 'cancelled' | ….
Anchor links should point to existing pages and section anchors. Common recent failures:
- "Use verifyPurchase" link pointed to
/docs/apis/get-active-subscriptions(totally unrelated). getExternalPurchaseCustomLinkTokenIOSReturns linked to theexternal-purchase-linkpage without an anchor — but that page documents onlyExternalPurchaseNoticeResultIOS, so users land in the wrong section. Add a precise#external-purchase-custom-link-token-result-iosanchor on the type page AND link to it.
The audit script crawls every internal <Link to="/docs/..."> and asserts
the target file (and anchor when given) exists.
enableBillingProgramAndroid: 'external-payments' is gated to Play Billing
8.3.0+ (Japan only); the 8.2.0+ programs are EXTERNAL_CONTENT_LINK /
EXTERNAL_OFFER. A doc page that mixes these up misleads readers about
what works on which SDK.
When you write <X> 8.2.0+, you should be able to point to the matching
release-notes line. Don't paraphrase — quote the version requirement
exactly as Google / Apple states it.
Code examples in doc pages should at minimum parse / type-check against the wrapper they target. The audit script does NOT yet run a full TypeScript / Kotlin / Dart parser, but it does:
- Verify imports (
import {…} from 'expo-iap') reference symbols that expo-iap actually exports. - Verify field accesses on shown objects (e.g.
purchase.purchaseToken) exist on the corresponding generated type.
When in doubt, run the example in a real example app before publishing.
iOS-suffixed APIs (syncIOS, getStorefrontIOS, …) and Android-suffixed
APIs (acknowledgePurchaseAndroid, …) are exposed via every framework
wrapper (expo-iap, rn-iap, kmp-iap, flutter, godot-iap). The TS / Dart /
KMP / GDScript example tabs MUST show how to call the function from each
wrapper, with a Platform.OS === 'ios' (or Platform.isIOS / etc.)
guard so readers don't accidentally call iOS-only methods on Android.
The native Swift / Kotlin tab keeps the platform-native call. The
wrapper tabs use the suffixed name (syncIOS(), etc.) — except in
packages/google Kotlin (the Android-only native), where convention
strips the Android suffix from method names.
When a release-note block is labeled Package Releases, every package/version
item in that list must link to the corresponding GitHub Release. Use
Planned Package Releases only while the release workflow is still running or
the GitHub Release does not exist yet.
bun run audit:docs fails bare package/version entries under published
Package Releases blocks so link regressions are caught before publishing.
Run before every git push on docs / SDK changes:
# 1. Format + lint the docs site
cd packages/docs
bunx prettier --check "src/**/*.{ts,tsx,css}"
bun run lint
# 2. Cross-library typecheck for SDKs you touched
cd libraries/expo-iap && bun run lint:tsc
cd libraries/react-native-iap && yarn typecheck # ignore example-expo errors
cd libraries/flutter_inapp_purchase && dart analyze lib
cd packages/apple && swift build
cd packages/google && ./gradlew :openiap:compilePlayDebugKotlin
# 3. SSOT audit — run the docs-consistency audit script
cd scripts && bun run audit-docs.tsAuto-mode users: the commit-push-pr skill runs steps 1 + 2 automatically
before pushing. Step 3 is opt-in until the audit script has zero false
positives in CI.
scripts/audit-docs.ts is the executable companion to this guide. It
parses every /docs/apis/*.tsx and /docs/types/*.tsx page, extracts:
<Link to="/docs/...">targets<code>fieldName</code>mentions inside Returns / Parameters tables- String-literal enum values in
<code>'…'</code>blocks @see {@link openiap.dev/...}URLs
…and cross-references each against the generated TypeScript types in
libraries/expo-iap/src/types.ts. Failures print as a punch-list with the
file, line, and the offending mention.
Run with:
cd /Users/hyo/Github/hyodotdev/openiap
bun run scripts/audit-docs.tsExit code 1 means at least one drift; 0 means clean.