Просмотр исходного кода

feat(core): Add `metadataModifiers` for low-level DB entity config

Relates to #1506, relates to #1502. This new config option allows the modification of TypeORM
metadata for the built-in entities. This allows, for instance, adding of
indices to columns to improve performance.
Michael Bromley 3 лет назад
Родитель
Сommit
16e52f2636

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

@@ -13,6 +13,7 @@ import { RuntimeVendureConfig, VendureConfig } from './config/vendure-config';
 import { Administrator } from './entity/administrator/administrator.entity';
 import { coreEntitiesMap } from './entity/entities';
 import { registerCustomEntityFields } from './entity/register-custom-entity-fields';
+import { runEntityMetadataModifiers } from './entity/run-entity-metadata-modifiers';
 import { setEntityIdStrategy } from './entity/set-entity-id-strategy';
 import { validateCustomFieldsConfig } from './entity/validate-custom-fields-config';
 import { getConfigurationFunction, getEntitiesFromPlugins } from './plugin/plugin-metadata';
@@ -150,6 +151,7 @@ export async function preBootstrapConfig(
     }
     config = await runPluginConfigurations(config);
     registerCustomEntityFields(config);
+    await runEntityMetadataModifiers(config);
     setExposedHeaders(config);
     return config;
 }

+ 1 - 0
packages/core/src/config/default-config.ts

