From 5f6e920401badabc033463eb6362392620ba4012 Mon Sep 17 00:00:00 2001 From: Ulad Kasach Date: Sat, 15 Jun 2024 12:32:20 -0400 Subject: [PATCH] feat(resources): support multiple resource declarations per sql file enables support for dbdump sql files closes #47 --- .prettierignore | 2 + package-lock.json | 1 + package.json | 1 + .../mysql-dbdump/codegen.sql.yml | 6 + .../mysqldump.schema.fromgithubissue.sql | 221 ++++++++++++++++++ .../mysql-dbdump/src/generated/.gitignore | 2 + .../generate.integration.test.ts.snap | 76 ++++++ .../generate/generate.integration.test.ts | 28 +++ .../readConfig.integration.test.ts.snap | 10 +- .../extractDeclarationFromGlobedFile.ts | 38 --- .../extractDeclarationsFromGlobedFile.ts | 116 +++++++++ .../config/getConfig/readConfig/readConfig.ts | 52 +++-- 12 files changed, 483 insertions(+), 70 deletions(-) create mode 100644 src/logic/__test_assets__/exampleProject/mysql-dbdump/codegen.sql.yml create mode 100644 src/logic/__test_assets__/exampleProject/mysql-dbdump/schema/mysqldump.schema.fromgithubissue.sql create mode 100644 src/logic/__test_assets__/exampleProject/mysql-dbdump/src/generated/.gitignore delete mode 100644 src/logic/config/getConfig/readConfig/extractDeclarationFromGlobedFile.ts create mode 100644 src/logic/config/getConfig/readConfig/extractDeclarationsFromGlobedFile.ts diff --git a/.prettierignore b/.prettierignore index dd44972..480da14 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,3 @@ *.md +src/logic/__test_assets__/exampleProject/**/* +src/contract/__test_assets__/src/generated/**/* diff --git a/package-lock.json b/package-lock.json index bc2833e..5cbb7f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "@ehmpathy/error-fns": "1.0.2", "@oclif/core": "2.0.11", "@oclif/plugin-help": "3.1.0", "chalk": "2.4.2", diff --git a/package.json b/package.json index 9e68849..c87a5cb 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "postinstall": "[ -d .git ] && npx husky install || exit 0" }, "dependencies": { + "@ehmpathy/error-fns": "1.0.2", "@oclif/core": "2.0.11", "@oclif/plugin-help": "3.1.0", "chalk": "2.4.2", diff --git a/src/logic/__test_assets__/exampleProject/mysql-dbdump/codegen.sql.yml b/src/logic/__test_assets__/exampleProject/mysql-dbdump/codegen.sql.yml new file mode 100644 index 0000000..dd3d89e --- /dev/null +++ b/src/logic/__test_assets__/exampleProject/mysql-dbdump/codegen.sql.yml @@ -0,0 +1,6 @@ +language: mysql +dialect: 5.7 +resources: + - "schema/**/*.sql" +generates: + types: src/generated/fromSql/types.ts diff --git a/src/logic/__test_assets__/exampleProject/mysql-dbdump/schema/mysqldump.schema.fromgithubissue.sql b/src/logic/__test_assets__/exampleProject/mysql-dbdump/schema/mysqldump.schema.fromgithubissue.sql new file mode 100644 index 0000000..91d465e --- /dev/null +++ b/src/logic/__test_assets__/exampleProject/mysql-dbdump/schema/mysqldump.schema.fromgithubissue.sql @@ -0,0 +1,221 @@ +-- per https://github.com/ehmpathy/sql-code-generator/issues/47#issuecomment-1473832676 + +-- MariaDB dump 10.19 Distrib 10.4.25-MariaDB, for Win64 (AMD64) +-- +-- Host: localhost Database: xcregv2 +-- ------------------------------------------------------ +-- Server version 10.4.25-MariaDB + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `meets` +-- + +DROP TABLE IF EXISTS `meets`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `meets` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `createdAt` datetime NOT NULL DEFAULT current_timestamp(), + `updatedAt` datetime(6) NOT NULL DEFAULT current_timestamp(6) ON UPDATE current_timestamp(6), + `name` varchar(255) NOT NULL, + `date` datetime DEFAULT NULL, + `url` varchar(255) DEFAULT NULL, + `registrationOpens` datetime DEFAULT NULL, + `registrationCloses` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `IDX_68c082afef70fa9572114da1ae` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `meets` +-- + +LOCK TABLES `meets` WRITE; +/*!40000 ALTER TABLE `meets` DISABLE KEYS */; +INSERT INTO `meets` VALUES (1,'2023-03-14 21:15:07','2023-03-14 21:15:07.866946','Lutheran National XC Meet',NULL,'http://purdue.rivals.com',NULL,NULL),(3,'2023-03-14 21:15:23','2023-03-14 21:15:23.742028','Lutheran National XC Meet = 2023',NULL,'http://purdue.rivals.com',NULL,NULL); +/*!40000 ALTER TABLE `meets` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `pendingusers` +-- + +DROP TABLE IF EXISTS `pendingusers`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `pendingusers` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `createdAt` datetime NOT NULL DEFAULT current_timestamp(), + `updatedAt` datetime(6) NOT NULL DEFAULT current_timestamp(6) ON UPDATE current_timestamp(6), + `schoolName` varchar(25) NOT NULL, + `city` varchar(25) NOT NULL, + `state` varchar(2) NOT NULL, + `email` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `IDX_dcbe1c9362e2a5954ed37cacb8` (`email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `pendingusers` +-- + +LOCK TABLES `pendingusers` WRITE; +/*!40000 ALTER TABLE `pendingusers` DISABLE KEYS */; +/*!40000 ALTER TABLE `pendingusers` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `races` +-- + +DROP TABLE IF EXISTS `races`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `races` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `createdAt` datetime NOT NULL DEFAULT current_timestamp(), + `updatedAt` datetime(6) NOT NULL DEFAULT current_timestamp(6) ON UPDATE current_timestamp(6), + `raceName` varchar(255) NOT NULL, + `distance` decimal(4,1) NOT NULL, + `units` enum('km','mi') NOT NULL, + `sex` enum('boy','girl') NOT NULL, + `maxRunners` int(11) NOT NULL DEFAULT -1, + `maxRunnersPerSchool` int(11) NOT NULL DEFAULT -1, + `meetId` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `IDX_bdd9289e2e5229c12245e60301` (`raceName`,`meetId`), + KEY `FK_0d00f6079e05f60cbc597e07a3e` (`meetId`), + CONSTRAINT `FK_0d00f6079e05f60cbc597e07a3e` FOREIGN KEY (`meetId`) REFERENCES `meets` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `races` +-- + +LOCK TABLES `races` WRITE; +/*!40000 ALTER TABLE `races` DISABLE KEYS */; +INSERT INTO `races` VALUES (1,'2023-03-14 21:16:05','2023-03-14 21:30:20.000000','Boys 1.0 mile',1.1,'mi','boy',-1,999999,1),(2,'2023-03-14 21:16:18','2023-03-14 21:16:18.157242','Boys 1.5 mile',1.5,'mi','boy',-1,-1,1); +/*!40000 ALTER TABLE `races` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `runners` +-- + +DROP TABLE IF EXISTS `runners`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `runners` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `createdAt` datetime NOT NULL DEFAULT current_timestamp(), + `updatedAt` datetime(6) NOT NULL DEFAULT current_timestamp(6) ON UPDATE current_timestamp(6), + `firstName` varchar(255) NOT NULL, + `lastName` varchar(255) NOT NULL, + `grade` int(11) NOT NULL, + `sex` enum('boy','girl') NOT NULL, + `raceId` int(11) NOT NULL, + `schoolId` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `IDX_643667fbd7cc4845902d8f6f8e` (`schoolId`,`raceId`,`firstName`,`lastName`,`sex`,`grade`), + KEY `FK_d84623e31d7a37508f3b7280261` (`raceId`), + CONSTRAINT `FK_9646c5e15b243bd94af980c068c` FOREIGN KEY (`schoolId`) REFERENCES `schools` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT `FK_d84623e31d7a37508f3b7280261` FOREIGN KEY (`raceId`) REFERENCES `races` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `runners` +-- + +LOCK TABLES `runners` WRITE; +/*!40000 ALTER TABLE `runners` DISABLE KEYS */; +INSERT INTO `runners` VALUES (1,'2023-03-14 21:17:53','2023-03-14 21:38:25.000000','GEORGE','OFTHEJUNGLE',3,'boy',1,1),(3,'2023-03-14 21:18:12','2023-03-14 21:18:12.834505','TOMJR','HOFFMAN',8,'boy',1,1); +/*!40000 ALTER TABLE `runners` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `schools` +-- + +DROP TABLE IF EXISTS `schools`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `schools` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `createdAt` datetime NOT NULL DEFAULT current_timestamp(), + `updatedAt` datetime(6) NOT NULL DEFAULT current_timestamp(6) ON UPDATE current_timestamp(6), + `schoolName` varchar(25) NOT NULL, + `city` varchar(25) NOT NULL, + `state` varchar(2) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `IDX_311a56dc3afc916619f75a29e2` (`schoolName`,`city`,`state`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `schools` +-- + +LOCK TABLES `schools` WRITE; +/*!40000 ALTER TABLE `schools` DISABLE KEYS */; +INSERT INTO `schools` VALUES (1,'2023-03-14 21:17:44','2023-03-14 21:17:44.980996','ST PETER LUTHERAN','ARLINGTON HEIGHTS','IL'); +/*!40000 ALTER TABLE `schools` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `users` +-- + +DROP TABLE IF EXISTS `users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `users` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `createdAt` datetime NOT NULL DEFAULT current_timestamp(), + `updatedAt` datetime(6) NOT NULL DEFAULT current_timestamp(6) ON UPDATE current_timestamp(6), + `email` varchar(255) NOT NULL, + `password` varchar(255) NOT NULL, + `failedLogins` bigint(20) DEFAULT 0, + `successfulLogins` bigint(20) DEFAULT 0, + `resetCode` smallint(6) DEFAULT -1, + `schoolId` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `IDX_97672ac88f789774dd47f7c8be` (`email`), + KEY `FK_435e192698a6b7d10849295643d` (`schoolId`), + CONSTRAINT `FK_435e192698a6b7d10849295643d` FOREIGN KEY (`schoolId`) REFERENCES `schools` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `users` +-- + +LOCK TABLES `users` WRITE; +/*!40000 ALTER TABLE `users` DISABLE KEYS */; +/*!40000 ALTER TABLE `users` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2023-03-17 8:13:19 diff --git a/src/logic/__test_assets__/exampleProject/mysql-dbdump/src/generated/.gitignore b/src/logic/__test_assets__/exampleProject/mysql-dbdump/src/generated/.gitignore new file mode 100644 index 0000000..654617b --- /dev/null +++ b/src/logic/__test_assets__/exampleProject/mysql-dbdump/src/generated/.gitignore @@ -0,0 +1,2 @@ +**/* +!.gitignore diff --git a/src/logic/commands/generate/__snapshots__/generate.integration.test.ts.snap b/src/logic/commands/generate/__snapshots__/generate.integration.test.ts.snap index 1945926..a59acad 100644 --- a/src/logic/commands/generate/__snapshots__/generate.integration.test.ts.snap +++ b/src/logic/commands/generate/__snapshots__/generate.integration.test.ts.snap @@ -259,6 +259,82 @@ export const sqlQuerySelectUserNameFromView = async ({ " `; +exports[`generate mysql-dbdump should be able to read the example config provisioned in __test_assets__ 1`] = ` +"// types for table 'meets' +export interface SqlTableMeets { + id: number; + createdAt: Date; + updatedAt: Date; + name: string; + date: Date | null; + url: string | null; + registrationOpens: Date | null; + registrationCloses: Date | null; +} + +// types for table 'pendingusers' +export interface SqlTablePendingusers { + id: number; + createdAt: Date; + updatedAt: Date; + schoolName: string; + city: string; + state: string; + email: string; +} + +// types for table 'races' +export interface SqlTableRaces { + id: number; + createdAt: Date; + updatedAt: Date; + raceName: string; + distance: number; + units: string; + sex: string; + maxRunners: number; + maxRunnersPerSchool: number; + meetId: number; +} + +// types for table 'runners' +export interface SqlTableRunners { + id: number; + createdAt: Date; + updatedAt: Date; + firstName: string; + lastName: string; + grade: number; + sex: string; + raceId: number; + schoolId: number; +} + +// types for table 'schools' +export interface SqlTableSchools { + id: number; + createdAt: Date; + updatedAt: Date; + schoolName: string; + city: string; + state: string; +} + +// types for table 'users' +export interface SqlTableUsers { + id: number; + createdAt: Date; + updatedAt: Date; + email: string; + password: string; + failedLogins: number | null; + successfulLogins: number | null; + resetCode: number | null; + schoolId: number; +} +" +`; + exports[`generate postgres should be able to read the example config provisioned in __test_assets__ 1`] = ` "// types for table 'ice_cream' export interface SqlTableIceCream { diff --git a/src/logic/commands/generate/generate.integration.test.ts b/src/logic/commands/generate/generate.integration.test.ts index 069a328..f54a1a7 100644 --- a/src/logic/commands/generate/generate.integration.test.ts +++ b/src/logic/commands/generate/generate.integration.test.ts @@ -92,6 +92,34 @@ describe('generate', () => { expect(error.message).toContain('Cannot find module'); expect(error.message).toContain('queryFunctions.ts'); + // expect the look right + const typesCode = ( + await readFile(testAssetPaths.generatedTypesCode) + ).toString(); + expect(typesCode).toMatchSnapshot(); + }); + }); + describe('mysql-dbdump', () => { + const testAssetPaths = { + codegenYml: `${TEST_ASSETS_ROOT_DIR}/exampleProject/mysql-dbdump/codegen.sql.yml`, + generatedTypesCode: `${TEST_ASSETS_ROOT_DIR}/exampleProject/mysql-dbdump/src/generated/fromSql/types.ts`, + generatedQueryFunctionsCode: `${TEST_ASSETS_ROOT_DIR}/exampleProject/mysql-dbdump/src/generated/fromSql/queryFunctions.ts`, + }; + it('should be able to read the example config provisioned in __test_assets__', async () => { + await generate({ + configPath: testAssetPaths.codegenYml, + }); + + // expect that the types code does not have compile errors + await import(testAssetPaths.generatedTypesCode); + + // expect that the query functions code does not get produced, since not asked for in the config + const error = await getError( + import(testAssetPaths.generatedQueryFunctionsCode), + ); + expect(error.message).toContain('Cannot find module'); + expect(error.message).toContain('queryFunctions.ts'); + // expect the look right const typesCode = ( await readFile(testAssetPaths.generatedTypesCode) diff --git a/src/logic/config/getConfig/readConfig/__snapshots__/readConfig.integration.test.ts.snap b/src/logic/config/getConfig/readConfig/__snapshots__/readConfig.integration.test.ts.snap index 78e146a..5f55f0b 100644 --- a/src/logic/config/getConfig/readConfig/__snapshots__/readConfig.integration.test.ts.snap +++ b/src/logic/config/getConfig/readConfig/__snapshots__/readConfig.integration.test.ts.snap @@ -83,10 +83,7 @@ END }, ResourceDeclaration { "path": "schema/functions/upsert_image.sql", - "sql": "/* - generated by https://github.com/uladkasach/schema-generator -*/ -CREATE FUNCTION \`upsert_image\`( + "sql": "CREATE FUNCTION \`upsert_image\`( in_url varchar(190), in_caption varchar(190), in_credit varchar(190), @@ -124,10 +121,7 @@ END }, ResourceDeclaration { "path": "schema/tables/communication_channel.sql", - "sql": "-- ---------------------------------------- --- create communication channel table --- ---------------------------------------- -CREATE TABLE \`communication_channel\` ( + "sql": "CREATE TABLE \`communication_channel\` ( -- meta \`id\` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, \`uuid\` VARCHAR(36) NOT NULL, diff --git a/src/logic/config/getConfig/readConfig/extractDeclarationFromGlobedFile.ts b/src/logic/config/getConfig/readConfig/extractDeclarationFromGlobedFile.ts deleted file mode 100644 index 2605284..0000000 --- a/src/logic/config/getConfig/readConfig/extractDeclarationFromGlobedFile.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { QueryDeclaration, ResourceDeclaration } from '../../../../domain'; -import { extractSqlFromFile } from '../../../common/extractSqlFromFile'; - -export enum DeclarationType { - QUERY = 'QUERY', - RESOURCE = 'RESOURCE",', -} - -export const extractDeclarationFromGlobedFile = async ({ - rootDir, - relativePath, - type, -}: { - rootDir: string; - relativePath: string; - type: DeclarationType; -}) => { - // 0. define the file path - const filePath = `${rootDir}/${relativePath}`; - - // 1. validate the file type - const sql = await extractSqlFromFile({ filePath }); - - // 3. define the declaration - if (type === DeclarationType.QUERY) { - return new QueryDeclaration({ - path: relativePath, - sql, - }); - } - if (type === DeclarationType.RESOURCE) { - return new ResourceDeclaration({ - path: relativePath, - sql, - }); - } - throw new Error('unexpected'); // fail fast -}; diff --git a/src/logic/config/getConfig/readConfig/extractDeclarationsFromGlobedFile.ts b/src/logic/config/getConfig/readConfig/extractDeclarationsFromGlobedFile.ts new file mode 100644 index 0000000..c1fdf43 --- /dev/null +++ b/src/logic/config/getConfig/readConfig/extractDeclarationsFromGlobedFile.ts @@ -0,0 +1,116 @@ +import { UnexpectedCodePathError } from '@ehmpathy/error-fns'; +import strip from 'sql-strip-comments'; + +import { QueryDeclaration, ResourceDeclaration } from '../../../../domain'; +import { extractSqlFromFile } from '../../../common/extractSqlFromFile'; + +export enum DeclarationType { + QUERY = 'QUERY', + RESOURCE = 'RESOURCE",', +} + +export const extractDeclarationsFromGlobedFile = async ({ + rootDir, + relativePath, + type, +}: { + rootDir: string; + relativePath: string; + type: DeclarationType; +}) => { + // define the file path + const filePath = `${rootDir}/${relativePath}`; + + // extract the sql + const sql = await extractSqlFromFile({ filePath }); + + // define the declaration for a query + if (type === DeclarationType.QUERY) { + // note: we only support one declaration per query, since we require the comments to define the query name + return [ + new QueryDeclaration({ + path: relativePath, + sql, + }), + ]; + } + + // define the declarations for a resource + if (type === DeclarationType.RESOURCE) { + // split up the sql by create resource statements, to support multiple resources per sql file + const sqlCreateResourceStatements = sql + .split(/(create(?:\sor\sreplace)?\s(?:table|function|view))/gi) + .reduce((pairs, thisPart) => { + // define what kind of part this is + const isThisPartCreateDeclaration = thisPart + .trim() + .toLowerCase() + .startsWith('create'); + + // define the last pair + const lastPair: + | { createDeclaration: string; resourceDeclaration?: string } + | undefined = pairs.slice(-1)[0]; + + // if the last pair is not defined yet + if (!lastPair) { + // if this part is not a create declaration, then do nothing; we can ignore the prefixes + if (!isThisPartCreateDeclaration) return pairs; + + // otherwise, since it is a create declaration, start the first pair + return [{ createDeclaration: thisPart }]; + } + + // if the last pair already has a resource declaration + if (lastPair.resourceDeclaration) { + // if this part is a create declaration, then start a new pair + if (isThisPartCreateDeclaration) + return [...pairs, { createDeclaration: thisPart }]; + + // otherwise, this is unexpected, so fail fast + throw new UnexpectedCodePathError( + 'lastPair.resourceDeclaration already exists but thisPart.!isCreateDeclaration. how is that possible?', + { lastPair, thisPart }, + ); + } + + // if the last pair does not have a resource declaration + if (!lastPair.resourceDeclaration) { + // if this part is a resource declaration, then complete this pair + if (!isThisPartCreateDeclaration) + return [ + ...pairs.slice(0, -1), + { ...lastPair, resourceDeclaration: thisPart }, + ]; + + // otherwise, this is unexpected, so fail fast + throw new UnexpectedCodePathError( + 'lastPair.resourceDeclaration does not exist, but thisPart.isCreateDeclaration. how is that possible?', + { + lastPair, + thisPart, + }, + ); + } + + // we should never reach here, as the above should have captured every scenario, so fail fast + throw new UnexpectedCodePathError('should never reach here. why?', { + lastPair, + thisPart, + }); + }, [] as { createDeclaration: string; resourceDeclaration?: string }[]) + .map((pair) => + [pair.createDeclaration, pair.resourceDeclaration].join(''), + ); + + // return a declaration for each + return sqlCreateResourceStatements.map( + (sql) => + new ResourceDeclaration({ + path: relativePath, + sql, + }), + ); + } + throw new Error('unexpected'); // fail fast +}; diff --git a/src/logic/config/getConfig/readConfig/readConfig.ts b/src/logic/config/getConfig/readConfig/readConfig.ts index af72b94..9364ece 100644 --- a/src/logic/config/getConfig/readConfig/readConfig.ts +++ b/src/logic/config/getConfig/readConfig/readConfig.ts @@ -2,8 +2,8 @@ import { GeneratorConfig } from '../../../../domain'; import { getAllPathsMatchingGlobs } from '../getAllPathsMatchingGlobs/getAllPathsMatchingGlobs'; import { DeclarationType, - extractDeclarationFromGlobedFile, -} from './extractDeclarationFromGlobedFile'; + extractDeclarationsFromGlobedFile, +} from './extractDeclarationsFromGlobedFile'; import { readYmlFile } from './utils/readYmlFile'; /* @@ -36,17 +36,19 @@ export const readConfig = async ({ filePath }: { filePath: string }) => { globs: resourceGlobs, root: configDir, }); - const resourceDeclarations = await Promise.all( - resourcePaths - .sort() // for determinism in order - .map((relativePath) => - extractDeclarationFromGlobedFile({ - rootDir: configDir, - relativePath, - type: DeclarationType.RESOURCE, - }), - ), - ); + const resourceDeclarations = ( + await Promise.all( + resourcePaths + .sort() // for determinism in order + .map((relativePath) => + extractDeclarationsFromGlobedFile({ + rootDir: configDir, + relativePath, + type: DeclarationType.RESOURCE, + }), + ), + ) + ).flat(); // get the query declarations const queryDeclarations = await (async () => { @@ -56,17 +58,19 @@ export const readConfig = async ({ filePath }: { filePath: string }) => { globs: queryGlobs, root: configDir, }); - return await Promise.all( - queryPaths - .sort() // for determinism in order - .map((relativePath) => - extractDeclarationFromGlobedFile({ - rootDir: configDir, - relativePath, - type: DeclarationType.QUERY, - }), - ), - ); + return ( + await Promise.all( + queryPaths + .sort() // for determinism in order + .map((relativePath) => + extractDeclarationsFromGlobedFile({ + rootDir: configDir, + relativePath, + type: DeclarationType.QUERY, + }), + ), + ) + ).flat(); })(); // return the results