UNPKG

@ikas/admin-api-client

Version:

ikas public node api client for store apps and private apps

575 lines 25.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.preset = exports.GENERATED_MUTATION_CLASS_NAME = exports.GENERATED_QUERY_CLASS_NAME = void 0; exports.processSources = processSources; const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1); const capitalizeQueries = (node) => { if (node.kind === 'FragmentDefinition') { return 'not used'; } // Match the names generated by typescript-operations plugin: // e.g. 'query Hello {...}' => HelloQuery // -- Anonymous queries are not supported. return capitalize(node.name.value) + capitalize(node.operation); }; function processSources(sources, buildName = capitalizeQueries) { const sourcesWithOperations = []; for (const originalSource of sources) { const source = fixLinebreaks(originalSource); const { document } = source; const operations = []; for (const definition of document?.definitions ?? []) { if (definition?.kind !== 'OperationDefinition' && definition?.kind !== 'FragmentDefinition') continue; if (definition.name?.kind !== `Name`) continue; operations.push({ initialName: buildName(definition), definition, }); } if (operations.length === 0) continue; sourcesWithOperations.push({ source, operations, }); } return sourcesWithOperations; } function fixLinebreaks(source) { const fixedSource = { ...source }; fixedSource.rawSDL = source.rawSDL?.replace(/\r\n/g, '\n'); return fixedSource; } const graphql_1 = require("graphql"); const plugin = (schema, documents, { sourcesWithOperations }, _info) => { const generatedTypes = new Map(); const code = []; code.push(`import { BaseGraphQLAPIClient, BaseGraphQLAPIClientOptions, APIResult } from '@ikas/admin-api-client';`); code.push(``); // Generate scalar types mapping const scalarTypes = new Map([ ['String', 'string'], ['Int', 'number'], ['Float', 'number'], ['Boolean', 'boolean'], ['ID', 'string'], ['DateTime', 'string'], ['Date', 'string'], ['JSON', 'any'], ['Upload', 'File'], ['Timestamp', 'number'], ]); // Helper function to convert GraphQL type to TypeScript type const convertGraphQLTypeToTS = (type) => { if ((0, graphql_1.isNonNullType)(type)) { return convertGraphQLTypeToTS(type.ofType); } if ((0, graphql_1.isListType)(type)) { return `Array<${convertGraphQLTypeToTS(type.ofType)}>`; } if ((0, graphql_1.isScalarType)(type)) { return scalarTypes.get(type.name) || 'unknown'; } if ((0, graphql_1.isEnumType)(type) || (0, graphql_1.isObjectType)(type) || (0, graphql_1.isInterfaceType)(type) || (0, graphql_1.isUnionType)(type) || (0, graphql_1.isInputObjectType)(type)) { return type.name; } return 'unknown'; }; // Helper function to check if type is nullable const isNullable = (type) => { return !(0, graphql_1.isNonNullType)(type); }; // Generate enum types const generateEnumType = (enumType) => { const values = enumType.getValues() .map(value => ` ${value.name} = "${value.value || value.name}"`) .join(',\n'); return { name: enumType.name, content: `export enum ${enumType.name} {\n${values}\n}`, isEnum: true, isInput: false }; }; // Generate object/interface types const generateObjectType = (objectType) => { const fields = Object.values(objectType.getFields()).map(field => { const tsType = convertGraphQLTypeToTS(field.type); const nullable = isNullable(field.type) ? '?' : ''; return ` ${field.name}${nullable}: ${tsType};`; }).join('\n'); return { name: objectType.name, content: `export interface ${objectType.name} {\n${fields}\n}`, isEnum: false, isInput: false }; }; // Generate input types const generateInputType = (inputType) => { const fields = Object.values(inputType.getFields()).map(field => { const tsType = convertGraphQLTypeToTS(field.type); const nullable = isNullable(field.type) ? '?' : ''; return ` ${field.name}${nullable}: ${tsType};`; }).join('\n'); return { name: inputType.name, content: `export interface ${inputType.name} {\n${fields}\n}`, isEnum: false, isInput: true }; }; // Generate union types const generateUnionType = (unionType) => { const types = unionType.getTypes().map(type => type.name).join(' | '); return { name: unionType.name, content: `export type ${unionType.name} = ${types};`, isEnum: false, isInput: false }; }; // Helper to add type and its dependencies to the set const addTypeToSet = (type, usedTypes) => { let unwrappedType = type; // Unwrap NonNull and List wrappers while ((0, graphql_1.isNonNullType)(unwrappedType) || (0, graphql_1.isListType)(unwrappedType)) { unwrappedType = unwrappedType.ofType; } if ((0, graphql_1.isScalarType)(unwrappedType)) { // Don't add built-in scalars, but add custom scalars if (!['String', 'Int', 'Float', 'Boolean', 'ID'].includes(unwrappedType.name)) { usedTypes.add(unwrappedType.name); } } else if ((0, graphql_1.isEnumType)(unwrappedType) || (0, graphql_1.isObjectType)(unwrappedType) || (0, graphql_1.isInterfaceType)(unwrappedType) || (0, graphql_1.isUnionType)(unwrappedType) || (0, graphql_1.isInputObjectType)(unwrappedType)) { usedTypes.add(unwrappedType.name); } }; // Recursively collect all dependent types for a given type const collectDependentTypes = (type, usedTypes, visited = new Set()) => { let unwrappedType = type; // Unwrap NonNull and List wrappers while ((0, graphql_1.isNonNullType)(unwrappedType) || (0, graphql_1.isListType)(unwrappedType)) { unwrappedType = unwrappedType.ofType; } // Avoid infinite recursion if (visited.has(unwrappedType.name)) { return; } visited.add(unwrappedType.name); if ((0, graphql_1.isScalarType)(unwrappedType)) { // Don't add built-in scalars, but add custom scalars if (!['String', 'Int', 'Float', 'Boolean', 'ID'].includes(unwrappedType.name)) { usedTypes.add(unwrappedType.name); } } else if ((0, graphql_1.isEnumType)(unwrappedType)) { usedTypes.add(unwrappedType.name); } else if ((0, graphql_1.isObjectType)(unwrappedType) || (0, graphql_1.isInterfaceType)(unwrappedType)) { usedTypes.add(unwrappedType.name); // Collect types from all fields const fields = unwrappedType.getFields(); for (const field of Object.values(fields)) { collectDependentTypes(field.type, usedTypes, visited); } } else if ((0, graphql_1.isUnionType)(unwrappedType)) { usedTypes.add(unwrappedType.name); // Collect types from union members const types = unwrappedType.getTypes(); for (const memberType of types) { collectDependentTypes(memberType, usedTypes, visited); } } else if ((0, graphql_1.isInputObjectType)(unwrappedType)) { usedTypes.add(unwrappedType.name); // Collect types from all input fields const fields = unwrappedType.getFields(); for (const field of Object.values(fields)) { collectDependentTypes(field.type, usedTypes, visited); } } }; // Collect all used types from selections recursively const collectUsedTypes = (selectionSet, parentType, usedTypes) => { if (!selectionSet) return; let objectType = parentType; if ((0, graphql_1.isNonNullType)(objectType)) { objectType = objectType.ofType; } if (!(0, graphql_1.isObjectType)(objectType) && !(0, graphql_1.isInterfaceType)(objectType)) return; for (const selection of selectionSet.selections) { if (selection.kind === 'Field') { const field = objectType.getFields()[selection.name.value]; if (field) { const fieldType = field.type; addTypeToSet(fieldType, usedTypes); if (selection.selectionSet) { collectUsedTypes(selection.selectionSet, fieldType, usedTypes); } } } else if (selection.kind === 'InlineFragment') { if (selection.typeCondition) { const fragmentType = schema.getType(selection.typeCondition.name.value); if (fragmentType) { usedTypes.add(fragmentType.name); collectUsedTypes(selection.selectionSet, fragmentType, usedTypes); } } } } }; // Helper to get type name from TypeNode const getTypeName = (typeNode) => { if (typeNode.kind === 'NonNullType' || typeNode.kind === 'ListType') { return getTypeName(typeNode.type); } return typeNode.name.value; }; // Collect all used types from variables const collectUsedTypesFromVariables = (variableDefinitions, usedTypes) => { for (const varDef of variableDefinitions) { const typeName = getTypeName(varDef.type); const type = schema.getType(typeName); if (type) { addTypeToSet(type, usedTypes); } } }; // Generate response type from selection set const generateResponseType = (selectionSet, parentType) => { if (!selectionSet) return '{}'; let objectType = parentType; if ((0, graphql_1.isNonNullType)(objectType)) { objectType = objectType.ofType; } if (!(0, graphql_1.isObjectType)(objectType) && !(0, graphql_1.isInterfaceType)(objectType)) return '{}'; const fields = []; for (const selection of selectionSet.selections) { if (selection.kind === 'Field') { const field = objectType.getFields()[selection.name.value]; if (field) { const fieldType = field.type; let tsType; if (selection.selectionSet) { // This field has nested selections - unwrap the field type to get the actual object type let nestedParentType = fieldType; if ((0, graphql_1.isNonNullType)(nestedParentType)) { nestedParentType = nestedParentType.ofType; } if ((0, graphql_1.isListType)(nestedParentType)) { nestedParentType = nestedParentType.ofType; if ((0, graphql_1.isNonNullType)(nestedParentType)) { nestedParentType = nestedParentType.ofType; } } const nestedType = generateResponseType(selection.selectionSet, nestedParentType); // Handle arrays properly if ((0, graphql_1.isListType)(fieldType) || ((0, graphql_1.isNonNullType)(fieldType) && (0, graphql_1.isListType)(fieldType.ofType))) { tsType = `Array<${nestedType}>`; } else { tsType = nestedType; } } else { tsType = convertGraphQLTypeToTS(fieldType); } const nullable = isNullable(fieldType) ? '?' : ''; fields.push(` ${selection.name.value}${nullable}: ${tsType};`); } } } return `{\n${fields.join('\n')}\n}`; }; // Generate operation types const generateOperationType = (operation, operationName) => { const usedTypes = new Set(); const operationTypes = []; // Collect types from variables if (operation.variableDefinitions) { collectUsedTypesFromVariables(operation.variableDefinitions, usedTypes); } // Get root type const rootType = operation.operation === 'query' ? schema.getQueryType() : operation.operation === 'mutation' ? schema.getMutationType() : schema.getSubscriptionType(); if (rootType && operation.selectionSet) { collectUsedTypes(operation.selectionSet, rootType, usedTypes); } // Generate all discovered types and their dependencies const typesToProcess = Array.from(usedTypes); // First pass: collect all dependent types recursively for (const typeName of typesToProcess) { const type = schema.getType(typeName); if (type) { collectDependentTypes(type, usedTypes); } } // Second pass: generate all types for (const typeName of usedTypes) { if (!generatedTypes.has(typeName)) { const type = schema.getType(typeName); if (type) { let typeDefinition = null; if ((0, graphql_1.isEnumType)(type)) { typeDefinition = generateEnumType(type); } else if ((0, graphql_1.isObjectType)(type) || (0, graphql_1.isInterfaceType)(type)) { typeDefinition = generateObjectType(type); } else if ((0, graphql_1.isInputObjectType)(type)) { typeDefinition = generateInputType(type); } else if ((0, graphql_1.isUnionType)(type)) { typeDefinition = generateUnionType(type); } if (typeDefinition) { generatedTypes.set(typeName, typeDefinition); } } } } // Generate variables type if (operation.variableDefinitions && operation.variableDefinitions.length > 0) { const variables = operation.variableDefinitions.map(varDef => { const typeName = getTypeName(varDef.type); const type = schema.getType(typeName); const tsType = type ? convertGraphQLTypeToTS(type) : 'unknown'; const nullable = varDef.type.kind !== 'NonNullType' ? '?' : ''; return ` ${varDef.variable.name.value}${nullable}: ${tsType};`; }).join('\n'); operationTypes.push(`export interface ${operationName}Variables {\n${variables}\n}`); } else { operationTypes.push(`export interface ${operationName}Variables {}`); } // Generate response type if (rootType && operation.selectionSet) { const responseType = generateResponseType(operation.selectionSet, rootType); operationTypes.push(`export interface ${operationName} ${responseType}`); } else { operationTypes.push(`export interface ${operationName} {}`); } return operationTypes; }; // Generate all types first const allOperationTypes = []; for (const { operations } of sourcesWithOperations) { for (const operation of operations) { if (operation.definition.kind === 'OperationDefinition') { const operationTypes = generateOperationType(operation.definition, operation.initialName); allOperationTypes.push(...operationTypes); } } } // Output all generated types const sortedTypes = Array.from(generatedTypes.values()).sort((a, b) => { // Enums first, then inputs, then regular types if (a.isEnum && !b.isEnum) return -1; if (!a.isEnum && b.isEnum) return 1; if (a.isInput && !b.isInput) return 1; if (!a.isInput && b.isInput) return -1; return a.name.localeCompare(b.name); }); code.push(...sortedTypes.map(type => type.content)); code.push(''); code.push(...allOperationTypes); code.push(''); // Generate query and mutation classes instead of interface mappings const classesCode = generateQueryMutationClasses(sourcesWithOperations); code.push(...classesCode); // Generate the GraphQL API client class const clientCode = generateGraphQLAPIClient(); code.push(...clientCode); return code.join('\n') + '\n'; }; exports.GENERATED_QUERY_CLASS_NAME = 'GeneratedQuery'; exports.GENERATED_MUTATION_CLASS_NAME = 'GeneratedMutation'; // Generate query and mutation classes with methods for each operation function generateQueryMutationClasses(sourcesWithOperations = []) { const queries = new Map(); const mutations = new Map(); const code = []; const variablesMap = new Map(); for (const { source } of sourcesWithOperations) { const variableName = source.rawSDL?.match(/#VAR_NAME=(\w+)/)?.[1]; if (variableName) { source.rawSDL = source.rawSDL.replace(/#VAR_NAME=\w+$/, ''); variablesMap.set(variableName, source.rawSDL); } } for (const { operations, source } of sourcesWithOperations) { const actualOperations = operations.filter((op) => op.definition.kind === 'OperationDefinition'); if (actualOperations.length === 0) continue; const sdlString = source.rawSDL; const target = isMutationRE.test(sdlString) ? mutations : queries; target.set(normalizeOperation(sdlString, variablesMap), actualOperations.map((o) => o.initialName)); } // Helper to convert GraphQL string to method name const getMethodName = (operationName) => { // Convert "ListSalesChannelQuery" to "listSalesChannel" return operationName.replace(/Query$|Mutation$/, '').charAt(0).toLowerCase() + operationName.replace(/Query$|Mutation$/, '').slice(1); }; // Generate Query class if (queries.size > 0) { code.push(`export class ${exports.GENERATED_QUERY_CLASS_NAME} {`); code.push(` client: BaseGraphQLAPIClient<any>;`); code.push(``); code.push(` constructor(client: BaseGraphQLAPIClient<any>) {`); code.push(` this.client = client;`); code.push(` }`); code.push(``); for (const [queryString, typeNames] of queries) { for (const typeName of typeNames) { const methodName = getMethodName(typeName); const hasVariables = queryString.includes('$'); code.push(` async ${methodName}(${hasVariables ? `variables: ${typeName}Variables` : ''}): Promise<APIResult<Partial<${typeName}>>> {`); code.push(` const query = \`${queryString.replace(/`/g, '\\`')}\`;`); if (hasVariables) { code.push(` return this.client.query<Partial<${typeName}>>({ query, variables });`); } else { code.push(` return this.client.query<Partial<${typeName}>>({ query });`); } code.push(` }`); code.push(``); } } code.push(`}`); code.push(``); } // Generate Mutation class if (mutations.size > 0) { code.push(`export class ${exports.GENERATED_MUTATION_CLASS_NAME} {`); code.push(` client: BaseGraphQLAPIClient<any>;`); code.push(``); code.push(` constructor(client: BaseGraphQLAPIClient<any>) {`); code.push(` this.client = client;`); code.push(` }`); code.push(``); for (const [mutationString, typeNames] of mutations) { for (const typeName of typeNames) { const methodName = getMethodName(typeName); const hasVariables = mutationString.includes('$'); code.push(` async ${methodName}(${hasVariables ? `variables: ${typeName}Variables` : ''}): Promise<APIResult<Partial<${typeName}>>> {`); code.push(` const mutation = \`${mutationString.replace(/`/g, '\\`')}\`;`); if (hasVariables) { code.push(` return this.client.mutate<Partial<${typeName}>>({ mutation, variables });`); } else { code.push(` return this.client.mutate<Partial<${typeName}>>({ mutation });`); } code.push(` }`); code.push(``); } } code.push(`}`); } return code; } // Generate the GraphQL API client class that uses the query and mutation classes function generateGraphQLAPIClient() { const code = []; code.push(`export class ikasAdminGraphQLAPIClient<TokenData> extends BaseGraphQLAPIClient<TokenData> {`); code.push(` queries: ${exports.GENERATED_QUERY_CLASS_NAME};`); code.push(` mutations: ${exports.GENERATED_MUTATION_CLASS_NAME};`); code.push(``); code.push(` constructor(options: BaseGraphQLAPIClientOptions<TokenData>) {`); code.push(` super(options);`); code.push(` this.queries = new ${exports.GENERATED_QUERY_CLASS_NAME}(this);`); code.push(` this.mutations = new ${exports.GENERATED_MUTATION_CLASS_NAME}(this);`); code.push(` }`); code.push(`}`); return code; } const isMutationRE = /(^|}\s|\n\s*)mutation[\s({]/im; // Iteratively replace fragment annotations with the actual fragment content // until there are no more annotations in the operation. const normalizeOperation = (rawSDL, variablesMap) => { let variableNotFound = false; while (/#REQUIRED_VAR=/.test(rawSDL) && !variableNotFound) { let requiredVariables = rawSDL.matchAll(/#REQUIRED_VAR=(\w+)/g); for (const [match, variableName] of requiredVariables) { if (variablesMap.has(variableName)) { rawSDL = rawSDL.replace(match, variablesMap.get(variableName)); } else { // An annotation cannot be replaced, so the operation is invalid. // This should not happen, but we'll handle it just in case // to prevent infinite loops. This should be logged as an error and fixed. variableNotFound = true; // break; console.error(new Error(`Variable "${variableName}" not found. This might be a bug in @shopify/graphql-codegen, please report it.`)); } } } return rawSDL; }; const ERROR_PREFIX = '[@ikas/graphql-codegen]'; exports.preset = { [Symbol.for('name')]: '@ikas/graphql-codegen', buildGeneratesSection: (options) => { if (!options.baseOutputDir.endsWith('.ts')) { throw new Error(ERROR_PREFIX + ' target output should be a .ts or a .d.ts file.'); } if (options.plugins?.length > 0 && Object.keys(options.plugins).some((p) => p.startsWith('typescript'))) { throw new Error(ERROR_PREFIX + ' providing additional typescript-based `plugins` leads to duplicated generated types'); } const sourcesWithOperations = processSources(options.documents); const sources = sourcesWithOperations.map(({ source }) => source); const pluginMap = { ...options.pluginMap, [`generate-ikas-graphql-api`]: { plugin }, }; const plugins = [ { [`generate-ikas-graphql-api`]: { sourcesWithOperations } }, // 3. Custom plugins from the user ...options.plugins, ]; // const version = options.config?.customVersion || DEFAULT_API_VERSION; // const schemaUrl = `https://api${STORE_DOMAIN}/api/${version}/admin/graphql`; // const schemaTemp: any = { // `${schemaUrl}`: { // headers: { // 'Content-Type': 'application/json', // }, // }, // }; // console.log('options', JSON.stringify(options)); return [ { filename: options.baseOutputDir, plugins, pluginMap, schema: options.schema, config: { // For the TS plugin: defaultScalarType: 'unknown', // Allow overwriting defaults: ...options.config, }, documents: sources, documentTransforms: options.documentTransforms, }, ]; }, }; //# sourceMappingURL=preset.js.map