@@ -3,41 +3,50 @@ import { QueryRunner } from 'typeorm';
33
44/**
55 * @description
6- * Removes duplicate rows from a translation table , keeping only the most recently updated
7- * row per `(baseId, languageCode)` pair.
6+ * Removes duplicate rows from one or more translation tables , keeping only the most recently
7+ * updated row per `(baseId, languageCode)` pair.
88 *
99 * Prior to the fix for a race condition in `TranslatableSaver.update()`, two concurrent
1010 * updates could both add a translation for the same language before either had committed,
1111 * resulting in two rows for the same `(baseId, languageCode)` pair. This is no longer
1212 * possible going forward, but a unique constraint on translation tables cannot be added to an
1313 * existing database that already contains such duplicates without first removing them.
1414 *
15+ * This applies to any table backing a `Translation<T>` entity used with `TranslatableSaver`,
16+ * including translation tables defined by plugins for their own custom translatable entities —
17+ * not just the 13 core tables shown below.
18+ *
1519 * Call this from your migration's `up()` method **before** the unique constraint on the
16- * translation table is created, once per affected table. `vendure migrate generate` will
17- * generate the DDL that creates the constraints; you only need to add the
18- * `deduplicateTranslations` calls ahead of it. On Postgres this looks like (constraint names
19- * are deterministic, so `vendure migrate generate` will produce these exact names):
20+ * translation table(s) is created. `vendure migrate generate` will generate the DDL that
21+ * creates the constraints; you only need to add a `deduplicateTranslations` call ahead of it,
22+ * passing either a single table name or an array of table names. On Postgres this looks like
23+ * (constraint names are deterministic, so `vendure migrate generate` will produce these exact
24+ * names):
2025 *
2126 * ```ts
2227 * import { MigrationInterface, QueryRunner } from 'typeorm';
2328 * import { deduplicateTranslations } from '\@vendure/core';
2429 *
2530 * export class AddTranslationUniqueConstraints1234567890 implements MigrationInterface {
2631 * public async up(queryRunner: QueryRunner): Promise<any> {
27- * // --- Remove any pre-existing duplicate rows, one call per translation table ---
28- * await deduplicateTranslations(queryRunner, 'product_translation');
29- * await deduplicateTranslations(queryRunner, 'product_variant_translation');
30- * await deduplicateTranslations(queryRunner, 'product_option_translation');
31- * await deduplicateTranslations(queryRunner, 'product_option_group_translation');
32- * await deduplicateTranslations(queryRunner, 'collection_translation');
33- * await deduplicateTranslations(queryRunner, 'facet_translation');
34- * await deduplicateTranslations(queryRunner, 'facet_value_translation');
35- * await deduplicateTranslations(queryRunner, 'asset_translation');
36- * await deduplicateTranslations(queryRunner, 'api_key_translation');
37- * await deduplicateTranslations(queryRunner, 'payment_method_translation');
38- * await deduplicateTranslations(queryRunner, 'promotion_translation');
39- * await deduplicateTranslations(queryRunner, 'region_translation');
40- * await deduplicateTranslations(queryRunner, 'shipping_method_translation');
32+ * // --- Remove any pre-existing duplicate rows across all core translation tables ---
33+ * await deduplicateTranslations(queryRunner, [
34+ * 'product_translation',
35+ * 'product_variant_translation',
36+ * 'product_option_translation',
37+ * 'product_option_group_translation',
38+ * 'collection_translation',
39+ * 'facet_translation',
40+ * 'facet_value_translation',
41+ * 'asset_translation',
42+ * 'api_key_translation',
43+ * 'payment_method_translation',
44+ * 'promotion_translation',
45+ * 'region_translation',
46+ * 'shipping_method_translation',
47+ * // Include any of your own custom translatable entities' tables here too, e.g.:
48+ * // 'my_custom_entity_translation',
49+ * ]);
4150 *
4251 * // --- Auto-generated DDL continues (Postgres) ---
4352 * await queryRunner.query(`ALTER TABLE "product_translation" ADD CONSTRAINT "UQ_dcc35f0d2b8d422634e878b813c" UNIQUE ("languageCode", "baseId")`);
@@ -72,6 +81,16 @@ import { QueryRunner } from 'typeorm';
7281 * @docsCategory migration
7382 */
7483export async function deduplicateTranslations (
84+ queryRunner : QueryRunner ,
85+ translationTableNames : string | string [ ] ,
86+ ) : Promise < void > {
87+ const tableNames = Array . isArray ( translationTableNames ) ? translationTableNames : [ translationTableNames ] ;
88+ for ( const tableName of tableNames ) {
89+ await deduplicateTranslationTable ( queryRunner , tableName ) ;
90+ }
91+ }
92+
93+ async function deduplicateTranslationTable (
7594 queryRunner : QueryRunner ,
7695 translationTableName : string ,
7796) : Promise < void > {
0 commit comments