Skip to content
This repository was archived by the owner on Oct 16, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions scripts/fix-generated-types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,89 @@ wrapReturns('Mutation');

content = content.replace(/^\s*_placeholder\??: [^;]+;\n/gm, '');

const ROOT_DEFINITIONS = ['Query', 'Mutation', 'Subscription'];

const helperMarkers = (root) => ({
start: `// -- ${root} helper types (auto-generated)`,
end: `// -- End ${root.toLowerCase()} helper types`,
});

const removeRootHelpers = (root) => {
const { start, end } = helperMarkers(root);
const startIdx = content.indexOf(start);
if (startIdx === -1) return;
const endIdx = content.indexOf(end);
if (endIdx === -1) return;
const sliceEnd = content.indexOf('\n', endIdx + end.length);
const finalEnd = sliceEnd === -1 ? content.length : sliceEnd + 1;
content = content.slice(0, startIdx) + content.slice(finalEnd);
};

const findArgsType = (root, pascalFieldName) => {
const prefixes = new Set([
`${root}${pascalFieldName}Args`,
`${root}${pascalFieldName.replace(/IOS/g, 'Ios')}Args`,
`${root}${pascalFieldName.replace(/Ios/g, 'IOS')}Args`,
]);
for (const name of prefixes) {
if (content.includes(`export interface ${name} {`)) {
return name;
}
}
return 'never';
};

const buildRootHelpers = (root) => {
const rootMatch = content.match(new RegExp(`export interface ${root} {\n([\\s\\S]*?)\n}\n`));
if (!rootMatch) return '';
const body = rootMatch[1];
const fieldPattern = /^\s*([A-Za-z0-9_]+)\??:\s*[^;]+;$/gm;
const entries = [];
let fieldMatch;
while ((fieldMatch = fieldPattern.exec(body)) !== null) {
const fieldName = fieldMatch[1];
const pascal = fieldName[0].toUpperCase() + fieldName.slice(1);
const argsType = findArgsType(root, pascal);
entries.push({ fieldName, argsType });
}
if (entries.length === 0) return '';
const { start, end } = helperMarkers(root);
const mapName = `${root}ArgsMap`;
const fieldAlias = `${root}Field`;
const mapAlias = `${root}FieldMap`;
const lines = [];
lines.push(start);
lines.push(`export type ${mapName} = {`);
for (const { fieldName, argsType } of entries) {
lines.push(` ${fieldName}: ${argsType};`);
}
lines.push('};');
lines.push('');
lines.push(`export type ${fieldAlias}<K extends keyof ${root}> =`);
lines.push(` ${mapName}[K] extends never`);
lines.push(` ? () => NonNullable<${root}[K]>`);
lines.push(` : (args: ${mapName}[K]) => NonNullable<${root}[K]>;`);
lines.push('');
lines.push(`export type ${mapAlias} = {`);
lines.push(` [K in keyof ${root}]?: ${fieldAlias}<K>;`);
lines.push('};');
lines.push(end);
lines.push('');
return lines.join('\n');
};

const helperBlocks = [];
for (const root of ROOT_DEFINITIONS) {
removeRootHelpers(root);
const block = buildRootHelpers(root);
if (block) helperBlocks.push(block);
}

if (helperBlocks.length > 0) {
if (!content.endsWith('\n')) {
content += '\n';
}
content += helperBlocks.join('\n');
}

writeFileSync(targetPath, content);
49 changes: 49 additions & 0 deletions scripts/generate-dart-types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,50 @@ const printOperationInterface = (operationType) => {
lines.push('}', '');
};

const printOperationHelpers = (operationType) => {
const rootName = operationType.name;
const fields = Object.values(operationType.getFields())
.filter((field) => field.name !== '_placeholder')
.sort((a, b) => a.name.localeCompare(b.name));
if (fields.length === 0) return;

lines.push(`// MARK: - ${rootName} Helpers`, '');

fields.forEach((field) => {
const pascalField = toPascalCase(field.name);
const aliasName = `${rootName}${pascalField}Handler`;
const { type, nullable } = getDartType(field.type);
const returnType = `${type}${nullable ? '?' : ''}`;
if (field.args.length === 0) {
lines.push(`typedef ${aliasName} = Future<${returnType}> Function();`);
return;
}
lines.push(`typedef ${aliasName} = Future<${returnType}> Function({`);
field.args.forEach((arg) => {
const { type: argType, nullable: argNullable } = getDartType(arg.type);
const finalType = `${argType}${argNullable ? '?' : ''}`;
const prefix = argNullable ? '' : 'required ';
lines.push(` ${prefix}${finalType} ${escapeDartName(arg.name)},`);
});
lines.push('});');
});

const helperClass = `${rootName}Handlers`;
lines.push('', `class ${helperClass} {`);
lines.push(` const ${helperClass}({`);
fields.forEach((field) => {
lines.push(` this.${escapeDartName(field.name)},`);
});
lines.push(' });', '');
fields.forEach((field) => {
const pascalField = toPascalCase(field.name);
const aliasName = `${rootName}${pascalField}Handler`;
const propertyName = escapeDartName(field.name);
lines.push(` final ${aliasName}? ${propertyName};`);
});
lines.push('}', '');
};

