@@ -541,96 +541,85 @@ const printObject = (objectType) => {
541541} ;
542542
543543const 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+
738740const 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 ) ;
0 commit comments