@@ -111,6 +111,7 @@ export const defaultConfig: RuntimeVendureConfig = {
     entityOptions: {
         channelCacheTtl: 30000,
         zoneCacheTtl: 30000,
+        metadataModifiers: [],
     },
     promotionOptions: {
         promotionConditions: defaultPromotionConditions,

+ 30 - 0
packages/core/src/config/entity-metadata/add-foreign-key-indices.ts

@@ -0,0 +1,30 @@
+import { Index } from 'typeorm';
+import { MetadataArgsStorage } from 'typeorm/metadata-args/MetadataArgsStorage';
+
+import { StockMovement } from '../../entity/stock-movement/stock-movement.entity';
+
+import { EntityMetadataModifier } from './entity-metadata-modifier';
+
+/**
+ * @description
+ * Dynamically adds `@Index()` metadata to all many-to-one relations. These are already added
+ * by default in MySQL/MariaDB, but not in Postgres. So this modification can lead to improved
+ * performance with Postgres - especially when dealing with large numbers of products, orders etc.
+ *
+ * TODO: In v2 we will add the Index to all relations manually, this making this redundant.
+ */
+export const addForeignKeyIndices: EntityMetadataModifier = (metadata: MetadataArgsStorage) => {
+    for (const relationMetadata of metadata.relations) {
+        const { relationType, target } = relationMetadata;
+        if (relationType === 'many-to-one') {
+            const embeddedIn = metadata.embeddeds.find(e => e.type() === relationMetadata.target)?.target;
+            const targetClass = (embeddedIn ?? target) as FunctionConstructor;
+            if (typeof targetClass === 'function') {
+                const instance = new targetClass();
+                if (!(instance instanceof StockMovement)) {
+                    Index()(instance, relationMetadata.propertyName);
+                }
+            }
+        }
+    }
+};

+ 56 - 0
packages/core/src/config/entity-metadata/entity-metadata-modifier.ts

@@ -0,0 +1,56 @@
+import { MetadataArgsStorage } from 'typeorm/metadata-args/MetadataArgsStorage';
+
+/**
+ * @description
+ * A function which allows TypeORM entity metadata to be manipulated prior to the DB schema being generated
+ * during bootstrap.
+ *
+ * {{% alert "warning" %}}
+ * Certain DB schema modifications will result in auto-generated migrations which will lead to data loss. For instance,
+ * changing the data type of a column will drop the column & data and then re-create it. To avoid loss of important data,
+ * always check and modify your migration scripts as needed.
+ * {{% /alert %}}
+ *
+ * @example
+ * ```TypeScript
+ * import { Index } from 'typeorm';
+ * import { EntityMetadataModifier, ProductVariant } from '\@vendure/core';
+ *
+ * // Adds a unique index to the ProductVariant.sku column
+ * export const addSkuUniqueIndex: EntityMetadataModifier = metadata => {
+ *   const instance = new ProductVariant();
+ *   Index({ unique: true })(instance, 'sku');
+ * };
+ * ```
+ *
+ * @example
+ * ```TypeScript
+ * import { Column } from 'typeorm';
+ * import { EntityMetadataModifier, ProductTranslation } from '\@vendure/core';
+ *
+ * // Use the "mediumtext" datatype for the Product's description rather than
+ * // the default "text" type.
+ * export const makeProductDescriptionMediumText: EntityMetadataModifier = metadata => {
+ *     const descriptionColumnIndex = metadata.columns.findIndex(
+ *         col => col.propertyName === 'description' && col.target === ProductTranslation,
+ *     );
+ *     if (-1 < descriptionColumnIndex) {
+ *         // First we need to remove the existing column definition
+ *         // from the metadata.
+ *         metadata.columns.splice(descriptionColumnIndex, 1);
+ *         // Then we add a new column definition with our custom
+ *         // data type "mediumtext"
+ *         // DANGER: this particular modification will generate a DB migration
+ *         // which will result in data loss of existing descriptions. Make sure
+ *         // to manually check & modify your migration scripts.
+ *         const instance = new ProductTranslation();
+ *         Column({ type: 'mediumtext' })(instance, 'description');
+ *     }
+ * };
+ * ```
+ *
+ * @docsCategory configuration
+ * @docsPage EntityOptions
+ * @since 1.6.0
+ */
+export type EntityMetadataModifier = (metadata: MetadataArgsStorage) => void | Promise<void>;

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

@@ -18,6 +18,8 @@ export * from './default-config';
 export * from './entity-id-strategy/auto-increment-id-strategy';
 export * from './entity-id-strategy/entity-id-strategy';
 export * from './entity-id-strategy/uuid-id-strategy';
+export * from './entity-metadata/entity-metadata-modifier';
+export * from './entity-metadata/add-foreign-key-indices';
 export * from './fulfillment/custom-fulfillment-process';
 export * from './fulfillment/fulfillment-handler';
 export * from './fulfillment/manual-fulfillment-handler';

+ 12 - 0
packages/core/src/config/vendure-config.ts

@@ -20,6 +20,7 @@ import { ProductVariantPriceCalculationStrategy } from './catalog/product-varian
 import { StockDisplayStrategy } from './catalog/stock-display-strategy';
 import { CustomFields } from './custom-field/custom-field-types';
 import { EntityIdStrategy } from './entity-id-strategy/entity-id-strategy';
+import { EntityMetadataModifier } from './entity-metadata/entity-metadata-modifier';
 import { CustomFulfillmentProcess } from './fulfillment/custom-fulfillment-process';
 import { FulfillmentHandler } from './fulfillment/fulfillment-handler';
 import { JobQueueStrategy } from './job-queue/job-queue-strategy';
@@ -866,6 +867,17 @@ export interface EntityOptions {
      * @default 30000
      */
     zoneCacheTtl?: number;
+    /**
+     * @description
+     * Allows the metadata of the built-in TypeORM entities to be manipulated. This allows you
+     * to do things like altering data types, adding indices etc. This is an advanced feature
+     * which should be used with some caution as it will result in DB schema changes. For examples
+     * see {@link EntityMetadataModifier}.
+     *
+     * @since 1.6.0
+     * @default []
+     */
+    metadataModifiers?: EntityMetadataModifier[];
 }
 
 /**

+ 12 - 0
packages/core/src/entity/run-entity-metadata-modifiers.ts

@@ -0,0 +1,12 @@
+import { getMetadataArgsStorage } from 'typeorm';
+
+import { VendureConfig } from '../config/index';
+
+export async function runEntityMetadataModifiers(config: VendureConfig) {
+    if (config.entityOptions?.metadataModifiers?.length) {
+        const metadataArgsStorage = getMetadataArgsStorage();
+        for (const modifier of config.entityOptions.metadataModifiers) {
+            await modifier(metadataArgsStorage);
+        }
+    }
+}