if (enums.length) {
lines.push('// MARK: - Enums', '');
enums.sort((a, b) => a.name.localeCompare(b.name)).forEach(printEnum);
Expand Down Expand Up @@ -638,6 +682,11 @@ if (operationTypes.length) {
operationTypes.sort((a, b) => a.name.localeCompare(b.name)).forEach(printOperationInterface);
}

if (operationTypes.length) {
lines.push('// MARK: - Root Operation Helpers', '');
operationTypes.sort((a, b) => a.name.localeCompare(b.name)).forEach(printOperationHelpers);
}

const outputPath = resolve(__dirname, '../src/generated/types.dart');
mkdirSync(dirname(outputPath), { recursive: true });
writeFileSync(outputPath, lines.join('\n'));
Expand Down
41 changes: 41 additions & 0 deletions scripts/generate-kotlin-types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,42 @@ const printOperationInterface = (operationType) => {
lines.push('}', '');
};

const printOperationHelpers = (operationType) => {
const rootName = operationType.name;
const fields = Object.values(operationType.getFields())
.filter((field) => field.name !== '_placeholder')
.sort((a, b) => a.name.localeCompare(b.name));
if (fields.length === 0) return;

lines.push(`// MARK: - ${rootName} Helpers`, '');

fields.forEach((field) => {
const aliasName = `${rootName}${capitalize(field.name)}Handler`;
const { type, nullable } = getKotlinType(field.type);
const returnType = type + (nullable ? '?' : '');
if (field.args.length === 0) {
lines.push(`public typealias ${aliasName} = suspend () -> ${returnType}`);
return;
}
const argsSignature = field.args.map((arg) => {
const { type: argType, nullable: argNullable } = getKotlinType(arg.type);
const argumentType = argType + (argNullable ? '?' : '');
return `${escapeKotlinName(arg.name)}: ${argumentType}`;
}).join(', ');
lines.push(`public typealias ${aliasName} = suspend (${argsSignature}) -> ${returnType}`);
});

const helperClass = `${rootName}Handlers`;
lines.push('', `public data class ${helperClass}(`);
fields.forEach((field, index) => {
const aliasName = `${rootName}${capitalize(field.name)}Handler`;
const propertyName = escapeKotlinName(field.name);
const suffix = index === fields.length - 1 ? '' : ',';
lines.push(` val ${propertyName}: ${aliasName}? = null${suffix}`);
});
lines.push(')', '');
};

if (enums.length) {
lines.push('// MARK: - Enums', '');
enums.sort((a, b) => a.name.localeCompare(b.name)).forEach(printEnum);
Expand Down Expand Up @@ -568,6 +604,11 @@ if (operationTypes.length) {
operationTypes.sort((a, b) => a.name.localeCompare(b.name)).forEach(printOperationInterface);
}

if (operationTypes.length) {
lines.push('// MARK: - Root Operation Helpers', '');
operationTypes.sort((a, b) => a.name.localeCompare(b.name)).forEach(printOperationHelpers);
}

const outputPath = resolve(__dirname, '../src/generated/Types.kt');
mkdirSync(dirname(outputPath), { recursive: true });
writeFileSync(outputPath, lines.join('\n'));
Expand Down
52 changes: 52 additions & 0 deletions scripts/generate-swift-types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ const lowerCamelCase = (value) => {
return parts[0] + parts.slice(1).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join('');
};

const capitalize = (value) => (value.length === 0 ? value : value.charAt(0).toUpperCase() + value.slice(1));

const toConstantCase = (value) => value
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
.replace(/([A-Z])([A-Z][a-z])/g, '$1_$2')
Expand Down Expand Up @@ -431,6 +433,51 @@ const printOperationProtocol = (operationType) => {
lines.push('}', '');
};

const printOperationHelpers = (operationType) => {
const rootName = operationType.name;
const fields = Object.values(operationType.getFields())
.filter((field) => field.name !== '_placeholder')
.sort((a, b) => a.name.localeCompare(b.name));
if (fields.length === 0) return;

lines.push(`// MARK: - ${rootName} Helpers`, '');

fields.forEach((field) => {
const aliasName = `${rootName}${capitalize(field.name)}Handler`;
const { type, optional } = swiftTypeFor(field.type);
const returnType = type + (optional ? '?' : '');
if (field.args.length === 0) {
lines.push(`public typealias ${aliasName} = () async throws -> ${returnType}`);
return;
}
const params = field.args.map((arg) => {
const { type: argType, optional: argOptional } = swiftTypeFor(arg.type);
const finalType = argType + (argOptional ? '?' : '');
return `_ ${escapeSwiftName(arg.name)}: ${finalType}`;
}).join(', ');
lines.push(`public typealias ${aliasName} = (${params}) async throws -> ${returnType}`);
});

const structName = `${rootName}Handlers`;
lines.push('', `public struct ${structName} {`);
fields.forEach((field) => {
const aliasName = `${rootName}${capitalize(field.name)}Handler`;
lines.push(` public var ${escapeSwiftName(field.name)}: ${aliasName}?`);
});
lines.push('');
const initParams = fields.map((field) => {
const aliasName = `${rootName}${capitalize(field.name)}Handler`;
return `${escapeSwiftName(field.name)}: ${aliasName}? = nil`;
}).join(',\n ');
lines.push(' public init(' + (fields.length ? `\n ${initParams}\n ` : '') + ') {');
fields.forEach((field) => {
const propertyName = escapeSwiftName(field.name);
lines.push(` self.${propertyName} = ${propertyName}`);
});
lines.push(' }');
lines.push('}', '');
};

if (enums.length) {
lines.push('// MARK: - Enums', '');
enums.sort((a, b) => a.name.localeCompare(b.name)).forEach(printEnum);
Expand Down Expand Up @@ -461,6 +508,11 @@ if (operations.length) {
operations.sort((a, b) => a.name.localeCompare(b.name)).forEach(printOperationProtocol);
}

if (operations.length) {
lines.push('// MARK: - Root Operation Helpers', '');
operations.sort((a, b) => a.name.localeCompare(b.name)).forEach(printOperationHelpers);
}

const outputPath = resolve(__dirname, '../src/generated/Types.swift');
mkdirSync(dirname(outputPath), { recursive: true });
writeFileSync(outputPath, lines.join('\n'));
Expand Down
86 changes: 86 additions & 0 deletions src/generated/Types.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2080,3 +2080,89 @@ public interface SubscriptionResolver {
*/
suspend fun purchaseUpdated(): Purchase
}

// MARK: - Root Operation Helpers

// MARK: - Mutation Helpers

public typealias MutationAcknowledgePurchaseAndroidHandler = suspend (purchaseToken: String) -> VoidResult
public typealias MutationBeginRefundRequestIOSHandler = suspend (sku: String) -> RefundResultIOS
public typealias MutationClearTransactionIOSHandler = suspend () -> VoidResult
public typealias MutationConsumePurchaseAndroidHandler = suspend (purchaseToken: String) -> VoidResult
public typealias MutationDeepLinkToSubscriptionsHandler = suspend (options: DeepLinkOptions?) -> VoidResult
public typealias MutationEndConnectionHandler = suspend () -> Boolean
public typealias MutationFinishTransactionHandler = suspend (purchase: PurchaseInput, isConsumable: Boolean?) -> VoidResult
public typealias MutationInitConnectionHandler = suspend () -> Boolean
public typealias MutationPresentCodeRedemptionSheetIOSHandler = suspend () -> VoidResult
public typealias MutationRequestPurchaseHandler = suspend (params: RequestPurchaseProps) -> RequestPurchaseResult?
public typealias MutationRequestPurchaseOnPromotedProductIOSHandler = suspend () -> PurchaseIOS
public typealias MutationRestorePurchasesHandler = suspend () -> VoidResult
public typealias MutationShowManageSubscriptionsIOSHandler = suspend () -> List<PurchaseIOS>
public typealias MutationSyncIOSHandler = suspend () -> VoidResult
public typealias MutationValidateReceiptHandler = suspend (options: ReceiptValidationProps) -> ReceiptValidationResult

public data class MutationHandlers(
val acknowledgePurchaseAndroid: MutationAcknowledgePurchaseAndroidHandler? = null,
val beginRefundRequestIOS: MutationBeginRefundRequestIOSHandler? = null,
val clearTransactionIOS: MutationClearTransactionIOSHandler? = null,
val consumePurchaseAndroid: MutationConsumePurchaseAndroidHandler? = null,
val deepLinkToSubscriptions: MutationDeepLinkToSubscriptionsHandler? = null,
val endConnection: MutationEndConnectionHandler? = null,
val finishTransaction: MutationFinishTransactionHandler? = null,
val initConnection: MutationInitConnectionHandler? = null,
val presentCodeRedemptionSheetIOS: MutationPresentCodeRedemptionSheetIOSHandler? = null,
val requestPurchase: MutationRequestPurchaseHandler? = null,
val requestPurchaseOnPromotedProductIOS: MutationRequestPurchaseOnPromotedProductIOSHandler? = null,
val restorePurchases: MutationRestorePurchasesHandler? = null,
val showManageSubscriptionsIOS: MutationShowManageSubscriptionsIOSHandler? = null,
val syncIOS: MutationSyncIOSHandler? = null,
val validateReceipt: MutationValidateReceiptHandler? = null
)

// MARK: - Query Helpers

public typealias QueryCurrentEntitlementIOSHandler = suspend (skus: List<String>?) -> List<EntitlementIOS>
public typealias QueryFetchProductsHandler = suspend (params: ProductRequest) -> FetchProductsResult
public typealias QueryGetActiveSubscriptionsHandler = suspend (subscriptionIds: List<String>?) -> List<ActiveSubscription>
public typealias QueryGetAppTransactionIOSHandler = suspend () -> AppTransaction?
public typealias QueryGetAvailablePurchasesHandler = suspend (options: PurchaseOptions?) -> List<Purchase>
public typealias QueryGetPendingTransactionsIOSHandler = suspend () -> List<PurchaseIOS>
public typealias QueryGetPromotedProductIOSHandler = suspend () -> ProductIOS?
public typealias QueryGetReceiptDataIOSHandler = suspend () -> String
public typealias QueryGetStorefrontIOSHandler = suspend () -> String
public typealias QueryGetTransactionJwsIOSHandler = suspend (transactionId: String) -> String
public typealias QueryHasActiveSubscriptionsHandler = suspend (subscriptionIds: List<String>?) -> Boolean
public typealias QueryIsEligibleForIntroOfferIOSHandler = suspend (productIds: List<String>) -> Boolean
public typealias QueryIsTransactionVerifiedIOSHandler = suspend (transactionId: String) -> Boolean
public typealias QueryLatestTransactionIOSHandler = suspend (sku: String) -> PurchaseIOS?
public typealias QuerySubscriptionStatusIOSHandler = suspend (skus: List<String>?) -> List<SubscriptionStatusIOS>

public data class QueryHandlers(
val currentEntitlementIOS: QueryCurrentEntitlementIOSHandler? = null,
val fetchProducts: QueryFetchProductsHandler? = null,
val getActiveSubscriptions: QueryGetActiveSubscriptionsHandler? = null,
val getAppTransactionIOS: QueryGetAppTransactionIOSHandler? = null,
val getAvailablePurchases: QueryGetAvailablePurchasesHandler? = null,
val getPendingTransactionsIOS: QueryGetPendingTransactionsIOSHandler? = null,
val getPromotedProductIOS: QueryGetPromotedProductIOSHandler? = null,
val getReceiptDataIOS: QueryGetReceiptDataIOSHandler? = null,
val getStorefrontIOS: QueryGetStorefrontIOSHandler? = null,
val getTransactionJwsIOS: QueryGetTransactionJwsIOSHandler? = null,
val hasActiveSubscriptions: QueryHasActiveSubscriptionsHandler? = null,
val isEligibleForIntroOfferIOS: QueryIsEligibleForIntroOfferIOSHandler? = null,
val isTransactionVerifiedIOS: QueryIsTransactionVerifiedIOSHandler? = null,
val latestTransactionIOS: QueryLatestTransactionIOSHandler? = null,
val subscriptionStatusIOS: QuerySubscriptionStatusIOSHandler? = null
)

// MARK: - Subscription Helpers

public typealias SubscriptionPromotedProductIOSHandler = suspend () -> String
public typealias SubscriptionPurchaseErrorHandler = suspend () -> PurchaseError
public typealias SubscriptionPurchaseUpdatedHandler = suspend () -> Purchase

public data class SubscriptionHandlers(
val promotedProductIOS: SubscriptionPromotedProductIOSHandler? = null,
val purchaseError: SubscriptionPurchaseErrorHandler? = null,
val purchaseUpdated: SubscriptionPurchaseUpdatedHandler? = null
)
Loading