Skip to content
This repository was archived by the owner on Oct 16, 2025. It is now read-only.

Commit dcfcd44

Browse files
committed
refactor: typealias PurchaseInput to Purchase and enh dart inputs
1 parent 8336ff3 commit dcfcd44

8 files changed

Lines changed: 281 additions & 275 deletions

File tree

scripts/fix-generated-types.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ for (const [from, to] of scalarReplacements) {
4646
content = content.replace(pattern, to);
4747
}
4848

49+
// Create simple type alias for PurchaseInput
50+
const purchaseInputPattern = /export interface PurchaseInput \{[\s\S]*?\}\n+/;
51+
if (purchaseInputPattern.test(content)) {
52+
content = content.replace(purchaseInputPattern, 'export type PurchaseInput = Purchase;\n\n');
53+
}
54+
4955
const iosTypeMap = new Map();
5056
const enumValueOrder = new Map();
5157
for (const schemaPath of schemaDefinitionFiles) {

scripts/generate-dart-types.mjs

Lines changed: 163 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -541,96 +541,85 @@ const printObject = (objectType) => {
541541
};
542542

543543
const printInput = (inputType) => {
544+
// Alias PurchaseInput to Purchase for cleaner API
545+
if (inputType.name === 'PurchaseInput') {
546+
lines.push('typedef PurchaseInput = Purchase;');
547+
lines.push('');
548+
return;
549+
}
550+
551+
// TypeScript-style discriminated union with compile-time safety
544552
if (inputType.name === 'RequestPurchaseProps') {
545553
addDocComment(lines, inputType.description);
546-
lines.push('class RequestPurchaseProps {');
547-
lines.push(' RequestPurchaseProps({');
548-
lines.push(' required this.request,');
549-
lines.push(' ProductQueryType? type,');
550-
lines.push(' this.useAlternativeBilling,');
551-
lines.push(' }) : type = type ?? (request is RequestPurchasePropsRequestPurchase');
552-
lines.push(' ? ProductQueryType.InApp');
553-
lines.push(' : ProductQueryType.Subs) {');
554-
lines.push(' if (request is RequestPurchasePropsRequestPurchase && this.type != ProductQueryType.InApp) {');
555-
lines.push(" throw ArgumentError('type must be IN_APP when requestPurchase is provided');");
556-
lines.push(' }');
557-
lines.push(' if (request is RequestPurchasePropsRequestSubscription && this.type != ProductQueryType.Subs) {');
558-
lines.push(" throw ArgumentError('type must be SUBS when requestSubscription is provided');");
559-
lines.push(' }');
560-
lines.push(' }');
554+
555+
// Sealed class for compile-time type safety
556+
lines.push('sealed class RequestPurchaseProps {');
557+
lines.push(' const RequestPurchaseProps._();');
561558
lines.push('');
562-
lines.push(' final RequestPurchasePropsRequest request;');
563-
lines.push(' final ProductQueryType type;');
564-
lines.push(' final bool? useAlternativeBilling;');
559+
lines.push(' const factory RequestPurchaseProps.inApp(({');
560+
lines.push(' RequestPurchaseIosProps? ios,');
561+
lines.push(' RequestPurchaseAndroidProps? android,');
562+
lines.push(' bool? useAlternativeBilling,');
563+
lines.push(' }) props) = _InAppPurchase;');
565564
lines.push('');
566-
lines.push(' factory RequestPurchaseProps.fromJson(Map<String, dynamic> json) {');
567-
lines.push(" final typeValue = json['type'] as String?;");
568-
lines.push(' final parsedType = typeValue != null ? ProductQueryType.fromJson(typeValue) : null;');
569-
lines.push(" final useAlternativeBilling = json['useAlternativeBilling'] as bool?;");
570-
lines.push(" final purchaseJson = json['requestPurchase'] as Map<String, dynamic>?;");
571-
lines.push(' if (purchaseJson != null) {');
572-
lines.push(' final request = RequestPurchasePropsRequestPurchase(RequestPurchasePropsByPlatforms.fromJson(purchaseJson));');
573-
lines.push(' final finalType = parsedType ?? ProductQueryType.InApp;');
574-
lines.push(' if (finalType != ProductQueryType.InApp) {');
575-
lines.push(" throw ArgumentError('type must be IN_APP when requestPurchase is provided');");
576-
lines.push(' }');
577-
lines.push(' return RequestPurchaseProps(request: request, type: finalType, useAlternativeBilling: useAlternativeBilling);');
578-
lines.push(' }');
579-
lines.push(" final subscriptionJson = json['requestSubscription'] as Map<String, dynamic>?;");
580-
lines.push(' if (subscriptionJson != null) {');
581-
lines.push(' final request = RequestPurchasePropsRequestSubscription(RequestSubscriptionPropsByPlatforms.fromJson(subscriptionJson));');
582-
lines.push(' final finalType = parsedType ?? ProductQueryType.Subs;');
583-
lines.push(' if (finalType != ProductQueryType.Subs) {');
584-
lines.push(" throw ArgumentError('type must be SUBS when requestSubscription is provided');");
585-
lines.push(' }');
586-
lines.push(' return RequestPurchaseProps(request: request, type: finalType, useAlternativeBilling: useAlternativeBilling);');
587-
lines.push(' }');
588-
lines.push(" throw ArgumentError('RequestPurchaseProps requires requestPurchase or requestSubscription');");
589-
lines.push(' }');
565+
lines.push(' const factory RequestPurchaseProps.subs(({');
566+
lines.push(' RequestSubscriptionIosProps? ios,');
567+
lines.push(' RequestSubscriptionAndroidProps? android,');
568+
lines.push(' bool? useAlternativeBilling,');
569+
lines.push(' }) props) = _SubsPurchase;');
590570
lines.push('');
591-
lines.push(' Map<String, dynamic> toJson() {');
592-
lines.push(' if (request is RequestPurchasePropsRequestPurchase) {');
593-
lines.push(' return {');
594-
lines.push(" 'requestPurchase': (request as RequestPurchasePropsRequestPurchase).value.toJson(),");
595-
lines.push(" 'type': type.toJson(),");
596-
lines.push(" 'useAlternativeBilling': useAlternativeBilling,");
597-
lines.push(' };');
598-
lines.push(' }');
599-
lines.push(' if (request is RequestPurchasePropsRequestSubscription) {');
600-
lines.push(' return {');
601-
lines.push(" 'requestSubscription': (request as RequestPurchasePropsRequestSubscription).value.toJson(),");
602-
lines.push(" 'type': type.toJson(),");
603-
lines.push(" 'useAlternativeBilling': useAlternativeBilling,");
604-
lines.push(' };');
605-
lines.push(' }');
606-
lines.push(" throw StateError('Unsupported RequestPurchaseProps request variant');");
607-
lines.push(' }');
571+
lines.push(' Map<String, dynamic> toJson();');
572+
lines.push('}');
608573
lines.push('');
609-
lines.push(' static RequestPurchaseProps inApp({required RequestPurchasePropsByPlatforms request, bool? useAlternativeBilling}) {');
610-
lines.push(' return RequestPurchaseProps(request: RequestPurchasePropsRequestPurchase(request), type: ProductQueryType.InApp, useAlternativeBilling: useAlternativeBilling);');
611-
lines.push(' }');
574+
575+
// Purchase implementation
576+
lines.push('class _InAppPurchase extends RequestPurchaseProps {');
577+
lines.push(' const _InAppPurchase(this.props) : super._();');
578+
lines.push(' final ({');
579+
lines.push(' RequestPurchaseIosProps? ios,');
580+
lines.push(' RequestPurchaseAndroidProps? android,');
581+
lines.push(' bool? useAlternativeBilling,');
582+
lines.push(' }) props;');
612583
lines.push('');
613-
lines.push(' static RequestPurchaseProps subs({required RequestSubscriptionPropsByPlatforms request, bool? useAlternativeBilling}) {');
614-
lines.push(' return RequestPurchaseProps(request: RequestPurchasePropsRequestSubscription(request), type: ProductQueryType.Subs, useAlternativeBilling: useAlternativeBilling);');
584+
lines.push(' @override');
585+
lines.push(' Map<String, dynamic> toJson() {');
586+
lines.push(' return {');
587+
lines.push(" 'requestPurchase': {");
588+
lines.push(" if (props.ios != null) 'ios': props.ios!.toJson(),");
589+
lines.push(" if (props.android != null) 'android': props.android!.toJson(),");
590+
lines.push(' },');
591+
lines.push(" 'type': ProductQueryType.InApp.toJson(),");
592+
lines.push(" if (props.useAlternativeBilling != null) 'useAlternativeBilling': props.useAlternativeBilling,");
593+
lines.push(' };');
615594
lines.push(' }');
616595
lines.push('}');
617596
lines.push('');
618-
lines.push('sealed class RequestPurchasePropsRequest {');
619-
lines.push(' const RequestPurchasePropsRequest();');
620-
lines.push('}');
621-
lines.push('');
622-
lines.push('class RequestPurchasePropsRequestPurchase extends RequestPurchasePropsRequest {');
623-
lines.push(' const RequestPurchasePropsRequestPurchase(this.value);');
624-
lines.push(' final RequestPurchasePropsByPlatforms value;');
625-
lines.push('}');
597+
598+
// Subscription implementation
599+
lines.push('class _SubsPurchase extends RequestPurchaseProps {');
600+
lines.push(' const _SubsPurchase(this.props) : super._();');
601+
lines.push(' final ({');
602+
lines.push(' RequestSubscriptionIosProps? ios,');
603+
lines.push(' RequestSubscriptionAndroidProps? android,');
604+
lines.push(' bool? useAlternativeBilling,');
605+
lines.push(' }) props;');
626606
lines.push('');
627-
lines.push('class RequestPurchasePropsRequestSubscription extends RequestPurchasePropsRequest {');
628-
lines.push(' const RequestPurchasePropsRequestSubscription(this.value);');
629-
lines.push(' final RequestSubscriptionPropsByPlatforms value;');
607+
lines.push(' @override');
608+
lines.push(' Map<String, dynamic> toJson() {');
609+
lines.push(' return {');
610+
lines.push(" 'requestSubscription': {");
611+
lines.push(" if (props.ios != null) 'ios': props.ios!.toJson(),");
612+
lines.push(" if (props.android != null) 'android': props.android!.toJson(),");
613+
lines.push(' },');
614+
lines.push(" 'type': ProductQueryType.Subs.toJson(),");
615+
lines.push(" if (props.useAlternativeBilling != null) 'useAlternativeBilling': props.useAlternativeBilling,");
616+
lines.push(' };');
617+
lines.push(' }');
630618
lines.push('}');
631619
lines.push('');
632620
return;
633621
}
622+
634623
addDocComment(lines, inputType.description);
635624
lines.push(`class ${inputType.name} {`);
636625
lines.push(` const ${inputType.name}({`);
@@ -735,6 +724,19 @@ const printUnion = (unionType) => {
735724
lines.push('}', '');
736725
};
737726

727+
const expandInputToParams = (inputTypeName) => {
728+
const inputType = typeMap[inputTypeName];
729+
if (!inputType || !isInputObjectType(inputType)) return [];
730+
731+
// Don't expand RequestPurchaseProps - it's now a sealed class union type
732+
if (inputTypeName === 'RequestPurchaseProps') {
733+
return [];
734+
}
735+
736+
const fields = Object.values(inputType.getFields()).sort((a, b) => a.name.localeCompare(b.name));
737+
return fields;
738+
};
739+
738740
const printOperationInterface = (operationType) => {
739741
const interfaceName = `${operationType.name}Resolver`;
740742
addDocComment(lines, operationType.description ?? `GraphQL root ${operationType.name.toLowerCase()} operations.`);
@@ -750,6 +752,49 @@ const printOperationInterface = (operationType) => {
750752
lines.push(` Future<${returnType}> ${escapeDartName(field.name)}();`);
751753
continue;
752754
}
755+
756+
// Special handling for v5.x style APIs - expand input objects
757+
// Don't expand 'purchase' - keep it as an object
758+
const expandableParams = ['params', 'options', 'config', 'props'];
759+
const expandableArg = field.args.find(arg => expandableParams.includes(arg.name));
760+
761+
if (expandableArg) {
762+
const namedType = getNamedGraphQLType(expandableArg.type);
763+
if (namedType && isInputObjectType(namedType)) {
764+
const expandedFields = expandInputToParams(namedType.name);
765+
const otherArgs = field.args.filter(arg => arg !== expandableArg);
766+
767+
if (expandedFields.length > 0) {
768+
// Always use named params when expanding
769+
lines.push(` Future<${returnType}> ${escapeDartName(field.name)}({`);
770+
771+
// Add expanded fields first
772+
expandedFields.forEach((f) => {
773+
// Handle synthetic fields (e.g., 'request' field in RequestPurchaseProps)
774+
if (f.isSynthetic) {
775+
lines.push(` required RequestPurchaseRequest ${escapeDartName(f.name)},`);
776+
} else {
777+
const { type: argType, nullable: argNullable } = getDartType(f.type);
778+
const finalType = `${argType}${argNullable ? '?' : ''}`;
779+
const prefix = argNullable ? '' : 'required ';
780+
lines.push(` ${prefix}${finalType} ${escapeDartName(f.name)},`);
781+
}
782+
});
783+
784+
// Add other args
785+
otherArgs.forEach((arg) => {
786+
const { type: argType, nullable: argNullable } = getDartType(arg.type);
787+
const finalType = `${argType}${argNullable ? '?' : ''}`;
788+
const prefix = argNullable ? '' : 'required ';
789+
lines.push(` ${prefix}${finalType} ${escapeDartName(arg.name)},`);
790+
});
791+
792+
lines.push(' });');
793+
continue;
794+
}
795+
}
796+
}
797+
753798
if (field.args.length === 1) {
754799
const arg = field.args[0];
755800
const { type: argType, nullable: argNullable } = getDartType(arg.type);
@@ -791,6 +836,49 @@ const printOperationHelpers = (operationType) => {
791836
lines.push(`typedef ${aliasName} = Future<${returnType}> Function();`);
792837
return;
793838
}
839+
840+
// Special handling for v5.x style APIs - expand input objects
841+
// Don't expand 'purchase' - keep it as an object
842+
const expandableParams = ['params', 'options', 'config', 'props'];
843+
const expandableArg = field.args.find(arg => expandableParams.includes(arg.name));
844+
845+
if (expandableArg) {
846+
const namedType = getNamedGraphQLType(expandableArg.type);
847+
if (namedType && isInputObjectType(namedType)) {
848+
const expandedFields = expandInputToParams(namedType.name);
849+
const otherArgs = field.args.filter(arg => arg !== expandableArg);
850+
851+
if (expandedFields.length > 0) {
852+
// Always use named params when expanding
853+
lines.push(`typedef ${aliasName} = Future<${returnType}> Function({`);
854+
855+
// Add expanded fields first
856+
expandedFields.forEach((f) => {
857+
// Handle synthetic fields (e.g., 'request' field in RequestPurchaseProps)
858+
if (f.isSynthetic) {
859+
lines.push(` required RequestPurchaseRequest ${escapeDartName(f.name)},`);
860+
} else {
861+
const { type: argType, nullable: argNullable } = getDartType(f.type);
862+
const finalType = `${argType}${argNullable ? '?' : ''}`;
863+
const prefix = argNullable ? '' : 'required ';
864+
lines.push(` ${prefix}${finalType} ${escapeDartName(f.name)},`);
865+
}
866+
});
867+
868+
// Add other args
869+
otherArgs.forEach((arg) => {
870+
const { type: argType, nullable: argNullable } = getDartType(arg.type);
871+
const finalType = `${argType}${argNullable ? '?' : ''}`;
872+
const prefix = argNullable ? '' : 'required ';
873+
lines.push(` ${prefix}${finalType} ${escapeDartName(arg.name)},`);
874+
});
875+
876+
lines.push('});');
877+
return;
878+
}
879+
}
880+
}
881+
794882
if (field.args.length === 1) {
795883
const arg = field.args[0];
796884
const { type: argType, nullable: argNullable } = getDartType(arg.type);

scripts/generate-kotlin-types.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,12 @@ const printDataClass = (objectType) => {
517517
};
518518

519519
const printInput = (inputType) => {
520+
// Alias PurchaseInput to Purchase for cleaner API
521+
if (inputType.name === 'PurchaseInput') {
522+
lines.push('public typealias PurchaseInput = Purchase');
523+
lines.push('');
524+
return;
525+
}
520526
if (inputType.name === 'RequestPurchaseProps') {
521527
addDocComment(lines, inputType.description);
522528
lines.push('public data class RequestPurchaseProps(');

scripts/generate-swift-types.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,12 @@ const printObject = (objectType) => {
360360
};
361361

362362
const printInput = (inputType) => {
363+
// Alias PurchaseInput to Purchase for cleaner API
364+
if (inputType.name === 'PurchaseInput') {
365+
lines.push('public typealias PurchaseInput = Purchase');
366+
lines.push('');
367+
return;
368+
}
363369
if (inputType.name === 'RequestPurchaseProps') {
364370
addDocComment(lines, inputType.description);
365371
lines.push('public struct RequestPurchaseProps: Codable {');

src/generated/Types.kt

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,45 +1641,7 @@ public data class ProductRequest(
16411641
)
16421642
}
16431643

1644-
public data class PurchaseInput(
1645-
val id: String,
1646-
val ids: List<String>? = null,
1647-
val isAutoRenewing: Boolean,
1648-
val platform: IapPlatform,
1649-
val productId: String,
1650-
val purchaseState: PurchaseState,
1651-
val purchaseToken: String? = null,
1652-
val quantity: Int,
1653-
val transactionDate: Double
1654-
) {
1655-
companion object {
1656-
fun fromJson(json: Map<String, Any?>): PurchaseInput {
1657-
return PurchaseInput(
1658-
id = json["id"] as String,
1659-
ids = (json["ids"] as List<*>?)?.map { it as String },
1660-
isAutoRenewing = json["isAutoRenewing"] as Boolean,
1661-
platform = IapPlatform.fromJson(json["platform"] as String),
1662-
productId = json["productId"] as String,
1663-
purchaseState = PurchaseState.fromJson(json["purchaseState"] as String),
1664-
purchaseToken = json["purchaseToken"] as String?,
1665-
quantity = (json["quantity"] as Number).toInt(),
1666-
transactionDate = (json["transactionDate"] as Number).toDouble(),
1667-
)
1668-
}
1669-
}
1670-
1671-
fun toJson(): Map<String, Any?> = mapOf(
1672-
"id" to id,
1673-
"ids" to ids?.map { it },
1674-
"isAutoRenewing" to isAutoRenewing,
1675-
"platform" to platform.toJson(),
1676-
"productId" to productId,
1677-
"purchaseState" to purchaseState.toJson(),
1678-
"purchaseToken" to purchaseToken,
1679-
"quantity" to quantity,
1680-
"transactionDate" to transactionDate,
1681-
)
1682-
}
1644+
public typealias PurchaseInput = Purchase
16831645

16841646
public data class PurchaseOptions(
16851647
/**

src/generated/Types.swift

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -497,17 +497,7 @@ public struct ProductRequest: Codable {
497497
public var type: ProductQueryType?
498498
}
499499

500-
public struct PurchaseInput: Codable {
501-
public var id: String
502-
public var ids: [String]?
503-
public var isAutoRenewing: Bool
504-
public var platform: IapPlatform
505-
public var productId: String
506-
public var purchaseState: PurchaseState
507-
public var purchaseToken: String?
508-
public var quantity: Int
509-
public var transactionDate: Double
510-
}
500+
public typealias PurchaseInput = Purchase
511501

512502
public struct PurchaseOptions: Codable {
513503
/// Also emit results through the iOS event listeners

0 commit comments

Comments
 (0)