Explorar o código

refactor(core): Refactor & expose some methods for dashboard

This allows us to do some automatic introspection in the
dashboard vite plugin
Michael Bromley hai 10 meses
pai
achega
1d51996ecf

+ 9 - 2
packages/core/src/api/api.module.ts

@@ -16,6 +16,13 @@ import { IdInterceptor } from './middleware/id-interceptor';
 import { TranslateErrorResultInterceptor } from './middleware/translate-error-result-interceptor';
 import { ValidateCustomFieldsInterceptor } from './middleware/validate-custom-fields-interceptor';
 
+export const VENDURE_SHOP_API_TYPE_PATHS = ['shop-api', 'common'].map(p =>
+    path.join(__dirname, 'schema', p, '*.graphql'),
+);
+export const VENDURE_ADMIN_API_TYPE_PATHS = ['admin-api', 'common'].map(p =>
+    path.join(__dirname, 'schema', p, '*.graphql'),
+);
+
 /**
  * The ApiModule is responsible for the public API of the application. This is where requests
  * come in, are parsed and then handed over to the ServiceModule classes which take care
@@ -35,7 +42,7 @@ import { ValidateCustomFieldsInterceptor } from './middleware/validate-custom-fi
             apiPath: configService.apiOptions.shopApiPath,
             playground: configService.apiOptions.shopApiPlayground,
             debug: configService.apiOptions.shopApiDebug,
-            typePaths: ['shop-api', 'common'].map(p => path.join(__dirname, 'schema', p, '*.graphql')),
+            typePaths: VENDURE_SHOP_API_TYPE_PATHS,
             resolverModule: ShopApiModule,
             validationRules: configService.apiOptions.shopApiValidationRules,
         })),
@@ -44,7 +51,7 @@ import { ValidateCustomFieldsInterceptor } from './middleware/validate-custom-fi
             apiPath: configService.apiOptions.adminApiPath,
             playground: configService.apiOptions.adminApiPlayground,
             debug: configService.apiOptions.adminApiDebug,
-            typePaths: ['admin-api', 'common'].map(p => path.join(__dirname, 'schema', p, '*.graphql')),
+            typePaths: VENDURE_ADMIN_API_TYPE_PATHS,
             resolverModule: AdminApiModule,
             validationRules: configService.apiOptions.adminApiValidationRules,
         })),

+ 95 - 34
packages/core/src/api/config/configure-graphql-module.ts

@@ -2,18 +2,34 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
 import { DynamicModule } from '@nestjs/common';
 import { GraphQLModule, GraphQLTypesLoader } from '@nestjs/graphql';
 import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
-import { buildSchema, extendSchema, GraphQLSchema, printSchema, ValidationContext } from 'graphql';
+import {
+    buildClientSchema,
+    buildSchema,
+    extendSchema,
+    GraphQLSchema,
+    IntrospectionQuery,
+    IntrospectionSchema,
+    printIntrospectionSchema,
+    printSchema,
+    ValidationContext,
+} from 'graphql';
 import path from 'path';
 
 import { ConfigModule } from '../../config/config.module';
 import { ConfigService } from '../../config/config.service';
-import { AutoIncrementIdStrategy, EntityIdStrategy, UuidIdStrategy } from '../../config/index';
+import {
+    AutoIncrementIdStrategy,
+    EntityIdStrategy,
+    RuntimeVendureConfig,
+    UuidIdStrategy,
+} from '../../config/index';
 import { I18nModule } from '../../i18n/i18n.module';
 import { I18nService } from '../../i18n/i18n.service';
 import { getPluginAPIExtensions } from '../../plugin/plugin-metadata';
 import { ServiceModule } from '../../service/service.module';
 import { ApiSharedModule } from '../api-internal-modules';
 import { CustomFieldRelationResolverService } from '../common/custom-field-relation-resolver.service';
+import { ApiType } from '../common/get-api-type';
 import { IdCodecService } from '../common/id-codec.service';
 import { AssetInterceptorPlugin } from '../middleware/asset-interceptor-plugin';
 import { IdCodecPlugin } from '../middleware/id-codec-plugin';
@@ -143,43 +159,88 @@ async function createGraphQLOptions(
      * 3. any schema extensions defined by plugins
      */
     async function buildSchemaForApi(apiType: 'shop' | 'admin'): Promise<GraphQLSchema> {
-        const customFields = configService.customFields;
-        // Paths must be normalized to use forward-slash separators.
-        // See https://github.com/nestjs/graphql/issues/336
-        const normalizedPaths = options.typePaths.map(p => p.split(path.sep).join('/'));
-        const typeDefs = await typesLoader.mergeTypesByPaths(normalizedPaths);
-        const authStrategies =
-            apiType === 'shop'
-                ? configService.authOptions.shopAuthenticationStrategy
-                : configService.authOptions.adminAuthenticationStrategy;
-        let schema = buildSchema(typeDefs);
-
-        getPluginAPIExtensions(configService.plugins, apiType)
-            .map(e => (typeof e.schema === 'function' ? e.schema() : e.schema))
-            .filter(notNullOrUndefined)
-            .forEach(documentNode => (schema = extendSchema(schema, documentNode)));
-        schema = generateListOptions(schema);
-        schema = addGraphQLCustomFields(schema, customFields, apiType === 'shop');
-        schema = addOrderLineCustomFieldsInput(schema, customFields.OrderLine || [], apiType === 'shop');
-        schema = addModifyOrderCustomFields(schema, customFields.Order || []);
-        schema = addShippingMethodQuoteCustomFields(schema, customFields.ShippingMethod || []);
-        schema = addPaymentMethodQuoteCustomFields(schema, customFields.PaymentMethod || []);
-        schema = generateAuthenticationTypes(schema, authStrategies);
-        schema = generateErrorCodeEnum(schema);
-        if (apiType === 'admin') {
-            schema = addServerConfigCustomFields(schema, customFields);
-            schema = addActiveAdministratorCustomFields(schema, customFields.Administrator);
-        }
-        if (apiType === 'shop') {
-            schema = addRegisterCustomerCustomFieldsInput(schema, customFields.Customer || []);
-            schema = generateActiveOrderTypes(schema, configService.orderOptions.activeOrderStrategy);
-        }
-        schema = generatePermissionEnum(schema, configService.authOptions.customPermissions);
+        return getFinalVendureSchema({
+            config: configService,
+            typePaths: options.typePaths,
+            typesLoader,
+            apiType,
+        });
+    }
+}
 
