@ikas/admin-api-client
Version:
ikas public node api client for store apps and private apps
575 lines • 25.5 kB
JavaScript
"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 /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