Skip to content

feat(gql): add discriminated union type support - ProductOrSubscription#33

Merged
hyochan merged 6 commits intomainfrom
types/product-or-subscription-union
Nov 4, 2025
Merged

feat(gql): add discriminated union type support - ProductOrSubscription#33
hyochan merged 6 commits intomainfrom
types/product-or-subscription-union

Conversation

@hyochan
Copy link
Member

@hyochan hyochan commented Nov 4, 2025

Add ProductOrSubscription union type to GraphQL schema and implement discriminated union type narrowing across all platforms (TypeScript, Swift, Kotlin, Dart).

Changes:

  • Add union ProductOrSubscription = Product | ProductSubscription to GraphQL schema
  • Update type generators to handle union of unions pattern
  • Optimize platform-specific type extraction with nested pattern matching
  • Fix TypeScript FetchProductsResult to support mixed arrays
  • Add ProductOrSubscription interface implementation to Product and ProductSubscription

TypeScript:

  • Extend FetchProductsResult type to include (Product | ProductSubscription)[]
  • Fix Query.fetchProducts to use FetchProductsResult type alias

Swift:

  • Use nested pattern matching for direct type extraction
  • Change .productSubscription(let sub), case .productSubscriptionIos(let val) to .productSubscription(.productSubscriptionIos(let val))

Kotlin:

  • Add ProductOrSubscription interface implementation with override modifiers
  • Optimize type extraction to filter platform-specific types directly
  • Change from wrapper types to direct sealed interface casting

Dart:

  • Add implements clause for Product and ProductSubscription
  • Handle union of unions in type generation

Generator scripts:

  • Add getInterfaces() type check for union members in all generators
  • Remove manual ProductOrSubscription post-processing (now auto-generated)
  • Add post-processing to make unions implement ProductOrSubscription

Benefits:

  • Enables type-safe discriminated union narrowing
  • Eliminates unnecessary intermediate object creation
  • Platform-specific code only handles its own types
  • Better IDE autocomplete and compile-time type safety

Summary by CodeRabbit

  • New Features

    • Fetch mixed collections of products and subscriptions in a single "all" response.
    • Introduced a unified ProductOrSubscription type to handle products and subscriptions consistently across platforms.
  • Chores

    • Updated GraphQL generation and language-specific type generators to emit wrapper-based union handling.
    • Updated GraphQL generator version metadata.
  • Refactor

    • Simplified platform-specific extraction and matching logic for product/subscription items.

Add ProductOrSubscription union type to GraphQL schema and implement
discriminated union type narrowing across all platforms (TypeScript,
Swift, Kotlin, Dart).

Changes:
- Add `union ProductOrSubscription = Product | ProductSubscription` to GraphQL schema
- Update type generators to handle union of unions pattern
- Optimize platform-specific type extraction with nested pattern matching
- Fix TypeScript FetchProductsResult to support mixed arrays
- Add ProductOrSubscription interface implementation to Product and ProductSubscription

TypeScript:
- Extend FetchProductsResult type to include `(Product | ProductSubscription)[]`
- Fix Query.fetchProducts to use FetchProductsResult type alias

Swift:
- Use nested pattern matching for direct type extraction
- Change `.productSubscription(let sub), case .productSubscriptionIos(let val)`
  to `.productSubscription(.productSubscriptionIos(let val))`

Kotlin:
- Add ProductOrSubscription interface implementation with override modifiers
- Optimize type extraction to filter platform-specific types directly
- Change from wrapper types to direct sealed interface casting

Dart:
- Add implements clause for Product and ProductSubscription
- Handle union of unions in type generation

Generator scripts:
- Add getInterfaces() type check for union members in all generators
- Remove manual ProductOrSubscription post-processing (now auto-generated)
- Add post-processing to make unions implement ProductOrSubscription

Benefits:
- Enables type-safe discriminated union narrowing
- Eliminates unnecessary intermediate object creation
- Platform-specific code only handles its own types
- Better IDE autocomplete and compile-time type safety
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 4, 2025

Warning

Rate limit exceeded

@hyochan has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 19 minutes and 15 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 1d1a7d4 and be89da6.

📒 Files selected for processing (2)
  • packages/gql/scripts/generate-dart-types.mjs (4 hunks)
  • packages/gql/scripts/generate-kotlin-types.mjs (3 hunks)

Walkthrough

Adds a ProductOrSubscription union and an all field on FetchProductsResult; updates generators to emit wrapper-based unions and guarded union handling; adjusts generated types and platform runtime code (Swift, Kotlin, Dart, JS/TS) and simplifies pattern-matching when extracting mixed product/subscription results. (34 words)

Changes