+type GetSchemaOptions = {
+    config: RuntimeVendureConfig;
+    typePaths: string[];
+    typesLoader: GraphQLTypesLoader;
+    apiType: 'shop' | 'admin';
+};
+type GetSchemaAsSDLOptions = GetSchemaOptions & { output: 'sdl' };
+
+export async function getFinalVendureSchema(options: GetSchemaOptions): Promise<GraphQLSchema>;
+export async function getFinalVendureSchema(options: GetSchemaAsSDLOptions): Promise<string>;
+export async function getFinalVendureSchema(
+    options: GetSchemaOptions & { output?: 'sdl' },
+): Promise<GraphQLSchema | string> {
+    const { config, typePaths, typesLoader, apiType } = options;
+    // Paths must be normalized to use forward-slash separators.
+    // See https://github.com/nestjs/graphql/issues/336
+    const normalizedPaths = typePaths.map(p => p.split(path.sep).join('/'));
+    const typeDefs = await typesLoader.mergeTypesByPaths(normalizedPaths);
+    let schema = buildSchema(typeDefs);
+    schema = buildSchemaFromVendureConfig(schema, config, apiType);
+    if (options.output === 'sdl') {
+        return printSchema(schema);
+    } else {
         return schema;
     }
 }
 
+export function buildSchemaFromVendureConfig(
+    schema: GraphQLSchema,
+    config: RuntimeVendureConfig,
+    apiType: 'shop' | 'admin',
+): GraphQLSchema {
+    const authStrategies =
+        apiType === 'shop'
+            ? config.authOptions.shopAuthenticationStrategy
+            : config.authOptions.adminAuthenticationStrategy;
+
+    const customFields = config.customFields;
+
+    schema = extendSchemaWithPluginApiExtensions(schema, config.plugins, apiType);
+    schema = generateListOptions(schema);
+    schema = addGraphQLCustomFields(schema, customFields, apiType === 'shop');
+    schema = addOrderLineCustomFieldsInput(schema, customFields.OrderLine || [], apiType === 'shop');
+    schema = addModifyOrderCustomFields(schema, customFields.Order || []);
+    schema = addShippingMethodQuoteCustomFields(schema, customFields.ShippingMethod || []);
+    schema = addPaymentMethodQuoteCustomFields(schema, customFields.PaymentMethod || []);
+    schema = generateAuthenticationTypes(schema, authStrategies);
+    schema = generateErrorCodeEnum(schema);
+    if (apiType === 'admin') {
+        schema = addServerConfigCustomFields(schema, customFields);
+        schema = addActiveAdministratorCustomFields(schema, customFields.Administrator);
+    }
+    if (apiType === 'shop') {
+        schema = addRegisterCustomerCustomFieldsInput(schema, customFields.Customer || []);
+        schema = generateActiveOrderTypes(schema, config.orderOptions.activeOrderStrategy);
+    }
+    schema = generatePermissionEnum(schema, config.authOptions.customPermissions);
+
+    return schema;
+}
+
+function extendSchemaWithPluginApiExtensions(
+    schema: GraphQLSchema,
+    plugins: RuntimeVendureConfig['plugins'],
+    apiType: 'admin' | 'shop',
+) {
+    getPluginAPIExtensions(plugins, apiType)
+        .map(e => (typeof e.schema === 'function' ? e.schema() : e.schema))
+        .filter(notNullOrUndefined)
+        .forEach(documentNode => (schema = extendSchema(schema, documentNode)));
+    return schema;
+}
+
 function isUsingDefaultEntityIdStrategy(entityIdStrategy: EntityIdStrategy<any>): boolean {
     return (
         entityIdStrategy.constructor === AutoIncrementIdStrategy ||

+ 2 - 0
packages/core/src/api/index.ts

@@ -1,6 +1,8 @@
 export { ApiType } from './common/get-api-type';
+export * from './api.module';
 export * from './common/request-context';
 export * from './common/extract-session-token';
+export * from './config/configure-graphql-module';
 export * from './decorators/allow.decorator';
 export * from './decorators/transaction.decorator';
 export * from './decorators/api.decorator';

+ 2 - 2
packages/core/src/bootstrap.ts

@@ -326,9 +326,9 @@ function checkPluginCompatibility(
 }
 
 /**
- * Initialize any configured plugins.
+ * Run the configuration functions of all plugins and return the final config object.
  */
-async function runPluginConfigurations(config: RuntimeVendureConfig): Promise<RuntimeVendureConfig> {
+export async function runPluginConfigurations(config: RuntimeVendureConfig): Promise<RuntimeVendureConfig> {
     for (const plugin of config.plugins) {
         const configFn = getConfigurationFunction(plugin);
         if (typeof configFn === 'function') {

+ 1 - 1
packages/core/src/index.ts

@@ -1,4 +1,4 @@
-export { bootstrap, bootstrapWorker } from './bootstrap';
+export * from './bootstrap';
 export { VENDURE_VERSION } from './version';
 export { generateMigration, revertLastMigration, runMigrations } from './migrate';
 export * from './api/index';