Release 2.0.0
Raises the deployment target to OS 26 and fixes a set of concurrency, entitlement and API defects found in an audit of the library. Every defect was reproduced before being fixed, and the package now has a test target that pins the behaviour.
⚠️ Breaking changes
- Platforms start at iOS / macOS / tvOS / watchOS / visionOS 26. Stay on 1.0.6 for anything earlier.
StoreProduct.productis now optional. It isnilon values you build yourself, and a new public initializer takes the fields directly — soPurchasesProtocolcan finally be mocked in tests and previews. Previously no conformer outside the module could produce a single return value.ProductTypegainsunknownandPurchasesErrorgainsunhandledPurchaseResult, so exhaustiveswitchstatements need a new branch. A StoreKit product type this SDK does not recognise no longer passes silently asnonConsumable.purchasedProductsno longer replays to a late subscriber. Every access now returns its own stream, so several observers each receive every event.
Fixed
- Purchase events were lost whenever a second observer appeared.
AsyncStreamhas exactly one consumer, so twofor awaitloops split the sequence instead of each receiving it — one observer saw[1,3,5,7,9], the other[2,4,6,8,10]. - The event buffer was unbounded and grew for the lifetime of the process: 100,000 yields with no consumer retained all of them. Per-subscriber buffers are now
bufferingNewest(32). - The transaction listener retained the manager forever.
Transaction.updatesnever ends, sodeinitwas unreachable and the cancel and finish it performs were dead code. - The singleton was read and written without synchronisation. ThreadSanitizer reports a data race on a single
configurecall racing ordinarysharedreads, and the guardingpreconditionwas itself a check-then-act on that memory. - The
PurchasesProtocoldefault implementation recursed forever for any conformer that did not implement the requirement — which is exactly what a mock is. The same omission is now a compile-time error. - Refunded subscriptions still counted as active in
activeSubscriptions()andactiveSubscription(inGroup:), the two places that answer "is this user subscribed". - Product order changed on every launch, shuffling the paywall, and could include products that were never configured.
- Duplicate purchase events, two force unwraps, and a redundant network round trip on every purchase.
Also
PurchasesError.notConfigured is finally thrown, by the new non-trapping PurchasesManager.resolved(), and the error type gains Equatable and LocalizedError.
The package gains its first test target — 24 tests in 5 suites — and CI is repaired: it could not build the 6.2 manifest with its pinned Xcode 16.2, and now also runs the tests.
See the migration notes in the README before upgrading from 1.x.