Cohort / File(s) Summary
GraphQL Schema
packages/gql/src/type.graphql
Added `union ProductOrSubscription = Product
Version config
openiap-versions.json
Updated gql version (1.2.51.2.4).
Generator scripts (JS/TS) & generation logic
packages/gql/scripts/fix-generated-types.mjs, packages/gql/scripts/generate-kotlin-types.mjs, packages/gql/scripts/generate-dart-types.mjs, packages/gql/scripts/generate-swift-types.mjs
Emit wrapper-style ProductOrSubscription; flatten nested unions to concrete members; add guards before calling getInterfaces; use FetchProductsResult alias including mixed `(Product
Generated / runtime types — Kotlin (Google)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
Added ProductOrSubscription sealed interface with wrapper implementations, Product/ProductSubscription implement it, and new FetchProductsResultAll(List<ProductOrSubscription>?) variant.
Store logic — Kotlin (Google)
packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
In FetchProductsResultAll handling, filter and cast mixed items to Android-specific Product/ProductSubscription wrappers and build products/subscriptions lists accordingly.
Generated / runtime types — Dart
(generation outputs affected by generate-dart-types.mjs)
Product/ProductSubscription now implement ProductOrSubscription; ProductOrSubscription emitted as wrapper class with concrete wrapper subclasses and fromJson factory; generation adjusted for wrapper delegation.
Apple / Swift runtime & ObjC bridge
packages/apple/Sources/Models/Types.swift, packages/apple/Sources/Models/OpenIapSerialization.swift, packages/apple/Sources/OpenIapModule+ObjC.swift, packages/apple/Sources/OpenIapStore.swift
Added public ProductOrSubscription type; FetchProductsResult includes all([ProductOrSubscription]?); simplified pattern matches to directly handle .productSubscription(.productSubscriptionIos(...)) and updated all-items extraction to narrow to iOS-specific subscription case.
JS/TS generated types insertion
packages/gql/scripts/fix-generated-types.mjs
Extended FetchProductsResult alias to include mixed `(Product

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant GraphQL
    participant Gen as Generators
    participant Runtime as PlatformRuntime

    Client->>GraphQL: fetchProducts()
    GraphQL-->>Gen: schema includes ProductOrSubscription + FetchProductsResult.all
    Gen->>Runtime: generate types (wrapper ProductOrSubscription, FetchProductsResult alias)
    Runtime->>Runtime: parse `all` items -> dispatch via __typename -> Product | ProductSubscription wrappers
    alt item -> Product
        Runtime-->>Client: include Product in all
    else item -> ProductSubscription
        Runtime-->>Client: include ProductSubscription in all
    end
    note right of Gen: union-flattening + getInterfaces guards added
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Files/areas needing extra attention:
    • Generator changes (JS/TS) that flatten nested unions and alter emitted public API shapes.
    • Kotlin Types.kt public interface changes and Android JSON casting/filtering in OpenIapStore.
    • Dart/Swift generation adjustments (wrapper pattern, enum casing).
    • Apple runtime pattern-match refactors in OpenIapStore and ObjC bridge to ensure correct extraction for mixed results.

Possibly related PRs

Poem

🐰 I hopped through unions, wide and bright,
Mixed products and subs now side-by-side.
Generators hum, wrappers take their cue,
Pattern matches trimmed — a tidy view. 🥕

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: adding discriminated union type support for ProductOrSubscription across the codebase. It is concise, specific, and directly reflects the primary objective of the PR.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt (1)

286-297: Consider using Kotlin's filterIsInstance for cleaner type filtering.

The current casting approach works correctly but could be simplified using Kotlin's built-in type filtering idioms.

Consider this more idiomatic Kotlin approach:

-                    val allProducts = items.mapNotNull {
-                        (it as? Product)?.let { product ->
-                            if (product is ProductAndroid) product else null
-                        }
-                    }
-                    val allSubs = items.mapNotNull {
-                        (it as? ProductSubscription)?.let { subscription ->
-                            if (subscription is ProductSubscriptionAndroid) subscription else null
-                        }
-                    }
+                    val allProducts = items.filterIsInstance<Product>()
+                        .filterIsInstance<ProductAndroid>()
+                    val allSubs = items.filterIsInstance<ProductSubscription>()
+                        .filterIsInstance<ProductSubscriptionAndroid>()

Both approaches are functionally equivalent, but filterIsInstance is more concise and idiomatic.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1fedfee and b5a1b2a.

⛔ Files ignored due to path filters (4)
  • packages/gql/src/generated/Types.kt is excluded by !**/generated/**
  • packages/gql/src/generated/Types.swift is excluded by !**/generated/**
  • packages/gql/src/generated/types.dart is excluded by !**/generated/**
  • packages/gql/src/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (12)
  • openiap-versions.json (1 hunks)
  • packages/apple/Sources/Models/OpenIapSerialization.swift (1 hunks)
  • packages/apple/Sources/Models/Types.swift (2 hunks)
  • packages/apple/Sources/OpenIapModule+ObjC.swift (1 hunks)
  • packages/apple/Sources/OpenIapStore.swift (1 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (3 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt (1 hunks)
  • packages/gql/scripts/fix-generated-types.mjs (2 hunks)
  • packages/gql/scripts/generate-dart-types.mjs (2 hunks)
  • packages/gql/scripts/generate-kotlin-types.mjs (2 hunks)
  • packages/gql/scripts/generate-swift-types.mjs (2 hunks)
  • packages/gql/src/type.graphql (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
packages/apple/Sources/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

packages/apple/Sources/**/*.swift: iOS-specific functions must have IOS suffix; cross-platform functions have no suffix
Swift acronym naming: acronyms are ALL CAPS only as a suffix; when at beginning/middle, use Pascal case (e.g., IapManager, ProductIAP)

Files:

  • packages/apple/Sources/OpenIapStore.swift
  • packages/apple/Sources/Models/OpenIapSerialization.swift
  • packages/apple/Sources/OpenIapModule+ObjC.swift
  • packages/apple/Sources/Models/Types.swift
packages/apple/Sources/Models/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Keep official OpenIAP types in Sources/Models/ matching openiap.dev/docs/types naming (e.g., Product.swift, Purchase.swift, ActiveSubscription.swift)

Files:

  • packages/apple/Sources/Models/OpenIapSerialization.swift
  • packages/apple/Sources/Models/Types.swift
packages/google/openiap/src/main/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

packages/google/openiap/src/main/**/*.kt: In Android-only package, do not add Android suffix to function names (e.g., acknowledgePurchase, not acknowledgePurchaseAndroid)
Only use Android suffix for types that are part of a cross-platform API (e.g., ProductAndroid, PurchaseAndroid)

Files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
packages/apple/Sources/Models/Types.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Do not edit auto-generated Types.swift in Sources/Models/

Files:

  • packages/apple/Sources/Models/Types.swift
🧠 Learnings (15)
📓 Common learnings
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/apple/Sources/Models/**/*.swift : Keep official OpenIAP types in Sources/Models/ matching openiap.dev/docs/types naming (e.g., Product.swift, Purchase.swift, ActiveSubscription.swift)
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/apple/Sources/Models/**/*.swift : Keep official OpenIAP types in Sources/Models/ matching openiap.dev/docs/types naming (e.g., Product.swift, Purchase.swift, ActiveSubscription.swift)

Applied to files:

  • packages/apple/Sources/OpenIapStore.swift
  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/apple/Sources/Models/OpenIapSerialization.swift
  • packages/gql/scripts/generate-dart-types.mjs
  • packages/apple/Sources/OpenIapModule+ObjC.swift
  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
  • packages/apple/Sources/Models/Types.swift
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/gql/scripts/generate-swift-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/apple/Sources/Helpers/**/*.swift : Place internal helper classes (not official OpenIAP types) under Sources/Helpers/ (e.g., ProductManager.swift, IapStatus.swift)

Applied to files:

  • packages/apple/Sources/OpenIapStore.swift
  • packages/apple/Sources/Models/OpenIapSerialization.swift
  • packages/apple/Sources/OpenIapModule+ObjC.swift
  • packages/apple/Sources/Models/Types.swift
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/apple/Sources/**/*.swift : Swift acronym naming: acronyms are ALL CAPS only as a suffix; when at beginning/middle, use Pascal case (e.g., IapManager, ProductIAP)

Applied to files:

  • packages/apple/Sources/OpenIapStore.swift
  • packages/apple/Sources/OpenIapModule+ObjC.swift
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/dist/kotlin/Types.kt : Generated Kotlin types location in GQL package: dist/kotlin/Types.kt

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/**/*.kt : Only use Android suffix for types that are part of a cross-platform API (e.g., ProductAndroid, PurchaseAndroid)

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/src/generated/types.ts : Generated TypeScript types location in GQL package: src/generated/types.ts

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/gql/src/type.graphql
  • packages/gql/scripts/generate-dart-types.mjs
  • packages/gql/scripts/fix-generated-types.mjs
  • packages/gql/scripts/generate-swift-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/Types.kt : Do not edit generated file openiap/src/main/Types.kt in Android package

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/dist/swift/Types.swift : Generated Swift types location in GQL package: dist/swift/Types.swift

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/gql/scripts/fix-generated-types.mjs
  • packages/gql/scripts/generate-swift-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/**/*.kt : In Android-only package, do not add Android suffix to function names (e.g., acknowledgePurchase, not acknowledgePurchaseAndroid)

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/CONVENTION.md : Before editing anything in GraphQL Types package, review packages/gql/CONVENTION.md

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/gql/src/type.graphql
  • packages/gql/scripts/generate-dart-types.mjs
  • packages/gql/scripts/fix-generated-types.mjs
  • packages/gql/scripts/generate-swift-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/dist/dart/types.dart : Generated Dart types location in GQL package: dist/dart/types.dart

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/gql/src/type.graphql
  • packages/gql/scripts/generate-dart-types.mjs
  • packages/gql/scripts/fix-generated-types.mjs
  • packages/gql/scripts/generate-swift-types.mjs
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/java/dev/hyo/openiap/utils/**/*.kt : Place reusable Kotlin helpers in openiap/src/main/java/dev/hyo/openiap/utils/

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/apple/Sources/Models/Types.swift : Do not edit auto-generated Types.swift in Sources/Models/

Applied to files:

  • packages/apple/Sources/Models/Types.swift
  • packages/gql/scripts/generate-swift-types.mjs
🧬 Code graph analysis (7)
packages/apple/Sources/OpenIapStore.swift (1)
packages/apple/Sources/Helpers/StoreKitTypesBridge.swift (1)
  • productSubscription (10-13)
packages/gql/scripts/generate-kotlin-types.mjs (2)
packages/gql/scripts/generate-dart-types.mjs (6)
  • firstMember (670-670)
  • firstInterfaces (673-673)
  • memberInterfaces (676-676)
  • sharedInterfaceNames (668-668)
  • output (999-999)
  • lines (380-380)
packages/gql/scripts/generate-swift-types.mjs (6)
  • firstMember (543-543)
  • firstInterfaces (546-546)
  • memberInterfaces (549-549)
  • sharedInterfaceNames (541-541)
  • output (783-783)
  • lines (193-193)
packages/apple/Sources/Models/OpenIapSerialization.swift (1)
packages/apple/Sources/Helpers/StoreKitTypesBridge.swift (1)
  • productSubscription (10-13)
packages/gql/scripts/generate-dart-types.mjs (2)
packages/gql/scripts/generate-kotlin-types.mjs (6)
  • firstMember (635-635)
  • firstInterfaces (638-638)
  • memberInterfaces (641-641)
  • sharedInterfaceNames (633-633)
  • output (803-803)
  • lines (407-407)
packages/gql/scripts/generate-swift-types.mjs (6)
  • firstMember (543-543)
  • firstInterfaces (546-546)
  • memberInterfaces (549-549)
  • sharedInterfaceNames (541-541)
  • output (783-783)
  • lines (193-193)
packages/apple/Sources/OpenIapModule+ObjC.swift (1)
packages/apple/Sources/Helpers/StoreKitTypesBridge.swift (1)
  • productSubscription (10-13)
packages/apple/Sources/Models/Types.swift (1)
packages/apple/Sources/Helpers/StoreKitTypesBridge.swift (2)
  • product (6-8)
  • productSubscription (10-13)
packages/gql/scripts/generate-swift-types.mjs (2)
packages/gql/scripts/generate-dart-types.mjs (4)
  • firstMember (670-670)
  • firstInterfaces (673-673)
  • memberInterfaces (676-676)
  • sharedInterfaceNames (668-668)
packages/gql/scripts/generate-kotlin-types.mjs (4)
  • firstMember (635-635)
  • firstInterfaces (638-638)
  • memberInterfaces (641-641)
  • sharedInterfaceNames (633-633)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Android
🔇 Additional comments (7)
packages/apple/Sources/Models/OpenIapSerialization.swift (1)

215-219: LGTM! Cleaner nested pattern matching.

The refactor from two-step pattern matching to direct nested matching simplifies the extraction logic while maintaining the same functionality. This aligns well with the new ProductOrSubscription union type structure.

packages/apple/Sources/OpenIapModule+ObjC.swift (1)

76-80: LGTM! Consistent pattern matching refactor.

The nested pattern matching aligns with the changes in OpenIapSerialization.swift and properly extracts iOS subscriptions from the new union structure.

packages/apple/Sources/OpenIapStore.swift (1)

195-200: LGTM! Correct extraction and type conversion.

The nested pattern matching extracts the iOS subscription and properly re-wraps it for the subscriptions array. The re-wrapping is necessary to convert from the ProductOrSubscription union context to the ProductSubscription type.

packages/gql/src/type.graphql (1)

80-88: LGTM! Well-structured union type addition.

The new ProductOrSubscription union and the all field in FetchProductsResult enable type-safe handling of mixed product/subscription collections. The schema change is well-commented and follows GraphQL conventions.

packages/apple/Sources/Models/Types.swift (2)

1002-1005: LGTM! Auto-generated ProductOrSubscription union.

The new ProductOrSubscription enum correctly implements the discriminated union defined in the GraphQL schema. As an auto-generated file, these changes properly reflect the schema updates.

Based on coding guidelines


249-249: LGTM! FetchProductsResult updated to use ProductOrSubscription.

The all case now properly uses the new ProductOrSubscription union type instead of an inline representation, improving type safety and consistency across platforms.

Based on coding guidelines

openiap-versions.json (1)

2-2: Verify the intentionality of the gql version downgrade.

The downgrade from 1.2.5 to 1.2.4 is confirmed as an isolated, explicit commit with no accompanying code changes. However, the minimal commit message ("chore: gql to 1.2.4") provides no explanation. Given that:

  • New union type functionality typically warrants version increments, not decrements
  • The downgrade contradicts standard versioning practices
  • No documentation explains why 1.2.5 was reverted

Confirm whether this downgrade is intentional (e.g., due to incompatibility between 1.2.5 and the union type changes, or a bug in 1.2.5) or if it was inadvertent.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/gql/scripts/generate-dart-types.mjs (1)

992-997: Minor: Use correct enum case in productTypeMapping.

The mapping uses lowercase enum values (IapPlatform.ios) that are later corrected via global replacement (lines 1043-1046). While this works, it would be clearer to use the correct PascalCase values directly in the mapping.

 const productTypeMapping = {
-  ProductIOS: { platform: 'IapPlatform.ios', type: 'ProductType.inApp' },
-  ProductAndroid: { platform: 'IapPlatform.android', type: 'ProductType.inApp' },
-  ProductSubscriptionIOS: { platform: 'IapPlatform.ios', type: 'ProductType.subs' },
-  ProductSubscriptionAndroid: { platform: 'IapPlatform.android', type: 'ProductType.subs' },
+  ProductIOS: { platform: 'IapPlatform.IOS', type: 'ProductType.InApp' },
+  ProductAndroid: { platform: 'IapPlatform.Android', type: 'ProductType.InApp' },
+  ProductSubscriptionIOS: { platform: 'IapPlatform.IOS', type: 'ProductType.Subs' },
+  ProductSubscriptionAndroid: { platform: 'IapPlatform.Android', type: 'ProductType.Subs' },
 };

Note: Verify if the global replacements at lines 1043-1046 serve other purposes before removing them.

packages/gql/scripts/generate-kotlin-types.mjs (1)

837-854: Post-processing regex replacements are fragile and lack validation.

The string replacements assume exact spacing and formatting in generated code. If the generation logic changes or the schema evolves, these could silently fail or match incorrectly.

Consider one of the following improvements:

  1. Add validation to confirm replacements succeeded:
 output = output.replace(
   /public sealed interface Product : ProductCommon \{/,
   'public sealed interface Product : ProductCommon, ProductOrSubscription {'
 );
+if (!output.includes('Product : ProductCommon, ProductOrSubscription')) {
+  throw new Error('Failed to add ProductOrSubscription to Product interface');
+}
  1. Generate the implements clause directly in printUnion or printDataClass rather than post-processing, by checking unionMembership or schema metadata.

  2. Use more flexible regex that handles varying whitespace:

-/public sealed interface Product : ProductCommon \{/
+/public\s+sealed\s+interface\s+Product\s*:\s*ProductCommon\s*\{/
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5a1b2a and 9338fde.

⛔ Files ignored due to path filters (2)
  • packages/gql/src/generated/Types.kt is excluded by !**/generated/**
  • packages/gql/src/generated/types.dart is excluded by !**/generated/**
📒 Files selected for processing (3)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (3 hunks)
  • packages/gql/scripts/generate-dart-types.mjs (3 hunks)
  • packages/gql/scripts/generate-kotlin-types.mjs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
packages/google/openiap/src/main/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

packages/google/openiap/src/main/**/*.kt: In Android-only package, do not add Android suffix to function names (e.g., acknowledgePurchase, not acknowledgePurchaseAndroid)
Only use Android suffix for types that are part of a cross-platform API (e.g., ProductAndroid, PurchaseAndroid)

Files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/apple/Sources/Models/**/*.swift : Keep official OpenIAP types in Sources/Models/ matching openiap.dev/docs/types naming (e.g., Product.swift, Purchase.swift, ActiveSubscription.swift)
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/docs/src/**/*.{md,mdx,ts,tsx} : Respect deprecations in documentation: replace buy-promoted-product-ios with requestPurchaseOnPromotedProductIOS; requestProducts with fetchProducts; get-storefront-ios with getStorefront
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/dist/dart/types.dart : Generated Dart types location in GQL package: dist/dart/types.dart

Applied to files:

  • packages/gql/scripts/generate-dart-types.mjs
  • packages/gql/scripts/generate-kotlin-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/src/generated/types.ts : Generated TypeScript types location in GQL package: src/generated/types.ts

Applied to files:

  • packages/gql/scripts/generate-dart-types.mjs
  • packages/gql/scripts/generate-kotlin-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/CONVENTION.md : Before editing anything in GraphQL Types package, review packages/gql/CONVENTION.md

Applied to files:

  • packages/gql/scripts/generate-dart-types.mjs
  • packages/gql/scripts/generate-kotlin-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/apple/Sources/Models/**/*.swift : Keep official OpenIAP types in Sources/Models/ matching openiap.dev/docs/types naming (e.g., Product.swift, Purchase.swift, ActiveSubscription.swift)

Applied to files:

  • packages/gql/scripts/generate-dart-types.mjs
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Run platform package tests/builds after updating versions and regenerating types (Android: Gradle; Apple: swift test/build; GQL: generation scripts)

Applied to files:

  • packages/gql/scripts/generate-dart-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/**/*.kt : Only use Android suffix for types that are part of a cross-platform API (e.g., ProductAndroid, PurchaseAndroid)

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/gql/scripts/generate-kotlin-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/Types.kt : Do not edit generated file openiap/src/main/Types.kt in Android package

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/gql/scripts/generate-kotlin-types.mjs
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/**/*.kt : In Android-only package, do not add Android suffix to function names (e.g., acknowledgePurchase, not acknowledgePurchaseAndroid)

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/gql/scripts/generate-kotlin-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/java/dev/hyo/openiap/utils/**/*.kt : Place reusable Kotlin helpers in openiap/src/main/java/dev/hyo/openiap/utils/

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/dist/kotlin/Types.kt : Generated Kotlin types location in GQL package: dist/kotlin/Types.kt

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/gql/scripts/generate-kotlin-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/dist/swift/Types.swift : Generated Swift types location in GQL package: dist/swift/Types.swift

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
🧬 Code graph analysis (2)
packages/gql/scripts/generate-dart-types.mjs (2)
packages/gql/scripts/generate-kotlin-types.mjs (11)
  • firstMember (635-635)
  • firstInterfaces (638-638)
  • memberInterfaces (641-641)
  • sharedInterfaceNames (633-633)
  • concreteMembers (661-661)
  • memberTypes (630-630)
  • nestedMembers (665-665)
  • nestedMembers (682-682)
  • sortedConcreteMembers (676-676)
  • delegateTo (679-679)
  • lines (407-407)
packages/gql/scripts/generate-swift-types.mjs (6)
  • firstMember (543-543)
  • firstInterfaces (546-546)
  • memberInterfaces (549-549)
  • sharedInterfaceNames (541-541)
  • memberTypes (535-535)
  • lines (193-193)
packages/gql/scripts/generate-kotlin-types.mjs (2)
packages/gql/scripts/generate-dart-types.mjs (12)
  • firstMember (670-670)
  • firstInterfaces (673-673)
  • memberInterfaces (676-676)
  • sharedInterfaceNames (668-668)
  • concreteMembers (697-697)
  • memberTypes (665-665)
  • nestedMembers (701-701)
  • nestedMembers (718-718)
  • sortedConcreteMembers (712-712)
  • delegateTo (715-715)
  • lines (380-380)
  • output (1029-1029)
packages/gql/scripts/generate-swift-types.mjs (7)
  • firstMember (543-543)
  • firstInterfaces (546-546)
  • memberInterfaces (549-549)
  • sharedInterfaceNames (541-541)
  • memberTypes (535-535)
  • lines (193-193)
  • output (783-783)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Android
🔇 Additional comments (8)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (4)

717-718: LGTM! Clean discriminated union container.

FetchProductsResultAll correctly uses List<ProductOrSubscription>? to represent mixed product/subscription collections, enabling type-safe narrowing in client code.


2152-2164: LGTM! Product correctly implements the union member contract.

The dual inheritance (ProductCommon, ProductOrSubscription) properly represents that Product is both a union variant and shares common fields with ProductSubscription. The fromJson routing on concrete typenames (ProductAndroid, ProductIOS) is correct for GraphQL union deserialization.


2166-2181: LGTM! Discriminated union correctly resolves concrete types.

The ProductOrSubscription.fromJson implementation correctly routes based on concrete platform-specific typenames (ProductAndroid, ProductIOS, ProductSubscriptionAndroid, ProductSubscriptionIOS) rather than abstract union names. This ensures GraphQL responses deserialize successfully at runtime.

The previously flagged concern about routing only on "Product"/"ProductSubscription" has been fully addressed—the current implementation matches exactly what was recommended.


2182-2194: LGTM! ProductSubscription mirrors Product's correct pattern.

The structure is consistent with Product: extends both ProductCommon and ProductOrSubscription, and fromJson correctly routes on concrete typenames (ProductSubscriptionAndroid, ProductSubscriptionIOS). The discriminated union implementation is complete and type-safe.

packages/gql/scripts/generate-dart-types.mjs (2)

671-686: LGTM - Defensive handling of union members without getInterfaces.

The runtime type checking correctly handles the "union of unions" scenario where union members may themselves be unions (which don't have a getInterfaces method). The intersection logic properly identifies interfaces shared across all concrete members.


695-726: LGTM - Correct handling of nested union flattening.

The logic properly expands nested unions to their concrete members and generates appropriate delegation in the fromJson method. When a concrete member belongs to a nested union, it correctly delegates to the parent union type rather than directly constructing the concrete type.

packages/gql/scripts/generate-kotlin-types.mjs (2)

636-650: LGTM: Proper type guards for union-of-unions.

The runtime checks correctly prevent errors when union members are themselves unions (which lack getInterfaces). The logic ensures shared interfaces are computed only when all members expose the method.


660-690: The verification confirms that concrete types do not currently overlap across nested unions. The schema shows:

  • ProductOrSubscription (union) contains Product | ProductSubscription (both unions)
  • Product contains only ProductAndroid | ProductIOS (concrete types)
  • ProductSubscription contains only ProductSubscriptionAndroid | ProductSubscriptionIOS (concrete types)
  • Similar pattern for ReceiptValidationResult variants

Each concrete type belongs to exactly one nested union, so the delegateTo logic at lines 679-688 works correctly. However, the code lacks explicit validation or documentation of this assumption. If the schema evolves to include overlapping unions (e.g., a concrete type in multiple unions), the code would silently pick the first match via the break at line 685, potentially causing incorrect deserialization. Consider adding a comment or compile-time check to document or enforce this constraint.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9338fde and 46d94c0.

⛔ Files ignored due to path filters (2)
  • packages/gql/src/generated/Types.kt is excluded by !**/generated/**
  • packages/gql/src/generated/types.dart is excluded by !**/generated/**
📒 Files selected for processing (3)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (2 hunks)
  • packages/gql/scripts/generate-dart-types.mjs (3 hunks)
  • packages/gql/scripts/generate-kotlin-types.mjs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
packages/google/openiap/src/main/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

packages/google/openiap/src/main/**/*.kt: In Android-only package, do not add Android suffix to function names (e.g., acknowledgePurchase, not acknowledgePurchaseAndroid)
Only use Android suffix for types that are part of a cross-platform API (e.g., ProductAndroid, PurchaseAndroid)

Files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
🧠 Learnings (11)
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/dist/kotlin/Types.kt : Generated Kotlin types location in GQL package: dist/kotlin/Types.kt

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/src/generated/types.ts : Generated TypeScript types location in GQL package: src/generated/types.ts

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/gql/scripts/generate-dart-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/**/*.kt : Only use Android suffix for types that are part of a cross-platform API (e.g., ProductAndroid, PurchaseAndroid)

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/dist/swift/Types.swift : Generated Swift types location in GQL package: dist/swift/Types.swift

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/CONVENTION.md : Before editing anything in GraphQL Types package, review packages/gql/CONVENTION.md

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/gql/scripts/generate-dart-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/dist/dart/types.dart : Generated Dart types location in GQL package: dist/dart/types.dart

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/gql/scripts/generate-dart-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/Types.kt : Do not edit generated file openiap/src/main/Types.kt in Android package

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/apple/Sources/Models/**/*.swift : Keep official OpenIAP types in Sources/Models/ matching openiap.dev/docs/types naming (e.g., Product.swift, Purchase.swift, ActiveSubscription.swift)

Applied to files:

  • packages/gql/scripts/generate-dart-types.mjs
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/**/*.kt : In Android-only package, do not add Android suffix to function names (e.g., acknowledgePurchase, not acknowledgePurchaseAndroid)

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/java/dev/hyo/openiap/utils/**/*.kt : Place reusable Kotlin helpers in openiap/src/main/java/dev/hyo/openiap/utils/

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
🧬 Code graph analysis (2)
packages/gql/scripts/generate-kotlin-types.mjs (2)
packages/gql/scripts/generate-dart-types.mjs (14)
  • firstMember (670-670)
  • firstInterfaces (673-673)
  • memberInterfaces (676-676)
  • sharedInterfaceNames (668-668)
  • concreteMembers (697-697)
  • memberTypes (665-665)
  • nestedMembers (701-701)
  • nestedMembers (718-718)
  • sortedConcreteMembers (712-712)
  • delegateTo (715-715)
  • lines (380-380)
  • output (1028-1028)
  • productOrSubscriptionPattern (1032-1032)
  • replacement (1034-1067)
packages/gql/scripts/generate-swift-types.mjs (7)
  • firstMember (543-543)
  • firstInterfaces (546-546)
  • memberInterfaces (549-549)
  • sharedInterfaceNames (541-541)
  • memberTypes (535-535)
  • lines (193-193)
  • output (783-783)
packages/gql/scripts/generate-dart-types.mjs (2)
packages/gql/scripts/generate-kotlin-types.mjs (14)
  • firstMember (635-635)
  • firstInterfaces (638-638)
  • memberInterfaces (641-641)
  • sharedInterfaceNames (633-633)
  • concreteMembers (661-661)
  • memberTypes (630-630)
  • nestedMembers (665-665)
  • nestedMembers (682-682)
  • sortedConcreteMembers (676-676)
  • delegateTo (679-679)
  • lines (407-407)
  • output (833-833)
  • productOrSubscriptionPattern (837-837)
  • replacement (839-859)
packages/gql/scripts/generate-swift-types.mjs (7)
  • firstMember (543-543)
  • firstInterfaces (546-546)
  • memberInterfaces (549-549)
  • sharedInterfaceNames (541-541)
  • memberTypes (535-535)
  • lines (193-193)
  • output (783-783)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test iOS
  • GitHub Check: Test Android

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46d94c0 and 1d1a7d4.

⛔ Files ignored due to path filters (2)
  • packages/gql/src/generated/Types.kt is excluded by !**/generated/**
  • packages/gql/src/generated/types.dart is excluded by !**/generated/**
📒 Files selected for processing (4)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (2 hunks)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt (1 hunks)
  • packages/gql/scripts/generate-dart-types.mjs (4 hunks)
  • packages/gql/scripts/generate-kotlin-types.mjs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
🧰 Additional context used
📓 Path-based instructions (1)
packages/google/openiap/src/main/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

packages/google/openiap/src/main/**/*.kt: In Android-only package, do not add Android suffix to function names (e.g., acknowledgePurchase, not acknowledgePurchaseAndroid)
Only use Android suffix for types that are part of a cross-platform API (e.g., ProductAndroid, PurchaseAndroid)

Files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
🧠 Learnings (11)
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/**/*.kt : Only use Android suffix for types that are part of a cross-platform API (e.g., ProductAndroid, PurchaseAndroid)

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/gql/scripts/generate-kotlin-types.mjs
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/apple/Sources/Models/**/*.swift : Keep official OpenIAP types in Sources/Models/ matching openiap.dev/docs/types naming (e.g., Product.swift, Purchase.swift, ActiveSubscription.swift)

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/gql/scripts/generate-dart-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/Types.kt : Do not edit generated file openiap/src/main/Types.kt in Android package

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/**/*.kt : In Android-only package, do not add Android suffix to function names (e.g., acknowledgePurchase, not acknowledgePurchaseAndroid)

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/dist/kotlin/Types.kt : Generated Kotlin types location in GQL package: dist/kotlin/Types.kt

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/gql/scripts/generate-kotlin-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/java/dev/hyo/openiap/utils/**/*.kt : Place reusable Kotlin helpers in openiap/src/main/java/dev/hyo/openiap/utils/

Applied to files:

  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/src/generated/types.ts : Generated TypeScript types location in GQL package: src/generated/types.ts

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/gql/scripts/generate-dart-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/dist/swift/Types.swift : Generated Swift types location in GQL package: dist/swift/Types.swift

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/CONVENTION.md : Before editing anything in GraphQL Types package, review packages/gql/CONVENTION.md

Applied to files:

  • packages/gql/scripts/generate-kotlin-types.mjs
  • packages/gql/scripts/generate-dart-types.mjs
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/gql/dist/dart/types.dart : Generated Dart types location in GQL package: dist/dart/types.dart

Applied to files:

  • packages/gql/scripts/generate-dart-types.mjs
🧬 Code graph analysis (2)
packages/gql/scripts/generate-kotlin-types.mjs (2)
packages/gql/scripts/generate-dart-types.mjs (15)
  • firstMember (670-670)
  • firstInterfaces (673-673)
  • memberInterfaces (676-676)
  • sharedInterfaceNames (668-668)
  • concreteMembers (697-697)
  • memberTypes (665-665)
  • nestedMembers (701-701)
  • nestedMembers (723-723)
  • nestedUnions (712-712)
  • sortedConcreteMembers (715-715)
  • delegateTo (718-718)
  • isNestedUnion (719-719)
  • wrapperName (735-735)
  • wrapperName (778-778)
  • lines (380-380)
packages/gql/scripts/generate-swift-types.mjs (6)
  • firstMember (543-543)
  • firstInterfaces (546-546)
  • memberInterfaces (549-549)
  • sharedInterfaceNames (541-541)
  • memberTypes (535-535)
  • lines (193-193)
packages/gql/scripts/generate-dart-types.mjs (2)
packages/gql/scripts/generate-kotlin-types.mjs (15)
  • firstMember (635-635)
  • firstInterfaces (638-638)
  • memberInterfaces (641-641)
  • sharedInterfaceNames (633-633)
  • concreteMembers (661-661)
  • memberTypes (630-630)
  • nestedMembers (665-665)
  • nestedMembers (687-687)
  • nestedUnions (676-676)
  • sortedConcreteMembers (679-679)
  • delegateTo (682-682)
  • isNestedUnion (683-683)
  • wrapperName (699-699)
  • wrapperName (714-714)
  • lines (407-407)
packages/gql/scripts/generate-swift-types.mjs (6)
  • firstMember (543-543)
  • firstInterfaces (546-546)
  • memberInterfaces (549-549)
  • sharedInterfaceNames (541-541)
  • memberTypes (535-535)
  • lines (193-193)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Android
🔇 Additional comments (9)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (2)

717-718: LGTM!

The FetchProductsResultAll variant correctly models mixed product/subscription results using the new ProductOrSubscription union type, aligning with the PR's discriminated-union goals.


2166-2188: Correctly addresses previous typename routing concern.

The fromJson implementation now properly routes on concrete platform types (ProductAndroid, ProductIOS, ProductSubscriptionAndroid, ProductSubscriptionIOS) rather than abstract union names, ensuring mixed product arrays deserialize successfully. The wrapper classes (ProductItem, ProductSubscriptionItem) provide type-safe discrimination for this union-of-unions pattern.

Note: This is a generated file—any future changes should be made in the generator scripts.

Based on learnings.

packages/gql/scripts/generate-dart-types.mjs (4)

671-686: LGTM – Safe runtime guard for interface extraction.

The typeof check correctly handles unions (which lack getInterfaces()) as members. When the first member or any subsequent member is a union, the code safely skips interface extraction, ensuring sharedInterfaceNames remains accurate without runtime errors.


695-741: LGTM – Nested union flattening with type-safe wrappers.

The flattening logic correctly expands union-of-unions (e.g., ProductOrSubscription = Product | ProductSubscription where both are unions) into concrete members. By wrapping nested union results in typed subclasses (line 736), the factory maintains the sealed-class contract—each returned instance properly implements the outer union type. This addresses the type-safety concern from the previous review.


775-786: LGTM – Clean wrapper implementation for nested unions.

The generated wrapper classes correctly extend the outer union type and delegate serialization to the wrapped value. This minimal design maintains type safety while avoiding duplication—each ${outerUnion}${nestedUnion} wrapper is scoped to its parent union, preventing naming collisions.


1054-1054: LGTM – Comment accurately reflects the wrapper-based approach.

packages/gql/scripts/generate-kotlin-types.mjs (3)

660-705: Excellent solution for nested union type handling.

The flattening and wrapper approach correctly addresses the concern from the previous review. When a union member is itself a union, the code:

  1. Flattens to collect all concrete types (lines 660-673)
  2. Routes each concrete type's __typename to the appropriate nested union's fromJson (lines 685-695)
  3. Wraps the nested union instance in a typed wrapper class (line 700) that implements the outer union interface

This ensures the return type is correct—the wrapper implements ProductOrSubscription while delegating to the nested union's serialization logic. The pattern mirrors the existing ProductItem/SubscriptionItem approach mentioned in the PR objectives.

Based on past review comments, this directly resolves the previously flagged issue about nested union .fromJson returning the wrong type.


712-719: Well-structured wrapper implementation.

The generated wrapper classes correctly implement the outer union interface while delegating serialization to the nested union value. The naming convention (${nestedUnionName}Item) follows the existing pattern referenced in the PR objectives and is scoped within the sealed interface, minimizing naming conflicts.


856-857: Accurate documentation of the new auto-generation approach.

The updated comments correctly reflect that ProductOrSubscription and FetchProductsResultAll are now auto-generated from the GraphQL schema, aligning with the PR's objective to remove manual post-processing.

@hyochan hyochan merged commit c5ef094 into main Nov 4, 2025
5 checks passed
@hyochan hyochan deleted the types/product-or-subscription-union branch November 4, 2025 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant