Browse Source

feat(core): Validate DB table structure on worker bootstrap

Michael Bromley 5 years ago
parent
commit
c1ccaa1e83

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

@@ -1,15 +1,17 @@
 import { INestApplication, INestMicroservice } from '@nestjs/common';
 import { NestFactory } from '@nestjs/core';
 import { TcpClientOptions, Transport } from '@nestjs/microservices';
+import { getConnectionToken } from '@nestjs/typeorm';
 import { Type } from '@vendure/common/lib/shared-types';
 import cookieSession = require('cookie-session');
-import { ConnectionOptions, EntitySubscriberInterface } from 'typeorm';
+import { Connection, ConnectionOptions, EntitySubscriberInterface } from 'typeorm';
 
 import { InternalServerError } from './common/error/errors';
 import { getConfig, setConfig } from './config/config-helpers';
 import { DefaultLogger } from './config/logger/default-logger';
 import { Logger } from './config/logger/vendure-logger';
 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 { setEntityIdStrategy } from './entity/set-entity-id-strategy';
@@ -70,7 +72,7 @@ export async function bootstrap(userConfig: Partial<VendureConfig>): Promise<INe
             Logger.warn(`[VendureConfig.workerOptions.runInMainProcess = true]`);
             closeWorkerOnAppClose(app, worker);
         } catch (e) {
-            Logger.error(`Could not start the worker process: ${e.message}`, 'Vendure Worker');
+            Logger.error(`Could not start the worker process: ${e.message || e}`, 'Vendure Worker');
         }
     }
     logWelcomeMessage(config);
@@ -130,6 +132,7 @@ async function bootstrapWorkerInternal(
     DefaultLogger.restoreOriginalLogLevel();
     workerApp.useLogger(new Logger());
     workerApp.enableShutdownHooks();
+    await validateDbTablesForWorker(workerApp);
     await runBeforeWorkerBootstrapHooks(config, workerApp);
     // A work-around to correctly handle errors when attempting to start the
     // microservice server listening.
@@ -338,6 +341,48 @@ function disableSynchronize(userConfig: Readonly<RuntimeVendureConfig>): Readonl
     return config;
 }
 
+/**
+ * Check that the Database tables exist. When running Vendure server & worker
+ * concurrently for the first time, the worker will attempt to access the
+ * DB tables before the server has populated them (assuming synchronize = true
+ * in config). This method will use polling to check the existence of a known table
+ * before allowing the rest of the worker bootstrap to continue.
+ * @param worker
+ */
+async function validateDbTablesForWorker(worker: INestMicroservice) {
+    const connection: Connection = worker.get(getConnectionToken());
+    await new Promise(async (resolve, reject) => {
+        const checkForTables = async (): Promise<boolean> => {
+            try {
+                const adminCount = await connection.getRepository(Administrator).count();
+                return 0 < adminCount;
+            } catch (e) {
+                return false;
+            }
+        };
+
+        const pollIntervalMs = 5000;
+        let attempts = 0;
+        const maxAttempts = 10;
+        let validTableStructure = false;
+        Logger.verbose('Checking for expected DB table structure...');
+        while (!validTableStructure && attempts < maxAttempts) {
+            attempts++;
+            validTableStructure = await checkForTables();
+            if (validTableStructure) {
+                Logger.verbose('Table structure verified');
+                resolve();
+                return;
+            }
+            Logger.verbose(
+                `Table structure could not be verified, trying again after ${pollIntervalMs}ms (attempt ${attempts} of ${maxAttempts})`,
+            );
+            await new Promise(resolve1 => setTimeout(resolve1, pollIntervalMs));
+        }
+        reject(`Could not validate DB table structure. Aborting bootstrap.`);
+    });
+}
+
 function checkForDeprecatedOptions(config: Partial<VendureConfig>) {
     const deprecatedApiOptions = [
         'hostname',

+ 1 - 0
packages/dev-server/index-worker.ts

@@ -9,4 +9,5 @@ devConfig.dbConnectionOptions = { ...devConfig.dbConnectionOptions, synchronize:
 bootstrapWorker(devConfig).catch(err => {
     // tslint:disable-next-line
     console.log(err);
+    process.exit(1);
 });

+ 1 - 0
packages/dev-server/index.ts

@@ -8,4 +8,5 @@ import { devConfig } from './dev-config';
 bootstrap(devConfig).catch(err => {
     // tslint:disable-next-line
     console.log(err);
+    process.exit(1);
 });