@sudobility/consumables_client 0.0.5 → 0.0.7
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/CLAUDE.md +79 -37
- package/dist/core/service.d.ts.map +1 -1
- package/dist/core/service.js.map +1 -1
- package/dist/core/singleton.d.ts.map +1 -1
- package/dist/core/singleton.js.map +1 -1
- package/dist/hooks/useBalance.d.ts.map +1 -1
- package/dist/hooks/useBalance.js.map +1 -1
- package/dist/hooks/useConsumableProducts.d.ts.map +1 -1
- package/dist/hooks/useConsumableProducts.js.map +1 -1
- package/dist/hooks/usePurchaseCredits.d.ts.map +1 -1
- package/dist/hooks/usePurchaseCredits.js.map +1 -1
- package/dist/hooks/usePurchaseHistory.d.ts.map +1 -1
- package/dist/hooks/usePurchaseHistory.js.map +1 -1
- package/dist/hooks/useUsageHistory.d.ts.map +1 -1
- package/dist/hooks/useUsageHistory.js.map +1 -1
- package/dist/network/ConsumablesApiClient.d.ts.map +1 -1
- package/dist/network/ConsumablesApiClient.js.map +1 -1
- package/dist/types/adapter.d.ts.map +1 -1
- package/dist/types/adapter.js.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +3 -3
package/CLAUDE.md
CHANGED
|
@@ -3,75 +3,113 @@
|
|
|
3
3
|
Cross-platform consumable credits client with RevenueCat adapter pattern.
|
|
4
4
|
|
|
5
5
|
**npm**: `@sudobility/consumables_client` (public)
|
|
6
|
+
**Version**: 0.0.6
|
|
7
|
+
**License**: BUSL-1.1
|
|
6
8
|
|
|
7
9
|
## Tech Stack
|
|
8
10
|
|
|
9
|
-
- **Language**: TypeScript (strict mode)
|
|
11
|
+
- **Language**: TypeScript 5.9.3 (strict mode)
|
|
10
12
|
- **Runtime**: Bun
|
|
11
|
-
- **Package Manager**: Bun
|
|
12
|
-
- **Build**: TypeScript compiler (ESM)
|
|
13
|
-
- **Test**:
|
|
13
|
+
- **Package Manager**: Bun (never npm/yarn/pnpm)
|
|
14
|
+
- **Build**: TypeScript compiler (ESM only)
|
|
15
|
+
- **Test**: Vitest 4.x
|
|
16
|
+
- **JSX**: react-jsx (React 18/19)
|
|
14
17
|
|
|
15
18
|
## Project Structure
|
|
16
19
|
|
|
17
20
|
```
|
|
18
21
|
src/
|
|
19
|
-
├── index.ts # Main exports
|
|
22
|
+
├── index.ts # Main barrel exports
|
|
20
23
|
├── types/
|
|
21
|
-
│ ├── index.ts # CreditPackage,
|
|
22
|
-
│ └── adapter.ts # ConsumablesAdapter interface
|
|
24
|
+
│ ├── index.ts # CreditPackage, CreditOffering, CreditBalance
|
|
25
|
+
│ └── adapter.ts # ConsumablesAdapter interface, ConsumablePurchaseResult, ConsumablePurchaseParams
|
|
23
26
|
├── core/
|
|
24
|
-
│ ├── index.ts
|
|
25
|
-
│ ├── service.ts # ConsumablesService class
|
|
26
|
-
│ └── singleton.ts #
|
|
27
|
+
│ ├── index.ts # Re-exports from service.ts and singleton.ts
|
|
28
|
+
│ ├── service.ts # ConsumablesService class (caching, purchase flow, usage)
|
|
29
|
+
│ └── singleton.ts # Module-level singleton + event listeners (init, userId, balance change)
|
|
27
30
|
├── network/
|
|
28
|
-
│ └── ConsumablesApiClient.ts # HTTP
|
|
31
|
+
│ └── ConsumablesApiClient.ts # HTTP client wrapping NetworkClient for /api/v1/consumables/* endpoints
|
|
29
32
|
├── adapters/
|
|
30
|
-
│ ├── index.ts
|
|
31
|
-
│ ├── revenuecat-web.ts # Web adapter
|
|
32
|
-
│ └── revenuecat-rn.ts # React Native adapter
|
|
33
|
+
│ ├── index.ts # Re-exports web and RN adapters
|
|
34
|
+
│ ├── revenuecat-web.ts # Web adapter (wraps @revenuecat/purchases-js, lazy-loaded)
|
|
35
|
+
│ └── revenuecat-rn.ts # React Native adapter (wraps react-native-purchases, lazy-loaded)
|
|
33
36
|
└── hooks/
|
|
34
|
-
├── index.ts
|
|
35
|
-
├── useBalance.ts
|
|
36
|
-
├── useConsumableProducts.ts
|
|
37
|
-
├── usePurchaseCredits.ts
|
|
38
|
-
├── usePurchaseHistory.ts
|
|
39
|
-
└── useUsageHistory.ts
|
|
37
|
+
├── index.ts # Re-exports all hooks
|
|
38
|
+
├── useBalance.ts # Current credit balance (subscribes to balance + userId changes)
|
|
39
|
+
├── useConsumableProducts.ts # Available credit packages from a specific offering
|
|
40
|
+
├── usePurchaseCredits.ts # Purchase flow (adapter.purchase -> api.recordPurchase -> notify)
|
|
41
|
+
├── usePurchaseHistory.ts # Paginated purchase audit trail with loadMore
|
|
42
|
+
└── useUsageHistory.ts # Paginated usage audit trail with loadMore
|
|
43
|
+
tests/
|
|
44
|
+
├── singleton.test.ts
|
|
45
|
+
├── ConsumablesService.test.ts
|
|
46
|
+
└── ConsumablesApiClient.test.ts
|
|
40
47
|
```
|
|
41
48
|
|
|
42
49
|
## Commands
|
|
43
50
|
|
|
44
51
|
```bash
|
|
45
|
-
bun run build # Build ESM
|
|
52
|
+
bun run build # Build ESM via tsc
|
|
46
53
|
bun run clean # Remove dist/
|
|
47
|
-
bun run dev # Watch mode
|
|
48
|
-
bun test # Run tests
|
|
49
|
-
bun run
|
|
54
|
+
bun run dev # Watch mode (tsc --watch)
|
|
55
|
+
bun test # Run tests (vitest run)
|
|
56
|
+
bun run test:watch # Watch tests
|
|
57
|
+
bun run typecheck # TypeScript check (bunx tsc --noEmit)
|
|
58
|
+
bun run lint # ESLint
|
|
59
|
+
bun run lint:fix # ESLint with auto-fix
|
|
60
|
+
bun run format # Prettier format
|
|
50
61
|
```
|
|
51
62
|
|
|
63
|
+
## Dependencies
|
|
64
|
+
|
|
65
|
+
### Peer Dependencies
|
|
66
|
+
- `@sudobility/types` ^1.9.53 -- shared type definitions (ConsumablePurchaseRecord, ConsumableUsageRecord, NetworkClient)
|
|
67
|
+
- `react` ^18.0.0 || ^19.0.0 -- hooks use React useState/useEffect/useCallback
|
|
68
|
+
- `@revenuecat/purchases-js` ^1.0.0 (optional) -- only needed for web adapter
|
|
69
|
+
- `react-native-purchases` >=7.0.0 (optional) -- only needed for RN adapter
|
|
70
|
+
|
|
71
|
+
### Dev Dependencies
|
|
72
|
+
- TypeScript ~5.9.3, Vitest ^4.0.4, ESLint ^9.x, Prettier ^3.x
|
|
73
|
+
|
|
52
74
|
## Key Concepts
|
|
53
75
|
|
|
54
76
|
### Adapter Pattern
|
|
55
|
-
- Web adapter wraps
|
|
56
|
-
- RN adapter wraps react-native-purchases
|
|
57
|
-
- Both implement ConsumablesAdapter interface
|
|
77
|
+
- Web adapter wraps `@revenuecat/purchases-js`
|
|
78
|
+
- RN adapter wraps `react-native-purchases`
|
|
79
|
+
- Both implement the `ConsumablesAdapter` interface (getOfferings, purchase, setUserId)
|
|
80
|
+
- Adapters lazy-load their SDK via dynamic `import()` (web) or `require()` (RN)
|
|
81
|
+
|
|
82
|
+
### ConsumablesService
|
|
83
|
+
- Core orchestration class constructed with `{ adapter, apiClient }`
|
|
84
|
+
- Manages two caches: `balanceCache` (per-user) and `offeringsCache` (global)
|
|
85
|
+
- Purchase flow: adapter.purchase() -> apiClient.recordPurchase() -> update cache
|
|
86
|
+
- Usage flow: apiClient.recordUsage() -> update cache
|
|
58
87
|
|
|
59
88
|
### Singleton + Event Listeners
|
|
60
|
-
- No React Context needed
|
|
61
|
-
- initializeConsumables() at app startup
|
|
62
|
-
- setConsumablesUserId() on auth
|
|
63
|
-
- onConsumablesBalanceChange()
|
|
89
|
+
- No React Context needed -- module-level singleton pattern
|
|
90
|
+
- `initializeConsumables(config)` at app startup
|
|
91
|
+
- `setConsumablesUserId(userId, email?)` on auth state changes
|
|
92
|
+
- `onConsumablesBalanceChange(listener)` returns unsubscribe function
|
|
93
|
+
- `onConsumablesUserIdChange(listener)` returns unsubscribe function
|
|
94
|
+
- `notifyBalanceChange()` manually triggers balance listeners
|
|
64
95
|
|
|
65
96
|
### Hooks
|
|
66
|
-
- useBalance()
|
|
67
|
-
- useConsumableProducts(offeringId)
|
|
68
|
-
- usePurchaseCredits()
|
|
69
|
-
- usePurchaseHistory()
|
|
97
|
+
- `useBalance()` -- current credit balance, subscribes to balance and userId changes
|
|
98
|
+
- `useConsumableProducts(offeringId)` -- available packages from a specific RevenueCat offering
|
|
99
|
+
- `usePurchaseCredits()` -- returns `purchase(packageId, offeringId)` callback + isPurchasing state
|
|
100
|
+
- `usePurchaseHistory(limit?)` -- paginated purchase records with loadMore
|
|
101
|
+
- `useUsageHistory(limit?)` -- paginated usage records with loadMore
|
|
102
|
+
|
|
103
|
+
### ConsumablesApiClient
|
|
104
|
+
- HTTP wrapper using `NetworkClient` from `@sudobility/types`
|
|
105
|
+
- Base URL pattern: `{baseUrl}/api/v1/consumables/{endpoint}`
|
|
106
|
+
- Endpoints: `/balance` (GET), `/purchase` (POST), `/use` (POST), `/purchases` (GET), `/usages` (GET)
|
|
107
|
+
- Maps snake_case API responses to camelCase client types
|
|
70
108
|
|
|
71
109
|
## Related Projects
|
|
72
110
|
|
|
73
|
-
- **consumables_pages** (`@sudobility/consumables_pages`)
|
|
74
|
-
- **consumables_service** (`@sudobility/consumables_service`)
|
|
111
|
+
- **consumables_pages** (`@sudobility/consumables_pages`) -- UI components for the credits store and history pages. Depends on this package for types (not runtime hooks).
|
|
112
|
+
- **consumables_service** (`@sudobility/consumables_service`) -- Backend counterpart that manages balances, purchases, and usage in the database. This client calls its API endpoints via HTTP.
|
|
75
113
|
|
|
76
114
|
Dependency direction: `consumables_pages` --> `consumables_client` --> `consumables_service` (via HTTP)
|
|
77
115
|
|
|
@@ -81,6 +119,7 @@ Dependency direction: `consumables_pages` --> `consumables_client` --> `consumab
|
|
|
81
119
|
- **Adapter pattern for RevenueCat**: Platform-specific SDK logic lives in adapters that implement `ConsumablesAdapter`. To support a new platform, add a new adapter file -- do not modify existing adapters.
|
|
82
120
|
- **Event-driven balance updates**: Balance changes are broadcast via `onConsumablesBalanceChange()` listeners. Hooks subscribe to these events. When modifying balance-related logic, always emit the balance change event so subscribers stay in sync.
|
|
83
121
|
- **Lazy-loaded adapters**: Adapters dynamically import their underlying SDK (`@revenuecat/purchases-js` or `react-native-purchases`) to keep initial bundle size small.
|
|
122
|
+
- **snake_case API <-> camelCase client**: The API client maps between backend snake_case (e.g., `initial_credits`) and client camelCase (e.g., `initialCredits`).
|
|
84
123
|
|
|
85
124
|
## Gotchas
|
|
86
125
|
|
|
@@ -88,6 +127,9 @@ Dependency direction: `consumables_pages` --> `consumables_client` --> `consumab
|
|
|
88
127
|
- **Singleton must be initialized before hooks work**: Calling `useBalance()` or any hook before `initializeConsumables()` will throw or return undefined. Ensure initialization happens at app startup (e.g., in a root layout or entry file).
|
|
89
128
|
- **Offerings cache survives user change, but balance cache does not**: When `setConsumablesUserId()` is called, the balance is refetched for the new user, but cached offerings (credit packages) are kept because they are not user-specific. Do not clear offerings on user change.
|
|
90
129
|
- **Two adapters, one interface**: Web and React Native adapters have different underlying SDKs with different APIs. Always code against `ConsumablesAdapter`, never against a specific SDK directly.
|
|
130
|
+
- **Web adapter uses `Purchases.close()` on user switch**: The web SDK requires closing and reconfiguring when switching users. The RN adapter uses `logOut()` + `logIn()` instead.
|
|
131
|
+
- **RN adapter defaults source to "apple"**: The React Native adapter currently always returns `"apple"` as the purchase source. Platform detection for Android ("google") is not implemented.
|
|
132
|
+
- **`removeComments: true` in tsconfig**: JSDoc comments are stripped from the build output. This is intentional for bundle size but means consumers cannot see inline docs from `dist/`.
|
|
91
133
|
|
|
92
134
|
## Testing
|
|
93
135
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/core/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/core/service.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,wBAAwB,EACxB,qBAAqB,EACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,wBAAwB,EACxB,kBAAkB,EAClB,aAAa,EACb,cAAc,EACf,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAG5E,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,SAAS,EAAE,oBAAoB,CAAC;CACjC;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,cAAc,CAA0C;IAChE,OAAO,CAAC,oBAAoB,CAA8B;IAC1D,OAAO,CAAC,gBAAgB,CAAS;gBAErB,MAAM,EAAE,wBAAwB;IAUtC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BpC,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAQtD,cAAc,IAAI,MAAM,EAAE;IASpB,WAAW,IAAI,OAAO,CAAC,aAAa,CAAC;IAiB3C,gBAAgB,IAAI,aAAa,GAAG,IAAI;IAUlC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,aAAa,CAAC;IA0BlE,WAAW,CACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAoB3C,kBAAkB,CACtB,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAUhC,eAAe,CACnB,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAQnC,UAAU,IAAI,IAAI;IAMlB,kBAAkB,IAAI,OAAO;IAK7B,gBAAgB,IAAI,OAAO;CAG5B"}
|
package/dist/core/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/core/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/core/service.ts"],"names":[],"mappings":"AAuBA,MAAM,OAAO,kBAAkB;IAQ7B,YAAY,MAAgC;QALpC,iBAAY,GAAyB,IAAI,CAAC;QAC1C,mBAAc,GAAgC,IAAI,GAAG,EAAE,CAAC;QACxD,yBAAoB,GAAyB,IAAI,CAAC;QAClD,qBAAgB,GAAG,KAAK,CAAC;QAG/B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC9B,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;IACpC,CAAC;IAOD,KAAK,CAAC,aAAa;QACjB,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC;YAAE,OAAO;QACzC,IAAI,IAAI,CAAC,oBAAoB;YAAE,OAAO,IAAI,CAAC,oBAAoB,CAAC;QAEhE,IAAI,CAAC,oBAAoB,GAAG,CAAC,KAAK,IAAI,EAAE;YACtC,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;gBACjD,KAAK,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;oBACzD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE;wBAC3B,UAAU,EAAE,QAAQ,CAAC,UAAU;wBAC/B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;qBAC5B,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;YACnC,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;QAEL,OAAO,IAAI,CAAC,oBAAoB,CAAC;IACnC,CAAC;IAOD,WAAW,CAAC,UAAkB;QAC5B,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;IACrD,CAAC;IAMD,cAAc;QACZ,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAOD,KAAK,CAAC,WAAW;QACf,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAC/C,OAAO,IAAI,CAAC,YAAY,CAAC;QAC3B,CAAC;QACD,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAC7B,IAAI,CAAC;YACH,IAAI,CAAC,YAAY,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC;YACtD,OAAO,IAAI,CAAC,YAAY,CAAC;QAC3B,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;QAChC,CAAC;IACH,CAAC;IAMD,gBAAgB;QACd,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAQD,KAAK,CAAC,QAAQ,CAAC,MAAgC;QAE7C,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAG3D,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC;YAClD,OAAO,EAAE,cAAc,CAAC,OAAO;YAC/B,MAAM,EAAE,cAAc,CAAC,MAAM;YAC7B,kBAAkB,EAAE,cAAc,CAAC,aAAa;YAChD,UAAU,EAAE,cAAc,CAAC,SAAS;YACpC,WAAW,EAAE,cAAc,CAAC,UAAU;YACtC,QAAQ,EAAE,cAAc,CAAC,QAAQ;SAClC,CAAC,CAAC;QAGH,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC;QAE5B,OAAO,OAAO,CAAC;IACjB,CAAC;IAQD,KAAK,CAAC,WAAW,CACf,QAAiB;QAEjB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAG1D,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,IAAI,CAAC,YAAY,GAAG;gBAClB,GAAG,IAAI,CAAC,YAAY;gBACpB,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,CAAC;QACJ,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAQD,KAAK,CAAC,kBAAkB,CACtB,KAAc,EACd,MAAe;QAEf,OAAO,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC1D,CAAC;IAQD,KAAK,CAAC,eAAe,CACnB,KAAc,EACd,MAAe;QAEf,OAAO,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACvD,CAAC;IAMD,UAAU;QACR,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;IAE3B,CAAC;IAGD,kBAAkB;QAChB,OAAO,IAAI,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,CAAC;IACtC,CAAC;IAGD,gBAAgB;QACd,OAAO,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC;IACpC,CAAC;CACF","sourcesContent":["/**\n * @fileoverview Core ConsumablesService class that orchestrates adapter and API client\n * interactions. Manages caching of offerings (global) and balance (per-user).\n */\n\nimport type {\n ConsumablePurchaseRecord,\n ConsumableUsageRecord,\n} from \"@sudobility/types\";\nimport type {\n ConsumablePurchaseParams,\n ConsumablesAdapter,\n CreditBalance,\n CreditOffering,\n} from \"../types\";\nimport type { ConsumablesApiClient } from \"../network/ConsumablesApiClient\";\n\n/** Configuration for constructing a ConsumablesService instance. */\nexport interface ConsumablesServiceConfig {\n adapter: ConsumablesAdapter;\n apiClient: ConsumablesApiClient;\n}\n\nexport class ConsumablesService {\n private adapter: ConsumablesAdapter;\n private apiClient: ConsumablesApiClient;\n private balanceCache: CreditBalance | null = null;\n private offeringsCache: Map<string, CreditOffering> = new Map();\n private loadOfferingsPromise: Promise<void> | null = null;\n private isLoadingBalance = false;\n\n constructor(config: ConsumablesServiceConfig) {\n this.adapter = config.adapter;\n this.apiClient = config.apiClient;\n }\n\n /**\n * Loads offerings from RevenueCat via the adapter.\n * Caches results globally; subsequent calls are no-ops if cache is populated.\n * Deduplicates concurrent calls via a shared promise.\n */\n async loadOfferings(): Promise<void> {\n if (this.offeringsCache.size > 0) return;\n if (this.loadOfferingsPromise) return this.loadOfferingsPromise;\n\n this.loadOfferingsPromise = (async () => {\n try {\n const result = await this.adapter.getOfferings();\n for (const [key, offering] of Object.entries(result.all)) {\n this.offeringsCache.set(key, {\n offeringId: offering.identifier,\n packages: offering.packages,\n });\n }\n } finally {\n this.loadOfferingsPromise = null;\n }\n })();\n\n return this.loadOfferingsPromise;\n }\n\n /**\n * Retrieves a cached offering by its identifier.\n * @param offeringId - The RevenueCat offering identifier.\n * @returns The offering with its packages, or null if not found in cache.\n */\n getOffering(offeringId: string): CreditOffering | null {\n return this.offeringsCache.get(offeringId) ?? null;\n }\n\n /**\n * Returns all cached offering identifiers.\n * @returns Array of offering ID strings.\n */\n getOfferingIds(): string[] {\n return Array.from(this.offeringsCache.keys());\n }\n\n /**\n * Loads the current user's balance from the backend API.\n * Caches the result; returns cached value if a load is already in progress.\n * @returns The user's credit balance.\n */\n async loadBalance(): Promise<CreditBalance> {\n if (this.isLoadingBalance && this.balanceCache) {\n return this.balanceCache;\n }\n this.isLoadingBalance = true;\n try {\n this.balanceCache = await this.apiClient.getBalance();\n return this.balanceCache;\n } finally {\n this.isLoadingBalance = false;\n }\n }\n\n /**\n * Returns the cached balance without making an API call.\n * @returns The cached credit balance, or null if not yet loaded.\n */\n getCachedBalance(): CreditBalance | null {\n return this.balanceCache;\n }\n\n /**\n * Executes a full purchase flow: opens RevenueCat payment UI via adapter,\n * records the purchase on the backend, and updates the local balance cache.\n * @param params - The package and offering IDs to purchase.\n * @returns The updated credit balance after the purchase.\n */\n async purchase(params: ConsumablePurchaseParams): Promise<CreditBalance> {\n // 1. Call adapter.purchase() — opens RevenueCat payment UI\n const purchaseResult = await this.adapter.purchase(params);\n\n // 2. Record on backend\n const balance = await this.apiClient.recordPurchase({\n credits: purchaseResult.credits,\n source: purchaseResult.source,\n transaction_ref_id: purchaseResult.transactionId,\n product_id: purchaseResult.productId,\n price_cents: purchaseResult.priceCents,\n currency: purchaseResult.currency,\n });\n\n // 3. Update cache\n this.balanceCache = balance;\n\n return balance;\n }\n\n /**\n * Records a credit usage (e.g., file download) on the backend.\n * Updates the local balance cache with the new balance.\n * @param filename - Optional filename associated with this usage.\n * @returns The updated balance and whether the usage was successful.\n */\n async recordUsage(\n filename?: string,\n ): Promise<{ balance: number; success: boolean }> {\n const result = await this.apiClient.recordUsage(filename);\n\n // Update cache\n if (this.balanceCache) {\n this.balanceCache = {\n ...this.balanceCache,\n balance: result.balance,\n };\n }\n\n return result;\n }\n\n /**\n * Fetches paginated purchase history from the API.\n * @param limit - Maximum number of records to return.\n * @param offset - Number of records to skip.\n * @returns Array of purchase records.\n */\n async getPurchaseHistory(\n limit?: number,\n offset?: number,\n ): Promise<ConsumablePurchaseRecord[]> {\n return this.apiClient.getPurchaseHistory(limit, offset);\n }\n\n /**\n * Fetches paginated usage history from the API.\n * @param limit - Maximum number of records to return.\n * @param offset - Number of records to skip.\n * @returns Array of usage records.\n */\n async getUsageHistory(\n limit?: number,\n offset?: number,\n ): Promise<ConsumableUsageRecord[]> {\n return this.apiClient.getUsageHistory(limit, offset);\n }\n\n /**\n * Clears the balance cache. Called on user change.\n * Preserves the offerings cache since products are not user-specific.\n */\n clearCache(): void {\n this.balanceCache = null;\n // Preserve offerings cache — products don't change per user\n }\n\n /** @returns Whether offerings have been loaded and cached. */\n hasLoadedOfferings(): boolean {\n return this.offeringsCache.size > 0;\n }\n\n /** @returns Whether the balance has been loaded and cached. */\n hasLoadedBalance(): boolean {\n return this.balanceCache !== null;\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"singleton.d.ts","sourceRoot":"","sources":["../../src/core/singleton.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"singleton.d.ts","sourceRoot":"","sources":["../../src/core/singleton.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAG/C,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,SAAS,EAAE,oBAAoB,CAAC;CACjC;AASD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAMrE;AAGD,wBAAgB,sBAAsB,IAAI,kBAAkB,CAO3D;AAGD,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAGD,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC;AAGD,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAM/D;AAMD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAgBf;AAGD,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,SAAS,CAEzD;AAGD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAM3E;AAGD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAM1E;AAGD,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"singleton.js","sourceRoot":"","sources":["../../src/core/singleton.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"singleton.js","sourceRoot":"","sources":["../../src/core/singleton.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAQ/C,IAAI,QAAQ,GAA8B,IAAI,CAAC;AAC/C,IAAI,cAAc,GAA8B,IAAI,CAAC;AACrD,IAAI,aAAa,GAAuB,SAAS,CAAC;AAClD,MAAM,sBAAsB,GAAsB,EAAE,CAAC;AACrD,MAAM,qBAAqB,GAAsB,EAAE,CAAC;AAGpD,MAAM,UAAU,qBAAqB,CAAC,MAAyB;IAC7D,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC;IAChC,QAAQ,GAAG,IAAI,kBAAkB,CAAC;QAChC,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAC,CAAC;AACL,CAAC;AAGD,MAAM,UAAU,sBAAsB;IACpC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAGD,MAAM,UAAU,wBAAwB;IACtC,OAAO,QAAQ,KAAK,IAAI,CAAC;AAC3B,CAAC;AAGD,MAAM,UAAU,gBAAgB;IAC9B,QAAQ,GAAG,IAAI,CAAC;IAChB,cAAc,GAAG,IAAI,CAAC;IACtB,aAAa,GAAG,SAAS,CAAC;AAC5B,CAAC;AAGD,MAAM,CAAC,KAAK,UAAU,yBAAyB;IAC7C,IAAI,CAAC,QAAQ;QAAE,OAAO;IACtB,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;IAC7B,KAAK,MAAM,QAAQ,IAAI,sBAAsB,EAAE,CAAC;QAC9C,QAAQ,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAMD,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAA0B,EAC1B,KAAc;IAEd,IAAI,aAAa,KAAK,MAAM;QAAE,OAAO;IAErC,aAAa,GAAG,MAAM,CAAC;IAEvB,IAAI,cAAc,EAAE,SAAS,EAAE,CAAC;QAC9B,MAAM,cAAc,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAChD,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,QAAQ,CAAC,UAAU,EAAE,CAAC;IACxB,CAAC;IAED,KAAK,MAAM,QAAQ,IAAI,qBAAqB,EAAE,CAAC;QAC7C,QAAQ,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAGD,MAAM,UAAU,oBAAoB;IAClC,OAAO,aAAa,CAAC;AACvB,CAAC;AAGD,MAAM,UAAU,0BAA0B,CAAC,QAAoB;IAC7D,sBAAsB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtC,OAAO,GAAG,EAAE;QACV,MAAM,KAAK,GAAG,sBAAsB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACvD,IAAI,KAAK,IAAI,CAAC;YAAE,sBAAsB,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC;AACJ,CAAC;AAGD,MAAM,UAAU,yBAAyB,CAAC,QAAoB;IAC5D,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrC,OAAO,GAAG,EAAE;QACV,MAAM,KAAK,GAAG,qBAAqB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,KAAK,IAAI,CAAC;YAAE,qBAAqB,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACzD,CAAC,CAAC;AACJ,CAAC;AAGD,MAAM,UAAU,mBAAmB;IACjC,KAAK,MAAM,QAAQ,IAAI,sBAAsB,EAAE,CAAC;QAC9C,QAAQ,EAAE,CAAC;IACb,CAAC;AACH,CAAC","sourcesContent":["/**\n * @fileoverview Module-level singleton for the consumables system.\n * Provides initialization, user management, and event subscription functions\n * without requiring a React Context provider.\n */\n\nimport type { ConsumablesAdapter } from \"../types/adapter\";\nimport type { ConsumablesApiClient } from \"../network/ConsumablesApiClient\";\nimport { ConsumablesService } from \"./service\";\n\n/** Configuration for initializing the consumables singleton. */\nexport interface ConsumablesConfig {\n adapter: ConsumablesAdapter;\n apiClient: ConsumablesApiClient;\n}\n\nlet instance: ConsumablesService | null = null;\nlet currentAdapter: ConsumablesAdapter | null = null;\nlet currentUserId: string | undefined = undefined;\nconst balanceChangeListeners: Array<() => void> = [];\nconst userIdChangeListeners: Array<() => void> = [];\n\n/** Initialize the consumables singleton. Call once at app startup. */\nexport function initializeConsumables(config: ConsumablesConfig): void {\n currentAdapter = config.adapter;\n instance = new ConsumablesService({\n adapter: config.adapter,\n apiClient: config.apiClient,\n });\n}\n\n/** Get the consumables service singleton. Throws if not initialized. */\nexport function getConsumablesInstance(): ConsumablesService {\n if (!instance) {\n throw new Error(\n \"Consumables not initialized. Call initializeConsumables() first.\",\n );\n }\n return instance;\n}\n\n/** Check if consumables singleton is initialized. */\nexport function isConsumablesInitialized(): boolean {\n return instance !== null;\n}\n\n/** Reset the singleton (mainly for testing). */\nexport function resetConsumables(): void {\n instance = null;\n currentAdapter = null;\n currentUserId = undefined;\n}\n\n/** Refresh balance from server and notify listeners. */\nexport async function refreshConsumablesBalance(): Promise<void> {\n if (!instance) return;\n await instance.loadBalance();\n for (const listener of balanceChangeListeners) {\n listener();\n }\n}\n\n/**\n * Set the user ID for the consumables service.\n * Clears cached balance and re-initializes RevenueCat adapter.\n */\nexport async function setConsumablesUserId(\n userId: string | undefined,\n email?: string,\n): Promise<void> {\n if (currentUserId === userId) return;\n\n currentUserId = userId;\n\n if (currentAdapter?.setUserId) {\n await currentAdapter.setUserId(userId, email);\n }\n\n if (instance) {\n instance.clearCache();\n }\n\n for (const listener of userIdChangeListeners) {\n listener();\n }\n}\n\n/** Get the current user ID. */\nexport function getConsumablesUserId(): string | undefined {\n return currentUserId;\n}\n\n/** Subscribe to balance changes. Returns unsubscribe function. */\nexport function onConsumablesBalanceChange(listener: () => void): () => void {\n balanceChangeListeners.push(listener);\n return () => {\n const index = balanceChangeListeners.indexOf(listener);\n if (index >= 0) balanceChangeListeners.splice(index, 1);\n };\n}\n\n/** Subscribe to user ID changes. Returns unsubscribe function. */\nexport function onConsumablesUserIdChange(listener: () => void): () => void {\n userIdChangeListeners.push(listener);\n return () => {\n const index = userIdChangeListeners.indexOf(listener);\n if (index >= 0) userIdChangeListeners.splice(index, 1);\n };\n}\n\n/** Notify balance change listeners (call after purchase/usage). */\nexport function notifyBalanceChange(): void {\n for (const listener of balanceChangeListeners) {\n listener();\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBalance.d.ts","sourceRoot":"","sources":["../../src/hooks/useBalance.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useBalance.d.ts","sourceRoot":"","sources":["../../src/hooks/useBalance.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAQD,wBAAgB,UAAU,IAAI,gBAAgB,CAoD7C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBalance.js","sourceRoot":"","sources":["../../src/hooks/useBalance.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useBalance.js","sourceRoot":"","sources":["../../src/hooks/useBalance.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC;AAiB3B,MAAM,UAAU,UAAU;IACxB,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAC5D,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAC1E,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACzC,IAAI,CAAC,wBAAwB,EAAE,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;YAC3D,UAAU,CAAC,IAAI,CAAC,CAAC;YACjB,iBAAiB,CAAC,IAAI,CAAC,CAAC;YACxB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC5C,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3B,iBAAiB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,WAAW,EAAE,CAAC;IAChB,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,YAAY,GAAG,0BAA0B,CAAC,GAAG,EAAE;YACnD,IAAI,wBAAwB,EAAE,EAAE,CAAC;gBAC/B,MAAM,MAAM,GAAG,sBAAsB,EAAE,CAAC,gBAAgB,EAAE,CAAC;gBAC3D,IAAI,MAAM,EAAE,CAAC;oBACX,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;oBAC3B,iBAAiB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;gBAC3C,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QACH,MAAM,SAAS,GAAG,yBAAyB,CAAC,GAAG,EAAE;YAC/C,WAAW,EAAE,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE;YACV,YAAY,EAAE,CAAC;YACf,SAAS,EAAE,CAAC;QACd,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AAC7E,CAAC","sourcesContent":["/**\n * @fileoverview React hook for tracking the current user's credit balance.\n * Subscribes to both balance change and user ID change events from the singleton.\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n getConsumablesInstance,\n getConsumablesUserId,\n isConsumablesInitialized,\n onConsumablesBalanceChange,\n onConsumablesUserIdChange,\n} from \"../core/singleton\";\n\n/** Result object returned by the useBalance hook. */\nexport interface UseBalanceResult {\n balance: number | null;\n initialCredits: number | null;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n}\n\n/**\n * Hook that tracks the current user's credit balance.\n * Automatically loads the balance on mount and re-fetches when the user ID changes.\n * Listens to balance change events emitted after purchases or usage.\n * @returns Current balance, loading/error state, and a refetch function.\n */\nexport function useBalance(): UseBalanceResult {\n const [balance, setBalance] = useState<number | null>(null);\n const [initialCredits, setInitialCredits] = useState<number | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const loadBalance = useCallback(async () => {\n if (!isConsumablesInitialized() || !getConsumablesUserId()) {\n setBalance(null);\n setInitialCredits(null);\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n try {\n const instance = getConsumablesInstance();\n const result = await instance.loadBalance();\n setBalance(result.balance);\n setInitialCredits(result.initialCredits);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, []);\n\n useEffect(() => {\n loadBalance();\n }, [loadBalance]);\n\n useEffect(() => {\n const unsubBalance = onConsumablesBalanceChange(() => {\n if (isConsumablesInitialized()) {\n const cached = getConsumablesInstance().getCachedBalance();\n if (cached) {\n setBalance(cached.balance);\n setInitialCredits(cached.initialCredits);\n }\n }\n });\n const unsubUser = onConsumablesUserIdChange(() => {\n loadBalance();\n });\n return () => {\n unsubBalance();\n unsubUser();\n };\n }, [loadBalance]);\n\n return { balance, initialCredits, isLoading, error, refetch: loadBalance };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useConsumableProducts.d.ts","sourceRoot":"","sources":["../../src/hooks/useConsumableProducts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useConsumableProducts.d.ts","sourceRoot":"","sources":["../../src/hooks/useConsumableProducts.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAQD,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,GACjB,2BAA2B,CA8B7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useConsumableProducts.js","sourceRoot":"","sources":["../../src/hooks/useConsumableProducts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useConsumableProducts.js","sourceRoot":"","sources":["../../src/hooks/useConsumableProducts.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EACL,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAiB3B,MAAM,UAAU,qBAAqB,CACnC,UAAkB;IAElB,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAkB,EAAE,CAAC,CAAC;IAC9D,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,MAAM,YAAY,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAC1C,IAAI,CAAC,wBAAwB,EAAE,EAAE,CAAC;YAChC,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,QAAQ,CAAC,aAAa,EAAE,CAAC;YAC/B,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;YAClD,WAAW,CAAC,QAAQ,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC;QACxC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,SAAS,CAAC,GAAG,EAAE;QACb,YAAY,EAAE,CAAC;IACjB,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAEnB,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;AAC/D,CAAC","sourcesContent":["/**\n * @fileoverview React hook for loading available credit packages from a RevenueCat offering.\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n getConsumablesInstance,\n isConsumablesInitialized,\n} from \"../core/singleton\";\nimport type { CreditPackage } from \"../types\";\n\n/** Result object returned by the useConsumableProducts hook. */\nexport interface UseConsumableProductsResult {\n packages: CreditPackage[];\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n}\n\n/**\n * Hook that loads available credit packages from a specific RevenueCat offering.\n * Caches offerings globally (they do not change per user).\n * @param offeringId - The RevenueCat offering identifier to load packages from.\n * @returns Available packages, loading/error state, and a refetch function.\n */\nexport function useConsumableProducts(\n offeringId: string,\n): UseConsumableProductsResult {\n const [packages, setPackages] = useState<CreditPackage[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const loadProducts = useCallback(async () => {\n if (!isConsumablesInitialized()) {\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n try {\n const instance = getConsumablesInstance();\n await instance.loadOfferings();\n const offering = instance.getOffering(offeringId);\n setPackages(offering?.packages ?? []);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [offeringId]);\n\n useEffect(() => {\n loadProducts();\n }, [loadProducts]);\n\n return { packages, isLoading, error, refetch: loadProducts };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePurchaseCredits.d.ts","sourceRoot":"","sources":["../../src/hooks/usePurchaseCredits.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePurchaseCredits.d.ts","sourceRoot":"","sources":["../../src/hooks/usePurchaseCredits.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAQD,wBAAgB,kBAAkB,IAAI,wBAAwB,CA6B7D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePurchaseCredits.js","sourceRoot":"","sources":["../../src/hooks/usePurchaseCredits.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePurchaseCredits.js","sourceRoot":"","sources":["../../src/hooks/usePurchaseCredits.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAe3B,MAAM,UAAU,kBAAkB;IAChC,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,MAAM,QAAQ,GAAG,WAAW,CAC1B,KAAK,EAAE,SAAiB,EAAE,UAAkB,EAAoB,EAAE;QAChE,IAAI,CAAC,wBAAwB,EAAE,EAAE,CAAC;YAChC,QAAQ,CAAC,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC,CAAC;YACnD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;YACnD,mBAAmB,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC9D,OAAO,KAAK,CAAC;QACf,CAAC;gBAAS,CAAC;YACT,eAAe,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;IACH,CAAC,EACD,EAAE,CACH,CAAC;IAEF,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;AAC3C,CAAC","sourcesContent":["/**\n * @fileoverview React hook for executing credit purchases via the RevenueCat adapter.\n * Handles the full purchase flow: adapter payment UI -> backend recording -> balance notification.\n */\n\nimport { useCallback, useState } from \"react\";\nimport {\n getConsumablesInstance,\n isConsumablesInitialized,\n notifyBalanceChange,\n} from \"../core/singleton\";\n\n/** Result object returned by the usePurchaseCredits hook. */\nexport interface UsePurchaseCreditsResult {\n purchase: (packageId: string, offeringId: string) => Promise<boolean>;\n isPurchasing: boolean;\n error: Error | null;\n}\n\n/**\n * Hook that provides a purchase callback for buying credit packages.\n * Opens the platform payment UI, records the purchase on the backend,\n * and notifies balance change listeners on success.\n * @returns A purchase function, purchasing state, and error state.\n */\nexport function usePurchaseCredits(): UsePurchaseCreditsResult {\n const [isPurchasing, setIsPurchasing] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const purchase = useCallback(\n async (packageId: string, offeringId: string): Promise<boolean> => {\n if (!isConsumablesInitialized()) {\n setError(new Error(\"Consumables not initialized\"));\n return false;\n }\n\n setIsPurchasing(true);\n setError(null);\n try {\n const instance = getConsumablesInstance();\n await instance.purchase({ packageId, offeringId });\n notifyBalanceChange();\n return true;\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n return false;\n } finally {\n setIsPurchasing(false);\n }\n },\n [],\n );\n\n return { purchase, isPurchasing, error };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePurchaseHistory.d.ts","sourceRoot":"","sources":["../../src/hooks/usePurchaseHistory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePurchaseHistory.d.ts","sourceRoot":"","sources":["../../src/hooks/usePurchaseHistory.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAGlE,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,wBAAwB,EAAE,CAAC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAQD,wBAAgB,kBAAkB,CAAC,KAAK,SAAK,GAAG,wBAAwB,CA8CvE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePurchaseHistory.js","sourceRoot":"","sources":["../../src/hooks/usePurchaseHistory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePurchaseHistory.js","sourceRoot":"","sources":["../../src/hooks/usePurchaseHistory.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC;AAkB3B,MAAM,UAAU,kBAAkB,CAAC,KAAK,GAAG,EAAE;IAC3C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAA6B,EAAE,CAAC,CAAC;IAC3E,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAClC,IAAI,CAAC,wBAAwB,EAAE,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;YAC3D,YAAY,CAAC,EAAE,CAAC,CAAC;YACjB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAC3D,YAAY,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACtC,IAAI,CAAC,wBAAwB,EAAE;YAAE,OAAO;QACxC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;YAC1E,YAAY,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAE9B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,EAAE,CAAC;IACT,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,KAAK,GAAG,yBAAyB,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;QACtD,OAAO,KAAK,CAAC;IACf,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAClE,CAAC","sourcesContent":["/**\n * @fileoverview React hook for loading paginated purchase history.\n * Supports initial load, load-more pagination, and automatic refresh on user change.\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n getConsumablesInstance,\n getConsumablesUserId,\n isConsumablesInitialized,\n onConsumablesUserIdChange,\n} from \"../core/singleton\";\nimport type { ConsumablePurchaseRecord } from \"@sudobility/types\";\n\n/** Result object returned by the usePurchaseHistory hook. */\nexport interface UsePurchaseHistoryResult {\n purchases: ConsumablePurchaseRecord[];\n isLoading: boolean;\n error: Error | null;\n loadMore: () => Promise<void>;\n refetch: () => Promise<void>;\n}\n\n/**\n * Hook that loads paginated purchase history for the current user.\n * Automatically reloads when the user ID changes.\n * @param limit - Maximum number of records per page. Defaults to 50.\n * @returns Purchase records, loading/error state, loadMore and refetch functions.\n */\nexport function usePurchaseHistory(limit = 50): UsePurchaseHistoryResult {\n const [purchases, setPurchases] = useState<ConsumablePurchaseRecord[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const load = useCallback(async () => {\n if (!isConsumablesInitialized() || !getConsumablesUserId()) {\n setPurchases([]);\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n try {\n const instance = getConsumablesInstance();\n const result = await instance.getPurchaseHistory(limit, 0);\n setPurchases(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [limit]);\n\n const loadMore = useCallback(async () => {\n if (!isConsumablesInitialized()) return;\n try {\n const instance = getConsumablesInstance();\n const result = await instance.getPurchaseHistory(limit, purchases.length);\n setPurchases((prev) => [...prev, ...result]);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n }, [limit, purchases.length]);\n\n useEffect(() => {\n load();\n }, [load]);\n\n useEffect(() => {\n const unsub = onConsumablesUserIdChange(() => load());\n return unsub;\n }, [load]);\n\n return { purchases, isLoading, error, loadMore, refetch: load };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUsageHistory.d.ts","sourceRoot":"","sources":["../../src/hooks/useUsageHistory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useUsageHistory.d.ts","sourceRoot":"","sources":["../../src/hooks/useUsageHistory.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAG/D,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAQD,wBAAgB,eAAe,CAAC,KAAK,SAAK,GAAG,qBAAqB,CA8CjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUsageHistory.js","sourceRoot":"","sources":["../../src/hooks/useUsageHistory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useUsageHistory.js","sourceRoot":"","sources":["../../src/hooks/useUsageHistory.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC;AAkB3B,MAAM,UAAU,eAAe,CAAC,KAAK,GAAG,EAAE;IACxC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAA0B,EAAE,CAAC,CAAC;IAClE,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAClC,IAAI,CAAC,wBAAwB,EAAE,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;YAC3D,SAAS,CAAC,EAAE,CAAC,CAAC;YACd,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YACxD,SAAS,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACtC,IAAI,CAAC,wBAAwB,EAAE;YAAE,OAAO;QACxC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YACpE,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAE3B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,EAAE,CAAC;IACT,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,KAAK,GAAG,yBAAyB,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;QACtD,OAAO,KAAK,CAAC;IACf,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC/D,CAAC","sourcesContent":["/**\n * @fileoverview React hook for loading paginated usage history.\n * Supports initial load, load-more pagination, and automatic refresh on user change.\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n getConsumablesInstance,\n getConsumablesUserId,\n isConsumablesInitialized,\n onConsumablesUserIdChange,\n} from \"../core/singleton\";\nimport type { ConsumableUsageRecord } from \"@sudobility/types\";\n\n/** Result object returned by the useUsageHistory hook. */\nexport interface UseUsageHistoryResult {\n usages: ConsumableUsageRecord[];\n isLoading: boolean;\n error: Error | null;\n loadMore: () => Promise<void>;\n refetch: () => Promise<void>;\n}\n\n/**\n * Hook that loads paginated usage history for the current user.\n * Automatically reloads when the user ID changes.\n * @param limit - Maximum number of records per page. Defaults to 50.\n * @returns Usage records, loading/error state, loadMore and refetch functions.\n */\nexport function useUsageHistory(limit = 50): UseUsageHistoryResult {\n const [usages, setUsages] = useState<ConsumableUsageRecord[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const load = useCallback(async () => {\n if (!isConsumablesInitialized() || !getConsumablesUserId()) {\n setUsages([]);\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n try {\n const instance = getConsumablesInstance();\n const result = await instance.getUsageHistory(limit, 0);\n setUsages(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [limit]);\n\n const loadMore = useCallback(async () => {\n if (!isConsumablesInitialized()) return;\n try {\n const instance = getConsumablesInstance();\n const result = await instance.getUsageHistory(limit, usages.length);\n setUsages((prev) => [...prev, ...result]);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n }, [limit, usages.length]);\n\n useEffect(() => {\n load();\n }, [load]);\n\n useEffect(() => {\n const unsub = onConsumablesUserIdChange(() => load());\n return unsub;\n }, [load]);\n\n return { usages, isLoading, error, loadMore, refetch: load };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConsumablesApiClient.d.ts","sourceRoot":"","sources":["../../src/network/ConsumablesApiClient.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ConsumablesApiClient.d.ts","sourceRoot":"","sources":["../../src/network/ConsumablesApiClient.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,wBAAwB,EACxB,qBAAqB,EACrB,aAAa,EACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAOD,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;gBAElC,MAAM,EAAE,0BAA0B;IAK9C,OAAO,CAAC,QAAQ;YAIF,GAAG;YAYH,IAAI;IAiBZ,UAAU,IAAI,OAAO,CAAC,aAAa,CAAC;IAgBpC,cAAc,CAAC,MAAM,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,aAAa,CAAC;IAgBpB,WAAW,CACf,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAY3C,kBAAkB,CACtB,KAAK,SAAK,EACV,MAAM,SAAI,GACT,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAYhC,eAAe,CACnB,KAAK,SAAK,EACV,MAAM,SAAI,GACT,OAAO,CAAC,qBAAqB,EAAE,CAAC;CAKpC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConsumablesApiClient.js","sourceRoot":"","sources":["../../src/network/ConsumablesApiClient.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ConsumablesApiClient.js","sourceRoot":"","sources":["../../src/network/ConsumablesApiClient.ts"],"names":[],"mappings":"AAwBA,MAAM,OAAO,oBAAoB;IAI/B,YAAY,MAAkC;QAC5C,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;IAC5C,CAAC;IAEO,QAAQ,CAAC,IAAY;QAC3B,OAAO,GAAG,IAAI,CAAC,OAAO,sBAAsB,IAAI,EAAE,CAAC;IACrD,CAAC;IAEO,KAAK,CAAC,GAAG,CAAI,IAAY;QAC/B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAC3C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CACpB,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CACb,QAAQ,CAAC,IAAI,EAAE,KAAK,IAAI,mBAAmB,QAAQ,CAAC,MAAM,EAAE,CAC7D,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;IAC5B,CAAC;IAEO,KAAK,CAAC,IAAI,CAAI,IAAY,EAAE,IAAc;QAChD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAC5C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EACnB,IAAI,CACL,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CACb,QAAQ,CAAC,IAAI,EAAE,KAAK,IAAI,mBAAmB,QAAQ,CAAC,MAAM,EAAE,CAC7D,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;IAC5B,CAAC;IAMD,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CAGxB,UAAU,CAAC,CAAC;QACf,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,cAAc,EAAE,IAAI,CAAC,eAAe;SACrC,CAAC;IACJ,CAAC;IAOD,KAAK,CAAC,cAAc,CAAC,MAOpB;QACC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAGzB,WAAW,EAAE,MAAM,CAAC,CAAC;QACxB,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,cAAc,EAAE,IAAI,CAAC,eAAe;SACrC,CAAC;IACJ,CAAC;IAOD,KAAK,CAAC,WAAW,CACf,QAAiB;QAEjB,OAAO,IAAI,CAAC,IAAI,CAAwC,MAAM,EAAE;YAC9D,QAAQ;SACT,CAAC,CAAC;IACL,CAAC;IAQD,KAAK,CAAC,kBAAkB,CACtB,KAAK,GAAG,EAAE,EACV,MAAM,GAAG,CAAC;QAEV,OAAO,IAAI,CAAC,GAAG,CACb,oBAAoB,KAAK,WAAW,MAAM,EAAE,CAC7C,CAAC;IACJ,CAAC;IAQD,KAAK,CAAC,eAAe,CACnB,KAAK,GAAG,EAAE,EACV,MAAM,GAAG,CAAC;QAEV,OAAO,IAAI,CAAC,GAAG,CACb,iBAAiB,KAAK,WAAW,MAAM,EAAE,CAC1C,CAAC;IACJ,CAAC;CACF","sourcesContent":["/**\n * @fileoverview HTTP client for the consumables API endpoints.\n * Wraps a NetworkClient to communicate with /api/v1/consumables/* endpoints,\n * mapping between snake_case API responses and camelCase client types.\n */\n\nimport type {\n ConsumablePurchaseRecord,\n ConsumableUsageRecord,\n NetworkClient,\n} from \"@sudobility/types\";\nimport type { CreditBalance } from \"../types\";\n\n/** Configuration for constructing a ConsumablesApiClient instance. */\nexport interface ConsumablesApiClientConfig {\n baseUrl: string;\n networkClient: NetworkClient;\n}\n\ninterface ApiResponse<T> {\n data: T;\n error?: string;\n}\n\nexport class ConsumablesApiClient {\n private readonly baseUrl: string;\n private readonly networkClient: NetworkClient;\n\n constructor(config: ConsumablesApiClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, \"\");\n this.networkClient = config.networkClient;\n }\n\n private buildUrl(path: string): string {\n return `${this.baseUrl}/api/v1/consumables${path}`;\n }\n\n private async get<T>(path: string): Promise<T> {\n const response = await this.networkClient.get<ApiResponse<T>>(\n this.buildUrl(path),\n );\n if (!response.ok || !response.data) {\n throw new Error(\n response.data?.error || `Request failed: ${response.status}`,\n );\n }\n return response.data.data;\n }\n\n private async post<T>(path: string, body?: unknown): Promise<T> {\n const response = await this.networkClient.post<ApiResponse<T>>(\n this.buildUrl(path),\n body,\n );\n if (!response.ok || !response.data) {\n throw new Error(\n response.data?.error || `Request failed: ${response.status}`,\n );\n }\n return response.data.data;\n }\n\n /**\n * Fetches the current user's credit balance from the API.\n * @returns The credit balance with current balance and initial credits.\n */\n async getBalance(): Promise<CreditBalance> {\n const data = await this.get<{\n balance: number;\n initial_credits: number;\n }>(\"/balance\");\n return {\n balance: data.balance,\n initialCredits: data.initial_credits,\n };\n }\n\n /**\n * Records a purchase on the backend and returns the updated balance.\n * @param params - Purchase details including credits, source, and optional transaction metadata.\n * @returns The updated credit balance after the purchase.\n */\n async recordPurchase(params: {\n credits: number;\n source: string;\n transaction_ref_id?: string;\n product_id?: string;\n price_cents?: number;\n currency?: string;\n }): Promise<CreditBalance> {\n const data = await this.post<{\n balance: number;\n initial_credits: number;\n }>(\"/purchase\", params);\n return {\n balance: data.balance,\n initialCredits: data.initial_credits,\n };\n }\n\n /**\n * Records a credit usage (download) on the backend.\n * @param filename - Optional filename associated with this usage.\n * @returns The updated balance and whether the usage was successful.\n */\n async recordUsage(\n filename?: string,\n ): Promise<{ balance: number; success: boolean }> {\n return this.post<{ balance: number; success: boolean }>(\"/use\", {\n filename,\n });\n }\n\n /**\n * Fetches paginated purchase history for the current user.\n * @param limit - Maximum number of records to return. Defaults to 50.\n * @param offset - Number of records to skip. Defaults to 0.\n * @returns Array of purchase records ordered by most recent first.\n */\n async getPurchaseHistory(\n limit = 50,\n offset = 0,\n ): Promise<ConsumablePurchaseRecord[]> {\n return this.get<ConsumablePurchaseRecord[]>(\n `/purchases?limit=${limit}&offset=${offset}`,\n );\n }\n\n /**\n * Fetches paginated usage history for the current user.\n * @param limit - Maximum number of records to return. Defaults to 50.\n * @param offset - Number of records to skip. Defaults to 0.\n * @returns Array of usage records ordered by most recent first.\n */\n async getUsageHistory(\n limit = 50,\n offset = 0,\n ): Promise<ConsumableUsageRecord[]> {\n return this.get<ConsumableUsageRecord[]>(\n `/usages?limit=${limit}&offset=${offset}`,\n );\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/types/adapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/types/adapter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;CACpC;AAGD,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD,MAAM,WAAW,kBAAkB;IAEjC,YAAY,IAAI,OAAO,CAAC;QACtB,GAAG,EAAE,MAAM,CACT,MAAM,EACN;YACE,UAAU,EAAE,MAAM,CAAC;YACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;YACzC,QAAQ,EAAE,aAAa,EAAE,CAAC;SAC3B,CACF,CAAC;KACH,CAAC,CAAC;IAGH,QAAQ,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAG9E,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/types/adapter.ts"],"names":[],"mappings":"","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/types/adapter.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * @fileoverview Adapter interface and related types for platform-specific purchase flows.\n * Defines the ConsumablesAdapter contract implemented by web and React Native adapters.\n */\n\nimport type { CreditPackage } from \"./index\";\n\n/** Platform-specific purchase result returned after a successful purchase. */\nexport interface ConsumablePurchaseResult {\n transactionId: string;\n productId: string;\n credits: number;\n priceCents: number;\n currency: string;\n source: \"web\" | \"apple\" | \"google\";\n}\n\n/** Purchase parameters */\nexport interface ConsumablePurchaseParams {\n packageId: string;\n offeringId: string;\n}\n\n/**\n * Adapter interface for platform-specific purchase flows.\n */\nexport interface ConsumablesAdapter {\n /** Get offerings (credit packages) from RevenueCat */\n getOfferings(): Promise<{\n all: Record<\n string,\n {\n identifier: string;\n metadata: Record<string, unknown> | null;\n packages: CreditPackage[];\n }\n >;\n }>;\n\n /** Execute a purchase via RevenueCat (opens payment UI) */\n purchase(params: ConsumablePurchaseParams): Promise<ConsumablePurchaseResult>;\n\n /** Set the current user (called on auth state change) */\n setUserId?(userId: string | undefined, email?: string): Promise<void>;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAMA,cAAc,WAAW,CAAC;AAK1B,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAGD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAGD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB"}
|
package/dist/types/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAMA,cAAc,WAAW,CAAC","sourcesContent":["/**\n * @fileoverview Client-only type definitions for the consumables credit system.\n * Defines CreditPackage, CreditOffering, and CreditBalance interfaces used\n * by hooks and the ConsumablesService.\n */\n\nexport * from \"./adapter\";\n\n// === Client-only types ===\n\n/** A purchasable credit package from RevenueCat. */\nexport interface CreditPackage {\n packageId: string;\n productId: string;\n title: string;\n description: string | null;\n credits: number;\n price: number;\n priceString: string;\n currencyCode: string;\n}\n\n/** An offering containing credit packages */\nexport interface CreditOffering {\n offeringId: string;\n packages: CreditPackage[];\n}\n\n/** Balance info from API (client-side camelCase mapping) */\nexport interface CreditBalance {\n balance: number;\n initialCredits: number;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudobility/consumables_client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Cross-platform consumable credits client with RevenueCat adapter pattern",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"prepublishOnly": "bun run build"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"@sudobility/types": "^1.9.
|
|
32
|
+
"@sudobility/types": "^1.9.54",
|
|
33
33
|
"react": "^18.0.0 || ^19.0.0",
|
|
34
34
|
"@revenuecat/purchases-js": "^1.0.0",
|
|
35
35
|
"react-native-purchases": ">=7.0.0"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@eslint/js": "^10.0.1",
|
|
47
47
|
"@revenuecat/purchases-js": "^1.1.3",
|
|
48
|
-
"@sudobility/types": "^1.9.
|
|
48
|
+
"@sudobility/types": "^1.9.54",
|
|
49
49
|
"@types/bun": "^1.2.8",
|
|
50
50
|
"@types/node": "^22.0.0",
|
|
51
51
|
"@types/react": "^19.2.5",
|